diff --git a/README.md b/README.md index 20df62b..0e471ac 100644 --- a/README.md +++ b/README.md @@ -53,18 +53,19 @@ The following flags are available: ```shell --auto-login Automatically redirect user to login if the user does not have a valid session for all proxied downstream requests. ---bind-address string Listen address for public connections. (default "127.0.0.1:3000") +--bind-address string Listen address for public connections. (default "127.0.0.1:8090") --encryption-key string Base64 encoded 256-bit cookie encryption key; must be identical in instances that share session store. --error-redirect-uri string URI to redirect user to on errors for custom error handling. ---ingress string Ingress used to access the main application. +--ingress string Ingress used to access the main application. (default "/") --log-format string Log format, either 'json' or 'text'. (default "json") --log-level string Logging verbosity level. (default "debug") ---metrics-bind-address string Listen address for metrics only. (default "127.0.0.1:3001") +--metrics-bind-address string Listen address for metrics only. (default "127.0.0.1:8091") --openid.acr-values string Space separated string that configures the default security level (acr_values) parameter for authorization requests. --openid.client-id string Client ID for the OpenID client. --openid.client-jwk string JWK containing the private key for the OpenID client in string format. --openid.post-logout-redirect-uri string URI for redirecting the user after successful logout at the Identity Provider. --openid.provider string Provider configuration to load and use, either 'openid', 'azure', 'idporten'. (default "openid") +--openid.redirect-uri string Redirect URI for the OpenID client that should be used in authorization requests. --openid.scopes strings List of additional scopes (other than 'openid') that should be used during the login flow. --openid.ui-locales string Space-separated string that configures the default UI locale (ui_locales) parameter for OAuth2 consent screen. --openid.well-known-url string URI to the well-known OpenID Configuration metadata document. @@ -80,18 +81,21 @@ At minimum, the following configuration must be provided: - `openid.client-id` - `openid.client-jwk` +- `openid.redirect-uri` - `openid.well-known-url` -- `ingress` #### ID-porten -When the `openid.provider` flag is set to `idporten`, the following environment variables are bound to the required `openid` +When the `openid.provider` flag is set to `idporten`, the following environment variables are bound to the required flags described previously: - `IDPORTEN_CLIENT_ID` Client ID for the client at ID-porten. - `IDPORTEN_CLIENT_JWK` Private key belonging to the client in JWK format. +- `IDPORTEN_REDIRECT_URI` + Valid pre-registered redirect URI that ID-porten should redirect the user to as part of the authentication flow. + For example: `http://localhost:8090/oauth2/callback` - `IDPORTEN_WELL_KNOWN_URL` Well-known OpenID Configuration endpoint for ID-porten: . @@ -111,6 +115,9 @@ described previously: Client ID for the client at Azure AD. - `AZURE_APP_CLIENT_JWK` Private key belonging to the client in JWK format. +- `AZURE_APP_REDIRECT_URI` + Valid pre-registered redirect URI that Azure AD should redirect the user to as part of the authentication flow. + For example: `http://localhost:8090/oauth2/callback` - `AZURE_APP_WELL_KNOWN_URL` Well-known OpenID Configuration endpoint for Azure AD. diff --git a/pkg/config/azure.go b/pkg/config/azure.go index 8e22828..db92e81 100644 --- a/pkg/config/azure.go +++ b/pkg/config/azure.go @@ -14,6 +14,7 @@ type azure struct { func azureFlags() { viper.BindEnv(OpenIDClientID, "AZURE_APP_CLIENT_ID") viper.BindEnv(OpenIDClientJWK, "AZURE_APP_JWK") + viper.BindEnv(OpenIDRedirectURI, "AZURE_APP_REDIRECT_URI") viper.BindEnv(OpenIDWellKnownURL, "AZURE_APP_WELL_KNOWN_URL") } diff --git a/pkg/config/config.go b/pkg/config/config.go index 283363d..841748f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -42,15 +42,15 @@ const ( func Initialize() (*Config, error) { conftools.Initialize("wonderwall") - flag.String(BindAddress, "127.0.0.1:3000", "Listen address for public connections.") + flag.String(BindAddress, "127.0.0.1:8090", "Listen address for public connections.") flag.String(LogFormat, "json", "Log format, either 'json' or 'text'.") flag.String(LogLevel, "debug", "Logging verbosity level.") - flag.String(MetricsBindAddress, "127.0.0.1:3001", "Listen address for metrics only.") + flag.String(MetricsBindAddress, "127.0.0.1:8091", "Listen address for metrics only.") flag.Bool(AutoLogin, false, "Automatically redirect user to login if the user does not have a valid session for all proxied downstream requests.") flag.String(EncryptionKey, "", "Base64 encoded 256-bit cookie encryption key; must be identical in instances that share session store.") flag.String(ErrorRedirectURI, "", "URI to redirect user to on errors for custom error handling.") - flag.String(Ingress, "", "Ingress used to access the main application.") + flag.String(Ingress, "/", "Ingress used to access the main application.") flag.Duration(SessionMaxLifetime, time.Hour, "Max lifetime for user sessions.") flag.String(UpstreamHost, "127.0.0.1:8080", "Address of upstream host.") diff --git a/pkg/config/idporten.go b/pkg/config/idporten.go index 3629f2e..3358c97 100644 --- a/pkg/config/idporten.go +++ b/pkg/config/idporten.go @@ -13,6 +13,7 @@ type idporten struct { func idportenFlags() { viper.BindEnv(OpenIDClientID, "IDPORTEN_CLIENT_ID") viper.BindEnv(OpenIDClientJWK, "IDPORTEN_CLIENT_JWK") + viper.BindEnv(OpenIDRedirectURI, "IDPORTEN_REDIRECT_URI") viper.BindEnv(OpenIDWellKnownURL, "IDPORTEN_WELL_KNOWN_URL") viper.SetDefault(OpenIDPostLogoutRedirectURI, "https://www.nav.no") diff --git a/pkg/config/openid.go b/pkg/config/openid.go index d4fc6a9..af1248d 100644 --- a/pkg/config/openid.go +++ b/pkg/config/openid.go @@ -12,6 +12,7 @@ const ( OpenIDClientID = "openid.client-id" OpenIDClientJWK = "openid.client-jwk" OpenIDPostLogoutRedirectURI = "openid.post-logout-redirect-uri" + OpenIDRedirectURI = "openid.redirect-uri" OpenIDScopes = "openid.scopes" OpenIDWellKnownURL = "openid.well-known-url" OpenIDACRValues = "openid.acr-values" @@ -23,6 +24,7 @@ type OpenID struct { ClientID string `json:"client-id"` ClientJWK string `json:"client-jwk"` PostLogoutRedirectURI string `json:"post-logout-redirect-uri"` + RedirectURI string `json:"redirect-uri"` Scopes []string `json:"scopes"` WellKnownURL string `json:"well-known-url"` ACRValues string `json:"acr-values"` @@ -39,12 +41,11 @@ const ( type BaseConfig struct { OpenID - clientJwk jwk.Key - redirectURI string + clientJwk jwk.Key } func (in *BaseConfig) GetRedirectURI() string { - return in.redirectURI + return in.RedirectURI } func (in *BaseConfig) GetClientID() string { @@ -75,11 +76,10 @@ func (in *BaseConfig) GetWellKnownURL() string { return in.WellKnownURL } -func (c *Config) NewBaseConfig(clientJwk jwk.Key, redirectURI string) *BaseConfig { +func (c *Config) NewBaseConfig(clientJwk jwk.Key) *BaseConfig { return &BaseConfig{ - OpenID: c.OpenID, - clientJwk: clientJwk, - redirectURI: redirectURI, + OpenID: c.OpenID, + clientJwk: clientJwk, } } @@ -87,6 +87,7 @@ func openIDFlags() { flag.String(OpenIDClientID, "", "Client ID for the OpenID client.") flag.String(OpenIDClientJWK, "", "JWK containing the private key for the OpenID client in string format.") flag.String(OpenIDPostLogoutRedirectURI, "", "URI for redirecting the user after successful logout at the Identity Provider.") + flag.String(OpenIDRedirectURI, "", "Redirect URI for the OpenID client that should be used in authorization requests.") flag.StringSlice(OpenIDScopes, []string{}, "List of additional scopes (other than 'openid') that should be used during the login flow.") flag.String(OpenIDWellKnownURL, "", "URI to the well-known OpenID Configuration metadata document.") diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 4e51e6f..d2603a0 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -3,13 +3,11 @@ package provider import ( "context" "fmt" - "net/url" "github.com/lestrrat-go/jwx/jwk" "github.com/nais/wonderwall/pkg/config" "github.com/nais/wonderwall/pkg/openid" - "github.com/nais/wonderwall/pkg/router/paths" ) type Provider interface { @@ -47,17 +45,7 @@ func NewProvider(cfg *config.Config) (Provider, error) { return nil, fmt.Errorf("parsing client JWK: %w", err) } - ingress := cfg.Ingress - if len(ingress) == 0 { - return nil, fmt.Errorf("missing required config %s", config.Ingress) - } - - redirectURI, err := redirectURI(ingress) - if err != nil { - return nil, fmt.Errorf("creating redirect URI from ingress: %w", err) - } - - baseConfig := cfg.NewBaseConfig(clientJwk, redirectURI) + baseConfig := cfg.NewBaseConfig(clientJwk) var clientConfig openid.ClientConfiguration switch cfg.OpenID.Provider { case config.ProviderIDPorten: @@ -78,6 +66,10 @@ func NewProvider(cfg *config.Config) (Provider, error) { return nil, fmt.Errorf("missing required config %s", config.OpenIDWellKnownURL) } + if len(clientConfig.GetRedirectURI()) == 0 { + return nil, fmt.Errorf("missing required config %s", config.OpenIDRedirectURI) + } + configuration, err := openid.FetchWellKnownConfig(clientConfig) if err != nil { return nil, fmt.Errorf("fetching well known config: %w", err) @@ -104,17 +96,3 @@ func NewProvider(cfg *config.Config) (Provider, error) { jwkSet: jwkSet, }, nil } - -func redirectURI(ingress string) (string, error) { - base, err := url.Parse(ingress) - if err != nil { - return "", err - } - - callbackPath, err := url.Parse(paths.OAuth2 + paths.Callback) - if err != nil { - return "", err - } - - return base.ResolveReference(callbackPath).String(), nil -} diff --git a/pkg/request/request.go b/pkg/request/request.go index 1240408..fe185a5 100644 --- a/pkg/request/request.go +++ b/pkg/request/request.go @@ -10,7 +10,6 @@ import ( "github.com/nais/wonderwall/pkg/config" "github.com/nais/wonderwall/pkg/cookie" "github.com/nais/wonderwall/pkg/openid" - "github.com/nais/wonderwall/pkg/router/paths" ) var ( @@ -85,7 +84,7 @@ func RetryURI(r *http.Request, ingress string, loginCookie *cookie.Login) string prefix := config.ParseIngress(ingress) - if strings.HasSuffix(retryURI, paths.OAuth2+paths.Logout) || strings.HasSuffix(retryURI, paths.OAuth2+paths.FrontChannelLogout) { + if strings.HasSuffix(retryURI, "/oauth2/logout") || strings.HasSuffix(retryURI, "/oauth2/logout/frontchannel") { return prefix + retryURI } @@ -114,7 +113,7 @@ func RetryURI(r *http.Request, ingress string, loginCookie *cookie.Login) string redirect = loginCookie.Referer } - retryURI = fmt.Sprintf(prefix + paths.OAuth2 + paths.Login) + retryURI = fmt.Sprintf("%s/oauth2/login", prefix) retryURI = retryURI + fmt.Sprintf("?%s=%s", RedirectURLParameter, redirect) return retryURI } diff --git a/pkg/router/paths/paths.go b/pkg/router/paths/paths.go deleted file mode 100644 index cbedc5b..0000000 --- a/pkg/router/paths/paths.go +++ /dev/null @@ -1,9 +0,0 @@ -package paths - -const ( - OAuth2 = "/oauth2" - Login = "/login" - Callback = "/callback" - Logout = "/logout" - FrontChannelLogout = "/logout/frontchannel" -) diff --git a/pkg/router/router.go b/pkg/router/router.go index 5a712f1..3f3f43e 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -6,7 +6,6 @@ import ( "github.com/nais/wonderwall/pkg/config" "github.com/nais/wonderwall/pkg/middleware" - "github.com/nais/wonderwall/pkg/router/paths" ) func New(handler *Handler) chi.Router { @@ -17,14 +16,14 @@ func New(handler *Handler) chi.Router { prefix := config.ParseIngress(handler.Config.Ingress) - r.Route(prefix+paths.OAuth2, func(r chi.Router) { + r.Route(prefix+"/oauth2", func(r chi.Router) { r.Use(middleware.LogEntryHandler(handler.httplogger)) r.Use(prometheusMiddleware.Handler) r.Use(chi_middleware.NoCache) - r.Get(paths.Login, handler.Login) - r.Get(paths.Callback, handler.Callback) - r.Get(paths.Logout, handler.Logout) - r.Get(paths.FrontChannelLogout, handler.FrontChannelLogout) + r.Get("/login", handler.Login) + r.Get("/callback", handler.Callback) + r.Get("/logout", handler.Logout) + r.Get("/logout/frontchannel", handler.FrontChannelLogout) }) r.HandleFunc("/*", handler.Default) return r