diff --git a/cmd/pinniped/cmd/kubeconfig.go b/cmd/pinniped/cmd/kubeconfig.go index 5bf8efe8..913e7ca4 100644 --- a/cmd/pinniped/cmd/kubeconfig.go +++ b/cmd/pinniped/cmd/kubeconfig.go @@ -102,12 +102,12 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command { f.StringVar(&flags.oidc.issuer, "oidc-issuer", "", "OpenID Connect issuer URL (default: autodiscover)") f.StringVar(&flags.oidc.clientID, "oidc-client-id", "pinniped-cli", "OpenID Connect client ID (default: autodiscover)") f.Uint16Var(&flags.oidc.listenPort, "oidc-listen-port", 0, "TCP port for localhost listener (authorization code flow only)") - f.StringSliceVar(&flags.oidc.scopes, "oidc-scopes", []string{oidc.ScopeOfflineAccess, oidc.ScopeOpenID, "pinniped.sts.unrestricted"}, "OpenID Connect scopes to request during login") + f.StringSliceVar(&flags.oidc.scopes, "oidc-scopes", []string{oidc.ScopeOfflineAccess, oidc.ScopeOpenID, "pinniped:request-audience"}, "OpenID Connect scopes to request during login") f.BoolVar(&flags.oidc.skipBrowser, "oidc-skip-browser", false, "During OpenID Connect login, skip opening the browser (just print the URL)") f.StringVar(&flags.oidc.sessionCachePath, "oidc-session-cache", "", "Path to OpenID Connect session cache file") f.StringSliceVar(&flags.oidc.caBundlePaths, "oidc-ca-bundle", nil, "Path to TLS certificate authority bundle (PEM format, optional, can be repeated)") f.BoolVar(&flags.oidc.debugSessionCache, "oidc-debug-session-cache", false, "Print debug logs related to the OpenID Connect session cache") - f.StringVar(&flags.oidc.requestAudience, "oidc-request-audience", "", "Request a token with an alternate audience using RF8693 token exchange") + f.StringVar(&flags.oidc.requestAudience, "oidc-request-audience", "", "Request a token with an alternate audience using RFC8693 token exchange") f.StringVar(&flags.kubeconfigPath, "kubeconfig", os.Getenv("KUBECONFIG"), "Path to kubeconfig file") f.StringVar(&flags.kubeconfigContextOverride, "kubeconfig-context", "", "Kubeconfig context name (default: current active context)") diff --git a/cmd/pinniped/cmd/kubeconfig_test.go b/cmd/pinniped/cmd/kubeconfig_test.go index 739df951..e0fc8480 100644 --- a/cmd/pinniped/cmd/kubeconfig_test.go +++ b/cmd/pinniped/cmd/kubeconfig_test.go @@ -68,8 +68,8 @@ func TestGetKubeconfig(t *testing.T) { --oidc-client-id string OpenID Connect client ID (default: autodiscover) (default "pinniped-cli") --oidc-issuer string OpenID Connect issuer URL (default: autodiscover) --oidc-listen-port uint16 TCP port for localhost listener (authorization code flow only) - --oidc-request-audience string Request a token with an alternate audience using RF8693 token exchange - --oidc-scopes strings OpenID Connect scopes to request during login (default [offline_access,openid,pinniped.sts.unrestricted]) + --oidc-request-audience string Request a token with an alternate audience using RFC8693 token exchange + --oidc-scopes strings OpenID Connect scopes to request during login (default [offline_access,openid,pinniped:request-audience]) --oidc-session-cache string Path to OpenID Connect session cache file --oidc-skip-browser During OpenID Connect login, skip opening the browser (just print the URL) --static-token string Instead of doing an OIDC-based login, specify a static token @@ -415,7 +415,7 @@ func TestGetKubeconfig(t *testing.T) { - --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== - --issuer=https://example.com/issuer - --client-id=pinniped-cli - - --scopes=offline_access,openid,pinniped.sts.unrestricted + - --scopes=offline_access,openid,pinniped:request-audience - --ca-bundle-data=%s - --request-audience=test-audience command: '.../path/to/pinniped' @@ -472,7 +472,7 @@ func TestGetKubeconfig(t *testing.T) { - --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== - --issuer=https://example.com/issuer - --client-id=pinniped-cli - - --scopes=offline_access,openid,pinniped.sts.unrestricted + - --scopes=offline_access,openid,pinniped:request-audience - --skip-browser - --listen-port=1234 - --ca-bundle-data=%s diff --git a/cmd/pinniped/cmd/login_oidc.go b/cmd/pinniped/cmd/login_oidc.go index e3078458..39718105 100644 --- a/cmd/pinniped/cmd/login_oidc.go +++ b/cmd/pinniped/cmd/login_oidc.go @@ -79,13 +79,13 @@ func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command { cmd.Flags().StringVar(&flags.issuer, "issuer", "", "OpenID Connect issuer URL") cmd.Flags().StringVar(&flags.clientID, "client-id", "pinniped-cli", "OpenID Connect client ID") cmd.Flags().Uint16Var(&flags.listenPort, "listen-port", 0, "TCP port for localhost listener (authorization code flow only)") - cmd.Flags().StringSliceVar(&flags.scopes, "scopes", []string{oidc.ScopeOfflineAccess, oidc.ScopeOpenID, "pinniped.sts.unrestricted"}, "OIDC scopes to request during login") + cmd.Flags().StringSliceVar(&flags.scopes, "scopes", []string{oidc.ScopeOfflineAccess, oidc.ScopeOpenID, "pinniped:request-audience"}, "OIDC scopes to request during login") cmd.Flags().BoolVar(&flags.skipBrowser, "skip-browser", false, "Skip opening the browser (just print the URL)") cmd.Flags().StringVar(&flags.sessionCachePath, "session-cache", filepath.Join(mustGetConfigDir(), "sessions.yaml"), "Path to session cache file") cmd.Flags().StringSliceVar(&flags.caBundlePaths, "ca-bundle", nil, "Path to TLS certificate authority bundle (PEM format, optional, can be repeated)") cmd.Flags().StringSliceVar(&flags.caBundleData, "ca-bundle-data", nil, "Base64 endcoded TLS certificate authority bundle (base64 encoded PEM format, optional, can be repeated)") cmd.Flags().BoolVar(&flags.debugSessionCache, "debug-session-cache", false, "Print debug logs related to the session cache") - cmd.Flags().StringVar(&flags.requestAudience, "request-audience", "", "Request a token with an alternate audience using RF8693 token exchange") + cmd.Flags().StringVar(&flags.requestAudience, "request-audience", "", "Request a token with an alternate audience using RFC8693 token exchange") cmd.Flags().BoolVar(&flags.conciergeEnabled, "enable-concierge", false, "Exchange the OIDC ID token with the Pinniped concierge during login") cmd.Flags().StringVar(&flags.conciergeNamespace, "concierge-namespace", "pinniped-concierge", "Namespace in which the concierge was installed") cmd.Flags().StringVar(&flags.conciergeAuthenticatorType, "concierge-authenticator-type", "", "Concierge authenticator type (e.g., 'webhook', 'jwt')") diff --git a/cmd/pinniped/cmd/login_oidc_test.go b/cmd/pinniped/cmd/login_oidc_test.go index df4f8f9d..72626418 100644 --- a/cmd/pinniped/cmd/login_oidc_test.go +++ b/cmd/pinniped/cmd/login_oidc_test.go @@ -69,8 +69,8 @@ func TestLoginOIDCCommand(t *testing.T) { -h, --help help for oidc --issuer string OpenID Connect issuer URL --listen-port uint16 TCP port for localhost listener (authorization code flow only) - --request-audience string Request a token with an alternate audience using RF8693 token exchange - --scopes strings OIDC scopes to request during login (default [offline_access,openid,pinniped.sts.unrestricted]) + --request-audience string Request a token with an alternate audience using RFC8693 token exchange + --scopes strings OIDC scopes to request during login (default [offline_access,openid,pinniped:request-audience]) --session-cache string Path to session cache file (default "` + cfgDir + `/sessions.yaml") --skip-browser Skip opening the browser (just print the URL) `), diff --git a/internal/oidc/auth/auth_handler.go b/internal/oidc/auth/auth_handler.go index 9375d6d6..f417f0c1 100644 --- a/internal/oidc/auth/auth_handler.go +++ b/internal/oidc/auth/auth_handler.go @@ -64,8 +64,8 @@ func NewHandler( // at this time, however we will temporarily grant the scope just in case that changes in a future release of fosite. oidc.GrantScopeIfRequested(authorizeRequester, coreosoidc.ScopeOfflineAccess) - // Grant the Pinniped STS scope if requested. - oidc.GrantScopeIfRequested(authorizeRequester, "pinniped.sts.unrestricted") + // Grant the pinniped:request-audience scope if requested. + oidc.GrantScopeIfRequested(authorizeRequester, "pinniped:request-audience") now := time.Now() _, err = oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, &openid.DefaultSession{ diff --git a/internal/oidc/callback/callback_handler.go b/internal/oidc/callback/callback_handler.go index c331e653..a8ad9b66 100644 --- a/internal/oidc/callback/callback_handler.go +++ b/internal/oidc/callback/callback_handler.go @@ -72,10 +72,10 @@ func NewHandler( return httperr.New(http.StatusBadRequest, "error using state downstream auth params") } - // Automatically grant the openid, offline_access, and Pinniped STS scopes, but only if they were requested. + // Automatically grant the openid, offline_access, and pinniped:request-audience scopes, but only if they were requested. oidc.GrantScopeIfRequested(authorizeRequester, coreosoidc.ScopeOpenID) oidc.GrantScopeIfRequested(authorizeRequester, coreosoidc.ScopeOfflineAccess) - oidc.GrantScopeIfRequested(authorizeRequester, "pinniped.sts.unrestricted") + oidc.GrantScopeIfRequested(authorizeRequester, "pinniped:request-audience") token, err := upstreamIDPConfig.ExchangeAuthcodeAndValidateTokens( r.Context(), diff --git a/internal/oidc/nullstorage_test.go b/internal/oidc/nullstorage_test.go index 523aafb5..74f1f8a1 100644 --- a/internal/oidc/nullstorage_test.go +++ b/internal/oidc/nullstorage_test.go @@ -28,7 +28,7 @@ func TestNullStorage_GetClient(t *testing.T) { RedirectURIs: []string{"http://127.0.0.1/callback"}, ResponseTypes: []string{"code"}, GrantTypes: []string{"authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange"}, - Scopes: []string{"openid", "offline_access", "profile", "email", "pinniped.sts.unrestricted"}, + Scopes: []string{"openid", "offline_access", "profile", "email", "pinniped:request-audience"}, }, TokenEndpointAuthMethod: "none", }, diff --git a/internal/oidc/oidc.go b/internal/oidc/oidc.go index a1cb56fe..152509aa 100644 --- a/internal/oidc/oidc.go +++ b/internal/oidc/oidc.go @@ -85,7 +85,7 @@ func PinnipedCLIOIDCClient() *fosite.DefaultOpenIDConnectClient { RedirectURIs: []string{"http://127.0.0.1/callback"}, ResponseTypes: []string{"code"}, GrantTypes: []string{"authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange"}, - Scopes: []string{coreosoidc.ScopeOpenID, coreosoidc.ScopeOfflineAccess, "profile", "email", "pinniped.sts.unrestricted"}, + Scopes: []string{coreosoidc.ScopeOpenID, coreosoidc.ScopeOfflineAccess, "profile", "email", "pinniped:request-audience"}, }, TokenEndpointAuthMethod: "none", } diff --git a/internal/oidc/token/token_handler_test.go b/internal/oidc/token/token_handler_test.go index f4610293..cddbb929 100644 --- a/internal/oidc/token/token_handler_test.go +++ b/internal/oidc/token/token_handler_test.go @@ -633,13 +633,13 @@ func TestTokenExchange(t *testing.T) { successfulAuthCodeExchange := tokenEndpointResponseExpectedValues{ wantStatus: http.StatusOK, wantSuccessBodyFields: []string{"id_token", "access_token", "token_type", "expires_in", "scope"}, - wantRequestedScopes: []string{"openid", "pinniped.sts.unrestricted"}, - wantGrantedScopes: []string{"openid", "pinniped.sts.unrestricted"}, + wantRequestedScopes: []string{"openid", "pinniped:request-audience"}, + wantGrantedScopes: []string{"openid", "pinniped:request-audience"}, } doValidAuthCodeExchange := authcodeExchangeInputs{ modifyAuthRequest: func(authRequest *http.Request) { - authRequest.Form.Set("scope", "openid pinniped.sts.unrestricted") + authRequest.Form.Set("scope", "openid pinniped:request-audience") }, want: successfulAuthCodeExchange, } @@ -730,7 +730,7 @@ func TestTokenExchange(t *testing.T) { wantResponseBodyContains: `invalid subject_token`, }, { - name: "access token missing pinniped.sts.unrestricted scope", + name: "access token missing pinniped:request-audience scope", authcodeExchange: authcodeExchangeInputs{ modifyAuthRequest: func(authRequest *http.Request) { authRequest.Form.Set("scope", "openid") @@ -744,19 +744,19 @@ func TestTokenExchange(t *testing.T) { }, requestedAudience: "some-workload-cluster", wantStatus: http.StatusForbidden, - wantResponseBodyContains: `missing the \"pinniped.sts.unrestricted\" scope`, + wantResponseBodyContains: `missing the \"pinniped:request-audience\" scope`, }, { name: "access token missing openid scope", authcodeExchange: authcodeExchangeInputs{ modifyAuthRequest: func(authRequest *http.Request) { - authRequest.Form.Set("scope", "pinniped.sts.unrestricted") + authRequest.Form.Set("scope", "pinniped:request-audience") }, want: tokenEndpointResponseExpectedValues{ wantStatus: http.StatusOK, wantSuccessBodyFields: []string{"access_token", "token_type", "expires_in", "scope"}, - wantRequestedScopes: []string{"pinniped.sts.unrestricted"}, - wantGrantedScopes: []string{"pinniped.sts.unrestricted"}, + wantRequestedScopes: []string{"pinniped:request-audience"}, + wantGrantedScopes: []string{"pinniped:request-audience"}, }, }, requestedAudience: "some-workload-cluster", @@ -767,7 +767,7 @@ func TestTokenExchange(t *testing.T) { name: "token minting failure", authcodeExchange: authcodeExchangeInputs{ modifyAuthRequest: func(authRequest *http.Request) { - authRequest.Form.Set("scope", "openid pinniped.sts.unrestricted") + authRequest.Form.Set("scope", "openid pinniped:request-audience") }, // Fail to fetch a JWK signing key after the authcode exchange has happened. makeOathHelper: makeOauthHelperWithJWTKeyThatWorksOnlyOnce, @@ -918,23 +918,23 @@ func TestRefreshGrant(t *testing.T) { { name: "when the refresh request removes a scope which was originally granted from the list of requested scopes then it is granted anyway", authcodeExchange: authcodeExchangeInputs{ - modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid offline_access pinniped.sts.unrestricted") }, + modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid offline_access pinniped:request-audience") }, want: tokenEndpointResponseExpectedValues{ wantStatus: http.StatusOK, wantSuccessBodyFields: []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"}, - wantRequestedScopes: []string{"openid", "offline_access", "pinniped.sts.unrestricted"}, - wantGrantedScopes: []string{"openid", "offline_access", "pinniped.sts.unrestricted"}, + wantRequestedScopes: []string{"openid", "offline_access", "pinniped:request-audience"}, + wantGrantedScopes: []string{"openid", "offline_access", "pinniped:request-audience"}, }, }, refreshRequest: refreshRequestInputs{ modifyTokenRequest: func(r *http.Request, refreshToken string, accessToken string) { - r.Body = happyRefreshRequestBody(refreshToken).WithScope("openid").ReadCloser() // do not ask for "pinniped.sts.unrestricted" again + r.Body = happyRefreshRequestBody(refreshToken).WithScope("openid").ReadCloser() // do not ask for "pinniped:request-audience" again }, want: tokenEndpointResponseExpectedValues{ wantStatus: http.StatusOK, wantSuccessBodyFields: []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"}, - wantRequestedScopes: []string{"openid", "offline_access", "pinniped.sts.unrestricted"}, - wantGrantedScopes: []string{"openid", "offline_access", "pinniped.sts.unrestricted"}, + wantRequestedScopes: []string{"openid", "offline_access", "pinniped:request-audience"}, + wantGrantedScopes: []string{"openid", "offline_access", "pinniped:request-audience"}, }}, }, { @@ -1400,8 +1400,8 @@ func simulateAuthEndpointHavingAlreadyRun(t *testing.T, authRequest *http.Reques if strings.Contains(authRequest.Form.Get("scope"), "offline_access") { authRequester.GrantScope("offline_access") } - if strings.Contains(authRequest.Form.Get("scope"), "pinniped.sts.unrestricted") { - authRequester.GrantScope("pinniped.sts.unrestricted") + if strings.Contains(authRequest.Form.Get("scope"), "pinniped:request-audience") { + authRequester.GrantScope("pinniped:request-audience") } authResponder, err := oauthHelper.NewAuthorizeResponse(ctx, authRequester, session) require.NoError(t, err) diff --git a/internal/oidc/token_exchange.go b/internal/oidc/token_exchange.go index 36ddfb83..8a933d37 100644 --- a/internal/oidc/token_exchange.go +++ b/internal/oidc/token_exchange.go @@ -18,7 +18,7 @@ import ( const ( tokenTypeAccessToken = "urn:ietf:params:oauth:token-type:access_token" //nolint: gosec tokenTypeJWT = "urn:ietf:params:oauth:token-type:jwt" //nolint: gosec - pinnipedTokenExchangeScope = "pinniped.sts.unrestricted" //nolint: gosec + pinnipedTokenExchangeScope = "pinniped:request-audience" //nolint: gosec ) type stsParams struct { @@ -65,7 +65,7 @@ func (t *TokenExchangeHandler) PopulateTokenEndpointResponse(ctx context.Context return errors.WithStack(err) } - // Require that the incoming access token has the STS and OpenID scopes. + // Require that the incoming access token has the pinniped:request-audience and OpenID scopes. if !originalRequester.GetGrantedScopes().Has(pinnipedTokenExchangeScope) { return errors.WithStack(fosite.ErrAccessDenied.WithHintf("missing the %q scope", pinnipedTokenExchangeScope)) } diff --git a/pkg/oidcclient/login.go b/pkg/oidcclient/login.go index 5ae1bf0c..9718a297 100644 --- a/pkg/oidcclient/login.go +++ b/pkg/oidcclient/login.go @@ -159,7 +159,7 @@ func WithClient(httpClient *http.Client) Option { } } -// WithRequestAudience causes the login flow to perform an additional token exchange using the RFC8693 STS flow. +// WithRequestAudience causes the login flow to perform an additional token exchange using the RFC8693 flow. func WithRequestAudience(audience string) Option { return func(h *handlerState) error { h.requestedAudience = audience diff --git a/test/integration/supervisor_login_test.go b/test/integration/supervisor_login_test.go index 6fc03a7d..eb2e28cd 100644 --- a/test/integration/supervisor_login_test.go +++ b/test/integration/supervisor_login_test.go @@ -133,7 +133,7 @@ func TestSupervisorLogin(t *testing.T) { ClientID: "pinniped-cli", Endpoint: discovery.Endpoint(), RedirectURL: localCallbackServer.URL, - Scopes: []string{"openid", "pinniped.sts.unrestricted", "offline_access"}, + Scopes: []string{"openid", "pinniped:request-audience", "offline_access"}, } // Build a valid downstream authorize URL for the supervisor. @@ -175,7 +175,7 @@ func TestSupervisorLogin(t *testing.T) { callback := localCallbackServer.waitForCallback(10 * time.Second) t.Logf("got callback request: %s", library.MaskTokens(callback.URL.String())) require.Equal(t, stateParam.String(), callback.URL.Query().Get("state")) - require.ElementsMatch(t, []string{"openid", "pinniped.sts.unrestricted", "offline_access"}, strings.Split(callback.URL.Query().Get("scope"), " ")) + require.ElementsMatch(t, []string{"openid", "pinniped:request-audience", "offline_access"}, strings.Split(callback.URL.Query().Get("scope"), " ")) authcode := callback.URL.Query().Get("code") require.NotEmpty(t, authcode)