diff --git a/docker-compose.armhf.yml b/docker-compose.armhf.yml index 673931c7..0189beff 100644 --- a/docker-compose.armhf.yml +++ b/docker-compose.armhf.yml @@ -15,6 +15,8 @@ services: faas_nats_port: 4222 direct_functions: "true" # Functions are invoked directly over the overlay network direct_functions_suffix: "" + basic_auth: "false" + secret_mount_path: "/run/secrets/" deploy: resources: # limits: # uncomment to enable limits diff --git a/docker-compose.yml b/docker-compose.yml index bf3dd59f..4b8f1f88 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,8 @@ services: faas_nats_port: 4222 direct_functions: "true" # Functions are invoked directly over the overlay network direct_functions_suffix: "" + basic_auth: "false" + secret_mount_path: "/run/secrets/" deploy: resources: # limits: # Enable if you want to limit memory usage diff --git a/gateway/README.md b/gateway/README.md index 91917b94..c1cf047b 100644 --- a/gateway/README.md +++ b/gateway/README.md @@ -54,3 +54,5 @@ The gateway can be configured through the following environment variables: | `faas_promethus_port` | Port to connect to Prometheus. Default: `9090` | | `direct_functions` | `true` or `false` - functions are invoked directly over overlay network without passing through provider | | `direct_functions_suffix` | Provide a DNS suffix for invoking functions directly over overlay network | +| `basic_auth` | Set to `true` or `false` to enable embedded basic auth on the /system and /ui endpoints (recommended) | +| `secret_mount_path` | Set a location where you have mounted `basic-auth-user` and `basic-auth-password`, default: `/run/secrets/`. | diff --git a/gateway/handlers/basic_auth.go b/gateway/handlers/basic_auth.go index 8d266c3f..d20da584 100644 --- a/gateway/handlers/basic_auth.go +++ b/gateway/handlers/basic_auth.go @@ -2,10 +2,12 @@ package handlers import ( "net/http" + + "github.com/openfaas/faas/gateway/types" ) // DecorateWithBasicAuth enforces basic auth as a middleware with given credentials -func DecorateWithBasicAuth(next http.HandlerFunc, credentials *BasicAuthCredentials) http.HandlerFunc { +func DecorateWithBasicAuth(next http.HandlerFunc, credentials *types.BasicAuthCredentials) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { user, password, ok := r.BasicAuth() @@ -21,9 +23,3 @@ func DecorateWithBasicAuth(next http.HandlerFunc, credentials *BasicAuthCredenti next.ServeHTTP(w, r) } } - -// BasicAuthCredentials for credentials -type BasicAuthCredentials struct { - User string - Password string -} diff --git a/gateway/handlers/basic_auth_test.go b/gateway/handlers/basic_auth_test.go index 7514bde7..b666597e 100644 --- a/gateway/handlers/basic_auth_test.go +++ b/gateway/handlers/basic_auth_test.go @@ -5,6 +5,8 @@ import ( "net/http" "net/http/httptest" "testing" + + "github.com/openfaas/faas/gateway/types" ) func Test_AuthWithValidPassword_Gives200(t *testing.T) { @@ -18,7 +20,7 @@ func Test_AuthWithValidPassword_Gives200(t *testing.T) { wantPassword := "password" r := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil) r.SetBasicAuth(wantUser, wantPassword) - wantCredentials := &BasicAuthCredentials{ + wantCredentials := &types.BasicAuthCredentials{ User: wantUser, Password: wantPassword, } @@ -47,7 +49,7 @@ func Test_AuthWithInvalidPassword_Gives403(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil) r.SetBasicAuth(wantUser, wantPassword) - wantCredentials := &BasicAuthCredentials{ + wantCredentials := &types.BasicAuthCredentials{ User: wantUser, Password: "", } diff --git a/gateway/server.go b/gateway/server.go index 090210ee..d920df26 100644 --- a/gateway/server.go +++ b/gateway/server.go @@ -5,10 +5,8 @@ package main import ( "fmt" - "io/ioutil" "log" "net/http" - "strings" "time" "github.com/gorilla/mux" @@ -35,24 +33,17 @@ func main() { log.Printf("Binding to external function provider: %s", config.FunctionsProviderURL) - var credentials *handlers.BasicAuthCredentials + var credentials *types.BasicAuthCredentials if config.UseBasicAuth { - userPath := "/var/secrets/basic_auth_user" - user, userErr := ioutil.ReadFile(userPath) - if userErr != nil { - log.Panicf("Unable to load %s", userPath) + var readErr error + reader := types.ReadBasicAuthFromDisk{ + SecretMountPath: config.SecretMountPath, } + credentials, readErr = reader.Read() - userPassword := "/var/secrets/basic_auth_password" - password, passErr := ioutil.ReadFile(userPassword) - if passErr != nil { - log.Panicf("Unable to load %s", userPassword) - } - - credentials = &handlers.BasicAuthCredentials{ - User: strings.TrimSpace(string(user)), - Password: strings.TrimSpace(string(password)), + if readErr != nil { + log.Panicf(readErr.Error()) } } diff --git a/gateway/types/load_credentials.go b/gateway/types/load_credentials.go new file mode 100644 index 00000000..7c67b2ec --- /dev/null +++ b/gateway/types/load_credentials.go @@ -0,0 +1,49 @@ +package types + +import ( + "fmt" + "io/ioutil" + "path" + "strings" +) + +// BasicAuthCredentials for credentials +type BasicAuthCredentials struct { + User string + Password string +} + +type ReadBasicAuth interface { + Read() (error, *BasicAuthCredentials) +} + +type ReadBasicAuthFromDisk struct { + SecretMountPath string +} + +func (r *ReadBasicAuthFromDisk) Read() (*BasicAuthCredentials, error) { + var credentials *BasicAuthCredentials + + if len(r.SecretMountPath) == 0 { + return nil, fmt.Errorf("invalid SecretMountPath specified for reading secrets") + } + + userPath := path.Join(r.SecretMountPath, "basic-auth-user") + user, userErr := ioutil.ReadFile(userPath) + if userErr != nil { + return nil, fmt.Errorf("unable to load %s", userPath) + } + + userPassword := path.Join(r.SecretMountPath, "basic-auth-password") + password, passErr := ioutil.ReadFile(userPassword) + if passErr != nil { + return nil, fmt.Errorf("Unable to load %s", userPassword) + } + + credentials = &BasicAuthCredentials{ + User: strings.TrimSpace(string(user)), + Password: strings.TrimSpace(string(password)), + } + + return credentials, nil +} diff --git a/gateway/types/readconfig.go b/gateway/types/readconfig.go index 8152b8ef..115c9d59 100644 --- a/gateway/types/readconfig.go +++ b/gateway/types/readconfig.go @@ -107,6 +107,12 @@ func (ReadConfig) Read(hasEnv HasEnv) GatewayConfig { cfg.UseBasicAuth = parseBoolValue(hasEnv.Getenv("basic_auth")) + secretPath := hasEnv.Getenv("secret_mount_path") + if len(secretPath) == 0 { + secretPath = "/run/secrets/" + } + cfg.SecretMountPath = secretPath + return cfg } @@ -145,6 +151,9 @@ type GatewayConfig struct { // If set, reads secrets from file-system for enabling basic auth. UseBasicAuth bool + + // SecretMountPath specifies where to read secrets from for embedded basic auth + SecretMountPath string } // UseNATS Use NATSor not diff --git a/gateway/types/readconfig_test.go b/gateway/types/readconfig_test.go index cd220931..f1a09489 100644 --- a/gateway/types/readconfig_test.go +++ b/gateway/types/readconfig_test.go @@ -209,11 +209,18 @@ func TestRead_BasicAuthDefaults(t *testing.T) { t.Logf("config.UseBasicAuth, want: %t, got: %t\n", false, config.UseBasicAuth) t.Fail() } + + wantSecretsMount := "/run/secrets/" + if config.SecretMountPath != wantSecretsMount { + t.Logf("config.SecretMountPath, want: %s, got: %s\n", wantSecretsMount, config.SecretMountPath) + t.Fail() + } } func TestRead_BasicAuth_SetTrue(t *testing.T) { defaults := NewEnvBucket() defaults.Setenv("basic_auth", "true") + defaults.Setenv("secret_mount_path", "/etc/openfaas/") readConfig := ReadConfig{} @@ -223,4 +230,10 @@ func TestRead_BasicAuth_SetTrue(t *testing.T) { t.Logf("config.UseBasicAuth, want: %t, got: %t\n", true, config.UseBasicAuth) t.Fail() } + + wantSecretsMount := "/etc/openfaas/" + if config.SecretMountPath != wantSecretsMount { + t.Logf("config.SecretMountPath, want: %s, got: %s\n", wantSecretsMount, config.SecretMountPath) + t.Fail() + } }