From fe9654c5bf816288f794f2576ab7e61e83c291b4 Mon Sep 17 00:00:00 2001 From: Rohith <gambol99@gmail.com> Date: Wed, 14 Sep 2016 23:45:03 +0100 Subject: [PATCH] - adding the ability to choose proxying the Authorization header (#133) --- CHANGELOG.md | 41 ++++++++++++++++++++++--------------- cli.go | 7 +++++++ config.go | 21 ++++++++++--------- doc.go | 5 ++++- middleware.go | 6 +++++- middleware_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++ server.go | 2 +- server_test.go | 31 +++++++++++++++------------- 8 files changed, 121 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b82eae..45aeeba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,13 @@ +#### **1.2.7** + +FIXES: + * Added unit tests for the logout handlers + * Added unit tests for the authorization header handling + +FEATURES: + * Allow the user to enable or disable adding the Authorization header + #### **1.2.6** FIXES: @@ -22,11 +31,11 @@ FIXES: * Fixes the expiration of the access token, if no idle-duration is * Fixed the forwarding proxy for SSL * Fixed the bug in the containedSubString method - + BREAKING CHANGES: - * Fixed up the config resource definition to use 'uri' not 'url' + * Fixed up the config resource definition to use 'uri' not 'url' * Removed the --idle-duration option, was never really implemented well - + #### **1.2.3** FEATURES: @@ -36,7 +45,7 @@ FEATURES: TODO: * Need a means to updating the client certificate once expired. - + CHANGES: * Updated the godeps for codegangsta cli to it's renamed version @@ -71,25 +80,25 @@ FIXES: FIXES: * Added a auto build to quay.io on the travis build for master and tags * Fixed the host header to proxy to upstreams outside of the proxy domain (https://github.com/golang/go/issues/7618) - * Adding a git+sha to the usage + * Adding a git+sha to the usage * Defaulting to gin mode release unless verbose is true * Removed the gin debug logging for tests and builds * Removed the default upstream, as it caught people by surprise and some accidentally forwarded to themselves * Changed the state parameter (which is used as a redirect) to base64 the value allowing you to use complex urls - + FEATURES: * Adding environment variables to some of the command line options * Adding the option of a forwarding agent, i.e. you can seat the proxy front of your application, - login to keycloak and use the proxy as forwarding agent to sign outbound requests. + login to keycloak and use the proxy as forwarding agent to sign outbound requests. * Adding the version information into a header on /oauth/health endpoint * Removed the need to specify a client-secret, which means to cope with authz only or public endpoints - * Added role url tokenizer, /auth/%role%/ will extract the role element and check the token as it + * Added role url tokenizer, /auth/%role%/ will extract the role element and check the token as it * Added proxy protocol support for the listening socket (--enable-proxy-protocol=true) * Added the ability to listen on a unix socket BREAKING CHANGES: * Changed the X-Auth-Subject, it not is the actual subject from the token (makes more sense). - X-Auth-UserID will either be the subject id or the preferred username + X-Auth-UserID will either be the subject id or the preferred username #### **1.0.6 (May 6th, 2016)** @@ -104,25 +113,25 @@ FEATURES: * An additional option --add-claims to inject custom claims from the token into the authentication headers i.e. --add-claims=given_name would add X-Auth-Given-Name (assumed the claims exists) * Added the --secure-cookie option to control the 'secure' flag on the cookie - + BREAKING CHANGES: * Changed the claims option from 'claims' to 'match-claims' (command line and config) - * Changed keepalive config option to the same as the command line 'keepalive' -> 'upstream-keepalives' + * Changed keepalive config option to the same as the command line 'keepalive' -> 'upstream-keepalives' * Changed the config option from 'upstream' to 'upstream-url', same as command line - + #### **1.0.4 (April 30th, 2016)** FIXES: * Fixes the cookie sessions expiration FEATURES: - * Adding a idle duration configuration option which controls the expiration of access token cookie and thus session. - If the session is not used within that period, the session is removed. + * Adding a idle duration configuration option which controls the expiration of access token cookie and thus session. + If the session is not used within that period, the session is removed. * The upstream endpoint has also be a unix socket - + BREAKING CHANGES: * Change the client id in json/yaml config file from clientid -> client-id - + #### **1.0.2 (April 22th, 2016)** FIXES: diff --git a/cli.go b/cli.go index 84ccffb..4fbe745 100644 --- a/cli.go +++ b/cli.go @@ -163,6 +163,10 @@ func getCLIOptions() []cli.Flag { Usage: "specifies the keep-alive period for an active network connection", Value: defaults.UpstreamKeepaliveTimeout, }, + cli.BoolTFlag{ + Name: "enable-authorization-header", + Usage: "adds the authorization header to the proxy request", + }, cli.BoolFlag{ Name: "enable-refresh-tokens", Usage: "enables the handling of the refresh tokens", @@ -423,6 +427,9 @@ func parseCLIOptions(cx *cli.Context, config *Config) (err error) { if cx.IsSet("enable-forwarding") { config.EnableForwarding = cx.Bool("enable-forwarding") } + if cx.IsSet("enable-authorization-header") { + config.EnableAuthorizationHeader = cx.Bool("enable-authorization-header") + } if cx.IsSet("enable-refresh-tokens") { config.EnableRefreshTokens = cx.Bool("enable-refresh-tokens") } diff --git a/config.go b/config.go index 682c0e7..173e81d 100644 --- a/config.go +++ b/config.go @@ -27,16 +27,17 @@ import ( // newDefaultConfig returns a initialized config func newDefaultConfig() *Config { return &Config{ - TagData: make(map[string]string, 0), - MatchClaims: make(map[string]string, 0), - Headers: make(map[string]string, 0), - UpstreamTimeout: time.Duration(10) * time.Second, - UpstreamKeepaliveTimeout: time.Duration(10) * time.Second, - CookieAccessName: "kc-access", - CookieRefreshName: "kc-state", - SecureCookie: true, - SkipUpstreamTLSVerify: true, - CrossOrigin: CORS{}, + TagData: make(map[string]string, 0), + MatchClaims: make(map[string]string, 0), + Headers: make(map[string]string, 0), + UpstreamTimeout: time.Duration(10) * time.Second, + UpstreamKeepaliveTimeout: time.Duration(10) * time.Second, + EnableAuthorizationHeader: true, + CookieAccessName: "kc-access", + CookieRefreshName: "kc-state", + SecureCookie: true, + SkipUpstreamTLSVerify: true, + CrossOrigin: CORS{}, } } diff --git a/doc.go b/doc.go index c9812cf..79dd5f7 100644 --- a/doc.go +++ b/doc.go @@ -24,7 +24,7 @@ import ( ) var ( - release = "v1.2.6" + release = "v1.2.7" gitsha = "no gitsha provided" version = release + " (git+sha: " + gitsha + ")" ) @@ -130,6 +130,9 @@ type Config struct { // LocalhostMetrics indicated the metrics can only be consume via localhost LocalhostMetrics bool `json:"localhost-only-metrics" yaml:"localhost-only-metrics"` + // EnableAuthorizationHeader indicates we should pass the authorization header + EnableAuthorizationHeader bool `json:"enable-authorization-header" yaml:"enable-authorization-header"` + // CookieDomain is a list of domains the cookie is available to CookieDomain string `json:"cookie-domain" yaml:"cookie-domain"` // CookieAccessName is the name of the access cookie holding the access token diff --git a/middleware.go b/middleware.go index bb62875..32b836c 100644 --- a/middleware.go +++ b/middleware.go @@ -427,7 +427,11 @@ func (r *oauthProxy) headersMiddleware(custom []string) gin.HandlerFunc { cx.Request.Header.Add("X-Auth-ExpiresIn", id.expiresAt.String()) cx.Request.Header.Add("X-Auth-Token", id.token.Encode()) cx.Request.Header.Add("X-Auth-Roles", strings.Join(id.roles, ",")) - cx.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", id.token.Encode())) + + // step: add the authorization header if requested + if r.config.EnableAuthorizationHeader { + cx.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", id.token.Encode())) + } // step: inject any custom claims for claim, header := range customClaims { diff --git a/middleware_test.go b/middleware_test.go index 6c92988..a1feb99 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -327,6 +327,57 @@ func TestCustomHeadersHandler(t *testing.T) { } } +func TestHeaderMiddlewareAuthorizationHeader(t *testing.T) { + cases := []struct { + Identity *userContext + Expected http.Header + Enabled bool + }{ + { + Enabled: true, + Identity: &userContext{ + email: "gambol99@gmail.com", + }, + Expected: http.Header{ + "X-Auth-Email": []string{"gambol99@gmail.com"}, + "Authorization": []string{"Bearer .."}, + }, + }, + { + Enabled: false, + Identity: &userContext{ + email: "gambol99@gmail.com", + }, + Expected: http.Header{ + "X-Auth-Email": []string{"gambol99@gmail.com"}, + "Authorization": []string{""}, + }, + }, + } + for i, x := range cases { + config := newFakeKeycloakConfig() + config.EnableAuthorizationHeader = x.Enabled + + // step: create the test proxy + p, _, _ := newTestProxyService(config) + context := newFakeGinContext("GET", "/test_url") + if x.Identity != nil { + context.Set(userContextName, x.Identity) + } + + // step: create a middleware handler + handler := p.headersMiddleware([]string{}) + handler(context) + + // step: and check we have all the headers + for k := range x.Expected { + assert.Equal(t, x.Expected.Get(k), context.Request.Header.Get(k), + "case %d, expected (%s: %s) got: (%s: %s)", + i, k, x.Expected.Get(k), k, context.Request.Header.Get(k)) + } + } +} + func TestAdmissionHandlerRoles(t *testing.T) { proxy := newFakeKeycloakProxyWithResources(t, []*Resource{ { diff --git a/server.go b/server.go index 46e539b..1fc3b45 100644 --- a/server.go +++ b/server.go @@ -275,7 +275,6 @@ func (r *oauthProxy) createForwardingProxy() error { // Run starts the proxy service // func (r *oauthProxy) Run() error { - var err error tlsConfig := &tls.Config{} // step: are we doing mutual tls? @@ -299,6 +298,7 @@ func (r *oauthProxy) Run() error { // step: create the listener var listener net.Listener + var err error switch strings.HasPrefix(r.config.Listen, "unix://") { case true: socket := strings.Trim(r.config.Listen, "unix://") diff --git a/server_test.go b/server_test.go index 8c2741f..d54edcd 100644 --- a/server_test.go +++ b/server_test.go @@ -128,16 +128,19 @@ func getFakeRealmAccessToken(t *testing.T) jose.JWT { func newFakeKeycloakConfig() *Config { return &Config{ - DiscoveryURL: "127.0.0.1:8080", - ClientID: fakeClientID, - ClientSecret: fakeSecret, - EncryptionKey: "AgXa7xRcoClDEU0ZDSH4X0XhL5Qy2Z2j", - SkipTokenVerification: true, - Scopes: []string{}, - EnableRefreshTokens: false, - SecureCookie: false, - CookieAccessName: "kc-access", - CookieRefreshName: "kc-state", + ClientID: fakeClientID, + ClientSecret: fakeSecret, + CookieAccessName: "kc-access", + CookieRefreshName: "kc-state", + DiscoveryURL: "127.0.0.1:8080", + EnableAuthorizationHeader: true, + EnableRefreshTokens: false, + EncryptionKey: "AgXa7xRcoClDEU0ZDSH4X0XhL5Qy2Z2j", + LogRequests: true, + Scopes: []string{}, + SecureCookie: false, + SkipTokenVerification: false, + Verbose: false, Resources: []*Resource{ { URL: fakeAdminRoleURL, @@ -194,11 +197,8 @@ func newTestProxyService(config *Config) (*oauthProxy, *fakeOAuthServer, string) } // step: set the config - config.LogRequests = true - config.SkipTokenVerification = false config.DiscoveryURL = auth.getLocation() config.RevocationEndpoint = auth.getRevocationURL() - config.Verbose = false // step: create a proxy proxy, err := newProxy(config) @@ -231,7 +231,10 @@ func newFakeKeycloakProxyWithResources(t *testing.T, resources []*Resource) *oau } func TestNewKeycloakProxy(t *testing.T) { - proxy, err := newProxy(newFakeKeycloakConfig()) + cfg := newFakeKeycloakConfig() + cfg.DiscoveryURL = newFakeOAuthServer().getLocation() + + proxy, err := newProxy(cfg) assert.NoError(t, err) assert.NotNil(t, proxy) assert.NotNil(t, proxy.config) -- GitLab