Skip to content

kubernetes exec-credential command for OIDC Authorization flow with PKCE#1840

Open
d-honeybadger wants to merge 3 commits intodigitalocean:mainfrom
d-honeybadger:doks-sso-authorization-flow
Open

kubernetes exec-credential command for OIDC Authorization flow with PKCE#1840
d-honeybadger wants to merge 3 commits intodigitalocean:mainfrom
d-honeybadger:doks-sso-authorization-flow

Conversation

@d-honeybadger
Copy link
Copy Markdown
Contributor

Adds a set of flags to doctl kubernetes kubeconfig exec-credential command to be able to handle OIDC authorization.
Currently, doctl kubernetes kubeconfig exec-credential is used to fetch a DO PAT from DOKS API, and this PAT is used by kubectl to authenticate inside the k8s cluster.
The additional flags enable doctl kubernetes kubeconfig exec-credential to instead receive a token (ID token) from an OIDC idendity provider such as Okta, Auth0.

The authorization flow implemented here is similar to how OSS kubectl plugins do it (e.g. https://github.com/int128/kubelogin) but with our own implementation we get seamless UX (users don't have to install and configure third-party plugins) and flexibility in adjusting & fixing any issies.

@@ -0,0 +1,162 @@
<!DOCTYPE html>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI-generated as I'm no html/css expert
This is what it looks like

Image

@@ -0,0 +1,164 @@
<!DOCTYPE html>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI-generated as I'm not an htlm/css expert
Here's what it looks like

Image

@d-honeybadger d-honeybadger force-pushed the doks-sso-authorization-flow branch from d5c453d to b8b6e22 Compare May 7, 2026 15:52
Comment thread commands/doit.go

// in case we ever want to change this, or let folks configure it...
func defaultConfigHome() string {
var defaultConfigHome = func() string {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that it can be stubbed in tests.

Comment thread commands/kubernetes.go
return filepath.Join(kubeconfigCachePath(), id+".json")
}

func cachedSSOExecCredentialPath(id string) string {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate files for SSO tokens

@d-honeybadger d-honeybadger force-pushed the doks-sso-authorization-flow branch from b8b6e22 to 84c9897 Compare May 7, 2026 16:08
Comment thread internal/kubernetes/sso/sso.go Outdated

server := &http.Server{
Handler: t.ssoServer,
Addr: fmt.Sprintf(":%d", t.port),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

":8080" listens on every interface, the browser only needs to reach localhost. Suggested to use fmt.Sprintf("127.0.0.1:%d", t.port)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, thanks!

}

t.logger.Println("Received an authorization code, exchanging for ID token")
token, err := t.oauth2Config.Exchange(ctx, code, oauth2.S256ChallengeOption(t.codeVerifier), oauth2.VerifierOption(t.codeVerifier))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is CodeChallenge required to be passed in token exchange step?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Maybe some IDPs don't expect it (code challenge should be enough?) but Auth0 throws me an error if the verifier is not provided:
Error: Failed to get ID token: exchanging authorization code for ID token: oauth2: "invalid_request" "Parameter 'code_verifier' is required"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@d-honeybadger I agree the verifier is required, but as per the PKCE standard I don’t think we need to pass code_challenge and code_challenge_method in the token request, which are currently being passed as the third argument in this function call.

As per the spec, the token exchange request should include the code_verifier, while code_challenge and code_challenge_method are only used during the authorization request.

Reference for token request:
https://www.oauth.com/oauth2-servers/pkce/authorization-code-exchange/

What are your thoughts?

return "", time.Time{}, errors.New("no ID token found")
}

return idToken, token.Expiry, nil
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we verify the ID token locally before returning it. We are pulling id_token out of the token response and forward it to kubectl as-is — no signature, iss, aud, or exp check

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This command doesn't authorize the user to do anything though, it just returns a token that will be passed to kube-apiserver which will perform all the necessary verifications. I added a simple verification, but I think we can be pretty minimal here and not duplicate kube-apiserver's job - WDYT?

Comment thread commands/kubernetes.go

execCredDesc := "INTERNAL: This hidden command is for printing a cluster's exec credential"
cmdExecCredential := CmdBuilder(cmd, k8sCmdService.RunKubernetesKubeconfigExecCredential, "exec-credential <cluster-id>", execCredDesc, execCredDesc, Writer, hiddenCmd())
cmdExecCredential := CmdBuilder(cmd, k8sCmdService.RunKubernetesKubeconfigExecCredential, "exec-credential <cluster-id>", execCredDesc, execCredDesc, Writer) //, hiddenCmd())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we’re making this command non-hidden, should we also update the execCredDesc accordingly?

@RohithSangati
Copy link
Copy Markdown
Contributor

Adds a set of flags to doctl kubernetes kubeconfig exec-credential command to be able to handle OIDC authorization. Currently, doctl kubernetes kubeconfig exec-credential is used to fetch a DO PAT from DOKS API, and this PAT is used by kubectl to authenticate inside the k8s cluster. The additional flags enable doctl kubernetes kubeconfig exec-credential to instead receive a token (ID token) from an OIDC idendity provider such as Okta, Auth0.

The authorization flow implemented here is similar to how OSS kubectl plugins do it (e.g. https://github.com/int128/kubelogin) but with our own implementation we get seamless UX (users don't have to install and configure third-party plugins) and flexibility in adjusting & fixing any issies.

Just a small clarification — should the command here be doctl kubernetes cluster kubeconfig exec-credential instead of doctl kubernetes kubeconfig exec-credential, since from the code it looks like kubeconfig is a child of the cluster command? I might be misunderstanding something, so wanted to confirm once.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants