From 0f2f54d4bfe2f216df0777ac6054e0c36abb9555 Mon Sep 17 00:00:00 2001 From: Hidetake Iwata Date: Mon, 16 Jun 2025 13:42:48 +0900 Subject: [PATCH] Add `--oidc-redirect-url` to override redirect URL (#1263) --- docs/usage.md | 31 ++++++++++--------- pkg/cmd/authentication.go | 17 +++++----- pkg/cmd/authentication_test.go | 9 +++--- pkg/cmd/cmd_test.go | 2 -- pkg/cmd/get_token.go | 7 +++++ pkg/cmd/setup.go | 3 ++ pkg/oidc/client/client.go | 18 ++++------- pkg/oidc/client/factory.go | 1 + pkg/oidc/oidc.go | 1 + .../authentication/authcode/browser.go | 2 +- .../authentication/authcode/browser_test.go | 4 --- .../authentication/authcode/keyboard.go | 9 ++---- pkg/usecases/setup/setup.go | 2 ++ 13 files changed, 54 insertions(+), 52 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 04c4e24..e205d50 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -10,6 +10,7 @@ Flags: --oidc-issuer-url string Issuer URL of the provider (mandatory) --oidc-client-id string Client ID of the provider (mandatory) --oidc-client-secret string Client secret of the provider + --oidc-redirect-url string [authcode, authcode-keyboard] Redirect URL --oidc-extra-scope strings Scopes to request to the provider --oidc-use-access-token Instead of using the id_token, use the access_token to authenticate to Kubernetes --force-refresh If set, refresh the ID token regardless of its expiration time @@ -29,8 +30,6 @@ Flags: --local-server-cert string [authcode] Certificate path for the local server --local-server-key string [authcode] Certificate key path for the local server --open-url-after-authentication string [authcode] If set, open the URL in the browser after authentication - --oidc-redirect-url-hostname string [authcode] Hostname of the redirect URL (default "localhost") - --oidc-redirect-url-authcode-keyboard string [authcode-keyboard] Redirect URL (default "urn:ietf:wg:oauth:2.0:oob") --oidc-auth-request-extra-params stringToString [authcode, authcode-keyboard] Extra query parameters to send with an authentication request (default []) --username string [password] Username for resource owner password credentials grant --password string [password] Password for resource owner password credentials grant @@ -159,6 +158,14 @@ You can change the listening address. - --listen-address=127.0.0.1:23456 ``` +The redirect URL defaults to `http://localhost` with the listening port. +You can override the redirect URL. + +```yaml +- --oidc-redirect-url=http://127.0.0.1:8000/ +- --oidc-redirect-url=http://your-local-hostname:8000/ +``` + You can specify a certificate for the local webserver if HTTPS is required by your identity provider. ```yaml @@ -166,12 +173,6 @@ You can specify a certificate for the local webserver if HTTPS is required by yo - --local-server-key=localhost.key ``` -You can change the hostname of redirect URI from the default value `localhost`. - -```yaml -- --oidc-redirect-url-hostname=127.0.0.1 -``` - You can add extra parameters to the authentication request. ```yaml @@ -202,6 +203,13 @@ If you cannot access the browser, instead use the authorization code flow with a - --grant-type=authcode-keyboard ``` +You need to explicitly set the redirect URL. + +```yaml +- --oidc-redirect-url=urn:ietf:wg:oauth:2.0:oob +- --oidc-redirect-url=http://localhost +``` + Kubelogin will show the URL and prompt. Open the URL in the browser and then copy the code shown. @@ -211,13 +219,6 @@ Open https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&client_id= Enter code: YOUR_CODE ``` -The default of redirect URI is `urn:ietf:wg:oauth:2.0:oob`. -You can overwrite it. - -```yaml -- oidc-redirect-url-authcode-keyboard=http://localhost -``` - You can add extra parameters to the authentication request. ```yaml diff --git a/pkg/cmd/authentication.go b/pkg/cmd/authentication.go index 368f3b7..3159438 100644 --- a/pkg/cmd/authentication.go +++ b/pkg/cmd/authentication.go @@ -12,8 +12,6 @@ import ( "github.com/spf13/pflag" ) -const oobRedirectURI = "urn:ietf:wg:oauth:2.0:oob" - type authenticationOptions struct { GrantType string ListenAddress []string @@ -23,8 +21,8 @@ type authenticationOptions struct { LocalServerCertFile string LocalServerKeyFile string OpenURLAfterAuthentication string - RedirectURLHostname string - RedirectURLAuthCodeKeyboard string + RedirectURLHostname string // DEPRECATED + RedirectURLAuthCodeKeyboard string // DEPRECATED AuthRequestExtraParams map[string]string Username string Password string @@ -47,8 +45,14 @@ func (o *authenticationOptions) addFlags(f *pflag.FlagSet) { f.StringVar(&o.LocalServerCertFile, "local-server-cert", "", "[authcode] Certificate path for the local server") f.StringVar(&o.LocalServerKeyFile, "local-server-key", "", "[authcode] Certificate key path for the local server") f.StringVar(&o.OpenURLAfterAuthentication, "open-url-after-authentication", "", "[authcode] If set, open the URL in the browser after authentication") - f.StringVar(&o.RedirectURLHostname, "oidc-redirect-url-hostname", "localhost", "[authcode] Hostname of the redirect URL") - f.StringVar(&o.RedirectURLAuthCodeKeyboard, "oidc-redirect-url-authcode-keyboard", oobRedirectURI, "[authcode-keyboard] Redirect URL") + f.StringVar(&o.RedirectURLHostname, "oidc-redirect-url-hostname", "", "[authcode] Hostname of the redirect URL") + if err := f.MarkDeprecated("oidc-redirect-url-hostname", "use --oidc-redirect-url instead."); err != nil { + panic(err) + } + f.StringVar(&o.RedirectURLAuthCodeKeyboard, "oidc-redirect-url-authcode-keyboard", "", "Equivalent to --oidc-redirect-url") + if err := f.MarkDeprecated("oidc-redirect-url-authcode-keyboard", "use --oidc-redirect-url instead."); err != nil { + panic(err) + } f.StringToStringVar(&o.AuthRequestExtraParams, "oidc-auth-request-extra-params", nil, "[authcode, authcode-keyboard] Extra query parameters to send with an authentication request") f.StringVar(&o.Username, "username", "", "[password] Username for resource owner password credentials grant") f.StringVar(&o.Password, "password", "", "[password] Password for resource owner password credentials grant") @@ -76,7 +80,6 @@ func (o *authenticationOptions) grantOptionSet() (s authentication.GrantOptionSe case o.GrantType == "authcode-keyboard": s.AuthCodeKeyboardOption = &authcode.KeyboardOption{ AuthRequestExtraParams: o.AuthRequestExtraParams, - RedirectURL: o.RedirectURLAuthCodeKeyboard, } case o.GrantType == "password" || (o.GrantType == "auto" && o.Username != ""): s.ROPCOption = &ropc.Option{ diff --git a/pkg/cmd/authentication_test.go b/pkg/cmd/authentication_test.go index 2fe772a..724f3b4 100644 --- a/pkg/cmd/authentication_test.go +++ b/pkg/cmd/authentication_test.go @@ -21,7 +21,6 @@ func Test_authenticationOptions_grantOptionSet(t *testing.T) { AuthCodeBrowserOption: &authcode.BrowserOption{ BindAddress: defaultListenAddress, AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second, - RedirectURLHostname: "localhost", }, }, }, @@ -61,19 +60,19 @@ func Test_authenticationOptions_grantOptionSet(t *testing.T) { "--grant-type", "authcode-keyboard", }, want: authentication.GrantOptionSet{ - AuthCodeKeyboardOption: &authcode.KeyboardOption{ - RedirectURL: oobRedirectURI, - }, + AuthCodeKeyboardOption: &authcode.KeyboardOption{}, }, }, "GrantType=authcode-keyboard with full options": { args: []string{ "--grant-type", "authcode-keyboard", "--oidc-redirect-url-authcode-keyboard", "http://localhost", + "--oidc-auth-request-extra-params", "ttl=86400", + "--oidc-auth-request-extra-params", "reauth=true", }, want: authentication.GrantOptionSet{ AuthCodeKeyboardOption: &authcode.KeyboardOption{ - RedirectURL: "http://localhost", + AuthRequestExtraParams: map[string]string{"ttl": "86400", "reauth": "true"}, }, }, }, diff --git a/pkg/cmd/cmd_test.go b/pkg/cmd/cmd_test.go index 7724510..4d65206 100644 --- a/pkg/cmd/cmd_test.go +++ b/pkg/cmd/cmd_test.go @@ -29,7 +29,6 @@ func TestCmd_Run(t *testing.T) { AuthCodeBrowserOption: &authcode.BrowserOption{ BindAddress: defaultListenAddress, AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second, - RedirectURLHostname: "localhost", }, } @@ -188,7 +187,6 @@ func TestCmd_Run(t *testing.T) { AuthCodeBrowserOption: &authcode.BrowserOption{ BindAddress: defaultListenAddress, AuthenticationTimeout: defaultAuthenticationTimeoutSec * time.Second, - RedirectURLHostname: "localhost", LocalServerCertFile: filepath.Join(userHomeDir, ".kube/oidc-server.crt"), LocalServerKeyFile: filepath.Join(userHomeDir, ".kube/oidc-server.key"), }, diff --git a/pkg/cmd/get_token.go b/pkg/cmd/get_token.go index 5765472..c6ba2ed 100644 --- a/pkg/cmd/get_token.go +++ b/pkg/cmd/get_token.go @@ -16,6 +16,7 @@ type getTokenOptions struct { IssuerURL string ClientID string ClientSecret string + RedirectURL string ExtraScopes []string UseAccessToken bool tokenCacheOptions tokenCacheOptions @@ -29,6 +30,7 @@ func (o *getTokenOptions) addFlags(f *pflag.FlagSet) { f.StringVar(&o.IssuerURL, "oidc-issuer-url", "", "Issuer URL of the provider (mandatory)") f.StringVar(&o.ClientID, "oidc-client-id", "", "Client ID of the provider (mandatory)") f.StringVar(&o.ClientSecret, "oidc-client-secret", "", "Client secret of the provider") + f.StringVar(&o.RedirectURL, "oidc-redirect-url", "", "[authcode, authcode-keyboard] Redirect URL") f.StringSliceVar(&o.ExtraScopes, "oidc-extra-scope", nil, "Scopes to request to the provider") f.BoolVar(&o.UseAccessToken, "oidc-use-access-token", false, "Instead of using the id_token, use the access_token to authenticate to Kubernetes") f.BoolVar(&o.ForceRefresh, "force-refresh", false, "If set, refresh the ID token regardless of its expiration time") @@ -80,11 +82,16 @@ func (cmd *GetToken) New() *cobra.Command { if err != nil { return fmt.Errorf("get-token: %w", err) } + redirectURL := o.RedirectURL + if o.authenticationOptions.RedirectURLAuthCodeKeyboard != "" { + redirectURL = o.authenticationOptions.RedirectURLAuthCodeKeyboard + } in := credentialplugin.Input{ Provider: oidc.Provider{ IssuerURL: o.IssuerURL, ClientID: o.ClientID, ClientSecret: o.ClientSecret, + RedirectURL: redirectURL, PKCEMethod: pkceMethod, UseAccessToken: o.UseAccessToken, ExtraScopes: o.ExtraScopes, diff --git a/pkg/cmd/setup.go b/pkg/cmd/setup.go index 1e57c8f..e00bae6 100644 --- a/pkg/cmd/setup.go +++ b/pkg/cmd/setup.go @@ -15,6 +15,7 @@ type setupOptions struct { IssuerURL string ClientID string ClientSecret string + RedirectURL string ExtraScopes []string UseAccessToken bool tlsOptions tlsOptions @@ -26,6 +27,7 @@ func (o *setupOptions) addFlags(f *pflag.FlagSet) { f.StringVar(&o.IssuerURL, "oidc-issuer-url", "", "Issuer URL of the provider") f.StringVar(&o.ClientID, "oidc-client-id", "", "Client ID of the provider") f.StringVar(&o.ClientSecret, "oidc-client-secret", "", "Client secret of the provider") + f.StringVar(&o.RedirectURL, "oidc-redirect-url", "", "[authcode, authcode-keyboard] Redirect URL") f.StringSliceVar(&o.ExtraScopes, "oidc-extra-scope", nil, "Scopes to request to the provider") f.BoolVar(&o.UseAccessToken, "oidc-use-access-token", false, "Instead of using the id_token, use the access_token to authenticate to Kubernetes") o.tlsOptions.addFlags(f) @@ -74,6 +76,7 @@ func (cmd *Setup) New() *cobra.Command { IssuerURL: o.IssuerURL, ClientID: o.ClientID, ClientSecret: o.ClientSecret, + RedirectURL: o.RedirectURL, ExtraScopes: o.ExtraScopes, UseAccessToken: o.UseAccessToken, PKCEMethod: pkceMethod, diff --git a/pkg/oidc/client/client.go b/pkg/oidc/client/client.go index 5fa0610..54ad5cd 100644 --- a/pkg/oidc/client/client.go +++ b/pkg/oidc/client/client.go @@ -31,15 +31,13 @@ type AuthCodeURLInput struct { State string Nonce string PKCEParams pkce.Params - RedirectURI string AuthRequestExtraParams map[string]string } type ExchangeAuthCodeInput struct { - Code string - PKCEParams pkce.Params - Nonce string - RedirectURI string + Code string + PKCEParams pkce.Params + Nonce string } type GetTokenByAuthCodeInput struct { @@ -47,7 +45,7 @@ type GetTokenByAuthCodeInput struct { State string Nonce string PKCEParams pkce.Params - RedirectURLHostname string + RedirectURLHostname string // DEPRECATED AuthRequestExtraParams map[string]string LocalServerSuccessHTML string LocalServerCertFile string @@ -97,19 +95,15 @@ func (c *client) GetTokenByAuthCode(ctx context.Context, in GetTokenByAuthCodeIn // GetAuthCodeURL returns the URL of authentication request for the authorization code flow. func (c *client) GetAuthCodeURL(in AuthCodeURLInput) string { - cfg := c.oauth2Config - cfg.RedirectURL = in.RedirectURI opts := authorizationRequestOptions(in.Nonce, in.PKCEParams, in.AuthRequestExtraParams) - return cfg.AuthCodeURL(in.State, opts...) + return c.oauth2Config.AuthCodeURL(in.State, opts...) } // ExchangeAuthCode exchanges the authorization code and token. func (c *client) ExchangeAuthCode(ctx context.Context, in ExchangeAuthCodeInput) (*oidc.TokenSet, error) { ctx = c.wrapContext(ctx) - cfg := c.oauth2Config - cfg.RedirectURL = in.RedirectURI opts := tokenRequestOptions(in.PKCEParams) - token, err := cfg.Exchange(ctx, in.Code, opts...) + token, err := c.oauth2Config.Exchange(ctx, in.Code, opts...) if err != nil { return nil, fmt.Errorf("exchange error: %w", err) } diff --git a/pkg/oidc/client/factory.go b/pkg/oidc/client/factory.go index ebcd9cd..2217fb1 100644 --- a/pkg/oidc/client/factory.go +++ b/pkg/oidc/client/factory.go @@ -72,6 +72,7 @@ func (f *Factory) New(ctx context.Context, prov oidc.Provider, tlsClientConfig t Endpoint: provider.Endpoint(), ClientID: prov.ClientID, ClientSecret: prov.ClientSecret, + RedirectURL: prov.RedirectURL, Scopes: append(prov.ExtraScopes, gooidc.ScopeOpenID), }, clock: f.Clock, diff --git a/pkg/oidc/oidc.go b/pkg/oidc/oidc.go index f10ae35..421c9dc 100644 --- a/pkg/oidc/oidc.go +++ b/pkg/oidc/oidc.go @@ -15,6 +15,7 @@ type Provider struct { ClientID string ClientSecret string // optional ExtraScopes []string // optional + RedirectURL string // optional PKCEMethod PKCEMethod UseAccessToken bool } diff --git a/pkg/usecases/authentication/authcode/browser.go b/pkg/usecases/authentication/authcode/browser.go index 0a74fef..23879e6 100644 --- a/pkg/usecases/authentication/authcode/browser.go +++ b/pkg/usecases/authentication/authcode/browser.go @@ -19,7 +19,7 @@ type BrowserOption struct { BindAddress []string AuthenticationTimeout time.Duration OpenURLAfterAuthentication string - RedirectURLHostname string + RedirectURLHostname string // DEPRECATED AuthRequestExtraParams map[string]string LocalServerCertFile string LocalServerKeyFile string diff --git a/pkg/usecases/authentication/authcode/browser_test.go b/pkg/usecases/authentication/authcode/browser_test.go index b7f620e..0e9dd78 100644 --- a/pkg/usecases/authentication/authcode/browser_test.go +++ b/pkg/usecases/authentication/authcode/browser_test.go @@ -28,7 +28,6 @@ func TestBrowser_Do(t *testing.T) { LocalServerCertFile: "/path/to/local-server-cert", LocalServerKeyFile: "/path/to/local-server-key", OpenURLAfterAuthentication: "https://example.com/success.html", - RedirectURLHostname: "localhost", AuthRequestExtraParams: map[string]string{"ttl": "86400", "reauth": "true"}, } mockClient := client_mock.NewMockInterface(t) @@ -42,9 +41,6 @@ func TestBrowser_Do(t *testing.T) { if diff := cmp.Diff(BrowserRedirectHTML("https://example.com/success.html"), in.LocalServerSuccessHTML); diff != "" { t.Errorf("LocalServerSuccessHTML mismatch (-want +got):\n%s", diff) } - if diff := cmp.Diff(o.RedirectURLHostname, in.RedirectURLHostname); diff != "" { - t.Errorf("RedirectURLHostname mismatch (-want +got):\n%s", diff) - } if diff := cmp.Diff(o.AuthRequestExtraParams, in.AuthRequestExtraParams); diff != "" { t.Errorf("AuthRequestExtraParams mismatch (-want +got):\n%s", diff) } diff --git a/pkg/usecases/authentication/authcode/keyboard.go b/pkg/usecases/authentication/authcode/keyboard.go index 491e809..b26088d 100644 --- a/pkg/usecases/authentication/authcode/keyboard.go +++ b/pkg/usecases/authentication/authcode/keyboard.go @@ -15,7 +15,6 @@ const keyboardPrompt = "Enter code: " type KeyboardOption struct { AuthRequestExtraParams map[string]string - RedirectURL string } // Keyboard provides the authorization code flow with keyboard interactive. @@ -42,7 +41,6 @@ func (u *Keyboard) Do(ctx context.Context, o *KeyboardOption, oidcClient client. State: state, Nonce: nonce, PKCEParams: pkceParams, - RedirectURI: o.RedirectURL, AuthRequestExtraParams: o.AuthRequestExtraParams, }) u.Logger.Printf("Please visit the following URL in your browser: %s", authCodeURL) @@ -53,10 +51,9 @@ func (u *Keyboard) Do(ctx context.Context, o *KeyboardOption, oidcClient client. u.Logger.V(1).Infof("exchanging the code and token") tokenSet, err := oidcClient.ExchangeAuthCode(ctx, client.ExchangeAuthCodeInput{ - Code: code, - PKCEParams: pkceParams, - Nonce: nonce, - RedirectURI: o.RedirectURL, + Code: code, + PKCEParams: pkceParams, + Nonce: nonce, }) if err != nil { return nil, fmt.Errorf("could not exchange the authorization code: %w", err) diff --git a/pkg/usecases/setup/setup.go b/pkg/usecases/setup/setup.go index dcee9da..9c25835 100644 --- a/pkg/usecases/setup/setup.go +++ b/pkg/usecases/setup/setup.go @@ -42,6 +42,7 @@ type Input struct { IssuerURL string ClientID string ClientSecret string + RedirectURL string ExtraScopes []string UseAccessToken bool PKCEMethod oidc.PKCEMethod @@ -57,6 +58,7 @@ func (u Setup) Do(ctx context.Context, in Input) error { IssuerURL: in.IssuerURL, ClientID: in.ClientID, ClientSecret: in.ClientSecret, + RedirectURL: in.RedirectURL, ExtraScopes: in.ExtraScopes, PKCEMethod: in.PKCEMethod, UseAccessToken: in.UseAccessToken,