mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-28 18:06:34 +02:00
* config: generate cookie secret if not set in all-in-one mode * fix tests * config: add warning about cookie_secret * breakup lines
811 lines
25 KiB
Go
811 lines
25 KiB
Go
package config
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/spf13/viper"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/pomerium/pomerium/pkg/cryptutil"
|
|
"github.com/pomerium/pomerium/pkg/grpc/config"
|
|
)
|
|
|
|
var cmpOptIgnoreUnexported = cmpopts.IgnoreUnexported(Options{}, Policy{})
|
|
|
|
func Test_Validate(t *testing.T) {
|
|
t.Parallel()
|
|
testOptions := func() *Options {
|
|
o := NewDefaultOptions()
|
|
|
|
o.SharedKey = "test"
|
|
o.Services = "all"
|
|
o.CertFile = "./testdata/example-cert.pem"
|
|
o.KeyFile = "./testdata/example-key.pem"
|
|
return o
|
|
}
|
|
good := testOptions()
|
|
badServices := testOptions()
|
|
badServices.Services = "blue"
|
|
badSecret := testOptions()
|
|
badSecret.SharedKey = ""
|
|
badSecret.Services = "authenticate"
|
|
badSecretAllServices := testOptions()
|
|
badSecretAllServices.SharedKey = ""
|
|
|
|
badPolicyFile := testOptions()
|
|
badPolicyFile.PolicyFile = "file"
|
|
invalidStorageType := testOptions()
|
|
invalidStorageType.DataBrokerStorageType = "foo"
|
|
missingStorageDSN := testOptions()
|
|
missingStorageDSN.DataBrokerStorageType = "redis"
|
|
badSignoutRedirectURL := testOptions()
|
|
badSignoutRedirectURL.SignOutRedirectURLString = "--"
|
|
|
|
tests := []struct {
|
|
name string
|
|
testOpts *Options
|
|
wantErr bool
|
|
}{
|
|
{"good default with no env settings", good, false},
|
|
{"invalid service type", badServices, true},
|
|
{"missing shared secret", badSecret, true},
|
|
{"missing shared secret but all service", badSecretAllServices, false},
|
|
{"policy file specified", badPolicyFile, true},
|
|
{"invalid databroker storage type", invalidStorageType, true},
|
|
{"missing databroker storage dsn", missingStorageDSN, true},
|
|
{"invalid signout redirect url", badSignoutRedirectURL, true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.testOpts.Validate()
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_bindEnvs(t *testing.T) {
|
|
o := new(Options)
|
|
o.viper = viper.New()
|
|
v := viper.New()
|
|
os.Clearenv()
|
|
defer os.Unsetenv("POMERIUM_DEBUG")
|
|
defer os.Unsetenv("POLICY")
|
|
defer os.Unsetenv("HEADERS")
|
|
os.Setenv("POMERIUM_DEBUG", "true")
|
|
os.Setenv("POLICY", "LSBmcm9tOiBodHRwczovL2h0dHBiaW4ubG9jYWxob3N0LnBvbWVyaXVtLmlvCiAgdG86IAogICAgLSBodHRwOi8vbG9jYWxob3N0OjgwODEsMQo=")
|
|
os.Setenv("HEADERS", `{"X-Custom-1":"foo", "X-Custom-2":"bar"}`)
|
|
err := bindEnvs(o, v)
|
|
if err != nil {
|
|
t.Fatalf("failed to bind options to env vars: %s", err)
|
|
}
|
|
err = v.Unmarshal(o, ViperPolicyHooks)
|
|
if err != nil {
|
|
t.Errorf("Could not unmarshal %#v: %s", o, err)
|
|
}
|
|
o.viper = v
|
|
if !o.Debug {
|
|
t.Errorf("Failed to load POMERIUM_DEBUG from environment")
|
|
}
|
|
if len(o.Policies) != 1 {
|
|
t.Error("failed to bind POLICY env")
|
|
}
|
|
if o.Services != "" {
|
|
t.Errorf("Somehow got SERVICES from environment without configuring it")
|
|
}
|
|
if o.HeadersEnv != `{"X-Custom-1":"foo", "X-Custom-2":"bar"}` {
|
|
t.Errorf("Failed to bind headers env var to HeadersEnv")
|
|
}
|
|
}
|
|
|
|
func Test_parseHeaders(t *testing.T) {
|
|
// t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
want map[string]string
|
|
envHeaders string
|
|
viperHeaders interface{}
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"good env",
|
|
map[string]string{"X-Custom-1": "foo", "X-Custom-2": "bar"},
|
|
`{"X-Custom-1":"foo", "X-Custom-2":"bar"}`,
|
|
map[string]string{"X": "foo"},
|
|
false,
|
|
},
|
|
{
|
|
"good env not_json",
|
|
map[string]string{"X-Custom-1": "foo", "X-Custom-2": "bar"},
|
|
`X-Custom-1:foo,X-Custom-2:bar`,
|
|
map[string]string{"X": "foo"},
|
|
false,
|
|
},
|
|
{
|
|
"bad env",
|
|
map[string]string{},
|
|
"xyyyy",
|
|
map[string]string{"X": "foo"},
|
|
true,
|
|
},
|
|
{
|
|
"bad env not_json",
|
|
map[string]string{"X-Custom-1": "foo", "X-Custom-2": "bar"},
|
|
`X-Custom-1:foo,X-Custom-2bar`,
|
|
map[string]string{"X": "foo"},
|
|
true,
|
|
},
|
|
{
|
|
"bad viper",
|
|
map[string]string{},
|
|
"",
|
|
"notaheaderstruct",
|
|
true,
|
|
},
|
|
{
|
|
"good viper",
|
|
map[string]string{"X-Custom-1": "foo", "X-Custom-2": "bar"},
|
|
"",
|
|
map[string]string{"X-Custom-1": "foo", "X-Custom-2": "bar"},
|
|
false,
|
|
},
|
|
{
|
|
"new field name",
|
|
map[string]string{"X-Custom-1": "foo"},
|
|
"",
|
|
map[string]string{"X-Custom-1": "foo"},
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var (
|
|
o *Options
|
|
mu sync.Mutex
|
|
)
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
o = NewDefaultOptions()
|
|
o.viperSet("set_response_headers", tt.viperHeaders)
|
|
o.viperSet("HeadersEnv", tt.envHeaders)
|
|
o.HeadersEnv = tt.envHeaders
|
|
err := o.parseHeaders(context.Background())
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Error condition unexpected: err=%s", err)
|
|
}
|
|
|
|
if !tt.wantErr && !cmp.Equal(tt.want, o.SetResponseHeaders) {
|
|
t.Errorf("Did get expected headers: %s", cmp.Diff(tt.want, o.SetResponseHeaders))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_parsePolicyFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
opts := []cmp.Option{
|
|
cmpopts.IgnoreFields(Policy{}, "EnvoyOpts"),
|
|
cmpOptIgnoreUnexported,
|
|
}
|
|
|
|
source := "https://pomerium.io"
|
|
sourceURL, _ := url.ParseRequestURI(source)
|
|
|
|
to, err := ParseWeightedURL("https://httpbin.org")
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
policyBytes []byte
|
|
want []Policy
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"simple json",
|
|
[]byte(fmt.Sprintf(`{"policy":[{"from": "%s","to":"%s"}]}`, source, to.URL.String())),
|
|
[]Policy{{
|
|
From: source,
|
|
To: []WeightedURL{*to},
|
|
Source: &StringURL{sourceURL},
|
|
}},
|
|
false,
|
|
},
|
|
{"bad from", []byte(`{"policy":[{"from": "%","to":"httpbin.org"}]}`), nil, true},
|
|
{"bad to", []byte(`{"policy":[{"from": "pomerium.io","to":"%"}]}`), nil, true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tempFile, _ := os.CreateTemp("", "*.json")
|
|
defer tempFile.Close()
|
|
defer os.Remove(tempFile.Name())
|
|
tempFile.Write(tt.policyBytes)
|
|
var o Options
|
|
o.viper = viper.New()
|
|
o.viper.SetConfigFile(tempFile.Name())
|
|
if err := o.viper.ReadInConfig(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err := o.parsePolicy()
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("parsePolicyEnv() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if err == nil {
|
|
if diff := cmp.Diff(o.Policies, tt.want, opts...); diff != "" {
|
|
t.Errorf("parsePolicyEnv() = diff:%s", diff)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_Checksum(t *testing.T) {
|
|
o := NewDefaultOptions()
|
|
|
|
oldChecksum := o.Checksum()
|
|
o.SharedKey = "changemeplease"
|
|
newChecksum := o.Checksum()
|
|
|
|
if newChecksum == oldChecksum {
|
|
t.Errorf("Checksum() failed to update old = %d, new = %d", oldChecksum, newChecksum)
|
|
}
|
|
|
|
if newChecksum == 0 || oldChecksum == 0 {
|
|
t.Error("Checksum() not returning data")
|
|
}
|
|
|
|
if o.Checksum() != newChecksum {
|
|
t.Error("Checksum() inconsistent")
|
|
}
|
|
}
|
|
|
|
func TestOptionsFromViper(t *testing.T) {
|
|
opts := []cmp.Option{
|
|
cmpopts.IgnoreFields(Options{}, "CookieSecret", "GRPCInsecure", "GRPCAddr", "DataBrokerURLString", "DataBrokerURLStrings", "AuthorizeURLString", "AuthorizeURLStrings", "DefaultUpstreamTimeout", "CookieExpire", "Services", "Addr", "LogLevel", "KeyFile", "CertFile", "SharedKey", "ReadTimeout", "IdleTimeout", "GRPCClientTimeout", "GRPCClientDNSRoundRobin", "TracingSampleRate", "ProgrammaticRedirectDomainWhitelist"),
|
|
cmpopts.IgnoreFields(Policy{}, "Source", "EnvoyOpts"),
|
|
cmpOptIgnoreUnexported,
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
configBytes []byte
|
|
want *Options
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"good",
|
|
[]byte(`{"autocert_dir":"","insecure_server":true,"policy":[{"from": "https://from.example","to":"https://to.example"}]}`),
|
|
&Options{
|
|
Policies: []Policy{{From: "https://from.example", To: mustParseWeightedURLs(t, "https://to.example")}},
|
|
CookieName: "_pomerium",
|
|
CookieSecure: true,
|
|
InsecureServer: true,
|
|
CookieHTTPOnly: true,
|
|
AuthenticateCallbackPath: "/oauth2/callback",
|
|
DataBrokerStorageType: "memory",
|
|
EnvoyAdminAccessLogPath: os.DevNull,
|
|
EnvoyAdminProfilePath: os.DevNull,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"good disable header",
|
|
[]byte(`{"autocert_dir":"","insecure_server":true,"set_response_headers": {"disable":"true"},"policy":[{"from": "https://from.example","to":"https://to.example"}]}`),
|
|
&Options{
|
|
Policies: []Policy{{From: "https://from.example", To: mustParseWeightedURLs(t, "https://to.example")}},
|
|
CookieName: "_pomerium",
|
|
AuthenticateCallbackPath: "/oauth2/callback",
|
|
CookieSecure: true,
|
|
CookieHTTPOnly: true,
|
|
InsecureServer: true,
|
|
SetResponseHeaders: map[string]string{"disable": "true"},
|
|
DataBrokerStorageType: "memory",
|
|
EnvoyAdminAccessLogPath: os.DevNull,
|
|
EnvoyAdminProfilePath: os.DevNull,
|
|
},
|
|
false,
|
|
},
|
|
{"bad url", []byte(`{"policy":[{"from": "https://","to":"https://to.example"}]}`), nil, true},
|
|
{"bad policy", []byte(`{"policy":[{"allow_public_unauthenticated_access": "dog","to":"https://to.example"}]}`), nil, true},
|
|
{"bad file", []byte(`{''''}`), nil, true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tempFile, _ := os.CreateTemp("", "*.json")
|
|
defer tempFile.Close()
|
|
defer os.Remove(tempFile.Name())
|
|
tempFile.Write(tt.configBytes)
|
|
got, err := optionsFromViper(tempFile.Name())
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("optionsFromViper() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if diff := cmp.Diff(got, tt.want, opts...); diff != "" {
|
|
t.Errorf("newOptionsFromConfig() = %s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_NewOptionsFromConfigEnvVar(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
envKeyPairs map[string]string
|
|
wantErr bool
|
|
}{
|
|
{"good", map[string]string{"INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, false},
|
|
{"bad no shared secret", map[string]string{"INSECURE_SERVER": "true", "SERVICES": "authenticate"}, true},
|
|
{"good no shared secret in all mode", map[string]string{"INSECURE_SERVER": "true"}, false},
|
|
{"bad header", map[string]string{"HEADERS": "x;y;z", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, true},
|
|
{"bad authenticate url", map[string]string{"AUTHENTICATE_SERVICE_URL": "authenticate.example", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, true},
|
|
{"bad authorize url", map[string]string{"AUTHORIZE_SERVICE_URL": "authorize.example", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, true},
|
|
{"bad cert base64", map[string]string{"CERTIFICATE": "bad cert", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, true},
|
|
{"bad cert key base64", map[string]string{"CERTIFICATE_KEY": "bad cert", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, true},
|
|
{"no certs no insecure mode set", map[string]string{"SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, false},
|
|
{"good disable headers ", map[string]string{"HEADERS": "disable:true", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, false},
|
|
{"bad whitespace in secret", map[string]string{"INSECURE_SERVER": "true", "SERVICES": "authenticate", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=\n"}, true},
|
|
{"good forward auth url", map[string]string{"FORWARD_AUTH_URL": "https://databroker.example", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, false},
|
|
{"bad forward auth url", map[string]string{"FORWARD_AUTH_URL": "databroker.example", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, true},
|
|
{"same addr and grpc addr", map[string]string{"SERVICES": "databroker", "ADDRESS": "0", "GRPC_ADDRESS": "0", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, false},
|
|
{"bad cert files", map[string]string{"INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", "CERTIFICATES": "./test-data/example-cert.pem"}, true},
|
|
{"good cert file", map[string]string{"CERTIFICATE_FILE": "./testdata/example-cert.pem", "CERTIFICATE_KEY_FILE": "./testdata/example-key.pem", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, false},
|
|
{"bad cert file", map[string]string{"CERTIFICATE_FILE": "./testdata/example-cert-bad.pem", "CERTIFICATE_KEY_FILE": "./testdata/example-key-bad.pem", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, true},
|
|
{"good client ca file", map[string]string{"CLIENT_CA_FILE": "./testdata/ca.pem", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, false},
|
|
{"bad client ca file", map[string]string{"CLIENT_CA_FILE": "./testdata/bad-ca.pem", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, true},
|
|
{"bad client ca b64", map[string]string{"CLIENT_CA": "bad cert", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
for k, v := range tt.envKeyPairs {
|
|
os.Setenv(k, v)
|
|
defer os.Unsetenv(k)
|
|
}
|
|
_, err := newOptionsFromConfig("")
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("newOptionsFromConfig() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_AutoCertOptionsFromEnvVar(t *testing.T) {
|
|
type test struct {
|
|
envs map[string]string
|
|
expected AutocertOptions
|
|
wantErr bool
|
|
cleanup func()
|
|
}
|
|
|
|
tests := map[string]func(t *testing.T) test{
|
|
"ok/simple": func(t *testing.T) test {
|
|
envs := map[string]string{
|
|
"AUTOCERT": "true",
|
|
"AUTOCERT_DIR": "/test",
|
|
"AUTOCERT_MUST_STAPLE": "true",
|
|
|
|
"INSECURE_SERVER": "true",
|
|
}
|
|
return test{
|
|
envs: envs,
|
|
expected: AutocertOptions{
|
|
Enable: true,
|
|
Folder: "/test",
|
|
MustStaple: true,
|
|
},
|
|
wantErr: false,
|
|
}
|
|
},
|
|
"ok/custom-ca": func(t *testing.T) test {
|
|
certPEM, err := newCACertPEM()
|
|
require.NoError(t, err)
|
|
envs := map[string]string{
|
|
"AUTOCERT": "true",
|
|
"AUTOCERT_CA": "test-ca.example.com/directory",
|
|
"AUTOCERT_EMAIL": "test@example.com",
|
|
"AUTOCERT_EAB_KEY_ID": "keyID",
|
|
"AUTOCERT_EAB_MAC_KEY": "fake-key",
|
|
"AUTOCERT_TRUSTED_CA": base64.StdEncoding.EncodeToString(certPEM),
|
|
"AUTOCERT_DIR": "/test",
|
|
"AUTOCERT_MUST_STAPLE": "true",
|
|
|
|
"INSECURE_SERVER": "true",
|
|
}
|
|
return test{
|
|
envs: envs,
|
|
wantErr: false,
|
|
expected: AutocertOptions{
|
|
Enable: true,
|
|
CA: "test-ca.example.com/directory",
|
|
Email: "test@example.com",
|
|
EABKeyID: "keyID",
|
|
EABMACKey: "fake-key",
|
|
TrustedCA: base64.StdEncoding.EncodeToString(certPEM),
|
|
Folder: "/test",
|
|
MustStaple: true,
|
|
},
|
|
}
|
|
},
|
|
"ok/custom-ca-file": func(t *testing.T) test {
|
|
certPEM, err := newCACertPEM()
|
|
require.NoError(t, err)
|
|
f, err := os.CreateTemp("", "pomerium-test-ca")
|
|
require.NoError(t, err)
|
|
n, err := f.Write(certPEM)
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(certPEM), n)
|
|
envs := map[string]string{
|
|
"AUTOCERT": "true",
|
|
"AUTOCERT_CA": "test-ca.example.com/directory",
|
|
"AUTOCERT_EMAIL": "test@example.com",
|
|
"AUTOCERT_EAB_KEY_ID": "keyID",
|
|
"AUTOCERT_EAB_MAC_KEY": "fake-key",
|
|
"AUTOCERT_TRUSTED_CA_FILE": f.Name(),
|
|
"AUTOCERT_DIR": "/test",
|
|
"AUTOCERT_MUST_STAPLE": "true",
|
|
|
|
"INSECURE_SERVER": "true",
|
|
}
|
|
return test{
|
|
envs: envs,
|
|
wantErr: false,
|
|
expected: AutocertOptions{
|
|
Enable: true,
|
|
CA: "test-ca.example.com/directory",
|
|
Email: "test@example.com",
|
|
EABKeyID: "keyID",
|
|
EABMACKey: "fake-key",
|
|
TrustedCAFile: f.Name(),
|
|
Folder: "/test",
|
|
MustStaple: true,
|
|
},
|
|
cleanup: func() { os.Remove(f.Name()) },
|
|
}
|
|
},
|
|
}
|
|
|
|
for name, run := range tests {
|
|
tc := run(t)
|
|
t.Run(name, func(t *testing.T) {
|
|
for k, v := range tc.envs {
|
|
os.Setenv(k, v)
|
|
defer os.Unsetenv(k)
|
|
}
|
|
o, err := newOptionsFromConfig("")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !cmp.Equal(tc.expected, o.AutocertOptions) {
|
|
t.Errorf("AutoCertOptionsFromEnvVar() diff = %s", cmp.Diff(tc.expected, o.AutocertOptions))
|
|
}
|
|
if tc.cleanup != nil {
|
|
tc.cleanup()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHTTPRedirectAddressStripQuotes(t *testing.T) {
|
|
o := NewDefaultOptions()
|
|
o.InsecureServer = true
|
|
o.HTTPRedirectAddr = `":80"`
|
|
assert.NoError(t, o.Validate())
|
|
assert.Equal(t, ":80", o.HTTPRedirectAddr)
|
|
}
|
|
|
|
func TestCertificatesArrayParsing(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCertFileRef := "./testdata/example-cert.pem"
|
|
testKeyFileRef := "./testdata/example-key.pem"
|
|
testCertFile, _ := os.ReadFile(testCertFileRef)
|
|
testKeyFile, _ := os.ReadFile(testKeyFileRef)
|
|
testCertAsBase64 := base64.StdEncoding.EncodeToString(testCertFile)
|
|
testKeyAsBase64 := base64.StdEncoding.EncodeToString(testKeyFile)
|
|
|
|
tests := []struct {
|
|
name string
|
|
certificateFiles []certificateFilePair
|
|
wantErr bool
|
|
}{
|
|
{"Handles base64 string as params", []certificateFilePair{{KeyFile: testKeyAsBase64, CertFile: testCertAsBase64}}, false},
|
|
{"Handles file reference as params", []certificateFilePair{{KeyFile: testKeyFileRef, CertFile: testCertFileRef}}, false},
|
|
{"Returns an error otherwise", []certificateFilePair{{KeyFile: "abc", CertFile: "abc"}}, true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
o := NewDefaultOptions()
|
|
o.CertificateFiles = tt.certificateFiles
|
|
err := o.Validate()
|
|
|
|
if err != nil && tt.wantErr == false {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompareByteSliceSlice(t *testing.T) {
|
|
type Bytes = [][]byte
|
|
|
|
tests := []struct {
|
|
expect int
|
|
a Bytes
|
|
b Bytes
|
|
}{
|
|
{
|
|
0,
|
|
Bytes{
|
|
{0, 1, 2, 3},
|
|
},
|
|
Bytes{
|
|
{0, 1, 2, 3},
|
|
},
|
|
},
|
|
{
|
|
-1,
|
|
Bytes{
|
|
{0, 1, 2, 3},
|
|
},
|
|
Bytes{
|
|
{0, 1, 2, 4},
|
|
},
|
|
},
|
|
{
|
|
1,
|
|
Bytes{
|
|
{0, 1, 2, 4},
|
|
},
|
|
Bytes{
|
|
{0, 1, 2, 3},
|
|
},
|
|
},
|
|
{
|
|
-1,
|
|
Bytes{
|
|
{0, 1, 2, 3},
|
|
},
|
|
Bytes{
|
|
{0, 1, 2, 3},
|
|
{4, 5, 6, 7},
|
|
},
|
|
},
|
|
{
|
|
1,
|
|
Bytes{
|
|
{0, 1, 2, 3},
|
|
{4, 5, 6, 7},
|
|
},
|
|
Bytes{
|
|
{0, 1, 2, 3},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
actual := compareByteSliceSlice(tt.a, tt.b)
|
|
if tt.expect != actual {
|
|
t.Errorf("expected compare(%v, %v) to be %v but got %v",
|
|
tt.a, tt.b, tt.expect, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOptions_DefaultURL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
firstURL := func(f func() ([]*url.URL, error)) func() (*url.URL, error) {
|
|
return func() (*url.URL, error) {
|
|
urls, err := f()
|
|
if err != nil {
|
|
return nil, err
|
|
} else if len(urls) == 0 {
|
|
return nil, fmt.Errorf("no url defined")
|
|
}
|
|
return urls[0], nil
|
|
}
|
|
}
|
|
|
|
defaultOptions := &Options{}
|
|
opts := &Options{
|
|
AuthenticateURLString: "https://authenticate.example.com",
|
|
AuthorizeURLString: "https://authorize.example.com",
|
|
DataBrokerURLString: "https://databroker.example.com",
|
|
ForwardAuthURLString: "https://forwardauth.example.com",
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
f func() (*url.URL, error)
|
|
expectedURLStr string
|
|
}{
|
|
{"default authenticate url", defaultOptions.GetAuthenticateURL, "https://127.0.0.1"},
|
|
{"default authorize url", defaultOptions.GetAuthenticateURL, "https://127.0.0.1"},
|
|
{"default databroker url", defaultOptions.GetAuthenticateURL, "https://127.0.0.1"},
|
|
{"default forward auth url", defaultOptions.GetAuthenticateURL, "https://127.0.0.1"},
|
|
{"good authenticate url", opts.GetAuthenticateURL, "https://authenticate.example.com"},
|
|
{"good authorize url", firstURL(opts.GetAuthorizeURLs), "https://authorize.example.com"},
|
|
{"good databroker url", firstURL(opts.GetDataBrokerURLs), "https://databroker.example.com"},
|
|
{"good forward auth url", opts.GetForwardAuthURL, "https://forwardauth.example.com"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
u, err := tc.f()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.expectedURLStr, u.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOptions_GetOauthOptions(t *testing.T) {
|
|
opts := &Options{AuthenticateURLString: "https://authenticate.example.com"}
|
|
oauthOptions, err := opts.GetOauthOptions()
|
|
require.NoError(t, err)
|
|
|
|
// Test that oauth redirect url hostname must point to authenticate url hostname.
|
|
u, err := opts.GetAuthenticateURL()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, u.Hostname(), oauthOptions.RedirectURL.Hostname())
|
|
}
|
|
|
|
func TestOptions_GetAllRouteableGRPCDomains(t *testing.T) {
|
|
opts := &Options{
|
|
AuthenticateURLString: "https://authenticate.example.com",
|
|
AuthorizeURLString: "https://authorize.example.com",
|
|
DataBrokerURLString: "https://databroker.example.com",
|
|
Services: "all",
|
|
}
|
|
domains, err := opts.GetAllRouteableGRPCDomains()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{
|
|
"authorize.example.com",
|
|
"authorize.example.com:443",
|
|
"databroker.example.com",
|
|
"databroker.example.com:443",
|
|
}, domains)
|
|
}
|
|
|
|
func TestOptions_GetAllRouteableHTTPDomains(t *testing.T) {
|
|
p1 := Policy{From: "https://from1.example.com"}
|
|
p1.Validate()
|
|
p2 := Policy{From: "https://from2.example.com"}
|
|
p2.Validate()
|
|
p3 := Policy{From: "https://from3.example.com", TLSDownstreamServerName: "from.example.com"}
|
|
p3.Validate()
|
|
|
|
opts := &Options{
|
|
AuthenticateURLString: "https://authenticate.example.com",
|
|
AuthorizeURLString: "https://authorize.example.com",
|
|
DataBrokerURLString: "https://databroker.example.com",
|
|
Policies: []Policy{p1, p2, p3},
|
|
Services: "all",
|
|
}
|
|
domains, err := opts.GetAllRouteableHTTPDomains()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{
|
|
"authenticate.example.com",
|
|
"authenticate.example.com:443",
|
|
"from.example.com",
|
|
"from.example.com:443",
|
|
"from1.example.com",
|
|
"from1.example.com:443",
|
|
"from2.example.com",
|
|
"from2.example.com:443",
|
|
"from3.example.com",
|
|
"from3.example.com:443",
|
|
}, domains)
|
|
}
|
|
|
|
func TestOptions_ApplySettings(t *testing.T) {
|
|
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Second)
|
|
defer clearTimeout()
|
|
|
|
t.Run("certificates", func(t *testing.T) {
|
|
options := NewDefaultOptions()
|
|
cert1, err := cryptutil.GenerateSelfSignedCertificate("example.com")
|
|
require.NoError(t, err)
|
|
options.CertificateFiles = append(options.CertificateFiles, certificateFilePair{
|
|
CertFile: base64.StdEncoding.EncodeToString(encodeCert(cert1)),
|
|
})
|
|
cert2, err := cryptutil.GenerateSelfSignedCertificate("example.com")
|
|
require.NoError(t, err)
|
|
cert3, err := cryptutil.GenerateSelfSignedCertificate("not.example.com")
|
|
require.NoError(t, err)
|
|
|
|
settings := &config.Settings{
|
|
Certificates: []*config.Settings_Certificate{
|
|
{CertBytes: encodeCert(cert2)},
|
|
{CertBytes: encodeCert(cert3)},
|
|
},
|
|
}
|
|
options.ApplySettings(ctx, settings)
|
|
assert.Len(t, options.CertificateFiles, 2, "should prevent adding duplicate certificates")
|
|
})
|
|
}
|
|
|
|
func TestOptions_GetSetResponseHeaders(t *testing.T) {
|
|
t.Run("lax", func(t *testing.T) {
|
|
options := NewDefaultOptions()
|
|
assert.Equal(t, map[string]string{
|
|
"X-Frame-Options": "SAMEORIGIN",
|
|
"X-XSS-Protection": "1; mode=block",
|
|
}, options.GetSetResponseHeaders(false))
|
|
})
|
|
t.Run("strict", func(t *testing.T) {
|
|
options := NewDefaultOptions()
|
|
assert.Equal(t, map[string]string{
|
|
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
|
|
"X-Frame-Options": "SAMEORIGIN",
|
|
"X-XSS-Protection": "1; mode=block",
|
|
}, options.GetSetResponseHeaders(true))
|
|
})
|
|
t.Run("disable", func(t *testing.T) {
|
|
options := NewDefaultOptions()
|
|
options.SetResponseHeaders = map[string]string{DisableHeaderKey: "1", "x-other": "xyz"}
|
|
assert.Equal(t, map[string]string{}, options.GetSetResponseHeaders(true))
|
|
})
|
|
}
|
|
|
|
func TestOptions_GetSharedKey(t *testing.T) {
|
|
t.Run("default", func(t *testing.T) {
|
|
o := NewDefaultOptions()
|
|
bs, err := o.GetSharedKey()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, randomSharedKey, base64.StdEncoding.EncodeToString(bs))
|
|
})
|
|
t.Run("missing", func(t *testing.T) {
|
|
o := NewDefaultOptions()
|
|
o.Services = ServiceProxy
|
|
_, err := o.GetSharedKey()
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestOptions_GetCookieSecret(t *testing.T) {
|
|
t.Run("default", func(t *testing.T) {
|
|
o := NewDefaultOptions()
|
|
bs, err := o.GetCookieSecret()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, randomSharedKey, base64.StdEncoding.EncodeToString(bs))
|
|
})
|
|
t.Run("missing", func(t *testing.T) {
|
|
o := NewDefaultOptions()
|
|
o.Services = ServiceProxy
|
|
_, err := o.GetCookieSecret()
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func encodeCert(cert *tls.Certificate) []byte {
|
|
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]})
|
|
}
|
|
|
|
func mustParseWeightedURLs(t *testing.T, urls ...string) []WeightedURL {
|
|
wu, err := ParseWeightedUrls(urls...)
|
|
require.NoError(t, err)
|
|
return wu
|
|
}
|