diff --git a/CHANGELOG.md b/CHANGELOG.md index f0ec61dd5..4be47759c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ FEATURES: * Added spelling check to the tests [#PR322](https://github.com/gambol99/keycloak-proxy/pull/322) * Added the X-Auth-Audience to the upstream headers [#PR319](https://github.com/gambol99/keycloak-proxy/pull/319) * Added the ability to control the timeout on the initial openid configuration from .well-known/openid-configuration [#PR315](https://github.com/gambol99/keycloak-proxy/pull/315) +* Added the feature to customize the oauth prefix (defaults to /oauth) [#PR326](https://github.com/gambol99/keycloak-proxy/pull/326) * Added a `enable-logout-redirect` which redirects the /oauth/logout to the provider [#PR327](https://github.com/gambol99/keycloak-proxy/pull/327) * Adding additional metrics covering provider request latency, token breakdown [#PR324](https://github.com/gambol99/keycloak-proxy/pull/324) * Added environment variables alternatives for the forwarding username and password [#PR329]https://github.com/gambol99/keycloak-proxy/pull/329) diff --git a/config.go b/config.go index 5cc293f51..877d33c75 100644 --- a/config.go +++ b/config.go @@ -36,6 +36,7 @@ func newDefaultConfig() *Config { Headers: make(map[string]string), LetsEncryptCacheDir: "./cache/", MatchClaims: make(map[string]string), + OAuthURI: "/oauth", OpenIDProviderTimeout: 30 * time.Second, SecureCookie: true, ServerIdleTimeout: 120 * time.Second, @@ -54,6 +55,11 @@ func newDefaultConfig() *Config { } } +// WithOAuthURI returns the oauth uri +func (r *Config) WithOAuthURI(uri string) string { + return fmt.Sprintf("%s/%s", r.OAuthURI, uri) +} + // isValid validates if the config is valid func (r *Config) isValid() error { if r.Listen == "" { diff --git a/doc.go b/doc.go index f16e7f213..5d92062b6 100644 --- a/doc.go +++ b/doc.go @@ -46,7 +46,6 @@ const ( httpSchema = "http" versionHeader = "X-Auth-Proxy-Version" - oauthURL = "/oauth" authorizationURL = "/authorize" callbackURL = "/callback" expiredURL = "/expired" @@ -165,6 +164,8 @@ type Config struct { OpenIDProviderProxy string `json:"openid-provider-proxy" yaml:"openid-provider-proxy" usage:"proxy for communication with the openid provider"` // OpenIDProviderTimeout is the timeout used to pulling the openid configuration from the provider OpenIDProviderTimeout time.Duration `json:"openid-provider-timeout" yaml:"openid-provider-timeout" usage:"timeout for openid configuration on .well-known/openid-configuration"` + // OAuthURI is the uri for the oauth endpoints for the proxy + OAuthURI string `json:"oauth-uri" yaml:"oauth-uri" usage:"the uri for proxy oauth endpoints" env:"OAUTH_URI"` // Scopes is a list of scope we should request Scopes []string `json:"scopes" yaml:"scopes" usage:"list of scopes requested when authenticating the user"` // Upstream is the upstream endpoint i.e whom were proxying to diff --git a/handlers.go b/handlers.go index dca0faf98..fb0874b13 100644 --- a/handlers.go +++ b/handlers.go @@ -55,7 +55,7 @@ func (r *oauthProxy) getRedirectionURL(w http.ResponseWriter, req *http.Request) redirect = r.config.RedirectionURL } - return fmt.Sprintf("%s/oauth/callback", redirect) + return fmt.Sprintf("%s%s", redirect, r.config.WithOAuthURI("callback")) } // oauthAuthorizationHandler is responsible for performing the redirection to oauth provider diff --git a/handlers_test.go b/handlers_test.go index 9455fc9c3..8042a7ef0 100644 --- a/handlers_test.go +++ b/handlers_test.go @@ -41,7 +41,8 @@ func TestDebugHandler(t *testing.T) { } func TestExpirationHandler(t *testing.T) { - uri := oauthURL + expiredURL + cfg := newFakeKeycloakConfig() + uri := cfg.WithOAuthURI(expiredURL) requests := []fakeRequest{ { URI: uri, @@ -78,8 +79,8 @@ func TestLoginHandlerDisabled(t *testing.T) { c := newFakeKeycloakConfig() c.EnableLoginHandler = false requests := []fakeRequest{ - {URI: oauthURL + loginURL, Method: http.MethodPost, ExpectedCode: http.StatusNotImplemented}, - {URI: oauthURL + loginURL, ExpectedCode: http.StatusMethodNotAllowed}, + {URI: c.WithOAuthURI(loginURL), Method: http.MethodPost, ExpectedCode: http.StatusNotImplemented}, + {URI: c.WithOAuthURI(loginURL), ExpectedCode: http.StatusMethodNotAllowed}, } newFakeProxy(c).RunTests(t, requests) } @@ -94,7 +95,7 @@ func TestLoginHandlerNotDisabled(t *testing.T) { } func TestLoginHandler(t *testing.T) { - uri := oauthURL + loginURL + uri := newFakeKeycloakConfig().WithOAuthURI(loginURL) requests := []fakeRequest{ { URI: uri, @@ -137,25 +138,26 @@ func TestLoginHandler(t *testing.T) { func TestLogoutHandlerBadRequest(t *testing.T) { requests := []fakeRequest{ - {URI: oauthURL + logoutURL, ExpectedCode: http.StatusBadRequest}, + {URI: newFakeKeycloakConfig().WithOAuthURI(logoutURL), ExpectedCode: http.StatusBadRequest}, } newFakeProxy(nil).RunTests(t, requests) } func TestLogoutHandlerBadToken(t *testing.T) { + c := newFakeKeycloakConfig() requests := []fakeRequest{ { - URI: oauthURL + logoutURL, + URI: c.WithOAuthURI(logoutURL), ExpectedCode: http.StatusBadRequest, }, { - URI: oauthURL + logoutURL, + URI: c.WithOAuthURI(logoutURL), HasCookieToken: true, RawToken: "this.is.a.bad.token", ExpectedCode: http.StatusBadRequest, }, { - URI: oauthURL + logoutURL, + URI: c.WithOAuthURI(logoutURL), RawToken: "this.is.a.bad.token", ExpectedCode: http.StatusBadRequest, }, @@ -164,14 +166,15 @@ func TestLogoutHandlerBadToken(t *testing.T) { } func TestLogoutHandlerGood(t *testing.T) { + c := newFakeKeycloakConfig() requests := []fakeRequest{ { - URI: oauthURL + logoutURL, + URI: c.WithOAuthURI(logoutURL), HasToken: true, ExpectedCode: http.StatusOK, }, { - URI: oauthURL + logoutURL + "?redirect=http://example.com", + URI: c.WithOAuthURI(logoutURL) + "?redirect=http://example.com", HasToken: true, ExpectedCode: http.StatusTemporaryRedirect, ExpectedLocation: "http://example.com", @@ -181,7 +184,7 @@ func TestLogoutHandlerGood(t *testing.T) { } func TestTokenHandler(t *testing.T) { - uri := oauthURL + tokenURL + uri := newFakeKeycloakConfig().WithOAuthURI(tokenURL) requests := []fakeRequest{ { URI: uri, @@ -228,7 +231,7 @@ func TestAuthorizationURLWithSkipToken(t *testing.T) { c.SkipTokenVerification = true newFakeProxy(c).RunTests(t, []fakeRequest{ { - URI: oauthURL + authorizationURL, + URI: c.WithOAuthURI(authorizationURL), ExpectedCode: http.StatusNotAcceptable, }, }) @@ -278,28 +281,28 @@ func TestCallbackURL(t *testing.T) { cfg := newFakeKeycloakConfig() requests := []fakeRequest{ { - URI: oauthURL + callbackURL, + URI: cfg.WithOAuthURI(callbackURL), Method: http.MethodPost, ExpectedCode: http.StatusMethodNotAllowed, }, { - URI: oauthURL + callbackURL, + URI: cfg.WithOAuthURI(callbackURL), ExpectedCode: http.StatusBadRequest, }, { - URI: oauthURL + callbackURL + "?code=fake", + URI: cfg.WithOAuthURI(callbackURL) + "?code=fake", ExpectedCookies: map[string]string{cfg.CookieAccessName: ""}, ExpectedLocation: "/", ExpectedCode: http.StatusTemporaryRedirect, }, { - URI: oauthURL + callbackURL + "?code=fake&state=/admin", + URI: cfg.WithOAuthURI(callbackURL) + "?code=fake&state=/admin", ExpectedCookies: map[string]string{cfg.CookieAccessName: ""}, ExpectedLocation: "/", ExpectedCode: http.StatusTemporaryRedirect, }, { - URI: oauthURL + callbackURL + "?code=fake&state=L2FkbWlu", + URI: cfg.WithOAuthURI(callbackURL) + "?code=fake&state=L2FkbWlu", ExpectedCookies: map[string]string{cfg.CookieAccessName: ""}, ExpectedLocation: "/admin", ExpectedCode: http.StatusTemporaryRedirect, @@ -309,14 +312,15 @@ func TestCallbackURL(t *testing.T) { } func TestHealthHandler(t *testing.T) { + c := newFakeKeycloakConfig() requests := []fakeRequest{ { - URI: oauthURL + healthURL, + URI: c.WithOAuthURI(healthURL), ExpectedCode: http.StatusOK, ExpectedContent: "OK\n", }, { - URI: oauthURL + healthURL, + URI: c.WithOAuthURI(healthURL), Method: http.MethodHead, ExpectedCode: http.StatusMethodNotAllowed, }, diff --git a/middleware_test.go b/middleware_test.go index eb80fa52c..084a5da73 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -296,12 +296,12 @@ func TestMetricsMiddleware(t *testing.T) { cfg.LocalhostMetrics = true requests := []fakeRequest{ { - URI: oauthURL + metricsURL, + URI: cfg.WithOAuthURI(metricsURL), ExpectedCode: http.StatusOK, ExpectedContentContains: "proxy_request_status_total", }, { - URI: oauthURL + metricsURL, + URI: cfg.WithOAuthURI(metricsURL), Headers: map[string]string{ "X-Forwarded-For": "10.0.0.1", }, diff --git a/misc.go b/misc.go index eab4fd85d..f279d3c59 100644 --- a/misc.go +++ b/misc.go @@ -104,7 +104,7 @@ func (r *oauthProxy) redirectToAuthorization(w http.ResponseWriter, req *http.Re w.WriteHeader(http.StatusForbidden) return r.revokeProxy(w, req) } - r.redirectToURL(oauthURL+authorizationURL+authQuery, w, req) + r.redirectToURL(r.config.WithOAuthURI(authorizationURL+authQuery), w, req) return r.revokeProxy(w, req) } diff --git a/resource.go b/resource.go index ddcc15265..635438b0d 100644 --- a/resource.go +++ b/resource.go @@ -77,9 +77,6 @@ func (r *Resource) valid() error { if r.Roles == nil { r.Roles = make([]string, 0) } - if strings.HasPrefix(r.URL, oauthURL) { - return errors.New("this is used by the oauth handlers") - } if r.URL == "" { return errors.New("resource does not have url") } diff --git a/server.go b/server.go index 637aeff05..db12b51e0 100644 --- a/server.go +++ b/server.go @@ -187,7 +187,7 @@ func (r *oauthProxy) createReverseProxy() error { r.router = engine // step: add the routing for oauth - engine.With(proxyDenyMiddleware).Route(oauthURL, func(e chi.Router) { + engine.With(proxyDenyMiddleware).Route(r.config.OAuthURI, func(e chi.Router) { e.MethodNotAllowed(methodNotAllowHandlder) e.Get(authorizationURL, r.oauthAuthorizationHandler) e.Get(callbackURL, r.oauthCallbackHandler) @@ -197,7 +197,7 @@ func (r *oauthProxy) createReverseProxy() error { e.Get(tokenURL, r.tokenHandler) e.Post(loginURL, r.loginHandler) if r.config.EnableMetrics { - r.log.Info("enabled the service metrics middleware, available on", zap.String("path", fmt.Sprintf("%s%s", oauthURL, metricsURL))) + r.log.Info("enabled the service metrics middleware", zap.String("path", r.config.WithOAuthURI(metricsURL))) e.Get(metricsURL, r.proxyMetricsHandler) } }) diff --git a/server_test.go b/server_test.go index 140284ec9..5f24774f9 100644 --- a/server_test.go +++ b/server_test.go @@ -202,7 +202,7 @@ func TestAuthorizationTemplate(t *testing.T) { } requests := []fakeRequest{ { - URI: oauthURL + authorizationURL, + URI: cfg.WithOAuthURI(authorizationURL), Redirects: true, ExpectedCode: http.StatusOK, ExpectedContentContains: "Sign In", @@ -447,15 +447,16 @@ func newFakeKeycloakConfig() *Config { CookieRefreshName: "kc-state", DisableAllLogging: true, DiscoveryURL: "127.0.0.1:0", - OpenIDProviderTimeout: time.Second * 5, - EnableAuthorizationHeader: true, EnableAuthorizationCookies: true, + EnableAuthorizationHeader: true, EnableLogging: false, EnableLoginHandler: true, EnableTokenHeader: true, Listen: "127.0.0.1:0", + OAuthURI: "/oauth", + OpenIDProviderTimeout: time.Second * 5, Scopes: []string{}, - Verbose: true, + Verbose: false, Resources: []*Resource{ { URL: fakeAdminRoleURL,