Add IDP selection to get-kubeconfig command.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
parent
481308215d
commit
07f0181fa3
@ -37,6 +37,8 @@ type getKubeConfigFlags struct {
|
||||
kubeconfig string
|
||||
contextOverride string
|
||||
namespace string
|
||||
idpName string
|
||||
idpType string
|
||||
}
|
||||
|
||||
type getKubeConfigCommand struct {
|
||||
@ -90,6 +92,8 @@ func (c *getKubeConfigCommand) Command() *cobra.Command {
|
||||
cmd.Flags().StringVar(&c.flags.kubeconfig, "kubeconfig", c.flags.kubeconfig, "Path to the kubeconfig file")
|
||||
cmd.Flags().StringVar(&c.flags.contextOverride, "kubeconfig-context", c.flags.contextOverride, "Kubeconfig context override")
|
||||
cmd.Flags().StringVar(&c.flags.namespace, "pinniped-namespace", c.flags.namespace, "Namespace in which Pinniped was installed")
|
||||
cmd.Flags().StringVar(&c.flags.idpType, "idp-type", c.flags.idpType, "Identity provider type (e.g., 'webhook')")
|
||||
cmd.Flags().StringVar(&c.flags.idpName, "idp-name", c.flags.idpType, "Identity provider name")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -115,6 +119,14 @@ func (c *getKubeConfigCommand) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
idpType, idpName := c.flags.idpType, c.flags.idpName
|
||||
if idpType == "" || idpName == "" {
|
||||
idpType, idpName, err = getDefaultIDP(clientset, c.flags.namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
credentialIssuerConfig, err := fetchPinnipedCredentialIssuerConfig(clientset, c.flags.namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -134,7 +146,7 @@ func (c *getKubeConfigCommand) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
config := newPinnipedKubeconfig(v1Cluster, fullPathToSelf, c.flags.token, c.flags.namespace)
|
||||
config := newPinnipedKubeconfig(v1Cluster, fullPathToSelf, c.flags.token, c.flags.namespace, idpType, idpName)
|
||||
|
||||
err = writeConfigAsYAML(cmd.OutOrStdout(), config)
|
||||
if err != nil {
|
||||
@ -159,6 +171,45 @@ func issueWarningForNonMatchingServerOrCA(v1Cluster v1.Cluster, credentialIssuer
|
||||
return nil
|
||||
}
|
||||
|
||||
type noIDPError struct{ Namespace string }
|
||||
|
||||
func (e noIDPError) Error() string {
|
||||
return fmt.Sprintf(`no identity providers were found in namespace %q`, e.Namespace)
|
||||
}
|
||||
|
||||
type indeterminateIDPError struct{ Namespace string }
|
||||
|
||||
func (e indeterminateIDPError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
`multiple identity providers were found in namespace %q, so --pinniped-idp-name/--pinniped-idp-type must be specified`,
|
||||
e.Namespace,
|
||||
)
|
||||
}
|
||||
|
||||
func getDefaultIDP(clientset pinnipedclientset.Interface, namespace string) (string, string, error) {
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancelFunc()
|
||||
|
||||
webhooks, err := clientset.IDPV1alpha1().WebhookIdentityProviders(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
type ref struct{ idpType, idpName string }
|
||||
idps := make([]ref, 0, len(webhooks.Items))
|
||||
for _, webhook := range webhooks.Items {
|
||||
idps = append(idps, ref{idpType: "webhook", idpName: webhook.Name})
|
||||
}
|
||||
|
||||
if len(idps) == 0 {
|
||||
return "", "", noIDPError{namespace}
|
||||
}
|
||||
if len(idps) > 1 {
|
||||
return "", "", indeterminateIDPError{namespace}
|
||||
}
|
||||
return idps[0].idpType, idps[0].idpName, nil
|
||||
}
|
||||
|
||||
func fetchPinnipedCredentialIssuerConfig(clientset pinnipedclientset.Interface, pinnipedInstallationNamespace string) (*configv1alpha1.CredentialIssuerConfig, error) {
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancelFunc()
|
||||
@ -229,7 +280,7 @@ func copyCurrentClusterFromExistingKubeConfig(currentKubeConfig clientcmdapi.Con
|
||||
return v1Cluster, nil
|
||||
}
|
||||
|
||||
func newPinnipedKubeconfig(v1Cluster v1.Cluster, fullPathToSelf string, token string, namespace string) v1.Config {
|
||||
func newPinnipedKubeconfig(v1Cluster v1.Cluster, fullPathToSelf string, token string, namespace string, idpType string, idpName string) v1.Config {
|
||||
clusterName := "pinniped-cluster"
|
||||
userName := "pinniped-user"
|
||||
|
||||
@ -275,6 +326,14 @@ func newPinnipedKubeconfig(v1Cluster v1.Cluster, fullPathToSelf string, token st
|
||||
Name: "PINNIPED_TOKEN",
|
||||
Value: token,
|
||||
},
|
||||
{
|
||||
Name: "PINNIPED_IDP_TYPE",
|
||||
Value: idpType,
|
||||
},
|
||||
{
|
||||
Name: "PINNIPED_IDP_NAME",
|
||||
Value: idpName,
|
||||
},
|
||||
},
|
||||
APIVersion: clientauthenticationv1beta1.SchemeGroupVersion.String(),
|
||||
InstallHint: "The Pinniped CLI is required to authenticate to the current cluster.\n" +
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
coretesting "k8s.io/client-go/testing"
|
||||
|
||||
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/config/v1alpha1"
|
||||
idpv1alpha "go.pinniped.dev/generated/1.19/apis/idp/v1alpha1"
|
||||
pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned"
|
||||
pinnipedfake "go.pinniped.dev/generated/1.19/client/clientset/versioned/fake"
|
||||
"go.pinniped.dev/internal/here"
|
||||
@ -30,6 +31,8 @@ var (
|
||||
|
||||
Flags:
|
||||
-h, --help help for get-kubeconfig
|
||||
--idp-name string Identity provider name
|
||||
--idp-type string Identity provider type (e.g., 'webhook')
|
||||
--kubeconfig string Path to the kubeconfig file
|
||||
--kubeconfig-context string Kubeconfig context override
|
||||
--pinniped-namespace string Namespace in which Pinniped was installed (default "pinniped")
|
||||
@ -59,6 +62,8 @@ var (
|
||||
|
||||
Flags:
|
||||
-h, --help help for get-kubeconfig
|
||||
--idp-name string Identity provider name
|
||||
--idp-type string Identity provider type (e.g., 'webhook')
|
||||
--kubeconfig string Path to the kubeconfig file
|
||||
--kubeconfig-context string Kubeconfig context override
|
||||
--pinniped-namespace string Namespace in which Pinniped was installed (default "pinniped")
|
||||
@ -118,6 +123,8 @@ type expectedKubeconfigYAML struct {
|
||||
pinnipedEndpoint string
|
||||
pinnipedCABundle string
|
||||
namespace string
|
||||
idpType string
|
||||
idpName string
|
||||
}
|
||||
|
||||
func (e expectedKubeconfigYAML) String() string {
|
||||
@ -153,10 +160,14 @@ func (e expectedKubeconfigYAML) String() string {
|
||||
value: %s
|
||||
- name: PINNIPED_TOKEN
|
||||
value: %s
|
||||
- name: PINNIPED_IDP_TYPE
|
||||
value: %s
|
||||
- name: PINNIPED_IDP_NAME
|
||||
value: %s
|
||||
installHint: |-
|
||||
The Pinniped CLI is required to authenticate to the current cluster.
|
||||
For more information, please visit https://pinniped.dev
|
||||
`, e.clusterCAData, e.clusterServer, e.command, e.pinnipedEndpoint, e.pinnipedCABundle, e.namespace, e.token)
|
||||
`, e.clusterCAData, e.clusterServer, e.command, e.pinnipedEndpoint, e.pinnipedCABundle, e.namespace, e.token, e.idpType, e.idpName)
|
||||
}
|
||||
|
||||
func newCredentialIssuerConfig(name, namespace, server, certificateAuthorityData string) *configv1alpha1.CredentialIssuerConfig {
|
||||
@ -212,6 +223,46 @@ func TestRun(t *testing.T) {
|
||||
},
|
||||
wantError: "some error configuring clientset",
|
||||
},
|
||||
{
|
||||
name: "fail to get IDPs",
|
||||
mocks: func(cmd *getKubeConfigCommand) {
|
||||
cmd.flags.idpName = ""
|
||||
cmd.flags.idpType = ""
|
||||
clientset := pinnipedfake.NewSimpleClientset()
|
||||
clientset.PrependReactor("*", "*", func(_ coretesting.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, fmt.Errorf("some error getting IDPs")
|
||||
})
|
||||
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
|
||||
return clientset, nil
|
||||
}
|
||||
},
|
||||
wantError: "some error getting IDPs",
|
||||
},
|
||||
{
|
||||
name: "zero IDPs",
|
||||
mocks: func(cmd *getKubeConfigCommand) {
|
||||
cmd.flags.idpName = ""
|
||||
cmd.flags.idpType = ""
|
||||
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
|
||||
return pinnipedfake.NewSimpleClientset(), nil
|
||||
}
|
||||
},
|
||||
wantError: `no identity providers were found in namespace "test-namespace"`,
|
||||
},
|
||||
{
|
||||
name: "multiple IDPs",
|
||||
mocks: func(cmd *getKubeConfigCommand) {
|
||||
cmd.flags.idpName = ""
|
||||
cmd.flags.idpType = ""
|
||||
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
|
||||
return pinnipedfake.NewSimpleClientset(
|
||||
&idpv1alpha.WebhookIdentityProvider{ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "webhook-one"}},
|
||||
&idpv1alpha.WebhookIdentityProvider{ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "webhook-two"}},
|
||||
), nil
|
||||
}
|
||||
},
|
||||
wantError: `multiple identity providers were found in namespace "test-namespace", so --pinniped-idp-name/--pinniped-idp-type must be specified`,
|
||||
},
|
||||
{
|
||||
name: "fail to get CredentialIssuerConfigs",
|
||||
mocks: func(cmd *getKubeConfigCommand) {
|
||||
@ -286,14 +337,21 @@ func TestRun(t *testing.T) {
|
||||
pinnipedEndpoint: "https://fake-server-url-value",
|
||||
pinnipedCABundle: "fake-certificate-authority-data-value",
|
||||
namespace: "test-namespace",
|
||||
idpType: "test-idp-type",
|
||||
idpName: "test-idp-name",
|
||||
}.String(),
|
||||
},
|
||||
{
|
||||
name: "success using local CA data",
|
||||
name: "success using local CA data and discovered IDP",
|
||||
mocks: func(cmd *getKubeConfigCommand) {
|
||||
cic := newCredentialIssuerConfig("pinniped-config", "test-namespace", "https://example.com", "test-ca")
|
||||
cmd.flags.idpName = ""
|
||||
cmd.flags.idpType = ""
|
||||
|
||||
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
|
||||
return pinnipedfake.NewSimpleClientset(cic), nil
|
||||
return pinnipedfake.NewSimpleClientset(
|
||||
&idpv1alpha.WebhookIdentityProvider{ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "discovered-idp"}},
|
||||
newCredentialIssuerConfig("pinniped-config", "test-namespace", "https://example.com", "test-ca"),
|
||||
), nil
|
||||
}
|
||||
},
|
||||
wantStderr: `WARNING: Server and certificate authority did not match between local kubeconfig and Pinniped's CredentialIssuerConfig on the cluster. Using local kubeconfig values.`,
|
||||
@ -305,6 +363,8 @@ func TestRun(t *testing.T) {
|
||||
pinnipedEndpoint: "https://fake-server-url-value",
|
||||
pinnipedCABundle: "fake-certificate-authority-data-value",
|
||||
namespace: "test-namespace",
|
||||
idpType: "webhook",
|
||||
idpName: "discovered-idp",
|
||||
}.String(),
|
||||
},
|
||||
}
|
||||
@ -317,6 +377,8 @@ func TestRun(t *testing.T) {
|
||||
c := newGetKubeConfigCommand()
|
||||
c.flags.token = "test-token"
|
||||
c.flags.namespace = "test-namespace"
|
||||
c.flags.idpName = "test-idp-name"
|
||||
c.flags.idpType = "test-idp-type"
|
||||
c.getPathToSelf = func() (string, error) { return "/path/to/pinniped", nil }
|
||||
c.flags.kubeconfig = "./testdata/kubeconfig.yaml"
|
||||
tt.mocks(c)
|
||||
|
Loading…
Reference in New Issue
Block a user