From 989062db8effde58e92f10e23d57216dac8174fb Mon Sep 17 00:00:00 2001 From: Travis Groth Date: Mon, 1 Jul 2019 21:23:14 -0400 Subject: [PATCH] Allow empty policies at startup --- CHANGELOG.md | 1 + authorize/authorize.go | 4 ---- authorize/authorize_test.go | 4 ++-- authorize/identity.go | 7 +++++++ authorize/identity_test.go | 1 + docs/reference/readme.md | 3 ++- proxy/handlers_test.go | 3 +++ proxy/proxy.go | 10 +++++++--- proxy/proxy_test.go | 8 +++++++- 9 files changed, 30 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14598135b..e4e1bc03d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### CHANGED - Proxy's sign out handler `{}/.pomerium/sign_out` now accepts an optional `redirect_uri` parameter which can be used to specify a custom redirect page, so long as it is under the same top-level domain. [GH-183] +- Policy configuration can now be empty at startup [GH-190] ### FIXED diff --git a/authorize/authorize.go b/authorize/authorize.go index 1fbff172c..9e45632c8 100644 --- a/authorize/authorize.go +++ b/authorize/authorize.go @@ -2,7 +2,6 @@ package authorize // import "github.com/pomerium/pomerium/authorize" import ( "encoding/base64" - "errors" "fmt" "github.com/pomerium/pomerium/internal/log" @@ -22,9 +21,6 @@ func ValidateOptions(o config.Options) error { if len(decoded) != 32 { return fmt.Errorf("authorize: `SHARED_SECRET` want 32 but got %d bytes", len(decoded)) } - if len(o.Policies) == 0 { - return errors.New("missing setting: no policies defined") - } return nil } diff --git a/authorize/authorize_test.go b/authorize/authorize_test.go index decddae92..73aa34279 100644 --- a/authorize/authorize_test.go +++ b/authorize/authorize_test.go @@ -22,8 +22,8 @@ func TestNew(t *testing.T) { {"bad shared secret", "AZA85podM73CjLCjViDNz1EUvvejKpWp7Hysr0knXA==", policies, true}, {"really bad shared secret", "sup", policies, true}, {"validation error, short secret", "AZA85podM73CjLCjViDNz1EUvvejKpWp7Hysr0knXA==", policies, true}, - {"empty options", "", []policy.Policy{}, true}, // special case - {"missing policies", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", []policy.Policy{}, true}, // special case + {"empty options", "", []policy.Policy{}, true}, // special case + {"missing policies", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", []policy.Policy{}, false}, // special case } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/authorize/identity.go b/authorize/identity.go index f8891ea15..9b7e05646 100644 --- a/authorize/identity.go +++ b/authorize/identity.go @@ -56,6 +56,13 @@ type whitelist struct { // newIdentityWhitelistMap takes a slice of policies and creates a hashmap of identity // authorizations per-route for each allowed group, domain, and email. func newIdentityWhitelistMap(policies []policy.Policy, admins []string) *whitelist { + + policyCount := len(policies) + if policyCount == 0 { + log.Warn().Msg("authorize: loaded configuration with no policies specified") + } + log.Info().Int("policy-count", policyCount).Msg("authorize: updated policies") + var wl whitelist wl.access = make(map[string]bool, len(policies)*3) for _, p := range policies { diff --git a/authorize/identity_test.go b/authorize/identity_test.go index bf6c9b884..2ac398225 100644 --- a/authorize/identity_test.go +++ b/authorize/identity_test.go @@ -50,6 +50,7 @@ func Test_IdentityWhitelistMap(t *testing.T) { {"valid user email", []policy.Policy{{From: "example.com", AllowedEmails: []string{"user@example.com"}}}, "example.com", &Identity{Email: "user@example.com"}, nil, true}, {"invalid user email", []policy.Policy{{From: "example.com", AllowedEmails: []string{"user@example.com"}}}, "example.com", &Identity{Email: "user2@example.com"}, nil, false}, {"empty everything", []policy.Policy{{From: "example.com"}}, "example.com", &Identity{Email: "user2@example.com"}, nil, false}, + {"empty policy", []policy.Policy{}, "example.com", &Identity{Email: "user@example.com"}, nil, false}, // impersonation related {"admin not impersonating allowed", []policy.Policy{{From: "example.com", AllowedDomains: []string{"example.com"}}}, "example.com", &Identity{Email: "admin@example.com"}, []string{"admin@example.com"}, true}, {"admin not impersonating denied", []policy.Policy{{From: "example.com", AllowedDomains: []string{"example.com"}}}, "example.com", &Identity{Email: "admin@admin-domain.com"}, []string{"admin@admin-domain.com"}, false}, diff --git a/docs/reference/readme.md b/docs/reference/readme.md index c0daae501..292c75df6 100644 --- a/docs/reference/readme.md +++ b/docs/reference/readme.md @@ -194,7 +194,8 @@ Expose a prometheus format HTTP endpoint on the specified port. Disabled by def - Environmental Variable: `POLICY` - Config File Key: `policy` - Type: [base64 encoded] `string` or inline policy structure in config file -- Required +- Required + - Required to forward traffic. Pomerium will safely start without a policy configured, but will be unable to authorize or proxy traffic until the configuration is updated to contain a policy. Policy contains route specific settings, and access control details. If you are configuring via POLICY environment variable, just the contents of the policy needs to be passed. If you are configuring via file, the policy should be present under the policy key. For example, diff --git a/proxy/handlers_test.go b/proxy/handlers_test.go index 21d9f912a..13e6926b3 100644 --- a/proxy/handlers_test.go +++ b/proxy/handlers_test.go @@ -242,6 +242,7 @@ func TestProxy_router(t *testing.T) { {"good with slash", "https://corp.example.com/", policies, nil, true}, {"good with path", "https://corp.example.com/123", policies, nil, true}, // {"multiple", "https://corp.example.com/", map[string]string{"corp.unrelated.com": "unrelated.com", "corp.example.com": "example.com"}, nil, true}, + {"no policies", "https://notcorp.example.com/123", []policy.Policy{}, nil, false}, {"bad corp", "https://notcorp.example.com/123", policies, nil, false}, {"bad sub-sub", "https://notcorp.corp.example.com/123", policies, nil, false}, } @@ -280,6 +281,7 @@ func TestProxy_Proxy(t *testing.T) { opts, optsWs := testOptionsTestServer(ts.URL), testOptionsTestServer(ts.URL) optsCORS := testOptionsWithCORS(ts.URL) optsPublic := testOptionsWithPublicAccess(ts.URL) + optsNoPolicies := testOptionsWithEmptyPolicies(ts.URL) optsWs.AllowWebsockets = true defaultHeaders, goodCORSHeaders, badCORSHeaders, headersWs := http.Header{}, http.Header{}, http.Header{}, http.Header{} @@ -327,6 +329,7 @@ func TestProxy_Proxy(t *testing.T) { {"ws supported, ws connection", optsWs, http.MethodGet, headersWs, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK}, {"ws supported, http connection", optsWs, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK}, {"ws unsupported, ws connection", opts, http.MethodGet, headersWs, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest}, + {"No policies", optsNoPolicies, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusNotFound}, } for _, tt := range tests { diff --git a/proxy/proxy.go b/proxy/proxy.go index 1128a4e37..5e8e36bf6 100755 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -44,9 +44,6 @@ func ValidateOptions(o config.Options) error { if len(decoded) != 32 { return fmt.Errorf("`SHARED_SECRET` want 32 but got %d bytes", len(decoded)) } - if len(o.Policies) == 0 { - return errors.New("missing setting: no policies defined") - } if o.AuthenticateURL.String() == "" { return errors.New("missing setting: authenticate-service-url") } @@ -182,6 +179,13 @@ func New(opts config.Options) (*Proxy, error) { // UpdatePolicies updates the handlers based on the configured policies func (p *Proxy) UpdatePolicies(opts config.Options) error { routeConfigs := make(map[string]*routeConfig) + + policyCount := len(opts.Policies) + if policyCount == 0 { + log.Warn().Msg("proxy: loaded configuration with no policies specified") + } + log.Info().Int("policy-count", policyCount).Msg("proxy: updated policies") + for _, route := range opts.Policies { proxy := NewReverseProxy(route.Destination) handler, err := NewReverseProxyHandler(opts, proxy, &route) diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index e565eba1f..2d87a57aa 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -149,6 +149,12 @@ func testOptionsWithPublicAccessAndWhitelist(uri string) config.Options { return opts } +func testOptionsWithEmptyPolicies(uri string) config.Options { + opts := testOptionsTestServer(uri) + opts.Policies = []policy.Policy{} + return opts +} + func TestOptions_Validate(t *testing.T) { good := testOptions() badAuthURL := testOptions() @@ -191,7 +197,7 @@ func TestOptions_Validate(t *testing.T) { {"short cookie secret", shortCookieLength, true}, {"no shared secret", badSharedKey, true}, {"invalid signing key", invalidSignKey, true}, - {"missing policy", missingPolicy, true}, + {"missing policy", missingPolicy, false}, {"shared secret bad base64", sharedKeyBadBas64, true}, } for _, tt := range tests {