login oidc cmd checks access token expiry before doing token exchange

In the RFC8693 token exchange, the CLI sends your access token and
receives in exchange a new cluster-scoped ID token.

Fix a bug in the CLI. Whenever the "pinniped login oidc" command was
planning to perform the RFC8693 token exchange, it failed to check if
the cached access token was still valid before performing the exchange,
which sends the access token. It instead checked if the cached ID token
was still valid, but that it not relevant in this situation because the
ID token is not going to be used for anything (instead the new ID token
returned by the RFC8693 token exchange will be used for auth).

This bug doesn't actually matter today, because the Supervisor-issued
access and ID tokens always both have the same 2-minute lifetimes.
However, future enhancements may cause them to have different lifetimes
in certain circumstances. Fixing this CLI bug now to prepare for those
potential future enhancements.
This commit is contained in:
Ryan Richard
2024-02-09 13:33:30 -08:00
parent d5ce48bd4b
commit dce9409ccc
6 changed files with 325 additions and 111 deletions
+7 -7
View File
@@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
@@ -241,12 +241,12 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
}
pLogger.Debug("Performing OIDC login", "issuer", flags.issuer, "client id", flags.clientID)
// Do the basic login to get an OIDC token.
// Do the basic login to get an OIDC token. Although this can return several tokens, we only need the ID token here.
token, err := deps.login(flags.issuer, flags.clientID, opts...)
if err != nil {
return fmt.Errorf("could not complete Pinniped login: %w", err)
}
cred := tokenCredential(token)
cred := tokenCredential(token.IDToken)
// If the concierge was configured, exchange the credential for a separate short-lived, cluster-specific credential.
if concierge != nil {
@@ -344,18 +344,18 @@ func makeClient(caBundlePaths []string, caBundleData []string) (*http.Client, er
return phttp.Default(pool), nil
}
func tokenCredential(token *oidctypes.Token) *clientauthv1beta1.ExecCredential {
func tokenCredential(idToken *oidctypes.IDToken) *clientauthv1beta1.ExecCredential {
cred := clientauthv1beta1.ExecCredential{
TypeMeta: metav1.TypeMeta{
Kind: "ExecCredential",
APIVersion: "client.authentication.k8s.io/v1beta1",
},
Status: &clientauthv1beta1.ExecCredentialStatus{
Token: token.IDToken.Token,
Token: idToken.Token,
},
}
if !token.IDToken.Expiry.IsZero() {
cred.Status.ExpirationTimestamp = &token.IDToken.Expiry
if !idToken.Expiry.IsZero() {
cred.Status.ExpirationTimestamp = &idToken.Expiry
}
return &cred
}
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
@@ -133,7 +133,7 @@ func runStaticLogin(cmd *cobra.Command, deps staticLoginDeps, flags staticLoginP
return fmt.Errorf("--token-env variable %q is empty", flags.staticTokenEnvName)
}
}
cred := tokenCredential(&oidctypes.Token{IDToken: &oidctypes.IDToken{Token: token}})
cred := tokenCredential(&oidctypes.IDToken{Token: token})
// Look up cached credentials based on a hash of all the CLI arguments, the current token value, and the cluster info.
cacheKey := struct {