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
|
kubeconfig string
|
||||||
contextOverride string
|
contextOverride string
|
||||||
namespace string
|
namespace string
|
||||||
|
idpName string
|
||||||
|
idpType string
|
||||||
}
|
}
|
||||||
|
|
||||||
type getKubeConfigCommand struct {
|
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.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.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.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
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +119,14 @@ func (c *getKubeConfigCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
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)
|
credentialIssuerConfig, err := fetchPinnipedCredentialIssuerConfig(clientset, c.flags.namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -134,7 +146,7 @@ func (c *getKubeConfigCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
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)
|
err = writeConfigAsYAML(cmd.OutOrStdout(), config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -159,6 +171,45 @@ func issueWarningForNonMatchingServerOrCA(v1Cluster v1.Cluster, credentialIssuer
|
|||||||
return nil
|
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) {
|
func fetchPinnipedCredentialIssuerConfig(clientset pinnipedclientset.Interface, pinnipedInstallationNamespace string) (*configv1alpha1.CredentialIssuerConfig, error) {
|
||||||
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
|
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
|
||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
@ -229,7 +280,7 @@ func copyCurrentClusterFromExistingKubeConfig(currentKubeConfig clientcmdapi.Con
|
|||||||
return v1Cluster, nil
|
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"
|
clusterName := "pinniped-cluster"
|
||||||
userName := "pinniped-user"
|
userName := "pinniped-user"
|
||||||
|
|
||||||
@ -275,6 +326,14 @@ func newPinnipedKubeconfig(v1Cluster v1.Cluster, fullPathToSelf string, token st
|
|||||||
Name: "PINNIPED_TOKEN",
|
Name: "PINNIPED_TOKEN",
|
||||||
Value: token,
|
Value: token,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "PINNIPED_IDP_TYPE",
|
||||||
|
Value: idpType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "PINNIPED_IDP_NAME",
|
||||||
|
Value: idpName,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
APIVersion: clientauthenticationv1beta1.SchemeGroupVersion.String(),
|
APIVersion: clientauthenticationv1beta1.SchemeGroupVersion.String(),
|
||||||
InstallHint: "The Pinniped CLI is required to authenticate to the current cluster.\n" +
|
InstallHint: "The Pinniped CLI is required to authenticate to the current cluster.\n" +
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
coretesting "k8s.io/client-go/testing"
|
coretesting "k8s.io/client-go/testing"
|
||||||
|
|
||||||
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/config/v1alpha1"
|
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"
|
pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned"
|
||||||
pinnipedfake "go.pinniped.dev/generated/1.19/client/clientset/versioned/fake"
|
pinnipedfake "go.pinniped.dev/generated/1.19/client/clientset/versioned/fake"
|
||||||
"go.pinniped.dev/internal/here"
|
"go.pinniped.dev/internal/here"
|
||||||
@ -30,6 +31,8 @@ var (
|
|||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
-h, --help help for get-kubeconfig
|
-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 string Path to the kubeconfig file
|
||||||
--kubeconfig-context string Kubeconfig context override
|
--kubeconfig-context string Kubeconfig context override
|
||||||
--pinniped-namespace string Namespace in which Pinniped was installed (default "pinniped")
|
--pinniped-namespace string Namespace in which Pinniped was installed (default "pinniped")
|
||||||
@ -59,6 +62,8 @@ var (
|
|||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
-h, --help help for get-kubeconfig
|
-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 string Path to the kubeconfig file
|
||||||
--kubeconfig-context string Kubeconfig context override
|
--kubeconfig-context string Kubeconfig context override
|
||||||
--pinniped-namespace string Namespace in which Pinniped was installed (default "pinniped")
|
--pinniped-namespace string Namespace in which Pinniped was installed (default "pinniped")
|
||||||
@ -118,6 +123,8 @@ type expectedKubeconfigYAML struct {
|
|||||||
pinnipedEndpoint string
|
pinnipedEndpoint string
|
||||||
pinnipedCABundle string
|
pinnipedCABundle string
|
||||||
namespace string
|
namespace string
|
||||||
|
idpType string
|
||||||
|
idpName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e expectedKubeconfigYAML) String() string {
|
func (e expectedKubeconfigYAML) String() string {
|
||||||
@ -153,10 +160,14 @@ func (e expectedKubeconfigYAML) String() string {
|
|||||||
value: %s
|
value: %s
|
||||||
- name: PINNIPED_TOKEN
|
- name: PINNIPED_TOKEN
|
||||||
value: %s
|
value: %s
|
||||||
|
- name: PINNIPED_IDP_TYPE
|
||||||
|
value: %s
|
||||||
|
- name: PINNIPED_IDP_NAME
|
||||||
|
value: %s
|
||||||
installHint: |-
|
installHint: |-
|
||||||
The Pinniped CLI is required to authenticate to the current cluster.
|
The Pinniped CLI is required to authenticate to the current cluster.
|
||||||
For more information, please visit https://pinniped.dev
|
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 {
|
func newCredentialIssuerConfig(name, namespace, server, certificateAuthorityData string) *configv1alpha1.CredentialIssuerConfig {
|
||||||
@ -212,6 +223,46 @@ func TestRun(t *testing.T) {
|
|||||||
},
|
},
|
||||||
wantError: "some error configuring clientset",
|
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",
|
name: "fail to get CredentialIssuerConfigs",
|
||||||
mocks: func(cmd *getKubeConfigCommand) {
|
mocks: func(cmd *getKubeConfigCommand) {
|
||||||
@ -286,14 +337,21 @@ func TestRun(t *testing.T) {
|
|||||||
pinnipedEndpoint: "https://fake-server-url-value",
|
pinnipedEndpoint: "https://fake-server-url-value",
|
||||||
pinnipedCABundle: "fake-certificate-authority-data-value",
|
pinnipedCABundle: "fake-certificate-authority-data-value",
|
||||||
namespace: "test-namespace",
|
namespace: "test-namespace",
|
||||||
|
idpType: "test-idp-type",
|
||||||
|
idpName: "test-idp-name",
|
||||||
}.String(),
|
}.String(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "success using local CA data",
|
name: "success using local CA data and discovered IDP",
|
||||||
mocks: func(cmd *getKubeConfigCommand) {
|
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) {
|
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.`,
|
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",
|
pinnipedEndpoint: "https://fake-server-url-value",
|
||||||
pinnipedCABundle: "fake-certificate-authority-data-value",
|
pinnipedCABundle: "fake-certificate-authority-data-value",
|
||||||
namespace: "test-namespace",
|
namespace: "test-namespace",
|
||||||
|
idpType: "webhook",
|
||||||
|
idpName: "discovered-idp",
|
||||||
}.String(),
|
}.String(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -317,6 +377,8 @@ func TestRun(t *testing.T) {
|
|||||||
c := newGetKubeConfigCommand()
|
c := newGetKubeConfigCommand()
|
||||||
c.flags.token = "test-token"
|
c.flags.token = "test-token"
|
||||||
c.flags.namespace = "test-namespace"
|
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.getPathToSelf = func() (string, error) { return "/path/to/pinniped", nil }
|
||||||
c.flags.kubeconfig = "./testdata/kubeconfig.yaml"
|
c.flags.kubeconfig = "./testdata/kubeconfig.yaml"
|
||||||
tt.mocks(c)
|
tt.mocks(c)
|
||||||
|
Loading…
Reference in New Issue
Block a user