Add --concierge-use-impersonation-proxy to static login

- also renamed --use-impersonation-proxy to
--concierge-use-impersonation-proxy
This commit is contained in:
Margo Crawford 2021-01-26 16:08:27 -08:00
parent 170b86d0c6
commit 2f891b4bfb
6 changed files with 88 additions and 12 deletions

View File

@ -115,7 +115,7 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
f.StringVar(&flags.concierge.caBundleData, "concierge-ca-bundle-data", "", "CA bundle 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, "use-impersonation-proxy", false, "Whether the concierge cluster uses an impersonation proxy")
f.BoolVar(&flags.concierge.useImpersonationProxy, "concierge-use-impersonation-proxy", false, "Whether the concierge cluster uses an impersonation proxy")
f.StringVar(&flags.oidc.issuer, "oidc-issuer", "", "OpenID Connect issuer URL (default: autodiscover)")
f.StringVar(&flags.oidc.clientID, "oidc-client-id", "pinniped-cli", "OpenID Connect client ID (default: autodiscover)")
@ -296,7 +296,7 @@ func configureConcierge(authenticator metav1.Object, flags *getKubeconfigParams,
)
if flags.concierge.useImpersonationProxy {
execConfig.Args = append(execConfig.Args,
"--use-impersonation-proxy",
"--concierge-use-impersonation-proxy",
)
}
return nil

View File

@ -64,6 +64,7 @@ func TestGetKubeconfig(t *testing.T) {
--concierge-ca-bundle-data string CA bundle 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
-h, --help help for kubeconfig
--kubeconfig string Path to kubeconfig file
--kubeconfig-context string Kubeconfig context name (default: current active context)
@ -78,7 +79,6 @@ func TestGetKubeconfig(t *testing.T) {
--oidc-skip-browser During OpenID Connect login, skip opening the browser (just print the URL)
--static-token string Instead of doing an OIDC-based login, specify a static token
--static-token-env string Instead of doing an OIDC-based login, read a static token from the environment
--use-impersonation-proxy Whether the concierge cluster uses an impersonation proxy
`),
},
{
@ -515,7 +515,7 @@ func TestGetKubeconfig(t *testing.T) {
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--concierge-ca-bundle-data", "blah", // TODO make this more realistic, maybe do some validation?
"--concierge-endpoint", "https://impersonation-proxy-endpoint.test",
"--use-impersonation-proxy",
"--concierge-use-impersonation-proxy",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.JWTAuthenticator{
@ -559,7 +559,7 @@ func TestGetKubeconfig(t *testing.T) {
- --concierge-authenticator-type=jwt
- --concierge-endpoint=https://impersonation-proxy-endpoint.test
- --concierge-ca-bundle-data=blah
- --use-impersonation-proxy
- --concierge-use-impersonation-proxy
- --issuer=https://example.com/issuer
- --client-id=pinniped-cli
- --scopes=offline_access,openid,pinniped:request-audience

View File

@ -101,7 +101,7 @@ func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
cmd.Flags().StringVar(&flags.conciergeEndpoint, "concierge-endpoint", "", "API base for the Pinniped 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", "pinniped.dev", "Concierge API group suffix")
cmd.Flags().BoolVar(&flags.useImpersonationProxy, "use-impersonation-proxy", false, "Whether the concierge cluster uses an impersonation proxy")
cmd.Flags().BoolVar(&flags.useImpersonationProxy, "concierge-use-impersonation-proxy", false, "Whether the concierge cluster uses an impersonation proxy")
mustMarkHidden(&cmd, "debug-session-cache")
mustMarkRequired(&cmd, "issuer")
@ -187,7 +187,8 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
}
}
if concierge != nil && flags.useImpersonationProxy {
// TODO add the right header???
// Put the token into a TokenCredentialRequest
// put the TokenCredentialRequest in an ExecCredential
req, err := execCredentialForImpersonationProxy(token, flags)
if err != nil {
return err

View File

@ -72,6 +72,7 @@ func TestLoginOIDCCommand(t *testing.T) {
--concierge-ca-bundle-data string CA bundle 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
--enable-concierge Exchange the OIDC ID token with the Pinniped concierge during login
-h, --help help for oidc
--issuer string OpenID Connect issuer URL
@ -80,7 +81,6 @@ func TestLoginOIDCCommand(t *testing.T) {
--scopes strings OIDC scopes to request during login (default [offline_access,openid,pinniped:request-audience])
--session-cache string Path to session cache file (default "` + cfgDir + `/sessions.yaml")
--skip-browser Skip opening the browser (just print the URL)
--use-impersonation-proxy Whether the concierge cluster uses an impersonation proxy
`),
},
{
@ -210,14 +210,14 @@ func TestLoginOIDCCommand(t *testing.T) {
"--client-id", "test-client-id",
"--issuer", "test-issuer",
"--enable-concierge",
"--use-impersonation-proxy",
"--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",
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"` + impersonationProxyTestToken("test-id-token") + `"}}` + "\n",
},
}
for _, tt := range tests {
@ -276,7 +276,7 @@ func TestLoginOIDCCommand(t *testing.T) {
}
}
func impersonationProxyToken(token string) string {
func impersonationProxyTestToken(token string) string {
reqJSON, _ := json.Marshal(&loginv1alpha1.TokenCredentialRequest{
ObjectMeta: metav1.ObjectMeta{
Namespace: "pinniped-concierge",

View File

@ -5,15 +5,22 @@ package cmd
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/spf13/cobra"
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
authenticationv1alpha1 "go.pinniped.dev/generated/1.20/apis/concierge/authentication/v1alpha1"
loginv1alpha1 "go.pinniped.dev/generated/1.20/apis/concierge/login/v1alpha1"
"go.pinniped.dev/pkg/conciergeclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
)
@ -47,6 +54,7 @@ type staticLoginParams struct {
conciergeEndpoint string
conciergeCABundle string
conciergeAPIGroupSuffix string
useImpersonationProxy bool
}
func staticLoginCommand(deps staticLoginDeps) *cobra.Command {
@ -68,6 +76,7 @@ func staticLoginCommand(deps staticLoginDeps) *cobra.Command {
cmd.Flags().StringVar(&flags.conciergeEndpoint, "concierge-endpoint", "", "API base for the Pinniped 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", "pinniped.dev", "Concierge API group suffix")
cmd.Flags().BoolVar(&flags.useImpersonationProxy, "concierge-use-impersonation-proxy", false, "Whether the concierge cluster uses an impersonation proxy")
cmd.RunE = func(cmd *cobra.Command, args []string) error { return runStaticLogin(cmd.OutOrStdout(), deps, flags) }
return &cmd
}
@ -109,7 +118,7 @@ func runStaticLogin(out io.Writer, deps staticLoginDeps, flags staticLoginParams
cred := tokenCredential(&oidctypes.Token{IDToken: &oidctypes.IDToken{Token: token}})
// Exchange that token with the concierge, if configured.
if concierge != nil {
if concierge != nil && !flags.useImpersonationProxy {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
@ -119,5 +128,58 @@ func runStaticLogin(out io.Writer, deps staticLoginDeps, flags staticLoginParams
return fmt.Errorf("could not complete concierge credential exchange: %w", err)
}
}
if concierge != nil && flags.useImpersonationProxy {
// Put the token into a TokenCredentialRequest
// put the TokenCredentialRequest in an ExecCredential
req, err := execCredentialForImpersonationProxyStatic(token, flags)
if err != nil {
return err
}
return json.NewEncoder(out).Encode(req)
}
return json.NewEncoder(out).Encode(cred)
}
func execCredentialForImpersonationProxyStatic(token string, flags staticLoginParams) (*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,
},
TypeMeta: metav1.TypeMeta{
Kind: "TokenCredentialRequest",
APIVersion: loginv1alpha1.GroupName + "/v1alpha1",
},
Spec: loginv1alpha1.TokenCredentialRequestSpec{
Token: token, // TODO
Authenticator: corev1.TypedLocalObjectReference{
APIGroup: &authenticationv1alpha1.SchemeGroupVersion.Group,
Kind: kind,
Name: flags.conciergeAuthenticatorName,
},
},
})
if err != nil {
return nil, err
}
encodedToken := base64.RawURLEncoding.EncodeToString(reqJSON)
cred := &clientauthv1beta1.ExecCredential{
TypeMeta: metav1.TypeMeta{
Kind: "ExecCredential",
APIVersion: "client.authentication.k8s.io/v1beta1",
},
Status: &clientauthv1beta1.ExecCredentialStatus{
Token: encodedToken,
},
}
return cred, nil
}

View File

@ -57,6 +57,7 @@ func TestLoginStaticCommand(t *testing.T) {
--concierge-ca-bundle-data string CA bundle 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
--enable-concierge Exchange the token with the Pinniped concierge during login
-h, --help help for static
--token string Static token to present during login
@ -153,6 +154,18 @@ func TestLoginStaticCommand(t *testing.T) {
},
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"test-token"}}` + "\n",
},
{
name: "impersonation proxy success",
args: []string{
"--enable-concierge",
"--concierge-use-impersonation-proxy",
"--token", "test-token",
"--concierge-endpoint", "https://127.0.0.1/",
"--concierge-authenticator-type", "webhook",
"--concierge-authenticator-name", "test-authenticator",
},
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"` + impersonationProxyTestToken("test-token") + `"}}` + "\n",
},
}
for _, tt := range tests {
tt := tt