Impersonation proxy cli arguments
This commit is contained in:
parent
64aff7b983
commit
07b7b743b4
@ -78,6 +78,9 @@ type getKubeconfigConciergeParams struct {
|
|||||||
authenticatorName string
|
authenticatorName string
|
||||||
authenticatorType string
|
authenticatorType string
|
||||||
apiGroupSuffix string
|
apiGroupSuffix string
|
||||||
|
caBundleData string
|
||||||
|
endpoint string
|
||||||
|
useImpersonationProxy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type getKubeconfigParams struct {
|
type getKubeconfigParams struct {
|
||||||
@ -110,6 +113,10 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
|
|||||||
f.StringVar(&flags.concierge.authenticatorName, "concierge-authenticator-name", "", "Concierge authenticator name (default: autodiscover)")
|
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.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.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.StringVar(&flags.oidc.issuer, "oidc-issuer", "", "OpenID Connect issuer URL (default: autodiscover)")
|
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)")
|
f.StringVar(&flags.oidc.clientID, "oidc-client-id", "pinniped-cli", "OpenID Connect client ID (default: autodiscover)")
|
||||||
f.Uint16Var(&flags.oidc.listenPort, "oidc-listen-port", 0, "TCP port for localhost listener (authorization code flow only)")
|
f.Uint16Var(&flags.oidc.listenPort, "oidc-listen-port", 0, "TCP port for localhost listener (authorization code flow only)")
|
||||||
@ -162,6 +169,10 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not load --kubeconfig/--kubeconfig-context: %w", err)
|
return fmt.Errorf("could not load --kubeconfig/--kubeconfig-context: %w", err)
|
||||||
}
|
}
|
||||||
|
if flags.concierge.useImpersonationProxy {
|
||||||
|
cluster.CertificateAuthorityData = []byte(flags.concierge.caBundleData)
|
||||||
|
cluster.Server = flags.concierge.endpoint
|
||||||
|
}
|
||||||
clientset, err := deps.getClientset(clientConfig, flags.concierge.apiGroupSuffix)
|
clientset, err := deps.getClientset(clientConfig, flags.concierge.apiGroupSuffix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not configure Kubernetes client: %w", err)
|
return fmt.Errorf("could not configure Kubernetes client: %w", err)
|
||||||
@ -266,6 +277,13 @@ 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)
|
||||||
|
}
|
||||||
|
|
||||||
// Append the flags to configure the Concierge credential exchange at runtime.
|
// Append the flags to configure the Concierge credential exchange at runtime.
|
||||||
execConfig.Args = append(execConfig.Args,
|
execConfig.Args = append(execConfig.Args,
|
||||||
"--enable-concierge",
|
"--enable-concierge",
|
||||||
@ -273,9 +291,14 @@ func configureConcierge(authenticator metav1.Object, flags *getKubeconfigParams,
|
|||||||
"--concierge-namespace="+flags.concierge.namespace,
|
"--concierge-namespace="+flags.concierge.namespace,
|
||||||
"--concierge-authenticator-name="+flags.concierge.authenticatorName,
|
"--concierge-authenticator-name="+flags.concierge.authenticatorName,
|
||||||
"--concierge-authenticator-type="+flags.concierge.authenticatorType,
|
"--concierge-authenticator-type="+flags.concierge.authenticatorType,
|
||||||
"--concierge-endpoint="+v1Cluster.Server,
|
"--concierge-endpoint="+flags.concierge.endpoint,
|
||||||
"--concierge-ca-bundle-data="+base64.StdEncoding.EncodeToString(v1Cluster.CertificateAuthorityData),
|
"--concierge-ca-bundle-data="+flags.concierge.caBundleData,
|
||||||
)
|
)
|
||||||
|
if flags.concierge.useImpersonationProxy {
|
||||||
|
execConfig.Args = append(execConfig.Args,
|
||||||
|
"--use-impersonation-proxy",
|
||||||
|
)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,8 @@ func TestGetKubeconfig(t *testing.T) {
|
|||||||
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
|
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
|
||||||
--concierge-authenticator-name string Concierge authenticator name (default: autodiscover)
|
--concierge-authenticator-name string Concierge authenticator name (default: autodiscover)
|
||||||
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') (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-endpoint string API base for the Pinniped concierge endpoint
|
||||||
--concierge-namespace string Namespace in which the concierge was installed (default "pinniped-concierge")
|
--concierge-namespace string Namespace in which the concierge was installed (default "pinniped-concierge")
|
||||||
-h, --help help for kubeconfig
|
-h, --help help for kubeconfig
|
||||||
--kubeconfig string Path to kubeconfig file
|
--kubeconfig string Path to kubeconfig file
|
||||||
@ -76,6 +78,7 @@ func TestGetKubeconfig(t *testing.T) {
|
|||||||
--oidc-skip-browser During OpenID Connect login, skip opening the browser (just print the URL)
|
--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 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
|
--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
|
||||||
`),
|
`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -506,6 +509,67 @@ func TestGetKubeconfig(t *testing.T) {
|
|||||||
`, base64.StdEncoding.EncodeToString(testCA.Bundle())),
|
`, base64.StdEncoding.EncodeToString(testCA.Bundle())),
|
||||||
wantAPIGroupSuffix: "tuna.io",
|
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-endpoint", "https://impersonation-proxy-endpoint.test",
|
||||||
|
"--use-impersonation-proxy",
|
||||||
|
},
|
||||||
|
conciergeObjects: []runtime.Object{
|
||||||
|
&conciergev1alpha1.JWTAuthenticator{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator", Namespace: "pinniped-concierge"},
|
||||||
|
Spec: conciergev1alpha1.JWTAuthenticatorSpec{
|
||||||
|
Issuer: "https://example.com/issuer",
|
||||||
|
Audience: "test-audience",
|
||||||
|
TLS: &conciergev1alpha1.TLSSpec{
|
||||||
|
CertificateAuthorityData: base64.StdEncoding.EncodeToString(testCA.Bundle()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantStdout: here.Docf(`
|
||||||
|
apiVersion: v1
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
certificate-authority-data: YmxhaA==
|
||||||
|
server: https://impersonation-proxy-endpoint.test
|
||||||
|
name: pinniped
|
||||||
|
contexts:
|
||||||
|
- context:
|
||||||
|
cluster: pinniped
|
||||||
|
user: pinniped
|
||||||
|
name: pinniped
|
||||||
|
current-context: pinniped
|
||||||
|
kind: Config
|
||||||
|
preferences: {}
|
||||||
|
users:
|
||||||
|
- name: pinniped
|
||||||
|
user:
|
||||||
|
exec:
|
||||||
|
apiVersion: client.authentication.k8s.io/v1beta1
|
||||||
|
args:
|
||||||
|
- login
|
||||||
|
- oidc
|
||||||
|
- --enable-concierge
|
||||||
|
- --concierge-api-group-suffix=pinniped.dev
|
||||||
|
- --concierge-namespace=pinniped-concierge
|
||||||
|
- --concierge-authenticator-name=test-authenticator
|
||||||
|
- --concierge-authenticator-type=jwt
|
||||||
|
- --concierge-endpoint=https://impersonation-proxy-endpoint.test
|
||||||
|
- --concierge-ca-bundle-data=blah
|
||||||
|
- --use-impersonation-proxy
|
||||||
|
- --issuer=https://example.com/issuer
|
||||||
|
- --client-id=pinniped-cli
|
||||||
|
- --scopes=offline_access,openid,pinniped:request-audience
|
||||||
|
- --ca-bundle-data=%s
|
||||||
|
- --request-audience=test-audience
|
||||||
|
command: '.../path/to/pinniped'
|
||||||
|
env: []
|
||||||
|
provideClusterInfo: true
|
||||||
|
`, base64.StdEncoding.EncodeToString(testCA.Bundle())),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
tt := tt
|
tt := tt
|
||||||
|
@ -16,6 +16,11 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"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/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -65,6 +70,7 @@ type oidcLoginFlags struct {
|
|||||||
conciergeEndpoint string
|
conciergeEndpoint string
|
||||||
conciergeCABundle string
|
conciergeCABundle string
|
||||||
conciergeAPIGroupSuffix string
|
conciergeAPIGroupSuffix string
|
||||||
|
useImpersonationProxy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
|
func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
|
||||||
@ -94,6 +100,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.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.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().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")
|
||||||
|
|
||||||
mustMarkHidden(&cmd, "debug-session-cache")
|
mustMarkHidden(&cmd, "debug-session-cache")
|
||||||
mustMarkRequired(&cmd, "issuer")
|
mustMarkRequired(&cmd, "issuer")
|
||||||
@ -171,12 +178,21 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if concierge != nil {
|
// do a credential exchange request, unless impersonation proxy is configured
|
||||||
|
if concierge != nil && !flags.useImpersonationProxy {
|
||||||
cred, err = deps.exchangeToken(ctx, concierge, token.IDToken.Token)
|
cred, err = deps.exchangeToken(ctx, concierge, token.IDToken.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not complete concierge credential exchange: %w", err)
|
return fmt.Errorf("could not complete concierge credential exchange: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if concierge != nil && flags.useImpersonationProxy {
|
||||||
|
// TODO add the right header???
|
||||||
|
req, err := execCredentialForImpersonationProxy(token, flags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.NewEncoder(cmd.OutOrStdout()).Encode(req)
|
||||||
|
}
|
||||||
return json.NewEncoder(cmd.OutOrStdout()).Encode(cred)
|
return json.NewEncoder(cmd.OutOrStdout()).Encode(cred)
|
||||||
}
|
}
|
||||||
func makeClient(caBundlePaths []string, caBundleData []string) (*http.Client, error) {
|
func makeClient(caBundlePaths []string, caBundleData []string) (*http.Client, error) {
|
||||||
@ -238,3 +254,36 @@ func mustGetConfigDir() string {
|
|||||||
}
|
}
|
||||||
return filepath.Join(home, ".config", xdgAppName)
|
return filepath.Join(home, ".config", xdgAppName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func execCredentialForImpersonationProxy(token *oidctypes.Token, flags oidcLoginFlags) (*clientauthv1beta1.ExecCredential, error) {
|
||||||
|
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.AccessToken.Token, // TODO
|
||||||
|
Authenticator: corev1.TypedLocalObjectReference{
|
||||||
|
APIGroup: &authenticationv1alpha1.SchemeGroupVersion.Group,
|
||||||
|
Kind: os.Getenv(flags.conciergeAuthenticatorType),
|
||||||
|
Name: os.Getenv(flags.conciergeAuthenticatorName),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
encodedToken := base64.RawURLEncoding.EncodeToString(reqJSON)
|
||||||
|
return &clientauthv1beta1.ExecCredential{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "ExecCredential",
|
||||||
|
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||||
|
},
|
||||||
|
Status: &clientauthv1beta1.ExecCredentialStatus{
|
||||||
|
Token: encodedToken,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -74,6 +74,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
|||||||
--scopes strings OIDC scopes to request during login (default [offline_access,openid,pinniped:request-audience])
|
--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")
|
--session-cache string Path to session cache file (default "` + cfgDir + `/sessions.yaml")
|
||||||
--skip-browser Skip opening the browser (just print the URL)
|
--skip-browser Skip opening the browser (just print the URL)
|
||||||
|
--use-impersonation-proxy Whether the concierge cluster uses an impersonation proxy
|
||||||
`),
|
`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user