diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b82eae2d905aaf6446d25df51e87819b157e3d9..45aeeba6143ed8192bed101296e4e2e98467b4ca 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 84ccffb18384c8d111aa86ecd5d400f24bf6bf39..4fbe745fb7320b71d7de80a5e76089278ff71f65 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 682c0e76b668c3736eaac80cd915993074a6dd8e..173e81dbc3ca1f56fe4e02e041c5e272d8fd290b 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 c9812cf3a9e52ff47e8b5b8209ca16c89ed1e4fd..79dd5f7b8436c16ce24c68633998a81b99a0f581 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 bb628757d89ff53b810b1fbdcd2360aa56a25f36..32b836c3940a2920addba867d1af1c5087251546 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 6c92988b7c39af38932a106090c2e3f4b3dae74d..a1feb99601c635810b297a82f38425517188775e 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 46e539b7039d249576ee5167a7ba2d589b58da06..1fc3b459bfa352b105cadf968885348ad275872a 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 8c2741ff3a3b6e153b2e59a91ee397db209d34a1..d54edcde224f2eda14541393cbcdaa998010f483 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)