diff --git a/cmd/pinniped/cmd/login_oidc.go b/cmd/pinniped/cmd/login_oidc.go index 9d986530..831e4f01 100644 --- a/cmd/pinniped/cmd/login_oidc.go +++ b/cmd/pinniped/cmd/login_oidc.go @@ -14,6 +14,7 @@ import ( "net/http" "os" "path/filepath" + "strings" "time" corev1 "k8s.io/api/core/v1" @@ -256,6 +257,16 @@ func mustGetConfigDir() string { } func execCredentialForImpersonationProxy(token *oidctypes.Token, flags oidcLoginFlags) (*clientauthv1beta1.ExecCredential, error) { + // TODO maybe de-dup this with conciergeclient.go + var kind string + switch strings.ToLower(flags.conciergeAuthenticatorType) { + case "webhook": + kind = "WebhookAuthenticator" + case "jwt": + kind = "JWTAuthenticator" + default: + return nil, fmt.Errorf(`invalid authenticator type: %q, supported values are "webhook" and "jwt"`, kind) + } reqJSON, err := json.Marshal(&loginv1alpha1.TokenCredentialRequest{ ObjectMeta: metav1.ObjectMeta{ Namespace: flags.conciergeNamespace, @@ -265,11 +276,11 @@ func execCredentialForImpersonationProxy(token *oidctypes.Token, flags oidcLogin APIVersion: loginv1alpha1.GroupName + "/v1alpha1", }, Spec: loginv1alpha1.TokenCredentialRequestSpec{ - Token: token.AccessToken.Token, // TODO + Token: token.IDToken.Token, // TODO Authenticator: corev1.TypedLocalObjectReference{ APIGroup: &authenticationv1alpha1.SchemeGroupVersion.Group, - Kind: os.Getenv(flags.conciergeAuthenticatorType), - Name: os.Getenv(flags.conciergeAuthenticatorName), + Kind: kind, + Name: flags.conciergeAuthenticatorName, }, }, }) @@ -277,7 +288,7 @@ func execCredentialForImpersonationProxy(token *oidctypes.Token, flags oidcLogin return nil, err } encodedToken := base64.RawURLEncoding.EncodeToString(reqJSON) - return &clientauthv1beta1.ExecCredential{ + cred := &clientauthv1beta1.ExecCredential{ TypeMeta: metav1.TypeMeta{ Kind: "ExecCredential", APIVersion: "client.authentication.k8s.io/v1beta1", @@ -285,5 +296,9 @@ func execCredentialForImpersonationProxy(token *oidctypes.Token, flags oidcLogin Status: &clientauthv1beta1.ExecCredentialStatus{ Token: encodedToken, }, - }, nil + } + if !token.IDToken.Expiry.IsZero() { + cred.Status.ExpirationTimestamp = &token.IDToken.Expiry + } + return cred, nil } diff --git a/cmd/pinniped/cmd/login_oidc_test.go b/cmd/pinniped/cmd/login_oidc_test.go index 70c81924..ba02bd8d 100644 --- a/cmd/pinniped/cmd/login_oidc_test.go +++ b/cmd/pinniped/cmd/login_oidc_test.go @@ -8,12 +8,18 @@ import ( "context" "crypto/x509/pkix" "encoding/base64" + "encoding/json" "fmt" "io/ioutil" "path/filepath" "testing" "time" + corev1 "k8s.io/api/core/v1" + + authenticationv1alpha1 "go.pinniped.dev/generated/1.20/apis/concierge/authentication/v1alpha1" + loginv1alpha1 "go.pinniped.dev/generated/1.20/apis/concierge/login/v1alpha1" + "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" @@ -198,6 +204,21 @@ func TestLoginOIDCCommand(t *testing.T) { wantOptionsCount: 7, wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"exchanged-token"}}` + "\n", }, + { + name: "success with impersonation proxy", + args: []string{ + "--client-id", "test-client-id", + "--issuer", "test-issuer", + "--enable-concierge", + "--use-impersonation-proxy", + "--concierge-authenticator-type", "webhook", + "--concierge-authenticator-name", "test-authenticator", + "--concierge-endpoint", "https://127.0.0.1:1234/", + "--concierge-ca-bundle-data", base64.StdEncoding.EncodeToString(testCA.Bundle()), + }, + wantOptionsCount: 3, + wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"` + impersonationProxyToken("test-id-token") + `"}}` + "\n", + }, } for _, tt := range tests { tt := tt @@ -254,3 +275,24 @@ func TestLoginOIDCCommand(t *testing.T) { }) } } + +func impersonationProxyToken(token string) string { + reqJSON, _ := json.Marshal(&loginv1alpha1.TokenCredentialRequest{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "pinniped-concierge", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "TokenCredentialRequest", + APIVersion: loginv1alpha1.GroupName + "/v1alpha1", + }, + Spec: loginv1alpha1.TokenCredentialRequestSpec{ + Token: token, + Authenticator: corev1.TypedLocalObjectReference{ + APIGroup: &authenticationv1alpha1.SchemeGroupVersion.Group, + Kind: "WebhookAuthenticator", + Name: "test-authenticator", + }, + }, + }) + return base64.RawURLEncoding.EncodeToString(reqJSON) +}