From 50e506f5e98a38702afea3d122102216e690d56a Mon Sep 17 00:00:00 2001 From: Jamie Sinn Date: Tue, 13 Feb 2024 11:52:03 -0500 Subject: [PATCH] Add chmod 777 option - Closes #12 (#13) * Add chmod 777 option * Return nil for errors instead of bubbling up * Return nil for errors instead of bubbling up * Set file permissions mask instead of forcing to 777 * Fix tests * Make sure proxy socket file has been created before chmoding it. * Add logging to file option and fix tests --- README.md | 22 ++++++++++++++++------ config.json.example | 1 + options.go | 42 +++++++++++++++++++++++++++--------------- options_test.go | 38 ++++++++++++++++++++++++-------------- proxy.go | 29 +++++++++++++++++++++++++---- 5 files changed, 93 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index dd7f633..dc4d2ba 100644 --- a/README.md +++ b/README.md @@ -11,17 +11,21 @@ The application is delivered in multiple formats - a Docker image, a deb, and RP format for local building and implementation. The proxy handles two modes of operation - you can expose the HTTP server over a TCP port, or over Unix domain sockets. -The latter is recommended for servers that will deploy this with the proxy running on the same machine as the SDK, preventing the need for network calls. +The latter is recommended for servers that will deploy this with the proxy running on the same machine as the SDK, +preventing the need for network calls. -The HTTP server mode is a 1:1 replacement for the Bucketing API used by all SDKs in cloud bucketing mode, or can be used directly without an SDK as an API. +The HTTP server mode is a 1:1 replacement for the Bucketing API used by all SDKs in cloud bucketing mode, or can be used +directly without an SDK as an API. ### Docker The docker image published here is the base runtime version - expecting to be used as a base image for you to extend. -The docker image expects that you use the environment variables to configure the proxy, but can be modified and extended to use a configuration -file instead. +The docker image expects that you use the environment variables to configure the proxy, but can be modified and extended +to use a configuration +file instead. -We also provide the raw application binary to wrap in your own daemon manager, or tie into your existing application lifecycle. +We also provide the raw application binary to wrap in your own daemon manager, or tie into your existing application +lifecycle. ## Options @@ -30,6 +34,11 @@ used to configure the proxy. A simple healthcheck for each proxy instance can be performed by sending a GET request to the `/healthz` endpoint. +We recommend setting the file permissions for the unix socket to be as restrictive as possible. However, as a workaround +for deployment issues, you can set the permissions to your own custom mask via the +`DVC_LB_PROXY_UNIX_SOCKET_PERMISSIONS` environment variable, or the unixSocketPermissions option in the config file. The +default is 0755 + ### Command Line Arguments | ARGUMENT | TYPE | DEFAULT | REQUIRED | DESCRIPTION | @@ -41,10 +50,11 @@ A simple healthcheck for each proxy instance can be performed by sending a GET r | KEY | TYPE | DEFAULT | REQUIRED | DESCRIPTION | |--------------------------------------------------------|---------------|---------|----------|---------------------------------------------------------------------------------| -| DVC_LB_PROXY_CONFIG | String | | | The path to a JSON configuration file. | +| DVC_LB_PROXY_CONFIG | String | | | The path to a JSON configuration file. | | DVC_LB_PROXY_UNIX_SOCKET_PATH | String | | | The path to the Unix socket. | | DVC_LB_PROXY_HTTP_PORT | Integer | 8080 | | The port to listen on for HTTP requests. Defaults to 8080. | | DVC_LB_PROXY_UNIX_SOCKET_ENABLED | True or False | false | | Whether to enable the Unix socket. Defaults to false. | +| DVC_LB_PROXY_UNIX_SOCKET_PERMISSIONS | String | 0755 | | The permissions to set on the Unix socket. Defaults to 0755 | | DVC_LB_PROXY_HTTP_ENABLED | True or False | true | | Whether to enable the HTTP server. Defaults to true. | | DVC_LB_PROXY_SDK_KEY | String | | true | The Server SDK key to use for this instance. | | DVC_LB_PROXY_PLATFORMDATA_SDKTYPE | String | | | | diff --git a/config.json.example b/config.json.example index f04782d..724bf71 100644 --- a/config.json.example +++ b/config.json.example @@ -6,6 +6,7 @@ "unixSocketEnabled": false, "httpEnabled": true, "sdkKey": "dvc_YOUR_KEY_HERE", + "logFile": "/var/log/devcycle.log", "platformData": { "sdkType": "server", "sdkVersion": "2.10.2", diff --git a/options.go b/options.go index 73a40d4..1015e62 100644 --- a/options.go +++ b/options.go @@ -22,14 +22,16 @@ type ProxyConfig struct { } type ProxyInstance struct { - UnixSocketPath string `json:"unixSocketPath" envconfig:"UNIX_SOCKET_PATH" desc:"The path to the Unix socket."` - HTTPPort int `json:"httpPort" envconfig:"HTTP_PORT" default:"8080" desc:"The port to listen on for HTTP requests. Defaults to 8080."` - UnixSocketEnabled bool `json:"unixSocketEnabled" envconfig:"UNIX_SOCKET_ENABLED" default:"false" desc:"Whether to enable the Unix socket. Defaults to false."` - HTTPEnabled bool `json:"httpEnabled" envconfig:"HTTP_ENABLED" default:"true" desc:"Whether to enable the HTTP server. Defaults to true."` - SDKKey string `json:"sdkKey" required:"true" envconfig:"SDK_KEY" desc:"The Server SDK key to use for this instance."` - PlatformData devcycle.PlatformData `json:"platformData" required:"true"` - SDKConfig SDKConfig `json:"sdkConfig" required:"true"` - dvcClient *devcycle.Client + UnixSocketPath string `json:"unixSocketPath" envconfig:"UNIX_SOCKET_PATH" desc:"The path to the Unix socket."` + UnixSocketPermissions int `json:"unixSocketPermissions" envconfig:"UNIX_SOCKET_PERMISSIONS" default:"755" desc:"The permissions to set on the Unix socket. Defaults to 0755"` + UnixSocketEnabled bool `json:"unixSocketEnabled" envconfig:"UNIX_SOCKET_ENABLED" default:"false" desc:"Whether to enable the Unix socket. Defaults to false."` + HTTPPort int `json:"httpPort" envconfig:"HTTP_PORT" default:"8080" desc:"The port to listen on for HTTP requests. Defaults to 8080."` + HTTPEnabled bool `json:"httpEnabled" envconfig:"HTTP_ENABLED" default:"true" desc:"Whether to enable the HTTP server. Defaults to true."` + SDKKey string `json:"sdkKey" required:"true" envconfig:"SDK_KEY" desc:"The Server SDK key to use for this instance."` + LogFile string `json:"logFile" default:"/var/log/devcycle.log" envconfig:"LOG_FILE" desc:"The path to the log file. Defaults to /var/log/devcycle.log"` + PlatformData devcycle.PlatformData `json:"platformData" required:"true"` + SDKConfig SDKConfig `json:"sdkConfig" required:"true"` + dvcClient *devcycle.Client } type SDKConfig struct { @@ -76,8 +78,16 @@ func (i *ProxyInstance) Default() { if i.HTTPEnabled && i.HTTPPort == 0 { i.HTTPPort = 8080 } - if i.UnixSocketEnabled && i.UnixSocketPath == "" { - i.UnixSocketPath = "/tmp/devcycle.sock" + if i.LogFile == "" { + i.LogFile = "/var/log/devcycle.log" + } + if i.UnixSocketEnabled { + if i.UnixSocketPath == "" { + i.UnixSocketPath = "/tmp/devcycle.sock" + } + if i.UnixSocketPermissions == 0 { + i.UnixSocketPermissions = 755 + } } } func (c *ProxyConfig) Default() { @@ -165,6 +175,7 @@ func ParseConfig(configPath string) (*ProxyConfig, error) { if err != nil { return nil, fmt.Errorf("failed to parse config from JSON: %w", err) } + proxyConfig.Default() } if !initialConfig.Debug { @@ -182,11 +193,12 @@ func SampleProxyConfig() ProxyConfig { proxyConfig := ProxyConfig{ Instances: []*ProxyInstance{{ - UnixSocketPath: "/tmp/devcycle.sock", - HTTPPort: 8080, - UnixSocketEnabled: false, - HTTPEnabled: true, - SDKKey: "", + UnixSocketPath: "/tmp/devcycle.sock", + HTTPPort: 8080, + UnixSocketEnabled: false, + UnixSocketPermissions: 755, + HTTPEnabled: true, + SDKKey: "", PlatformData: devcycle.PlatformData{ SdkType: "server", SdkVersion: devcycle.VERSION, diff --git a/options_test.go b/options_test.go index 2faf315..31eaa6b 100644 --- a/options_test.go +++ b/options_test.go @@ -12,6 +12,8 @@ import ( ) func TestParseConfig(t *testing.T) { + defaultSDKConfig := SDKConfig{} + defaultSDKConfig.Default() tests := []struct { name string flag string @@ -33,13 +35,15 @@ func TestParseConfig(t *testing.T) { expected: &ProxyConfig{ Instances: []*ProxyInstance{ { - UnixSocketPath: "", - HTTPPort: 8080, - UnixSocketEnabled: false, - HTTPEnabled: true, - SDKKey: "dvc-test-key", - PlatformData: api.PlatformData{}, - SDKConfig: SDKConfig{}, + UnixSocketPath: "", + UnixSocketPermissions: 755, + HTTPPort: 8080, + UnixSocketEnabled: false, + HTTPEnabled: true, + SDKKey: "dvc-test-key", + LogFile: "/var/log/devcycle.log", + PlatformData: api.PlatformData{}, + SDKConfig: SDKConfig{}, }, }, }, @@ -72,11 +76,13 @@ func TestParseConfig(t *testing.T) { expected: &ProxyConfig{ Instances: []*ProxyInstance{ { - UnixSocketPath: "/tmp/dvc2.sock", - HTTPPort: 1234, - UnixSocketEnabled: true, - HTTPEnabled: false, - SDKKey: "dvc-test-key", + UnixSocketPath: "/tmp/dvc2.sock", + HTTPPort: 1234, + UnixSocketEnabled: true, + UnixSocketPermissions: 755, + HTTPEnabled: false, + SDKKey: "dvc-test-key", + LogFile: "/var/log/devcycle.log", PlatformData: api.PlatformData{ SdkType: "sdk type", SdkVersion: "v1.2.3", @@ -118,8 +124,9 @@ func TestParseConfig(t *testing.T) { UnixSocketEnabled: false, HTTPEnabled: false, SDKKey: "dvc-sample-key", + LogFile: "/var/log/devcycle.log", PlatformData: api.PlatformData{}, - SDKConfig: SDKConfig{}, + SDKConfig: defaultSDKConfig, }, }, }, @@ -137,8 +144,9 @@ func TestParseConfig(t *testing.T) { UnixSocketEnabled: false, HTTPEnabled: false, SDKKey: "dvc-sample-key", + LogFile: "/var/log/devcycle.log", PlatformData: api.PlatformData{}, - SDKConfig: SDKConfig{}, + SDKConfig: defaultSDKConfig, }, }, }, @@ -155,6 +163,8 @@ func TestParseConfig(t *testing.T) { UnixSocketEnabled: false, HTTPEnabled: true, SDKKey: "dvc_YOUR_KEY_HERE", + LogFile: "/var/log/devcycle.log", + PlatformData: api.PlatformData{ SdkType: "server", SdkVersion: "2.10.2", diff --git a/proxy.go b/proxy.go index 6d30ce6..11adf4e 100644 --- a/proxy.go +++ b/proxy.go @@ -2,15 +2,27 @@ package local_bucketing_proxy import ( "fmt" + "io" "log" "os" "strconv" + "time" devcycle "github.com/devcyclehq/go-server-sdk/v2" "github.com/gin-gonic/gin" ) func NewBucketingProxyInstance(instance *ProxyInstance) (*ProxyInstance, error) { + gin.DisableConsoleColor() + logFile, err := os.OpenFile(instance.LogFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) + if err != nil { + _ = fmt.Errorf("error opening log file: %s", err) + return nil, err + } + mw := io.MultiWriter(os.Stdout, logFile) + log.SetOutput(mw) + gin.DefaultWriter = mw + options := instance.BuildDevCycleOptions() client, err := devcycle.NewClient(instance.SDKKey, options) instance.dvcClient = client @@ -41,7 +53,7 @@ func NewBucketingProxyInstance(instance *ProxyInstance) (*ProxyInstance, error) return nil, fmt.Errorf("HTTP port must be set") } go func() { - err := r.Run(":" + strconv.Itoa(instance.HTTPPort)) + err = r.Run(":" + strconv.Itoa(instance.HTTPPort)) if err != nil { log.Printf("Error running HTTP server: %s", err) } @@ -49,16 +61,25 @@ func NewBucketingProxyInstance(instance *ProxyInstance) (*ProxyInstance, error) log.Printf("HTTP server started on port %d", instance.HTTPPort) } if instance.UnixSocketEnabled { - if _, err := os.Stat(instance.UnixSocketPath); err == nil { + if _, err = os.Stat(instance.UnixSocketPath); err == nil { return nil, fmt.Errorf("unix socket path %s already exists. Skipping instance creation", instance.UnixSocketPath) } + err = nil go func() { - err := r.RunUnix(instance.UnixSocketPath) + err = r.RunUnix(instance.UnixSocketPath) if err != nil { log.Printf("Error running Unix socket server: %s", err) } }() - log.Printf("Running on unix socket: %s", instance.UnixSocketPath) + fileMode := os.FileMode(instance.UnixSocketPermissions) + _, err = os.Stat(instance.UnixSocketPath) + for ; err != nil; _, err = os.Stat(instance.UnixSocketPath) { + time.Sleep(1 * time.Second) + } + if err = os.Chmod(instance.UnixSocketPath, fileMode); err != nil { + log.Printf("Error setting Unix socket permissions: %s", err) + } + log.Printf("Running on unix socket: %s with file permissions %d", instance.UnixSocketPath, instance.UnixSocketPermissions) } return instance, err }