Add CLI caching of cluster-specific credentials.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
Matt Moyer
2021-04-08 14:12:34 -05:00
parent fec24d307e
commit 2296faaeef
11 changed files with 949 additions and 0 deletions
+22
View File
@@ -22,6 +22,7 @@ import (
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"k8s.io/klog/v2/klogr"
"go.pinniped.dev/internal/execcredcache"
"go.pinniped.dev/internal/groupsuffix"
"go.pinniped.dev/pkg/conciergeclient"
"go.pinniped.dev/pkg/oidcclient"
@@ -65,6 +66,7 @@ type oidcLoginFlags struct {
conciergeEndpoint string
conciergeCABundle string
conciergeAPIGroupSuffix string
credentialCachePath string
}
func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
@@ -95,6 +97,7 @@ func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
cmd.Flags().StringVar(&flags.conciergeEndpoint, "concierge-endpoint", "", "API base for the Concierge endpoint")
cmd.Flags().StringVar(&flags.conciergeCABundle, "concierge-ca-bundle-data", "", "CA bundle to use when connecting to the Concierge")
cmd.Flags().StringVar(&flags.conciergeAPIGroupSuffix, "concierge-api-group-suffix", groupsuffix.PinnipedDefaultSuffix, "Concierge API group suffix")
cmd.Flags().StringVar(&flags.credentialCachePath, "credential-cache", filepath.Join(mustGetConfigDir(), "credentials.yaml"), "Cluster-specific credentials cache path (\"\" disables the cache)")
mustMarkHidden(cmd, "debug-session-cache")
mustMarkRequired(cmd, "issuer")
@@ -164,6 +167,20 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
opts = append(opts, oidcclient.WithClient(client))
}
// Look up cached credentials based on a hash of all the CLI arguments.
cacheKey := struct {
Args []string `json:"args"`
}{
Args: os.Args[1:],
}
var credCache *execcredcache.Cache
if flags.credentialCachePath != "" {
credCache = execcredcache.New(flags.credentialCachePath)
if cred := credCache.Get(cacheKey); cred != nil {
return json.NewEncoder(cmd.OutOrStdout()).Encode(cred)
}
}
// Do the basic login to get an OIDC token.
token, err := deps.login(flags.issuer, flags.clientID, opts...)
if err != nil {
@@ -181,6 +198,11 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
return fmt.Errorf("could not complete Concierge credential exchange: %w", err)
}
}
// If there was a credential cache, save the resulting credential for future use.
if credCache != nil {
credCache.Put(cacheKey, cred)
}
return json.NewEncoder(cmd.OutOrStdout()).Encode(cred)
}
+2
View File
@@ -64,6 +64,7 @@ func TestLoginOIDCCommand(t *testing.T) {
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt')
--concierge-ca-bundle-data string CA bundle to use when connecting to the Concierge
--concierge-endpoint string API base for the Concierge endpoint
--credential-cache string Cluster-specific credentials cache path ("" disables the cache) (default "` + cfgDir + `/credentials.yaml")
--enable-concierge Use the Concierge to login
-h, --help help for oidc
--issuer string OpenID Connect issuer URL
@@ -189,6 +190,7 @@ func TestLoginOIDCCommand(t *testing.T) {
"--concierge-endpoint", "https://127.0.0.1:1234/",
"--concierge-ca-bundle-data", base64.StdEncoding.EncodeToString(testCA.Bundle()),
"--concierge-api-group-suffix", "some.suffix.com",
"--credential-cache", testutil.TempDir(t) + "/credentials.yaml",
},
wantOptionsCount: 7,
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"exchanged-token"}}` + "\n",
+27
View File
@@ -9,11 +9,13 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"time"
"github.com/spf13/cobra"
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"go.pinniped.dev/internal/execcredcache"
"go.pinniped.dev/internal/groupsuffix"
"go.pinniped.dev/pkg/conciergeclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
@@ -47,6 +49,7 @@ type staticLoginParams struct {
conciergeEndpoint string
conciergeCABundle string
conciergeAPIGroupSuffix string
credentialCachePath string
}
func staticLoginCommand(deps staticLoginDeps) *cobra.Command {
@@ -69,6 +72,7 @@ func staticLoginCommand(deps staticLoginDeps) *cobra.Command {
cmd.Flags().StringVar(&flags.conciergeEndpoint, "concierge-endpoint", "", "API base for the Concierge endpoint")
cmd.Flags().StringVar(&flags.conciergeCABundle, "concierge-ca-bundle-data", "", "CA bundle to use when connecting to the Concierge")
cmd.Flags().StringVar(&flags.conciergeAPIGroupSuffix, "concierge-api-group-suffix", groupsuffix.PinnipedDefaultSuffix, "Concierge API group suffix")
cmd.Flags().StringVar(&flags.credentialCachePath, "credential-cache", filepath.Join(mustGetConfigDir(), "credentials.yaml"), "Cluster-specific credentials cache path (\"\" disables the cache)")
cmd.RunE = func(cmd *cobra.Command, args []string) error { return runStaticLogin(cmd.OutOrStdout(), deps, flags) }
@@ -113,6 +117,22 @@ func runStaticLogin(out io.Writer, deps staticLoginDeps, flags staticLoginParams
}
cred := tokenCredential(&oidctypes.Token{IDToken: &oidctypes.IDToken{Token: token}})
// Look up cached credentials based on a hash of all the CLI arguments and the current token value.
cacheKey := struct {
Args []string `json:"args"`
Token string `json:"token"`
}{
Args: os.Args[1:],
Token: token,
}
var credCache *execcredcache.Cache
if flags.credentialCachePath != "" {
credCache = execcredcache.New(flags.credentialCachePath)
if cred := credCache.Get(cacheKey); cred != nil {
return json.NewEncoder(out).Encode(cred)
}
}
// If the concierge was configured, exchange the credential for a separate short-lived, cluster-specific credential.
if concierge != nil {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
@@ -124,5 +144,12 @@ func runStaticLogin(out io.Writer, deps staticLoginDeps, flags staticLoginParams
return fmt.Errorf("could not complete Concierge credential exchange: %w", err)
}
}
// If there was a credential cache, save the resulting credential for future use. We only save to the cache if
// the credential came from the concierge, since that's the only static token case where the cache is useful.
if credCache != nil && concierge != nil {
credCache.Put(cacheKey, cred)
}
return json.NewEncoder(out).Encode(cred)
}
+3
View File
@@ -23,6 +23,8 @@ import (
)
func TestLoginStaticCommand(t *testing.T) {
cfgDir := mustGetConfigDir()
testCA, err := certauthority.New("Test CA", 1*time.Hour)
require.NoError(t, err)
tmpdir := testutil.TempDir(t)
@@ -55,6 +57,7 @@ func TestLoginStaticCommand(t *testing.T) {
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt')
--concierge-ca-bundle-data string CA bundle to use when connecting to the Concierge
--concierge-endpoint string API base for the Concierge endpoint
--credential-cache string Cluster-specific credentials cache path ("" disables the cache) (default "` + cfgDir + `/credentials.yaml")
--enable-concierge Use the Concierge to login
-h, --help help for static
--token string Static token to present during login