"pinniped get kubeconfig" discovers CA bundle from CertificateAuthorityDataSource

This commit is contained in:
Ryan Richard
2025-02-04 13:43:27 -08:00
parent e90f19f8ab
commit 02eb26f135
7 changed files with 845 additions and 71 deletions

View File

@@ -1,34 +1,36 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
"go.pinniped.dev/internal/groupsuffix"
"go.pinniped.dev/internal/kubeclient"
)
// getConciergeClientsetFunc is a function that can return a clientset for the Concierge API given a
// getClientsetsFunc is a function that can return clients for the Concierge and Kubernetes APIs given a
// clientConfig and the apiGroupSuffix with which the API is running.
type getConciergeClientsetFunc func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error)
type getClientsetsFunc func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, kubernetes.Interface, aggregatorclient.Interface, error)
// getRealConciergeClientset returns a real implementation of a conciergeclientset.Interface.
func getRealConciergeClientset(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error) {
// getRealClientsets returns real implementations of the Concierge and Kubernetes client interfaces.
func getRealClientsets(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, kubernetes.Interface, aggregatorclient.Interface, error) {
restConfig, err := clientConfig.ClientConfig()
if err != nil {
return nil, err
return nil, nil, nil, err
}
client, err := kubeclient.New(
kubeclient.WithConfig(restConfig),
kubeclient.WithMiddleware(groupsuffix.New(apiGroupSuffix)),
)
if err != nil {
return nil, err
return nil, nil, nil, err
}
return client.PinnipedConcierge, nil
return client.PinnipedConcierge, client.Kubernetes, client.Aggregation, nil
}
// newClientConfig returns a clientcmd.ClientConfig given an optional kubeconfig path override and

View File

@@ -1,4 +1,4 @@
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
@@ -20,10 +20,12 @@ import (
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
_ "k8s.io/client-go/plugin/pkg/client/auth" // Adds handlers for various dynamic auth plugins in client-go
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
authenticationv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
conciergeconfigv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
@@ -38,7 +40,7 @@ import (
type kubeconfigDeps struct {
getenv func(key string) string
getPathToSelf func() (string, error)
getClientset getConciergeClientsetFunc
getClientsets getClientsetsFunc
log plog.MinLogger
}
@@ -46,7 +48,7 @@ func kubeconfigRealDeps() kubeconfigDeps {
return kubeconfigDeps{
getenv: os.Getenv,
getPathToSelf: os.Executable,
getClientset: getRealConciergeClientset,
getClientsets: getRealClientsets,
log: plog.New(),
}
}
@@ -215,7 +217,7 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
return fmt.Errorf("could not load --kubeconfig/--kubeconfig-context: %w", err)
}
cluster := currentKubeConfig.Clusters[currentKubeconfigNames.ClusterName]
clientset, err := deps.getClientset(clientConfig, flags.concierge.apiGroupSuffix)
conciergeClient, kubeClient, aggregatorClient, err := deps.getClientsets(clientConfig, flags.concierge.apiGroupSuffix)
if err != nil {
return fmt.Errorf("could not configure Kubernetes client: %w", err)
}
@@ -228,13 +230,15 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
}
if !flags.concierge.disabled {
credentialIssuer, err := waitForCredentialIssuer(ctx, clientset, flags, deps)
// Look up the Concierge's CredentialIssuer, and optionally wait for it to have no pending strategies showing in its status.
credentialIssuer, err := waitForCredentialIssuer(ctx, conciergeClient, flags, deps)
if err != nil {
return err
}
// Decide which Concierge authenticator should be used in the resulting kubeconfig.
authenticator, err := lookupAuthenticator(
clientset,
conciergeClient,
flags.concierge.authenticatorType,
flags.concierge.authenticatorName,
deps.log,
@@ -242,10 +246,15 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
if err != nil {
return err
}
// Discover from the CredentialIssuer how the resulting kubeconfig should be configured to talk to this Concierge.
if err := discoverConciergeParams(credentialIssuer, &flags, cluster, deps.log); err != nil {
return err
}
if err := discoverAuthenticatorParams(authenticator, &flags, deps.log); err != nil {
// Discover how the resulting kubeconfig should interact with the selected authenticator.
// For a JWTAuthenticator, this includes discovering how to talk to the OIDC issuer configured in its spec fields.
if err := discoverAuthenticatorParams(ctx, authenticator, &flags, kubeClient, aggregatorClient, deps.log); err != nil {
return err
}
@@ -255,6 +264,7 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
}
if len(flags.oidc.issuer) > 0 {
// The OIDC provider may or may not be a Pinniped Supervisor. Find out.
err = pinnipedSupervisorDiscovery(ctx, &flags, deps.log)
if err != nil {
return err
@@ -488,7 +498,14 @@ func logStrategies(credentialIssuer *conciergeconfigv1alpha1.CredentialIssuer, l
}
}
func discoverAuthenticatorParams(authenticator metav1.Object, flags *getKubeconfigParams, log plog.MinLogger) error {
func discoverAuthenticatorParams(
ctx context.Context,
authenticator metav1.Object,
flags *getKubeconfigParams,
kubeClient kubernetes.Interface,
aggregatorClient aggregatorclient.Interface,
log plog.MinLogger,
) error {
switch auth := authenticator.(type) {
case *authenticationv1alpha1.WebhookAuthenticator:
// If the --concierge-authenticator-type/--concierge-authenticator-name flags were not set explicitly, set
@@ -520,19 +537,130 @@ func discoverAuthenticatorParams(authenticator metav1.Object, flags *getKubeconf
}
// If the --oidc-ca-bundle flags was not set explicitly, default it to the
// spec.tls.certificateAuthorityData field of the JWTAuthenticator.
if len(flags.oidc.caBundle) == 0 && auth.Spec.TLS != nil && auth.Spec.TLS.CertificateAuthorityData != "" {
decoded, err := base64.StdEncoding.DecodeString(auth.Spec.TLS.CertificateAuthorityData)
// spec.tls.certificateAuthorityData field of the JWTAuthenticator, if that field is set, or else
// try to discover it from the spec.tls.certificateAuthorityDataSource, if that field is set.
if len(flags.oidc.caBundle) == 0 && auth.Spec.TLS != nil {
err := discoverOIDCCABundle(ctx, auth, flags, kubeClient, aggregatorClient, log)
if err != nil {
return fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator %s has invalid spec.tls.certificateAuthorityData: %w", auth.Name, err)
return err
}
log.Info("discovered OIDC CA bundle", "roots", countCACerts(decoded))
flags.oidc.caBundle = decoded
}
}
return nil
}
func discoverOIDCCABundle(
ctx context.Context,
jwtAuthenticator *authenticationv1alpha1.JWTAuthenticator,
flags *getKubeconfigParams,
kubeClient kubernetes.Interface,
aggregatorClient aggregatorclient.Interface,
log plog.MinLogger,
) error {
if jwtAuthenticator.Spec.TLS.CertificateAuthorityData != "" {
decodedCABundleData, err := base64.StdEncoding.DecodeString(jwtAuthenticator.Spec.TLS.CertificateAuthorityData)
if err != nil {
return fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator %s has invalid spec.tls.certificateAuthorityData: %w", jwtAuthenticator.Name, err)
}
log.Info("discovered OIDC CA bundle", "roots", countCACerts(decodedCABundleData))
flags.oidc.caBundle = decodedCABundleData
} else if jwtAuthenticator.Spec.TLS.CertificateAuthorityDataSource != nil {
caBundleData, err := discoverOIDCCABundleFromCertificateAuthorityDataSource(
ctx, jwtAuthenticator, flags.concierge.apiGroupSuffix, kubeClient, aggregatorClient, log)
if err != nil {
return err
}
flags.oidc.caBundle = caBundleData
}
return nil
}
func discoverOIDCCABundleFromCertificateAuthorityDataSource(
ctx context.Context,
jwtAuthenticator *authenticationv1alpha1.JWTAuthenticator,
apiGroupSuffix string,
kubeClient kubernetes.Interface,
aggregatorClient aggregatorclient.Interface,
log plog.MinLogger,
) ([]byte, error) {
conciergeNamespace, err := discoverConciergeNamespace(ctx, apiGroupSuffix, aggregatorClient)
if err != nil {
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but encountered error discovering namespace of Concierge for JWTAuthenticator %s: %w", jwtAuthenticator.Name, err)
}
log.Info("discovered Concierge namespace for API group suffix", "apiGroupSuffix", apiGroupSuffix)
var caBundleData []byte
var keyExisted bool
caSource := jwtAuthenticator.Spec.TLS.CertificateAuthorityDataSource
// Note that the Kind, Name, and Key fields must all be non-empty, and Kind must be Secret or ConfigMap, due to CRD validations.
switch caSource.Kind {
case authenticationv1alpha1.CertificateAuthorityDataSourceKindConfigMap:
caBundleConfigMap, err := kubeClient.CoreV1().ConfigMaps(conciergeNamespace).Get(ctx, caSource.Name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but encountered error getting %s %s/%s specified by JWTAuthenticator %s spec.tls.certificateAuthorityDataSource: %w",
caSource.Kind, conciergeNamespace, caSource.Name, jwtAuthenticator.Name, err)
}
var caBundleDataStr string
caBundleDataStr, keyExisted = caBundleConfigMap.Data[caSource.Key]
caBundleData = []byte(caBundleDataStr)
case authenticationv1alpha1.CertificateAuthorityDataSourceKindSecret:
caBundleSecret, err := kubeClient.CoreV1().Secrets(conciergeNamespace).Get(ctx, caSource.Name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but encountered error getting %s %s/%s specified by JWTAuthenticator %s spec.tls.certificateAuthorityDataSource: %w",
caSource.Kind, conciergeNamespace, caSource.Name, jwtAuthenticator.Name, err)
}
caBundleData, keyExisted = caBundleSecret.Data[caSource.Key]
default:
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator %s spec.tls.certificateAuthorityDataSource.Kind value %q is not supported by this CLI version",
jwtAuthenticator.Name, caSource.Kind)
}
if !keyExisted {
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but key %q specified by JWTAuthenticator %s spec.tls.certificateAuthorityDataSource.key does not exist in %s %s/%s",
caSource.Key, jwtAuthenticator.Name, caSource.Kind, conciergeNamespace, caSource.Name)
}
if len(caBundleData) == 0 {
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but key %q specified by JWTAuthenticator %s spec.tls.certificateAuthorityDataSource.key exists but has empty value in %s %s/%s",
caSource.Key, jwtAuthenticator.Name, caSource.Kind, conciergeNamespace, caSource.Name)
}
numCACerts := countCACerts(caBundleData)
if numCACerts == 0 {
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but value at key %q specified by JWTAuthenticator %s spec.tls.certificateAuthorityDataSource.key does not contain any CA certificates in %s %s/%s",
caSource.Key, jwtAuthenticator.Name, caSource.Kind, conciergeNamespace, caSource.Name)
}
log.Info("discovered OIDC CA bundle from JWTAuthenticator spec.tls.certificateAuthorityDataSource", "roots", numCACerts)
return caBundleData, nil
}
func discoverConciergeNamespace(ctx context.Context, apiGroupSuffix string, aggregatorClient aggregatorclient.Interface) (string, error) {
// Let's look for the APIService for the API group of the Concierge's TokenCredentialRequest aggregated API.
apiGroup := "login.concierge." + apiGroupSuffix
// List all APIServices.
apiServiceList, err := aggregatorClient.ApiregistrationV1().APIServices().List(ctx, metav1.ListOptions{})
if err != nil {
return "", fmt.Errorf("error listing APIServices: %w", err)
}
// Find one with the expected API group name.
for _, apiService := range apiServiceList.Items {
if apiService.Spec.Group == apiGroup {
if apiService.Spec.Service.Namespace != "" {
// We are assuming that all API versions (e.g. v1alpha1) of this API group are backed by service(s)
// in the same namespace, which is the namespace of the Concierge hosting this API suffix.
return apiService.Spec.Service.Namespace, nil
}
}
}
// Couldn't find any APIService for the expected API group name which contained a namespace reference in its spec.
return "", fmt.Errorf("could not find APIService with non-empty spec.service.namespace for API group %s", apiGroup)
}
func getConciergeFrontend(credentialIssuer *conciergeconfigv1alpha1.CredentialIssuer, mode conciergeModeFlag) (*conciergeconfigv1alpha1.CredentialIssuerFrontend, error) {
for _, strategy := range credentialIssuer.Status.Strategies {
// Skip unhealthy strategies.

View File

@@ -1,4 +1,4 @@
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
@@ -15,10 +15,16 @@ import (
"time"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
kubetesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/clientcmd"
v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
aggregatorfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
"k8s.io/utils/ptr"
authenticationv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
@@ -64,14 +70,69 @@ func TestGetKubeconfig(t *testing.T) {
}
}
jwtAuthenticator := func(issuerCABundle string, issuerURL string) runtime.Object {
caBundleInSecret := func(issuerCABundle, secretName, secretNamespace, secretDataKey string) runtime.Object {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: secretNamespace,
},
Data: map[string][]byte{
secretDataKey: []byte(issuerCABundle),
"other": []byte("unrelated"),
},
}
}
caBundleInConfigmap := func(issuerCABundle, cmName, cmNamespace, cmDataKey string) runtime.Object {
return &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: cmName,
Namespace: cmNamespace,
},
Data: map[string]string{
cmDataKey: issuerCABundle,
"other": "unrelated",
},
}
}
jwtAuthenticator := func(issuerCABundle, issuerURL string) *authenticationv1alpha1.JWTAuthenticator {
encodedCABundle := ""
if issuerCABundle != "" {
encodedCABundle = base64.StdEncoding.EncodeToString([]byte(issuerCABundle))
}
return &authenticationv1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
Spec: authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: issuerURL,
Audience: "test-audience",
TLS: &authenticationv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(issuerCABundle)),
CertificateAuthorityData: encodedCABundle,
},
},
}
}
jwtAuthenticatorWithCABundleDataSource := func(sourceKind, sourceName, sourceKey, issuerURL string) runtime.Object {
authenticator := jwtAuthenticator("", issuerURL)
authenticator.Spec.TLS.CertificateAuthorityDataSource = &authenticationv1alpha1.CertificateAuthorityDataSourceSpec{
Kind: authenticationv1alpha1.CertificateAuthorityDataSourceKind(sourceKind),
Name: sourceName,
Key: sourceKey,
}
return authenticator
}
apiService := func(group, version, serviceNamespace string) *v1.APIService {
return &v1.APIService{
ObjectMeta: metav1.ObjectMeta{
Name: version + "." + group,
},
Spec: v1.APIServiceSpec{
Group: group,
Version: version,
Service: &v1.ServiceReference{
Namespace: serviceNamespace,
},
},
}
@@ -144,7 +205,11 @@ func TestGetKubeconfig(t *testing.T) {
getPathToSelfErr error
getClientsetErr error
conciergeObjects func(string, string) []runtime.Object
kubeObjects func(string) []runtime.Object
apiServiceObjects []runtime.Object
conciergeReactions []kubetesting.Reactor
kubeReactions []kubetesting.Reactor
apiServiceReactions []kubetesting.Reactor
oidcDiscoveryResponse func(string) string
oidcDiscoveryStatusCode int
idpsDiscoveryResponse string
@@ -656,6 +721,321 @@ func TestGetKubeconfig(t *testing.T) {
return testutil.WantExactErrorString(`Error: tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator test-authenticator has invalid spec.tls.certificateAuthorityData: illegal base64 data at input byte 7` + "\n")
},
},
{
name: "autodetect JWT authenticator with CA bundle in Secret, but Secret not found",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticatorWithCABundleDataSource("Secret", "my-ca-secret", "ca.crt", issuerURL),
}
},
apiServiceObjects: []runtime.Object{
apiService("login.concierge.pinniped.dev", "v1alpha1", "test-concierge-namespace"),
},
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered CredentialIssuer {"name": "test-credential-issuer"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge operating in TokenCredentialRequest API mode`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge endpoint {"endpoint": "https://fake-server-url-value"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge certificate authority bundle {"roots": 0}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered JWTAuthenticator {"name": "test-authenticator"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC issuer {"issuer": "` + issuerURL + `"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC audience {"audience": "test-audience"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge namespace for API group suffix {"apiGroupSuffix": "pinniped.dev"}`,
}
},
wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc {
return testutil.WantExactErrorString(`Error: tried to autodiscover --oidc-ca-bundle, but encountered error getting Secret test-concierge-namespace/my-ca-secret specified by JWTAuthenticator test-authenticator spec.tls.certificateAuthorityDataSource: secrets "my-ca-secret" not found` + "\n")
},
},
{
name: "autodetect JWT authenticator with CA bundle in ConfigMap, but ConfigMap not found",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticatorWithCABundleDataSource("ConfigMap", "my-ca-configmap", "ca.crt", issuerURL),
}
},
apiServiceObjects: []runtime.Object{
apiService("login.concierge.pinniped.dev", "v1alpha1", "test-concierge-namespace"),
},
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered CredentialIssuer {"name": "test-credential-issuer"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge operating in TokenCredentialRequest API mode`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge endpoint {"endpoint": "https://fake-server-url-value"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge certificate authority bundle {"roots": 0}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered JWTAuthenticator {"name": "test-authenticator"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC issuer {"issuer": "` + issuerURL + `"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC audience {"audience": "test-audience"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge namespace for API group suffix {"apiGroupSuffix": "pinniped.dev"}`,
}
},
wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc {
return testutil.WantExactErrorString(`Error: tried to autodiscover --oidc-ca-bundle, but encountered error getting ConfigMap test-concierge-namespace/my-ca-configmap specified by JWTAuthenticator test-authenticator spec.tls.certificateAuthorityDataSource: configmaps "my-ca-configmap" not found` + "\n")
},
},
{
name: "autodetect JWT authenticator with CA bundle in Secret, but invalid TLS bundle found in Secret",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticatorWithCABundleDataSource("Secret", "my-ca-secret", "ca.crt", issuerURL),
}
},
kubeObjects: func(issuerCABundle string) []runtime.Object {
return []runtime.Object{
caBundleInSecret("invalid CA bundle data", "my-ca-secret", "test-concierge-namespace", "ca.crt"),
}
},
apiServiceObjects: []runtime.Object{
apiService("login.concierge.pinniped.dev", "v1alpha1", "test-concierge-namespace"),
},
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered CredentialIssuer {"name": "test-credential-issuer"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge operating in TokenCredentialRequest API mode`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge endpoint {"endpoint": "https://fake-server-url-value"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge certificate authority bundle {"roots": 0}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered JWTAuthenticator {"name": "test-authenticator"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC issuer {"issuer": "` + issuerURL + `"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC audience {"audience": "test-audience"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge namespace for API group suffix {"apiGroupSuffix": "pinniped.dev"}`,
}
},
wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc {
return testutil.WantExactErrorString(`Error: tried to autodiscover --oidc-ca-bundle, but value at key "ca.crt" specified by JWTAuthenticator test-authenticator spec.tls.certificateAuthorityDataSource.key does not contain any CA certificates in Secret test-concierge-namespace/my-ca-secret` + "\n")
},
},
{
name: "autodetect JWT authenticator with CA bundle in Secret, but specified key not found in Secret",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticatorWithCABundleDataSource("Secret", "my-ca-secret", "ca.crt", issuerURL),
}
},
kubeObjects: func(issuerCABundle string) []runtime.Object {
return []runtime.Object{
caBundleInSecret(issuerCABundle, "my-ca-secret", "test-concierge-namespace", "wrong_key_name"),
}
},
apiServiceObjects: []runtime.Object{
apiService("login.concierge.pinniped.dev", "v1alpha1", "test-concierge-namespace"),
},
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered CredentialIssuer {"name": "test-credential-issuer"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge operating in TokenCredentialRequest API mode`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge endpoint {"endpoint": "https://fake-server-url-value"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge certificate authority bundle {"roots": 0}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered JWTAuthenticator {"name": "test-authenticator"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC issuer {"issuer": "` + issuerURL + `"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC audience {"audience": "test-audience"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge namespace for API group suffix {"apiGroupSuffix": "pinniped.dev"}`,
}
},
wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc {
return testutil.WantExactErrorString(`Error: tried to autodiscover --oidc-ca-bundle, but key "ca.crt" specified by JWTAuthenticator test-authenticator spec.tls.certificateAuthorityDataSource.key does not exist in Secret test-concierge-namespace/my-ca-secret` + "\n")
},
},
{
name: "autodetect JWT authenticator with CA bundle in Secret, but specified key has empty value in Secret",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticatorWithCABundleDataSource("Secret", "my-ca-secret", "ca.crt", issuerURL),
}
},
kubeObjects: func(issuerCABundle string) []runtime.Object {
return []runtime.Object{
caBundleInSecret("", "my-ca-secret", "test-concierge-namespace", "ca.crt"),
}
},
apiServiceObjects: []runtime.Object{
apiService("login.concierge.pinniped.dev", "v1", "test-concierge-namespace"),
},
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered CredentialIssuer {"name": "test-credential-issuer"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge operating in TokenCredentialRequest API mode`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge endpoint {"endpoint": "https://fake-server-url-value"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge certificate authority bundle {"roots": 0}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered JWTAuthenticator {"name": "test-authenticator"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC issuer {"issuer": "` + issuerURL + `"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC audience {"audience": "test-audience"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge namespace for API group suffix {"apiGroupSuffix": "pinniped.dev"}`,
}
},
wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc {
return testutil.WantExactErrorString(`Error: tried to autodiscover --oidc-ca-bundle, but key "ca.crt" specified by JWTAuthenticator test-authenticator spec.tls.certificateAuthorityDataSource.key exists but has empty value in Secret test-concierge-namespace/my-ca-secret` + "\n")
},
},
{
name: "autodetect JWT authenticator with CA bundle source, but source's Kind is not supported",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticatorWithCABundleDataSource("Unsupported-Value", "my-ca-secret", "ca.crt", issuerURL),
}
},
apiServiceObjects: []runtime.Object{
apiService("login.concierge.pinniped.dev", "v1alpha1", "test-concierge-namespace"),
},
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered CredentialIssuer {"name": "test-credential-issuer"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge operating in TokenCredentialRequest API mode`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge endpoint {"endpoint": "https://fake-server-url-value"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge certificate authority bundle {"roots": 0}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered JWTAuthenticator {"name": "test-authenticator"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC issuer {"issuer": "` + issuerURL + `"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC audience {"audience": "test-audience"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge namespace for API group suffix {"apiGroupSuffix": "pinniped.dev"}`,
}
},
wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc {
return testutil.WantExactErrorString(`Error: tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator test-authenticator spec.tls.certificateAuthorityDataSource.Kind value "Unsupported-Value" is not supported by this CLI version` + "\n")
},
},
{
name: "autodetect JWT authenticator with CA bundle in Secret, but no related APIService found",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticatorWithCABundleDataSource("Secret", "my-ca-secret", "ca.crt", issuerURL),
}
},
apiServiceObjects: []runtime.Object{
apiService("unrelated.example.com", "v1alpha1", "test-concierge-namespace"),
},
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered CredentialIssuer {"name": "test-credential-issuer"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge operating in TokenCredentialRequest API mode`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge endpoint {"endpoint": "https://fake-server-url-value"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge certificate authority bundle {"roots": 0}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered JWTAuthenticator {"name": "test-authenticator"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC issuer {"issuer": "` + issuerURL + `"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC audience {"audience": "test-audience"}`,
}
},
wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc {
return testutil.WantExactErrorString(`Error: tried to autodiscover --oidc-ca-bundle, but encountered error discovering namespace of Concierge for JWTAuthenticator test-authenticator: could not find APIService with non-empty spec.service.namespace for API group login.concierge.pinniped.dev` + "\n")
},
},
{
name: "autodetect JWT authenticator with CA bundle in Secret, but related APIService has empty namespace in spec",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticatorWithCABundleDataSource("Secret", "my-ca-secret", "ca.crt", issuerURL),
}
},
apiServiceObjects: []runtime.Object{
apiService("login.concierge.pinniped.dev", "v1alpha1", ""),
},
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered CredentialIssuer {"name": "test-credential-issuer"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge operating in TokenCredentialRequest API mode`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge endpoint {"endpoint": "https://fake-server-url-value"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge certificate authority bundle {"roots": 0}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered JWTAuthenticator {"name": "test-authenticator"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC issuer {"issuer": "` + issuerURL + `"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC audience {"audience": "test-audience"}`,
}
},
wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc {
return testutil.WantExactErrorString(`Error: tried to autodiscover --oidc-ca-bundle, but encountered error discovering namespace of Concierge for JWTAuthenticator test-authenticator: could not find APIService with non-empty spec.service.namespace for API group login.concierge.pinniped.dev` + "\n")
},
},
{
name: "autodetect JWT authenticator with CA bundle in Secret, but error when listing APIServices",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticatorWithCABundleDataSource("Secret", "my-ca-secret", "ca.crt", issuerURL),
}
},
apiServiceReactions: []kubetesting.Reactor{
&kubetesting.SimpleReactor{
Verb: "*",
Resource: "apiservices",
Reaction: func(kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, fmt.Errorf("some list error")
},
},
},
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered CredentialIssuer {"name": "test-credential-issuer"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge operating in TokenCredentialRequest API mode`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge endpoint {"endpoint": "https://fake-server-url-value"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge certificate authority bundle {"roots": 0}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered JWTAuthenticator {"name": "test-authenticator"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC issuer {"issuer": "` + issuerURL + `"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC audience {"audience": "test-audience"}`,
}
},
wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc {
return testutil.WantExactErrorString(`Error: tried to autodiscover --oidc-ca-bundle, but encountered error discovering namespace of Concierge for JWTAuthenticator test-authenticator: error listing APIServices: some list error` + "\n")
},
},
{
name: "autodetect JWT authenticator, invalid substring in audience",
args: func(issuerCABundle string, issuerURL string) []string {
@@ -1600,6 +1980,257 @@ func TestGetKubeconfig(t *testing.T) {
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
},
},
{
name: "autodetect JWT authenticator with CA bundle in Secret",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--skip-validation",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticatorWithCABundleDataSource("Secret", "my-ca-secret", "ca.crt", issuerURL),
}
},
kubeObjects: func(issuerCABundle string) []runtime.Object {
return []runtime.Object{
caBundleInSecret(issuerCABundle, "my-ca-secret", "test-concierge-namespace", "ca.crt"),
}
},
apiServiceObjects: []runtime.Object{
apiService("login.concierge.pinniped.dev", "v1alpha1", "test-concierge-namespace"),
apiService("unrelated.pinniped.dev", "v1alpha1", "unrelated-namespace"),
apiService("login.concierge.pinniped.dev", "v1alpha2", "test-concierge-namespace"),
},
oidcDiscoveryResponse: onlyIssuerOIDCDiscoveryResponse,
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered CredentialIssuer {"name": "test-credential-issuer"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge operating in TokenCredentialRequest API mode`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge endpoint {"endpoint": "https://fake-server-url-value"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge certificate authority bundle {"roots": 0}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered JWTAuthenticator {"name": "test-authenticator"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC issuer {"issuer": "` + issuerURL + `"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC audience {"audience": "test-audience"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge namespace for API group suffix {"apiGroupSuffix": "pinniped.dev"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC CA bundle from JWTAuthenticator spec.tls.certificateAuthorityDataSource {"roots": 1}`,
}
},
wantStdout: func(issuerCABundle string, issuerURL string) string {
return here.Docf(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: kind-cluster-pinniped
contexts:
- context:
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- login
- oidc
- --enable-concierge
- --concierge-api-group-suffix=pinniped.dev
- --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=jwt
- --concierge-endpoint=https://fake-server-url-value
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
- --issuer=%s
- --client-id=pinniped-cli
- --scopes=offline_access,openid,pinniped:request-audience,username,groups
- --ca-bundle-data=%s
- --request-audience=test-audience
command: '.../path/to/pinniped'
env: []
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
for more details
provideClusterInfo: true
`,
issuerURL,
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
},
},
{
name: "autodetect JWT authenticator with CA bundle in ConfigMap",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--skip-validation",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticatorWithCABundleDataSource("ConfigMap", "my-ca-configmap", "ca.crt", issuerURL),
}
},
kubeObjects: func(issuerCABundle string) []runtime.Object {
return []runtime.Object{
caBundleInConfigmap(issuerCABundle, "my-ca-configmap", "test-concierge-namespace", "ca.crt"),
}
},
apiServiceObjects: []runtime.Object{
apiService("login.concierge.pinniped.dev", "v1alpha1", "test-concierge-namespace"),
apiService("unrelated.pinniped.dev", "v1alpha1", "unrelated-namespace"),
apiService("login.concierge.pinniped.dev", "v1alpha2", "test-concierge-namespace"),
},
oidcDiscoveryResponse: onlyIssuerOIDCDiscoveryResponse,
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered CredentialIssuer {"name": "test-credential-issuer"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge operating in TokenCredentialRequest API mode`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge endpoint {"endpoint": "https://fake-server-url-value"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge certificate authority bundle {"roots": 0}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered JWTAuthenticator {"name": "test-authenticator"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC issuer {"issuer": "` + issuerURL + `"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC audience {"audience": "test-audience"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge namespace for API group suffix {"apiGroupSuffix": "pinniped.dev"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC CA bundle from JWTAuthenticator spec.tls.certificateAuthorityDataSource {"roots": 1}`,
}
},
wantStdout: func(issuerCABundle string, issuerURL string) string {
return here.Docf(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: kind-cluster-pinniped
contexts:
- context:
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- login
- oidc
- --enable-concierge
- --concierge-api-group-suffix=pinniped.dev
- --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=jwt
- --concierge-endpoint=https://fake-server-url-value
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
- --issuer=%s
- --client-id=pinniped-cli
- --scopes=offline_access,openid,pinniped:request-audience,username,groups
- --ca-bundle-data=%s
- --request-audience=test-audience
command: '.../path/to/pinniped'
env: []
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
for more details
provideClusterInfo: true
`,
issuerURL,
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
},
},
{
name: "autodetect JWT authenticator with CA bundle in ConfigMap with a custom API group suffix",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--concierge-api-group-suffix=acme.com",
"--skip-validation",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticatorWithCABundleDataSource("ConfigMap", "my-ca-configmap", "ca.crt", issuerURL),
}
},
kubeObjects: func(issuerCABundle string) []runtime.Object {
return []runtime.Object{
caBundleInConfigmap(issuerCABundle, "my-ca-configmap", "test-concierge-namespace", "ca.crt"),
}
},
apiServiceObjects: []runtime.Object{
apiService("login.concierge.acme.com", "v1alpha1", "test-concierge-namespace"),
apiService("unrelated.pinniped.dev", "v1alpha1", "unrelated-namespace"),
apiService("login.concierge.pinniped.dev", "v1alpha2", "another-unrelated-namespace"),
},
oidcDiscoveryResponse: onlyIssuerOIDCDiscoveryResponse,
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered CredentialIssuer {"name": "test-credential-issuer"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge operating in TokenCredentialRequest API mode`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge endpoint {"endpoint": "https://fake-server-url-value"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge certificate authority bundle {"roots": 0}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered JWTAuthenticator {"name": "test-authenticator"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC issuer {"issuer": "` + issuerURL + `"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC audience {"audience": "test-audience"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered Concierge namespace for API group suffix {"apiGroupSuffix": "acme.com"}`,
`2099-08-08T13:57:36.123456Z info cmd/kubeconfig.go:<line> discovered OIDC CA bundle from JWTAuthenticator spec.tls.certificateAuthorityDataSource {"roots": 1}`,
}
},
wantAPIGroupSuffix: "acme.com",
wantStdout: func(issuerCABundle string, issuerURL string) string {
return here.Docf(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: kind-cluster-pinniped
contexts:
- context:
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- login
- oidc
- --enable-concierge
- --concierge-api-group-suffix=acme.com
- --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=jwt
- --concierge-endpoint=https://fake-server-url-value
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
- --issuer=%s
- --client-id=pinniped-cli
- --scopes=offline_access,openid,pinniped:request-audience,username,groups
- --ca-bundle-data=%s
- --request-audience=test-audience
command: '.../path/to/pinniped'
env: []
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
for more details
provideClusterInfo: true
`,
issuerURL,
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
},
},
{
name: "autodetect nothing, set a bunch of options",
args: func(issuerCABundle string, issuerURL string) []string {
@@ -3211,6 +3842,7 @@ func TestGetKubeconfig(t *testing.T) {
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var issuerEndpointPtr *string
@@ -3245,6 +3877,37 @@ func TestGetKubeconfig(t *testing.T) {
}), nil)
issuerEndpointPtr = ptr.To(testServer.URL)
getClientsetFunc := func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, kubernetes.Interface, aggregatorclient.Interface, error) {
if tt.wantAPIGroupSuffix == "" {
require.Equal(t, "pinniped.dev", apiGroupSuffix) // "pinniped.dev" = api group suffix default
} else {
require.Equal(t, tt.wantAPIGroupSuffix, apiGroupSuffix)
}
if tt.getClientsetErr != nil {
return nil, nil, nil, tt.getClientsetErr
}
fakeAggregatorClient := aggregatorfake.NewSimpleClientset(tt.apiServiceObjects...)
fakeKubeClient := fake.NewClientset()
if tt.kubeObjects != nil {
kubeObjects := tt.kubeObjects(string(testServerCA))
fakeKubeClient = fake.NewClientset(kubeObjects...)
}
fakeConciergeClient := conciergefake.NewSimpleClientset()
if tt.conciergeObjects != nil {
fakeConciergeClient = conciergefake.NewSimpleClientset(tt.conciergeObjects(string(testServerCA), testServer.URL)...)
}
if len(tt.conciergeReactions) > 0 {
fakeConciergeClient.ReactionChain = slices.Concat(tt.conciergeReactions, fakeConciergeClient.ReactionChain)
}
if len(tt.kubeReactions) > 0 {
fakeKubeClient.ReactionChain = slices.Concat(tt.kubeReactions, fakeKubeClient.ReactionChain)
}
if len(tt.apiServiceReactions) > 0 {
fakeAggregatorClient.ReactionChain = slices.Concat(tt.apiServiceReactions, fakeAggregatorClient.ReactionChain)
}
return fakeConciergeClient, fakeKubeClient, fakeAggregatorClient, nil
}
var log bytes.Buffer
cmd := kubeconfigCommand(kubeconfigDeps{
@@ -3257,25 +3920,8 @@ func TestGetKubeconfig(t *testing.T) {
}
return ".../path/to/pinniped", nil
},
getClientset: func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error) {
if tt.wantAPIGroupSuffix == "" {
require.Equal(t, "pinniped.dev", apiGroupSuffix) // "pinniped.dev" = api group suffix default
} else {
require.Equal(t, tt.wantAPIGroupSuffix, apiGroupSuffix)
}
if tt.getClientsetErr != nil {
return nil, tt.getClientsetErr
}
fake := conciergefake.NewSimpleClientset()
if tt.conciergeObjects != nil {
fake = conciergefake.NewSimpleClientset(tt.conciergeObjects(string(testServerCA), testServer.URL)...)
}
if len(tt.conciergeReactions) > 0 {
fake.ReactionChain = slices.Concat(tt.conciergeReactions, fake.ReactionChain)
}
return fake, nil
},
log: plog.TestConsoleLogger(t, &log),
getClientsets: getClientsetFunc,
log: plog.TestConsoleLogger(t, &log),
})
require.NotNil(t, cmd)

View File

@@ -1,4 +1,4 @@
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
@@ -25,14 +25,14 @@ import (
)
type whoamiDeps struct {
getenv func(key string) string
getClientset getConciergeClientsetFunc
getenv func(key string) string
getClientsets getClientsetsFunc
}
func whoamiRealDeps() whoamiDeps {
return whoamiDeps{
getenv: os.Getenv,
getClientset: getRealConciergeClientset,
getenv: os.Getenv,
getClientsets: getRealClientsets,
}
}
@@ -82,7 +82,7 @@ func newWhoamiCommand(deps whoamiDeps) *cobra.Command {
func runWhoami(output io.Writer, deps whoamiDeps, flags *whoamiFlags) error {
clientConfig := newClientConfig(flags.kubeconfigPath, flags.kubeconfigContextOverride)
clientset, err := deps.getClientset(clientConfig, flags.apiGroupSuffix)
conciergeClient, _, _, err := deps.getClientsets(clientConfig, flags.apiGroupSuffix)
if err != nil {
return fmt.Errorf("could not configure Kubernetes client: %w", err)
}
@@ -108,7 +108,7 @@ func runWhoami(output io.Writer, deps whoamiDeps, flags *whoamiFlags) error {
defer cancelFunc()
}
whoAmI, err := clientset.IdentityV1alpha1().WhoAmIRequests().Create(ctx, &identityv1alpha1.WhoAmIRequest{}, metav1.CreateOptions{})
whoAmI, err := conciergeClient.IdentityV1alpha1().WhoAmIRequests().Create(ctx, &identityv1alpha1.WhoAmIRequest{}, metav1.CreateOptions{})
if err != nil {
hint := ""
if apierrors.IsNotFound(err) {

View File

@@ -1,4 +1,4 @@
// Copyright 2023-2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2023-2025 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
@@ -11,8 +11,10 @@ import (
"github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
kubetesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/clientcmd"
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
identityv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/identity/v1alpha1"
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
@@ -290,14 +292,15 @@ func TestWhoami(t *testing.T) {
wantStderr: "Error: could not complete WhoAmIRequest (is the Pinniped WhoAmI API running and healthy?): whoamirequests.identity.concierge.pinniped.dev \"whatever\" not found\n",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
getClientset := func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error) {
getClientsetFunc := func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, kubernetes.Interface, aggregatorclient.Interface, error) {
if test.gettingClientsetErr != nil {
return nil, test.gettingClientsetErr
return nil, nil, nil, test.gettingClientsetErr
}
clientset := conciergefake.NewSimpleClientset()
clientset.PrependReactor("create", "whoamirequests", func(_ kubetesting.Action) (bool, runtime.Object, error) {
conciergeClient := conciergefake.NewSimpleClientset()
conciergeClient.PrependReactor("create", "whoamirequests", func(_ kubetesting.Action) (bool, runtime.Object, error) {
if test.callingAPIErr != nil {
return true, nil, test.callingAPIErr
}
@@ -316,13 +319,14 @@ func TestWhoami(t *testing.T) {
},
}, nil
})
return clientset, nil
return conciergeClient, nil, nil, nil
}
cmd := newWhoamiCommand(whoamiDeps{
getenv: func(key string) string {
return test.env[key]
},
getClientset: getClientset,
getClientsets: getClientsetFunc,
})
stdout, stderr := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Copyright 2022-2024 the Pinniped contributors. All Rights Reserved.
# Copyright 2022-2025 the Pinniped contributors. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
set -euo pipefail
@@ -13,7 +13,11 @@ go version
lint_version="v$(cat hack/lib/lint-version.txt)"
echo "Installing golangci-lint@${lint_version}"
# Find the toolchain version from our go.mod file. "go install" pays attention to $GOTOOLCHAIN.
GOTOOLCHAIN=$(sed -rn 's/^toolchain (go[0-9\.]+)$/\1/p' go.mod)
export GOTOOLCHAIN
echo "Installing golangci-lint@${lint_version} using toolchain ${GOTOOLCHAIN}"
# Install the same version of the linter that the pipelines will use
# so you can get the same results when running the linter locally.

View File

@@ -1,4 +1,4 @@
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package integration
@@ -174,7 +174,6 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
"--concierge-authenticator-type", "jwt",
"--concierge-authenticator-name", authenticator.Name,
"--oidc-skip-browser",
"--oidc-ca-bundle", federationDomainCABundlePath,
"--oidc-session-cache", sessionCachePath,
"--credential-cache", credentialCachePath,
// use default for --oidc-scopes, which is to request all relevant scopes
@@ -276,7 +275,6 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
"--concierge-authenticator-type", "jwt",
"--concierge-authenticator-name", authenticator.Name,
"--oidc-skip-browser",
"--oidc-ca-bundle", federationDomainCABundlePath,
"--oidc-session-cache", sessionCachePath,
"--credential-cache", credentialCachePath,
"--oidc-scopes", "offline_access,openid,pinniped:request-audience", // does not request username or groups
@@ -379,7 +377,6 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
"--concierge-authenticator-name", authenticator.Name,
"--oidc-skip-browser",
"--oidc-skip-listen",
"--oidc-ca-bundle", federationDomainCABundlePath,
"--oidc-session-cache", sessionCachePath,
"--credential-cache", credentialCachePath,
// use default for --oidc-scopes, which is to request all relevant scopes
@@ -517,7 +514,6 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
"--concierge-authenticator-name", authenticator.Name,
"--oidc-skip-browser",
"--oidc-skip-listen",
"--oidc-ca-bundle", federationDomainCABundlePath,
"--oidc-session-cache", sessionCachePath,
"--credential-cache", credentialCachePath,
// use default for --oidc-scopes, which is to request all relevant scopes
@@ -651,7 +647,6 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
"--oidc-skip-browser",
"--oidc-skip-listen",
"--upstream-identity-provider-flow", "cli_password", // create a kubeconfig configured to use the cli_password flow
"--oidc-ca-bundle", federationDomainCABundlePath,
"--oidc-session-cache", sessionCachePath,
"--credential-cache", credentialCachePath,
// use default for --oidc-scopes, which is to request all relevant scopes
@@ -731,7 +726,6 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
"--upstream-identity-provider-name", oidcIdentityProvider.Name,
"--upstream-identity-provider-type", "oidc",
"--upstream-identity-provider-flow", "cli_password",
"--oidc-ca-bundle", federationDomainCABundlePath,
"--oidc-session-cache", sessionCachePath,
"--credential-cache", credentialCachePath,
// use default for --oidc-scopes, which is to request all relevant scopes
@@ -1118,7 +1112,6 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
"--concierge-authenticator-type", "jwt",
"--concierge-authenticator-name", authenticator.Name,
"--oidc-skip-browser",
"--oidc-ca-bundle", federationDomainCABundlePath,
"--upstream-identity-provider-flow", "browser_authcode",
"--oidc-session-cache", sessionCachePath,
"--credential-cache", credentialCachePath,
@@ -1174,7 +1167,6 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
"--concierge-authenticator-type", "jwt",
"--concierge-authenticator-name", authenticator.Name,
"--oidc-skip-browser",
"--oidc-ca-bundle", federationDomainCABundlePath,
"--upstream-identity-provider-flow", "browser_authcode",
"--oidc-session-cache", sessionCachePath,
"--credential-cache", credentialCachePath,
@@ -1230,7 +1222,6 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
"--concierge-authenticator-type", "jwt",
"--concierge-authenticator-name", authenticator.Name,
"--oidc-skip-browser",
"--oidc-ca-bundle", federationDomainCABundlePath,
"--upstream-identity-provider-flow", "cli_password", // put cli_password in the kubeconfig, so we can override it with the env var
"--oidc-session-cache", sessionCachePath,
"--credential-cache", credentialCachePath,
@@ -1319,7 +1310,6 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
"--concierge-authenticator-type", "jwt",
"--concierge-authenticator-name", authenticator.Name,
"--oidc-skip-browser",
"--oidc-ca-bundle", federationDomainCABundlePath,
"--oidc-session-cache", sessionCachePath,
"--credential-cache", credentialCachePath,
// use default for --oidc-scopes, which is to request all relevant scopes