diff --git a/cmd/pinniped/cmd/kubeconfig.go b/cmd/pinniped/cmd/kubeconfig.go index a4e74883..4937b03d 100644 --- a/cmd/pinniped/cmd/kubeconfig.go +++ b/cmd/pinniped/cmd/kubeconfig.go @@ -78,7 +78,7 @@ type getKubeconfigConciergeParams struct { authenticatorName string authenticatorType string apiGroupSuffix string - caBundleData string + caBundlePath string endpoint string useImpersonationProxy bool } @@ -113,7 +113,7 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command { f.StringVar(&flags.concierge.authenticatorName, "concierge-authenticator-name", "", "Concierge authenticator name (default: autodiscover)") f.StringVar(&flags.concierge.apiGroupSuffix, "concierge-api-group-suffix", "pinniped.dev", "Concierge API group suffix") - f.StringVar(&flags.concierge.caBundleData, "concierge-ca-bundle-data", "", "CA bundle to use when connecting to the concierge") + f.StringVar(&flags.concierge.caBundlePath, "concierge-ca-bundle", "", "Path to TLS certificate authority bundle (PEM format, optional, can be repeated) to use when connecting to the concierge") f.StringVar(&flags.concierge.endpoint, "concierge-endpoint", "", "API base for the Pinniped concierge endpoint") f.BoolVar(&flags.concierge.useImpersonationProxy, "concierge-use-impersonation-proxy", false, "Whether the concierge cluster uses an impersonation proxy") @@ -170,7 +170,14 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar return fmt.Errorf("could not load --kubeconfig/--kubeconfig-context: %w", err) } if flags.concierge.useImpersonationProxy { - cluster.CertificateAuthorityData = []byte(flags.concierge.caBundleData) + // TODO what to do if --use-impersonation-proxy is set but flags.concierge.caBundlePath is not??? + // TODO dont do this twice + conciergeCaBundleData, err := loadCABundlePaths([]string{flags.concierge.caBundlePath}) + if err != nil { + return fmt.Errorf("could not read --concierge-ca-bundle: %w", err) + } + + cluster.CertificateAuthorityData = []byte(conciergeCaBundleData) cluster.Server = flags.concierge.endpoint } clientset, err := deps.getClientset(clientConfig, flags.concierge.apiGroupSuffix) @@ -280,8 +287,16 @@ func configureConcierge(authenticator metav1.Object, flags *getKubeconfigParams, if flags.concierge.endpoint == "" { flags.concierge.endpoint = v1Cluster.Server } - if flags.concierge.caBundleData == "" { - flags.concierge.caBundleData = base64.StdEncoding.EncodeToString(v1Cluster.CertificateAuthorityData) + + var encodedConciergeCaBundleData string + if flags.concierge.caBundlePath == "" { + encodedConciergeCaBundleData = base64.StdEncoding.EncodeToString(v1Cluster.CertificateAuthorityData) + } else { + conciergeCaBundleData, err := loadCABundlePaths([]string{flags.concierge.caBundlePath}) + if err != nil { + return fmt.Errorf("could not read --concierge-ca-bundle: %w", err) + } + encodedConciergeCaBundleData = base64.StdEncoding.EncodeToString([]byte(conciergeCaBundleData)) } // Append the flags to configure the Concierge credential exchange at runtime. @@ -292,7 +307,7 @@ func configureConcierge(authenticator metav1.Object, flags *getKubeconfigParams, "--concierge-authenticator-name="+flags.concierge.authenticatorName, "--concierge-authenticator-type="+flags.concierge.authenticatorType, "--concierge-endpoint="+flags.concierge.endpoint, - "--concierge-ca-bundle-data="+flags.concierge.caBundleData, + "--concierge-ca-bundle-data="+encodedConciergeCaBundleData, ) if flags.concierge.useImpersonationProxy { execConfig.Args = append(execConfig.Args, diff --git a/cmd/pinniped/cmd/kubeconfig_test.go b/cmd/pinniped/cmd/kubeconfig_test.go index 9c588adb..c806e5e3 100644 --- a/cmd/pinniped/cmd/kubeconfig_test.go +++ b/cmd/pinniped/cmd/kubeconfig_test.go @@ -28,11 +28,14 @@ import ( ) func TestGetKubeconfig(t *testing.T) { - testCA, err := certauthority.New(pkix.Name{CommonName: "Test CA"}, 1*time.Hour) + testOIDCCA, err := certauthority.New(pkix.Name{CommonName: "Test CA"}, 1*time.Hour) require.NoError(t, err) tmpdir := testutil.TempDir(t) - testCABundlePath := filepath.Join(tmpdir, "testca.pem") - require.NoError(t, ioutil.WriteFile(testCABundlePath, testCA.Bundle(), 0600)) + testOIDCCABundlePath := filepath.Join(tmpdir, "testca.pem") + require.NoError(t, ioutil.WriteFile(testOIDCCABundlePath, testOIDCCA.Bundle(), 0600)) + + testConciergeCABundlePath := filepath.Join(tmpdir, "testconciergeca.pem") + require.NoError(t, ioutil.WriteFile(testConciergeCABundlePath, []byte("test-concierge-ca"), 0600)) tests := []struct { name string @@ -61,7 +64,7 @@ func TestGetKubeconfig(t *testing.T) { --concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev") --concierge-authenticator-name string Concierge authenticator name (default: autodiscover) --concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover) - --concierge-ca-bundle-data string CA bundle to use when connecting to the concierge + --concierge-ca-bundle string Path to TLS certificate authority bundle (PEM format, optional, can be repeated) to use when connecting to the concierge --concierge-endpoint string API base for the Pinniped concierge endpoint --concierge-namespace string Namespace in which the concierge was installed (default "pinniped-concierge") --concierge-use-impersonation-proxy Whether the concierge cluster uses an impersonation proxy @@ -268,6 +271,19 @@ func TestGetKubeconfig(t *testing.T) { Error: tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator test-namespace/test-authenticator has invalid spec.tls.certificateAuthorityData: illegal base64 data at input byte 7 `), }, + { + name: " invalid concierge ca bundle", + args: []string{ + "--kubeconfig", "./testdata/kubeconfig.yaml", + "--concierge-ca-bundle", "./does/not/exist", + "--concierge-endpoint", "https://impersonation-proxy-endpoint.test", + "--concierge-use-impersonation-proxy", + }, + wantError: true, + wantStderr: here.Doc(` + Error: could not read --concierge-ca-bundle: open ./does/not/exist: no such file or directory + `), + }, { name: "invalid static token flags", args: []string{ @@ -399,7 +415,7 @@ func TestGetKubeconfig(t *testing.T) { Issuer: "https://example.com/issuer", Audience: "test-audience", TLS: &conciergev1alpha1.TLSSpec{ - CertificateAuthorityData: base64.StdEncoding.EncodeToString(testCA.Bundle()), + CertificateAuthorityData: base64.StdEncoding.EncodeToString(testOIDCCA.Bundle()), }, }, }, @@ -442,7 +458,7 @@ func TestGetKubeconfig(t *testing.T) { command: '.../path/to/pinniped' env: [] provideClusterInfo: true - `, base64.StdEncoding.EncodeToString(testCA.Bundle())), + `, base64.StdEncoding.EncodeToString(testOIDCCA.Bundle())), }, { name: "autodetect nothing, set a bunch of options", @@ -454,7 +470,7 @@ func TestGetKubeconfig(t *testing.T) { "--oidc-issuer", "https://example.com/issuer", "--oidc-skip-browser", "--oidc-listen-port", "1234", - "--oidc-ca-bundle", testCABundlePath, + "--oidc-ca-bundle", testOIDCCABundlePath, "--oidc-session-cache", "/path/to/cache/dir/sessions.yaml", "--oidc-debug-session-cache", "--oidc-request-audience", "test-audience", @@ -506,14 +522,14 @@ func TestGetKubeconfig(t *testing.T) { command: '.../path/to/pinniped' env: [] provideClusterInfo: true - `, base64.StdEncoding.EncodeToString(testCA.Bundle())), + `, base64.StdEncoding.EncodeToString(testOIDCCA.Bundle())), wantAPIGroupSuffix: "tuna.io", }, { name: "configure impersonation proxy with autodetected JWT authenticator", args: []string{ "--kubeconfig", "./testdata/kubeconfig.yaml", - "--concierge-ca-bundle-data", "blah", // TODO make this more realistic, maybe do some validation? + "--concierge-ca-bundle", testConciergeCABundlePath, "--concierge-endpoint", "https://impersonation-proxy-endpoint.test", "--concierge-use-impersonation-proxy", }, @@ -524,7 +540,7 @@ func TestGetKubeconfig(t *testing.T) { Issuer: "https://example.com/issuer", Audience: "test-audience", TLS: &conciergev1alpha1.TLSSpec{ - CertificateAuthorityData: base64.StdEncoding.EncodeToString(testCA.Bundle()), + CertificateAuthorityData: base64.StdEncoding.EncodeToString(testOIDCCA.Bundle()), }, }, }, @@ -533,7 +549,7 @@ func TestGetKubeconfig(t *testing.T) { apiVersion: v1 clusters: - cluster: - certificate-authority-data: YmxhaA== + certificate-authority-data: dGVzdC1jb25jaWVyZ2UtY2E= server: https://impersonation-proxy-endpoint.test name: pinniped contexts: @@ -558,7 +574,7 @@ func TestGetKubeconfig(t *testing.T) { - --concierge-authenticator-name=test-authenticator - --concierge-authenticator-type=jwt - --concierge-endpoint=https://impersonation-proxy-endpoint.test - - --concierge-ca-bundle-data=blah + - --concierge-ca-bundle-data=dGVzdC1jb25jaWVyZ2UtY2E= - --concierge-use-impersonation-proxy - --issuer=https://example.com/issuer - --client-id=pinniped-cli @@ -568,7 +584,7 @@ func TestGetKubeconfig(t *testing.T) { command: '.../path/to/pinniped' env: [] provideClusterInfo: true - `, base64.StdEncoding.EncodeToString(testCA.Bundle())), + `, base64.StdEncoding.EncodeToString(testOIDCCA.Bundle())), }, } for _, tt := range tests { diff --git a/cmd/pinniped/cmd/login_oidc.go b/cmd/pinniped/cmd/login_oidc.go index b4eb62ee..3d38fb2d 100644 --- a/cmd/pinniped/cmd/login_oidc.go +++ b/cmd/pinniped/cmd/login_oidc.go @@ -189,7 +189,7 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin if concierge != nil && flags.useImpersonationProxy { // Put the token into a TokenCredentialRequest // put the TokenCredentialRequest in an ExecCredential - req, err := execCredentialForImpersonationProxy(token.IDToken.Token, flags.conciergeAuthenticatorType, flags.conciergeNamespace, flags.conciergeAuthenticatorName, token.IDToken.Expiry) + req, err := execCredentialForImpersonationProxy(token.IDToken.Token, flags.conciergeAuthenticatorType, flags.conciergeNamespace, flags.conciergeAuthenticatorName, &token.IDToken.Expiry) if err != nil { return err } @@ -262,7 +262,7 @@ func execCredentialForImpersonationProxy( conciergeAuthenticatorType string, conciergeNamespace string, conciergeAuthenticatorName string, - tokenExpiry metav1.Time, + tokenExpiry *metav1.Time, ) (*clientauthv1beta1.ExecCredential, error) { // TODO maybe de-dup this with conciergeclient.go var kind string @@ -292,7 +292,7 @@ func execCredentialForImpersonationProxy( }, }) if err != nil { - return nil, err + return nil, fmt.Errorf("Error creating TokenCredentialRequest for impersonation proxy: %w", err) } encodedToken := base64.RawURLEncoding.EncodeToString(reqJSON) cred := &clientauthv1beta1.ExecCredential{ @@ -305,7 +305,7 @@ func execCredentialForImpersonationProxy( }, } if !tokenExpiry.IsZero() { - cred.Status.ExpirationTimestamp = &tokenExpiry + cred.Status.ExpirationTimestamp = tokenExpiry } return cred, nil } diff --git a/cmd/pinniped/cmd/login_static.go b/cmd/pinniped/cmd/login_static.go index 3b90c29c..a8090935 100644 --- a/cmd/pinniped/cmd/login_static.go +++ b/cmd/pinniped/cmd/login_static.go @@ -11,8 +11,6 @@ import ( "os" "time" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/spf13/cobra" clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" @@ -124,10 +122,9 @@ func runStaticLogin(out io.Writer, deps staticLoginDeps, flags staticLoginParams } } if concierge != nil && flags.useImpersonationProxy { - var nilExpiry metav1.Time // Put the token into a TokenCredentialRequest // put the TokenCredentialRequest in an ExecCredential - req, err := execCredentialForImpersonationProxy(token, flags.conciergeAuthenticatorType, flags.conciergeNamespace, flags.conciergeAuthenticatorName, nilExpiry) + req, err := execCredentialForImpersonationProxy(token, flags.conciergeAuthenticatorType, flags.conciergeNamespace, flags.conciergeAuthenticatorName, nil) if err != nil { return err }