Read secrets from variable path

This change enables secrets to be read from any mount on disk
rather than hard-coding a certain location which suits Swarm or
K8s. The default value if not specified will look in the Swarm
location of /run/secrets/

README.md (docs) updated and set to off by default.

Signed-off-by: Alex Ellis (VMware) <alexellis2@gmail.com>
This commit is contained in:
Alex Ellis (VMware)
2018-05-22 14:46:29 +01:00
committed by Alex Ellis
parent a38931ce69
commit 8133414183
9 changed files with 91 additions and 25 deletions

View File

@ -15,6 +15,8 @@ services:
faas_nats_port: 4222 faas_nats_port: 4222
direct_functions: "true" # Functions are invoked directly over the overlay network direct_functions: "true" # Functions are invoked directly over the overlay network
direct_functions_suffix: "" direct_functions_suffix: ""
basic_auth: "false"
secret_mount_path: "/run/secrets/"
deploy: deploy:
resources: resources:
# limits: # uncomment to enable limits # limits: # uncomment to enable limits

View File

@ -16,6 +16,8 @@ services:
faas_nats_port: 4222 faas_nats_port: 4222
direct_functions: "true" # Functions are invoked directly over the overlay network direct_functions: "true" # Functions are invoked directly over the overlay network
direct_functions_suffix: "" direct_functions_suffix: ""
basic_auth: "false"
secret_mount_path: "/run/secrets/"
deploy: deploy:
resources: resources:
# limits: # Enable if you want to limit memory usage # limits: # Enable if you want to limit memory usage

View File

@ -54,3 +54,5 @@ The gateway can be configured through the following environment variables:
| `faas_promethus_port` | Port to connect to Prometheus. Default: `9090` | | `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` | `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 | | `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/`. |

View File

@ -2,10 +2,12 @@ package handlers
import ( import (
"net/http" "net/http"
"github.com/openfaas/faas/gateway/types"
) )
// DecorateWithBasicAuth enforces basic auth as a middleware with given credentials // 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) { return func(w http.ResponseWriter, r *http.Request) {
user, password, ok := r.BasicAuth() user, password, ok := r.BasicAuth()
@ -21,9 +23,3 @@ func DecorateWithBasicAuth(next http.HandlerFunc, credentials *BasicAuthCredenti
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
} }
} }
// BasicAuthCredentials for credentials
type BasicAuthCredentials struct {
User string
Password string
}

View File

@ -5,6 +5,8 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/openfaas/faas/gateway/types"
) )
func Test_AuthWithValidPassword_Gives200(t *testing.T) { func Test_AuthWithValidPassword_Gives200(t *testing.T) {
@ -18,7 +20,7 @@ func Test_AuthWithValidPassword_Gives200(t *testing.T) {
wantPassword := "password" wantPassword := "password"
r := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil) r := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
r.SetBasicAuth(wantUser, wantPassword) r.SetBasicAuth(wantUser, wantPassword)
wantCredentials := &BasicAuthCredentials{ wantCredentials := &types.BasicAuthCredentials{
User: wantUser, User: wantUser,
Password: wantPassword, Password: wantPassword,
} }
@ -47,7 +49,7 @@ func Test_AuthWithInvalidPassword_Gives403(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil) r := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
r.SetBasicAuth(wantUser, wantPassword) r.SetBasicAuth(wantUser, wantPassword)
wantCredentials := &BasicAuthCredentials{ wantCredentials := &types.BasicAuthCredentials{
User: wantUser, User: wantUser,
Password: "", Password: "",
} }

View File

@ -5,10 +5,8 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"strings"
"time" "time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -35,24 +33,17 @@ func main() {
log.Printf("Binding to external function provider: %s", config.FunctionsProviderURL) log.Printf("Binding to external function provider: %s", config.FunctionsProviderURL)
var credentials *handlers.BasicAuthCredentials var credentials *types.BasicAuthCredentials
if config.UseBasicAuth { if config.UseBasicAuth {
userPath := "/var/secrets/basic_auth_user" var readErr error
user, userErr := ioutil.ReadFile(userPath) reader := types.ReadBasicAuthFromDisk{
if userErr != nil { SecretMountPath: config.SecretMountPath,
log.Panicf("Unable to load %s", userPath)
} }
credentials, readErr = reader.Read()
userPassword := "/var/secrets/basic_auth_password" if readErr != nil {
password, passErr := ioutil.ReadFile(userPassword) log.Panicf(readErr.Error())
if passErr != nil {
log.Panicf("Unable to load %s", userPassword)
}
credentials = &handlers.BasicAuthCredentials{
User: strings.TrimSpace(string(user)),
Password: strings.TrimSpace(string(password)),
} }
} }

View File

@ -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
}

View File

@ -107,6 +107,12 @@ func (ReadConfig) Read(hasEnv HasEnv) GatewayConfig {
cfg.UseBasicAuth = parseBoolValue(hasEnv.Getenv("basic_auth")) 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 return cfg
} }
@ -145,6 +151,9 @@ type GatewayConfig struct {
// If set, reads secrets from file-system for enabling basic auth. // If set, reads secrets from file-system for enabling basic auth.
UseBasicAuth bool UseBasicAuth bool
// SecretMountPath specifies where to read secrets from for embedded basic auth
SecretMountPath string
} }
// UseNATS Use NATSor not // UseNATS Use NATSor not

View File

@ -209,11 +209,18 @@ func TestRead_BasicAuthDefaults(t *testing.T) {
t.Logf("config.UseBasicAuth, want: %t, got: %t\n", false, config.UseBasicAuth) t.Logf("config.UseBasicAuth, want: %t, got: %t\n", false, config.UseBasicAuth)
t.Fail() 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) { func TestRead_BasicAuth_SetTrue(t *testing.T) {
defaults := NewEnvBucket() defaults := NewEnvBucket()
defaults.Setenv("basic_auth", "true") defaults.Setenv("basic_auth", "true")
defaults.Setenv("secret_mount_path", "/etc/openfaas/")
readConfig := ReadConfig{} 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.Logf("config.UseBasicAuth, want: %t, got: %t\n", true, config.UseBasicAuth)
t.Fail() t.Fail()
} }
wantSecretsMount := "/etc/openfaas/"
if config.SecretMountPath != wantSecretsMount {
t.Logf("config.SecretMountPath, want: %s, got: %s\n", wantSecretsMount, config.SecretMountPath)
t.Fail()
}
} }