diff --git a/cmd/local-user-authenticator/main.go b/cmd/local-user-authenticator/main.go index c533704d..f8e6d24f 100644 --- a/cmd/local-user-authenticator/main.go +++ b/cmd/local-user-authenticator/main.go @@ -301,12 +301,15 @@ func startControllers( ) { aVeryLongTime := time.Hour * 24 * 365 * 100 + const certsSecretResourceName = "local-user-authenticator-tls-serving-certificate" + // Create controller manager. controllerManager := controllerlib. NewManager(). WithController( apicerts.NewCertsManagerController( namespace, + certsSecretResourceName, kubeClient, kubeInformers.Core().V1().Secrets(), controllerlib.WithInformer, @@ -320,6 +323,7 @@ func startControllers( WithController( apicerts.NewCertsObserverController( namespace, + certsSecretResourceName, dynamicCertProvider, kubeInformers.Core().V1().Secrets(), controllerlib.WithInformer, @@ -367,7 +371,7 @@ func run() error { startControllers(ctx, dynamicCertProvider, kubeClient, kubeInformers) klog.InfoS("controllers are ready") - //nolint: gosec + //nolint: gosec // Intentionally binding to all network interfaces. l, err := net.Listen("tcp", ":443") if err != nil { return fmt.Errorf("cannot create listener: %w", err) diff --git a/cmd/pinniped/cmd/get_kubeconfig.go b/cmd/pinniped/cmd/get_kubeconfig.go index 444292e1..b4674f29 100644 --- a/cmd/pinniped/cmd/get_kubeconfig.go +++ b/cmd/pinniped/cmd/get_kubeconfig.go @@ -14,7 +14,6 @@ import ( "github.com/ghodss/yaml" "github.com/spf13/cobra" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" "k8s.io/client-go/rest" @@ -25,7 +24,6 @@ import ( "github.com/vmware-tanzu/pinniped/generated/1.19/apis/crdpinniped/v1alpha1" pinnipedclientset "github.com/vmware-tanzu/pinniped/generated/1.19/client/clientset/versioned" "github.com/vmware-tanzu/pinniped/internal/constable" - "github.com/vmware-tanzu/pinniped/internal/controller/issuerconfig" "github.com/vmware-tanzu/pinniped/internal/here" ) @@ -239,20 +237,27 @@ func fetchPinnipedCredentialIssuerConfig(clientConfig clientcmd.ClientConfig, ku ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20) defer cancelFunc() - credentialIssuerConfig, err := clientset.CrdV1alpha1().CredentialIssuerConfigs(pinnipedInstallationNamespace).Get(ctx, issuerconfig.ConfigName, metav1.GetOptions{}) + credentialIssuerConfigs, err := clientset.CrdV1alpha1().CredentialIssuerConfigs(pinnipedInstallationNamespace).List(ctx, metav1.ListOptions{}) if err != nil { - if apierrors.IsNotFound(err) { - return nil, constable.Error(fmt.Sprintf( - `CredentialIssuerConfig "%s" was not found in namespace "%s". Is Pinniped installed on this cluster in namespace "%s"?`, - issuerconfig.ConfigName, - pinnipedInstallationNamespace, - pinnipedInstallationNamespace, - )) - } return nil, err } - return credentialIssuerConfig, nil + if len(credentialIssuerConfigs.Items) == 0 { + return nil, constable.Error(fmt.Sprintf( + `No CredentialIssuerConfig was found in namespace "%s". Is Pinniped installed on this cluster in namespace "%s"?`, + pinnipedInstallationNamespace, + pinnipedInstallationNamespace, + )) + } + + if len(credentialIssuerConfigs.Items) > 1 { + return nil, constable.Error(fmt.Sprintf( + `More than one CredentialIssuerConfig was found in namespace "%s"`, + pinnipedInstallationNamespace, + )) + } + + return &credentialIssuerConfigs.Items[0], nil } func newClientConfig(kubeconfigPathOverride string, currentContextName string) clientcmd.ClientConfig { diff --git a/cmd/pinniped/cmd/get_kubeconfig_test.go b/cmd/pinniped/cmd/get_kubeconfig_test.go index 5b1b65f6..3096d0e6 100644 --- a/cmd/pinniped/cmd/get_kubeconfig_test.go +++ b/cmd/pinniped/cmd/get_kubeconfig_test.go @@ -200,8 +200,17 @@ func TestNewGetKubeConfigCmd(t *testing.T) { }, spec.Parallel(), spec.Report(report.Terminal{})) } -//nolint: unparam -func expectedKubeconfigYAML(clusterCAData, clusterServer, command, token, pinnipedEndpoint, pinnipedCABundle, namespace string) string { +func expectedKubeconfigYAML( + clusterCAData, + clusterServer, + command, + // nolint: unparam // Pass in the token even if it is always the same in practice + token, + pinnipedEndpoint, + pinnipedCABundle, + // nolint: unparam // Pass in the namespace even if it is always the same in practice + namespace string, +) string { return here.Docf(` apiVersion: v1 clusters: @@ -240,15 +249,21 @@ func expectedKubeconfigYAML(clusterCAData, clusterServer, command, token, pinnip `, clusterCAData, clusterServer, command, pinnipedEndpoint, pinnipedCABundle, namespace, token) } -func newCredentialIssuerConfig(server, certificateAuthorityData string) *crdpinnipedv1alpha1.CredentialIssuerConfig { +func newCredentialIssuerConfig( + name, + //nolint: unparam // Pass in the namespace even if it is always the same in practice + namespace, + server, + certificateAuthorityData string, +) *crdpinnipedv1alpha1.CredentialIssuerConfig { return &crdpinnipedv1alpha1.CredentialIssuerConfig{ TypeMeta: metav1.TypeMeta{ Kind: "CredentialIssuerConfig", APIVersion: crdpinnipedv1alpha1.SchemeGroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ - Name: "pinniped-config", - Namespace: "some-namespace", + Name: name, + Namespace: namespace, }, Status: crdpinnipedv1alpha1.CredentialIssuerConfigStatus{ KubeConfigInfo: &crdpinnipedv1alpha1.CredentialIssuerConfigKubeConfigInfo{ @@ -266,6 +281,7 @@ func TestGetKubeConfig(t *testing.T) { var warningsBuffer *bytes.Buffer var fullPathToSelf string var pinnipedClient *pinnipedfake.Clientset + const installationNamespace = "some-namespace" it.Before(func() { r = require.New(t) @@ -283,7 +299,12 @@ func TestGetKubeConfig(t *testing.T) { when("the CredentialIssuerConfig is found on the cluster with a configuration that matches the existing kubeconfig", func() { it.Before(func() { r.NoError(pinnipedClient.Tracker().Add( - newCredentialIssuerConfig("https://fake-server-url-value", "fake-certificate-authority-data-value"), + newCredentialIssuerConfig( + "some-cic-name", + installationNamespace, + "https://fake-server-url-value", + "fake-certificate-authority-data-value", + ), )) }) @@ -294,7 +315,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "./testdata/kubeconfig.yaml", "", - "some-namespace", + installationNamespace, func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { kubeClientCreatorFuncWasCalled = true r.Equal("https://fake-server-url-value", restConfig.Host) @@ -313,7 +334,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "https://fake-server-url-value", "fake-certificate-authority-data-value", - "some-namespace", + installationNamespace, ), outputBuffer.String()) }) @@ -327,10 +348,12 @@ func TestGetKubeConfig(t *testing.T) { Resource: "credentialissuerconfigs", }, newCredentialIssuerConfig( + "some-cic-name", + installationNamespace, "https://some-other-fake-server-url-value", "some-other-fake-certificate-authority-data-value", ), - "some-namespace", + installationNamespace, )) }) @@ -342,7 +365,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "./testdata/kubeconfig.yaml", "some-other-context", - "some-namespace", + installationNamespace, func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { kubeClientCreatorFuncWasCalled = true r.Equal("https://some-other-fake-server-url-value", restConfig.Host) @@ -361,7 +384,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "https://some-other-fake-server-url-value", "some-other-fake-certificate-authority-data-value", - "some-namespace", + installationNamespace, ), outputBuffer.String()) }) }) @@ -373,7 +396,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "./testdata/kubeconfig.yaml", "this-context-name-does-not-exist-in-kubeconfig.yaml", - "some-namespace", + installationNamespace, func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { return pinnipedClient, nil }, ) r.EqualError(err, `context "this-context-name-does-not-exist-in-kubeconfig.yaml" does not exist`) @@ -390,7 +413,7 @@ func TestGetKubeConfig(t *testing.T) { "", "./testdata/kubeconfig.yaml", "", - "some-namespace", + installationNamespace, func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { return pinnipedClient, nil }, ) r.EqualError(err, "--token flag value cannot be empty") @@ -406,7 +429,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "./testdata/this-file-does-not-exist.yaml", "", - "some-namespace", + installationNamespace, func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { return pinnipedClient, nil }, ) r.EqualError(err, "stat ./testdata/this-file-does-not-exist.yaml: no such file or directory") @@ -434,7 +457,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "", "", - "some-namespace", + installationNamespace, func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { kubeClientCreatorFuncWasCalled = true r.Equal("https://fake-server-url-value", restConfig.Host) @@ -453,7 +476,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "https://fake-server-url-value", "fake-certificate-authority-data-value", - "some-namespace", + installationNamespace, ), outputBuffer.String()) }) }) @@ -474,7 +497,39 @@ func TestGetKubeConfig(t *testing.T) { return pinnipedClient, nil }, ) - r.EqualError(err, `CredentialIssuerConfig "pinniped-config" was not found in namespace "this-is-the-wrong-namespace". Is Pinniped installed on this cluster in namespace "this-is-the-wrong-namespace"?`) + r.EqualError(err, `No CredentialIssuerConfig was found in namespace "this-is-the-wrong-namespace". Is Pinniped installed on this cluster in namespace "this-is-the-wrong-namespace"?`) + r.True(kubeClientCreatorFuncWasCalled) + }) + }) + + when("there is more than one CredentialIssuerConfig is found on the cluster", func() { + it.Before(func() { + r.NoError(pinnipedClient.Tracker().Add( + newCredentialIssuerConfig( + "another-cic-name", + installationNamespace, + "https://fake-server-url-value", + "fake-certificate-authority-data-value", + ), + )) + }) + + it("returns an error", func() { + kubeClientCreatorFuncWasCalled := false + err := getKubeConfig(outputBuffer, + warningsBuffer, + "some-token", + "./testdata/kubeconfig.yaml", + "", + installationNamespace, + func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { + kubeClientCreatorFuncWasCalled = true + r.Equal("https://fake-server-url-value", restConfig.Host) + r.Equal("fake-certificate-authority-data-value", string(restConfig.CAData)) + return pinnipedClient, nil + }, + ) + r.EqualError(err, `More than one CredentialIssuerConfig was found in namespace "some-namespace"`) r.True(kubeClientCreatorFuncWasCalled) }) }) @@ -484,7 +539,12 @@ func TestGetKubeConfig(t *testing.T) { when("the Server doesn't match", func() { it.Before(func() { r.NoError(pinnipedClient.Tracker().Add( - newCredentialIssuerConfig("non-matching-pinniped-server-url", "fake-certificate-authority-data-value"), + newCredentialIssuerConfig( + "some-cic-name", + installationNamespace, + "non-matching-pinniped-server-url", + "fake-certificate-authority-data-value", + ), )) }) @@ -495,7 +555,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "./testdata/kubeconfig.yaml", "", - "some-namespace", + installationNamespace, func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { kubeClientCreatorFuncWasCalled = true r.Equal("https://fake-server-url-value", restConfig.Host) @@ -517,7 +577,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "https://fake-server-url-value", "fake-certificate-authority-data-value", - "some-namespace", + installationNamespace, ), outputBuffer.String()) }) }) @@ -525,7 +585,12 @@ func TestGetKubeConfig(t *testing.T) { when("the CA doesn't match", func() { it.Before(func() { r.NoError(pinnipedClient.Tracker().Add( - newCredentialIssuerConfig("https://fake-server-url-value", "non-matching-certificate-authority-data-value"), + newCredentialIssuerConfig( + "some-cic-name", + installationNamespace, + "https://fake-server-url-value", + "non-matching-certificate-authority-data-value", + ), )) }) @@ -536,7 +601,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "./testdata/kubeconfig.yaml", "", - "some-namespace", + installationNamespace, func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { kubeClientCreatorFuncWasCalled = true r.Equal("https://fake-server-url-value", restConfig.Host) @@ -558,7 +623,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "https://fake-server-url-value", "fake-certificate-authority-data-value", - "some-namespace", + installationNamespace, ), outputBuffer.String()) }) }) @@ -574,7 +639,7 @@ func TestGetKubeConfig(t *testing.T) { }, ObjectMeta: metav1.ObjectMeta{ Name: "pinniped-config", - Namespace: "some-namespace", + Namespace: installationNamespace, }, Status: crdpinnipedv1alpha1.CredentialIssuerConfigStatus{}, }, @@ -588,7 +653,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "./testdata/kubeconfig.yaml", "", - "some-namespace", + installationNamespace, func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { kubeClientCreatorFuncWasCalled = true r.Equal("https://fake-server-url-value", restConfig.Host) @@ -611,7 +676,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "./testdata/kubeconfig.yaml", "", - "some-namespace", + installationNamespace, func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { kubeClientCreatorFuncWasCalled = true r.Equal("https://fake-server-url-value", restConfig.Host) @@ -620,7 +685,7 @@ func TestGetKubeConfig(t *testing.T) { }, ) r.True(kubeClientCreatorFuncWasCalled) - r.EqualError(err, `CredentialIssuerConfig "pinniped-config" was not found in namespace "some-namespace". Is Pinniped installed on this cluster in namespace "some-namespace"?`) + r.EqualError(err, `No CredentialIssuerConfig was found in namespace "some-namespace". Is Pinniped installed on this cluster in namespace "some-namespace"?`) r.Empty(warningsBuffer.String()) r.Empty(outputBuffer.String()) }) @@ -633,7 +698,7 @@ func TestGetKubeConfig(t *testing.T) { "some-token", "./testdata/kubeconfig.yaml", "", - "some-namespace", + installationNamespace, func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { return nil, fmt.Errorf("some error getting CredentialIssuerConfig") }, diff --git a/deploy-local-user-authenticator/README.md b/deploy-local-user-authenticator/README.md index c305e113..de448aca 100644 --- a/deploy-local-user-authenticator/README.md +++ b/deploy-local-user-authenticator/README.md @@ -54,7 +54,7 @@ kubectl create secret generic ryan \ Fetch the auto-generated CA bundle for the `local-user-authenticator`'s HTTP TLS endpoint. ```bash -kubectl get secret api-serving-cert --namespace local-user-authenticator \ +kubectl get secret local-user-authenticator-tls-serving-certificate --namespace local-user-authenticator \ -o jsonpath={.data.caCertificate} \ | base64 -d \ | tee /tmp/local-user-authenticator-ca diff --git a/deploy-local-user-authenticator/deployment.yaml b/deploy-local-user-authenticator/deployment.yaml index abc2409f..71a21701 100644 --- a/deploy-local-user-authenticator/deployment.yaml +++ b/deploy-local-user-authenticator/deployment.yaml @@ -14,7 +14,7 @@ metadata: apiVersion: v1 kind: ServiceAccount metadata: - name: local-user-authenticator-service-account + name: local-user-authenticator namespace: local-user-authenticator --- #@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "": @@ -47,7 +47,7 @@ spec: labels: app: local-user-authenticator spec: - serviceAccountName: local-user-authenticator-service-account + serviceAccountName: local-user-authenticator #@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "": imagePullSecrets: - name: image-pull-secret diff --git a/deploy-local-user-authenticator/rbac.yaml b/deploy-local-user-authenticator/rbac.yaml index 71ac5846..a32d757d 100644 --- a/deploy-local-user-authenticator/rbac.yaml +++ b/deploy-local-user-authenticator/rbac.yaml @@ -8,7 +8,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: local-user-authenticator-aggregated-api-server-role + name: local-user-authenticator namespace: local-user-authenticator rules: - apiGroups: [""] @@ -18,13 +18,13 @@ rules: kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: local-user-authenticator-aggregated-api-server-role-binding + name: local-user-authenticator namespace: local-user-authenticator subjects: - kind: ServiceAccount - name: local-user-authenticator-service-account + name: local-user-authenticator namespace: local-user-authenticator roleRef: kind: Role - name: local-user-authenticator-aggregated-api-server-role + name: local-user-authenticator apiGroup: rbac.authorization.k8s.io diff --git a/deploy/deployment.yaml b/deploy/deployment.yaml index c1dcd8d9..4cb37273 100644 --- a/deploy/deployment.yaml +++ b/deploy/deployment.yaml @@ -14,7 +14,7 @@ metadata: apiVersion: v1 kind: ServiceAccount metadata: - name: #@ data.values.app_name + "-service-account" + name: #@ data.values.app_name namespace: #@ data.values.namespace --- apiVersion: v1 @@ -25,6 +25,7 @@ metadata: labels: app: #@ data.values.app_name data: + #! If names.apiService is changed in this ConfigMap, must also change name of the ClusterIP Service resource below. #@yaml/text-templated-strings pinniped.yaml: | discovery: @@ -33,6 +34,10 @@ data: servingCertificate: durationSeconds: (@= str(data.values.api_serving_certificate_duration_seconds) @) renewBeforeSeconds: (@= str(data.values.api_serving_certificate_renew_before_seconds) @) + names: + servingCertificateSecret: (@= data.values.app_name + "-api-tls-serving-certificate" @) + credentialIssuerConfig: (@= data.values.app_name + "-config" @) + apiService: (@= data.values.app_name + "-api" @) --- #@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "": apiVersion: v1 @@ -66,7 +71,7 @@ spec: annotations: scheduler.alpha.kubernetes.io/critical-pod: "" spec: - serviceAccountName: #@ data.values.app_name + "-service-account" + serviceAccountName: #@ data.values.app_name #@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "": imagePullSecrets: - name: image-pull-secret @@ -144,7 +149,8 @@ spec: apiVersion: v1 kind: Service metadata: - name: pinniped-api #! the golang code assumes this specific name as part of the common name during cert generation + #! If name is changed, must also change names.apiService in the ConfigMap above + name: #@ data.values.app_name + "-api" namespace: #@ data.values.namespace labels: app: #@ data.values.app_name diff --git a/deploy/rbac.yaml b/deploy/rbac.yaml index 0600ff7f..2ce9daba 100644 --- a/deploy/rbac.yaml +++ b/deploy/rbac.yaml @@ -8,7 +8,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: #@ data.values.app_name + "-aggregated-api-server-cluster-role" + name: #@ data.values.app_name + "-aggregated-api-server" rules: - apiGroups: [""] resources: [namespaces] @@ -23,14 +23,14 @@ rules: kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: #@ data.values.app_name + "-aggregated-api-server-cluster-role-binding" + name: #@ data.values.app_name + "-aggregated-api-server" subjects: - kind: ServiceAccount - name: #@ data.values.app_name + "-service-account" + name: #@ data.values.app_name namespace: #@ data.values.namespace roleRef: kind: ClusterRole - name: #@ data.values.app_name + "-aggregated-api-server-cluster-role" + name: #@ data.values.app_name + "-aggregated-api-server" apiGroup: rbac.authorization.k8s.io #! Give permission to various objects within the app's own namespace @@ -38,7 +38,7 @@ roleRef: apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: #@ data.values.app_name + "-aggregated-api-server-role" + name: #@ data.values.app_name + "-aggregated-api-server" namespace: #@ data.values.namespace rules: - apiGroups: [""] @@ -54,15 +54,15 @@ rules: kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: #@ data.values.app_name + "-aggregated-api-server-role-binding" + name: #@ data.values.app_name + "-aggregated-api-server" namespace: #@ data.values.namespace subjects: - kind: ServiceAccount - name: #@ data.values.app_name + "-service-account" + name: #@ data.values.app_name namespace: #@ data.values.namespace roleRef: kind: Role - name: #@ data.values.app_name + "-aggregated-api-server-role" + name: #@ data.values.app_name + "-aggregated-api-server" apiGroup: rbac.authorization.k8s.io #! Give permission to list pods and pod exec in the kube-system namespace so we can find the API server's private key @@ -70,7 +70,7 @@ roleRef: apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: #@ data.values.app_name + "-kube-system-pod-exec-role" + name: #@ data.values.app_name + "-kube-system-pod-exec" namespace: kube-system rules: - apiGroups: [""] @@ -83,23 +83,23 @@ rules: kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: #@ data.values.app_name + "-kube-system-pod-exec-role-binding" + name: #@ data.values.app_name + "-kube-system-pod-exec" namespace: kube-system subjects: - kind: ServiceAccount - name: #@ data.values.app_name + "-service-account" + name: #@ data.values.app_name namespace: #@ data.values.namespace roleRef: kind: Role - name: #@ data.values.app_name + "-kube-system-pod-exec-role" + name: #@ data.values.app_name + "-kube-system-pod-exec" apiGroup: rbac.authorization.k8s.io -#! Allow both authenticated and unauthenticated CredentialRequests (i.e. allow all requests) +#! Allow both authenticated and unauthenticated TokenCredentialRequests (i.e. allow all requests) --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: #@ data.values.app_name + "-credentialrequests-cluster-role" + name: #@ data.values.app_name + "-create-token-credential-requests" rules: - apiGroups: [pinniped.dev] resources: [credentialrequests] @@ -111,7 +111,7 @@ rules: kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: #@ data.values.app_name + "-credentialrequests-cluster-role-binding" + name: #@ data.values.app_name + "-create-token-credential-requests" subjects: - kind: Group name: system:authenticated @@ -121,7 +121,7 @@ subjects: apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole - name: #@ data.values.app_name + "-credentialrequests-cluster-role" + name: #@ data.values.app_name + "-create-token-credential-requests" apiGroup: rbac.authorization.k8s.io #! Give permissions for subjectaccessreviews, tokenreview that is needed by aggregated api servers @@ -129,11 +129,11 @@ roleRef: kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: #@ data.values.app_name + "-service-account-cluster-role-binding" + name: #@ data.values.app_name namespace: #@ data.values.namespace subjects: - kind: ServiceAccount - name: #@ data.values.app_name + "-service-account" + name: #@ data.values.app_name namespace: #@ data.values.namespace roleRef: kind: ClusterRole @@ -145,11 +145,11 @@ roleRef: kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: #@ data.values.app_name + "-extension-apiserver-authentication-reader-role-binding" + name: #@ data.values.app_name + "-extension-apiserver-authentication-reader" namespace: kube-system subjects: - kind: ServiceAccount - name: #@ data.values.app_name + "-service-account" + name: #@ data.values.app_name namespace: #@ data.values.namespace roleRef: kind: Role @@ -161,7 +161,7 @@ roleRef: kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: #@ data.values.app_name + "-cluster-info-lister-watcher-role" + name: #@ data.values.app_name + "-cluster-info-lister-watcher" namespace: kube-public rules: - apiGroups: [""] @@ -171,13 +171,13 @@ rules: kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: #@ data.values.app_name + "-cluster-info-lister-watcher-role-binding" + name: #@ data.values.app_name + "-cluster-info-lister-watcher" namespace: kube-public subjects: - kind: ServiceAccount - name: #@ data.values.app_name + "-service-account" + name: #@ data.values.app_name namespace: #@ data.values.namespace roleRef: kind: Role - name: #@ data.values.app_name + "-cluster-info-lister-watcher-role" + name: #@ data.values.app_name + "-cluster-info-lister-watcher" apiGroup: rbac.authorization.k8s.io diff --git a/doc/demo.md b/doc/demo.md index aa5bda02..7fb2feb2 100644 --- a/doc/demo.md +++ b/doc/demo.md @@ -75,7 +75,7 @@ 1. Fetch the auto-generated CA bundle for the `local-user-authenticator`'s HTTP TLS endpoint. ```bash - kubectl get secret api-serving-cert --namespace local-user-authenticator \ + kubectl get secret local-user-authenticator-tls-serving-certificate --namespace local-user-authenticator \ -o jsonpath={.data.caCertificate} \ | tee /tmp/local-user-authenticator-ca-base64-encoded ``` @@ -101,7 +101,7 @@ ```bash pinniped get-kubeconfig --token "pinny-the-seal:password123" > /tmp/pinniped-kubeconfig ``` - + Note that the above command will print a warning to the screen. You can ignore this warning. Pinniped tries to auto-discover the URL for the Kubernetes API server, but it is not able to do so on kind clusters. The warning is just letting you know that the Pinniped CLI decided diff --git a/hack/prepare-for-integration-tests.sh b/hack/prepare-for-integration-tests.sh index 9cc8c3b6..e22fc2fd 100755 --- a/hack/prepare-for-integration-tests.sh +++ b/hack/prepare-for-integration-tests.sh @@ -177,7 +177,7 @@ kubectl create secret generic "$test_username" \ app_name="pinniped" namespace="integration" webhook_url="https://local-user-authenticator.local-user-authenticator.svc/authenticate" -webhook_ca_bundle="$(kubectl get secret api-serving-cert --namespace local-user-authenticator -o 'jsonpath={.data.caCertificate}')" +webhook_ca_bundle="$(kubectl get secret local-user-authenticator-tls-serving-certificate --namespace local-user-authenticator -o 'jsonpath={.data.caCertificate}')" discovery_url="$(TERM=dumb kubectl cluster-info | awk '/Kubernetes master/ {print $NF}')" # diff --git a/internal/apiserver/apiserver.go b/internal/apiserver/apiserver.go index 66d074be..b893e766 100644 --- a/internal/apiserver/apiserver.go +++ b/internal/apiserver/apiserver.go @@ -28,8 +28,7 @@ import ( var ( //nolint: gochecknoglobals scheme = runtime.NewScheme() - //nolint: gochecknoglobals - //nolint: golint + //nolint: gochecknoglobals, golint Codecs = serializer.NewCodecFactory(scheme) ) diff --git a/internal/controller/apicerts/apiservice_updater.go b/internal/controller/apicerts/apiservice_updater.go index c47ebe79..30b35256 100644 --- a/internal/controller/apicerts/apiservice_updater.go +++ b/internal/controller/apicerts/apiservice_updater.go @@ -16,14 +16,16 @@ import ( ) type apiServiceUpdaterController struct { - namespace string - aggregatorClient aggregatorclient.Interface - secretInformer corev1informers.SecretInformer - apiServiceName string + namespace string + certsSecretResourceName string + aggregatorClient aggregatorclient.Interface + secretInformer corev1informers.SecretInformer + apiServiceName string } func NewAPIServiceUpdaterController( namespace string, + certsSecretResourceName string, apiServiceName string, aggregatorClient aggregatorclient.Interface, secretInformer corev1informers.SecretInformer, @@ -33,15 +35,16 @@ func NewAPIServiceUpdaterController( controllerlib.Config{ Name: "certs-manager-controller", Syncer: &apiServiceUpdaterController{ - namespace: namespace, - aggregatorClient: aggregatorClient, - secretInformer: secretInformer, - apiServiceName: apiServiceName, + namespace: namespace, + certsSecretResourceName: certsSecretResourceName, + aggregatorClient: aggregatorClient, + secretInformer: secretInformer, + apiServiceName: apiServiceName, }, }, withInformer( secretInformer, - pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretName, namespace), + pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretResourceName, namespace), controllerlib.InformerOption{}, ), ) @@ -49,10 +52,10 @@ func NewAPIServiceUpdaterController( func (c *apiServiceUpdaterController) Sync(ctx controllerlib.Context) error { // Try to get the secret from the informer cache. - certSecret, err := c.secretInformer.Lister().Secrets(c.namespace).Get(certsSecretName) + certSecret, err := c.secretInformer.Lister().Secrets(c.namespace).Get(c.certsSecretResourceName) notFound := k8serrors.IsNotFound(err) if err != nil && !notFound { - return fmt.Errorf("failed to get %s/%s secret: %w", c.namespace, certsSecretName, err) + return fmt.Errorf("failed to get %s/%s secret: %w", c.namespace, c.certsSecretResourceName, err) } if notFound { // The secret does not exist yet, so nothing to do. diff --git a/internal/controller/apicerts/apiservice_updater_test.go b/internal/controller/apicerts/apiservice_updater_test.go index 0d08348d..2c4ae134 100644 --- a/internal/controller/apicerts/apiservice_updater_test.go +++ b/internal/controller/apicerts/apiservice_updater_test.go @@ -30,6 +30,7 @@ import ( func TestAPIServiceUpdaterControllerOptions(t *testing.T) { spec.Run(t, "options", func(t *testing.T, when spec.G, it spec.S) { const installedInNamespace = "some-namespace" + const certsSecretResourceName = "some-resource-name" var r *require.Assertions var observableWithInformerOption *testutil.ObservableWithInformerOption @@ -41,6 +42,7 @@ func TestAPIServiceUpdaterControllerOptions(t *testing.T) { secretsInformer := kubeinformers.NewSharedInformerFactory(nil, 0).Core().V1().Secrets() _ = NewAPIServiceUpdaterController( installedInNamespace, + certsSecretResourceName, pinnipedv1alpha1.SchemeGroupVersion.Version+"."+pinnipedv1alpha1.GroupName, nil, secretsInformer, @@ -55,8 +57,8 @@ func TestAPIServiceUpdaterControllerOptions(t *testing.T) { it.Before(func() { subject = secretsInformerFilter - target = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "api-serving-cert", Namespace: installedInNamespace}} - wrongNamespace = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "api-serving-cert", Namespace: "wrong-namespace"}} + target = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: certsSecretResourceName, Namespace: installedInNamespace}} + wrongNamespace = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: certsSecretResourceName, Namespace: "wrong-namespace"}} wrongName = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: installedInNamespace}} unrelated = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "wrong-namespace"}} }) @@ -102,6 +104,7 @@ func TestAPIServiceUpdaterControllerOptions(t *testing.T) { func TestAPIServiceUpdaterControllerSync(t *testing.T) { spec.Run(t, "Sync", func(t *testing.T, when spec.G, it spec.S) { const installedInNamespace = "some-namespace" + const certsSecretResourceName = "some-resource-name" var r *require.Assertions @@ -119,6 +122,7 @@ func TestAPIServiceUpdaterControllerSync(t *testing.T) { // Set this at the last second to allow for injection of server override. subject = NewAPIServiceUpdaterController( installedInNamespace, + certsSecretResourceName, pinnipedv1alpha1.SchemeGroupVersion.Version+"."+pinnipedv1alpha1.GroupName, aggregatorAPIClient, kubeInformers.Core().V1().Secrets(), @@ -131,7 +135,7 @@ func TestAPIServiceUpdaterControllerSync(t *testing.T) { Name: subject.Name(), Key: controllerlib.Key{ Namespace: installedInNamespace, - Name: "api-serving-cert", + Name: certsSecretResourceName, }, } @@ -154,7 +158,7 @@ func TestAPIServiceUpdaterControllerSync(t *testing.T) { timeoutContextCancel() }) - when("there is not yet an api-serving-cert Secret in the installation namespace or it was deleted", func() { + when("there is not yet a serving cert Secret in the installation namespace or it was deleted", func() { it.Before(func() { unrelatedSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -174,11 +178,11 @@ func TestAPIServiceUpdaterControllerSync(t *testing.T) { }) }) - when("there is an api-serving-cert Secret already in the installation namespace", func() { + when("there is a serving cert Secret already in the installation namespace", func() { it.Before(func() { apiServingCertSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "api-serving-cert", + Name: certsSecretResourceName, Namespace: installedInNamespace, }, Data: map[string][]byte{ diff --git a/internal/controller/apicerts/certs_expirer.go b/internal/controller/apicerts/certs_expirer.go index cadb1824..08c62c6c 100644 --- a/internal/controller/apicerts/certs_expirer.go +++ b/internal/controller/apicerts/certs_expirer.go @@ -22,9 +22,10 @@ import ( ) type certsExpirerController struct { - namespace string - k8sClient kubernetes.Interface - secretInformer corev1informers.SecretInformer + namespace string + certsSecretResourceName string + k8sClient kubernetes.Interface + secretInformer corev1informers.SecretInformer // renewBefore is the amount of time after the cert's issuance where // this controller will start to try to rotate it. @@ -36,6 +37,7 @@ type certsExpirerController struct { // deletion forces rotation of the secret with the help of other controllers. func NewCertsExpirerController( namespace string, + certsSecretResourceName string, k8sClient kubernetes.Interface, secretInformer corev1informers.SecretInformer, withInformer pinnipedcontroller.WithInformerOptionFunc, @@ -45,15 +47,16 @@ func NewCertsExpirerController( controllerlib.Config{ Name: "certs-expirer-controller", Syncer: &certsExpirerController{ - namespace: namespace, - k8sClient: k8sClient, - secretInformer: secretInformer, - renewBefore: renewBefore, + namespace: namespace, + certsSecretResourceName: certsSecretResourceName, + k8sClient: k8sClient, + secretInformer: secretInformer, + renewBefore: renewBefore, }, }, withInformer( secretInformer, - pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretName, namespace), + pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretResourceName, namespace), controllerlib.InformerOption{}, ), ) @@ -61,10 +64,10 @@ func NewCertsExpirerController( // Sync implements controller.Syncer.Sync. func (c *certsExpirerController) Sync(ctx controllerlib.Context) error { - secret, err := c.secretInformer.Lister().Secrets(c.namespace).Get(certsSecretName) + secret, err := c.secretInformer.Lister().Secrets(c.namespace).Get(c.certsSecretResourceName) notFound := k8serrors.IsNotFound(err) if err != nil && !notFound { - return fmt.Errorf("failed to get %s/%s secret: %w", c.namespace, certsSecretName, err) + return fmt.Errorf("failed to get %s/%s secret: %w", c.namespace, c.certsSecretResourceName, err) } if notFound { klog.Info("certsExpirerController Sync found that the secret does not exist yet or was deleted") @@ -87,7 +90,7 @@ func (c *certsExpirerController) Sync(ctx controllerlib.Context) error { err := c.k8sClient. CoreV1(). Secrets(c.namespace). - Delete(ctx.Context, certsSecretName, metav1.DeleteOptions{}) + Delete(ctx.Context, c.certsSecretResourceName, metav1.DeleteOptions{}) if err != nil { // Do return an error here so that the controller library will reschedule // us to try deleting this cert again. diff --git a/internal/controller/apicerts/certs_expirer_test.go b/internal/controller/apicerts/certs_expirer_test.go index f29a25c7..cfdee06e 100644 --- a/internal/controller/apicerts/certs_expirer_test.go +++ b/internal/controller/apicerts/certs_expirer_test.go @@ -29,6 +29,8 @@ import ( func TestExpirerControllerFilters(t *testing.T) { t.Parallel() + const certsSecretResourceName = "some-resource-name" + tests := []struct { name string namespace string @@ -40,7 +42,7 @@ func TestExpirerControllerFilters(t *testing.T) { namespace: "good-namespace", secret: corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "api-serving-cert", + Name: certsSecretResourceName, Namespace: "good-namespace", }, }, @@ -62,7 +64,7 @@ func TestExpirerControllerFilters(t *testing.T) { namespace: "good-namespacee", secret: corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "api-serving-cert", + Name: certsSecretResourceName, Namespace: "bad-namespace", }, }, @@ -92,6 +94,7 @@ func TestExpirerControllerFilters(t *testing.T) { withInformer := testutil.NewObservableWithInformerOption() _ = NewCertsExpirerController( test.namespace, + certsSecretResourceName, nil, // k8sClient, not needed secretsInformer, withInformer.WithInformer, @@ -111,6 +114,8 @@ func TestExpirerControllerFilters(t *testing.T) { func TestExpirerControllerSync(t *testing.T) { t.Parallel() + const certsSecretResourceName = "some-resource-name" + tests := []struct { name string renewBefore time.Duration @@ -220,7 +225,7 @@ func TestExpirerControllerSync(t *testing.T) { } kubeInformerClient := kubernetesfake.NewSimpleClientset() - name := "api-serving-cert" // See certs_manager.go. + name := certsSecretResourceName namespace := "some-namespace" if test.fillSecretData != nil { secret := &corev1.Secret{ @@ -243,6 +248,7 @@ func TestExpirerControllerSync(t *testing.T) { c := NewCertsExpirerController( namespace, + certsSecretResourceName, kubeAPIClient, kubeInformers.Core().V1().Secrets(), controllerlib.WithInformer, diff --git a/internal/controller/apicerts/certs_manager.go b/internal/controller/apicerts/certs_manager.go index ee0219d4..af9e2dae 100644 --- a/internal/controller/apicerts/certs_manager.go +++ b/internal/controller/apicerts/certs_manager.go @@ -21,17 +21,16 @@ import ( ) const ( - //nolint: gosec - certsSecretName = "api-serving-cert" caCertificateSecretKey = "caCertificate" tlsPrivateKeySecretKey = "tlsPrivateKey" tlsCertificateChainSecretKey = "tlsCertificateChain" ) type certsManagerController struct { - namespace string - k8sClient kubernetes.Interface - secretInformer corev1informers.SecretInformer + namespace string + certsSecretResourceName string + k8sClient kubernetes.Interface + secretInformer corev1informers.SecretInformer // certDuration is the lifetime of both the serving certificate and its CA // certificate that this controller will use when issuing the certificates. @@ -41,7 +40,9 @@ type certsManagerController struct { serviceNameForGeneratedCertCommonName string } -func NewCertsManagerController(namespace string, +func NewCertsManagerController( + namespace string, + certsSecretResourceName string, k8sClient kubernetes.Interface, secretInformer corev1informers.SecretInformer, withInformer pinnipedcontroller.WithInformerOptionFunc, @@ -55,6 +56,7 @@ func NewCertsManagerController(namespace string, Name: "certs-manager-controller", Syncer: &certsManagerController{ namespace: namespace, + certsSecretResourceName: certsSecretResourceName, k8sClient: k8sClient, secretInformer: secretInformer, certDuration: certDuration, @@ -64,23 +66,23 @@ func NewCertsManagerController(namespace string, }, withInformer( secretInformer, - pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretName, namespace), + pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretResourceName, namespace), controllerlib.InformerOption{}, ), // Be sure to run once even if the Secret that the informer is watching doesn't exist. withInitialEvent(controllerlib.Key{ Namespace: namespace, - Name: certsSecretName, + Name: certsSecretResourceName, }), ) } func (c *certsManagerController) Sync(ctx controllerlib.Context) error { // Try to get the secret from the informer cache. - _, err := c.secretInformer.Lister().Secrets(c.namespace).Get(certsSecretName) + _, err := c.secretInformer.Lister().Secrets(c.namespace).Get(c.certsSecretResourceName) notFound := k8serrors.IsNotFound(err) if err != nil && !notFound { - return fmt.Errorf("failed to get %s/%s secret: %w", c.namespace, certsSecretName, err) + return fmt.Errorf("failed to get %s/%s secret: %w", c.namespace, c.certsSecretResourceName, err) } if !notFound { // The secret already exists, so nothing to do. @@ -112,7 +114,7 @@ func (c *certsManagerController) Sync(ctx controllerlib.Context) error { secret := corev1.Secret{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ - Name: certsSecretName, + Name: c.certsSecretResourceName, Namespace: c.namespace, }, StringData: map[string]string{ diff --git a/internal/controller/apicerts/certs_manager_test.go b/internal/controller/apicerts/certs_manager_test.go index 2fa29d58..137fe910 100644 --- a/internal/controller/apicerts/certs_manager_test.go +++ b/internal/controller/apicerts/certs_manager_test.go @@ -27,6 +27,7 @@ import ( func TestManagerControllerOptions(t *testing.T) { spec.Run(t, "options", func(t *testing.T, when spec.G, it spec.S) { const installedInNamespace = "some-namespace" + const certsSecretResourceName = "some-resource-name" var r *require.Assertions var observableWithInformerOption *testutil.ObservableWithInformerOption @@ -38,7 +39,17 @@ func TestManagerControllerOptions(t *testing.T) { observableWithInformerOption = testutil.NewObservableWithInformerOption() observableWithInitialEventOption = testutil.NewObservableWithInitialEventOption() secretsInformer := kubeinformers.NewSharedInformerFactory(nil, 0).Core().V1().Secrets() - _ = NewCertsManagerController(installedInNamespace, nil, secretsInformer, observableWithInformerOption.WithInformer, observableWithInitialEventOption.WithInitialEvent, 0, "Pinniped CA", "pinniped-api") + _ = NewCertsManagerController( + installedInNamespace, + certsSecretResourceName, + nil, + secretsInformer, + observableWithInformerOption.WithInformer, + observableWithInitialEventOption.WithInitialEvent, + 0, + "Pinniped CA", + "pinniped-api", + ) secretsInformerFilter = observableWithInformerOption.GetFilterForInformer(secretsInformer) }) @@ -48,8 +59,8 @@ func TestManagerControllerOptions(t *testing.T) { it.Before(func() { subject = secretsInformerFilter - target = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "api-serving-cert", Namespace: installedInNamespace}} - wrongNamespace = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "api-serving-cert", Namespace: "wrong-namespace"}} + target = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: certsSecretResourceName, Namespace: installedInNamespace}} + wrongNamespace = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: certsSecretResourceName, Namespace: "wrong-namespace"}} wrongName = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: installedInNamespace}} unrelated = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "wrong-namespace"}} }) @@ -94,7 +105,7 @@ func TestManagerControllerOptions(t *testing.T) { it("asks for an initial event because the Secret may not exist yet and it needs to run anyway", func() { r.Equal(controllerlib.Key{ Namespace: installedInNamespace, - Name: "api-serving-cert", + Name: certsSecretResourceName, }, observableWithInitialEventOption.GetInitialEventKey()) }) }) @@ -104,6 +115,7 @@ func TestManagerControllerOptions(t *testing.T) { func TestManagerControllerSync(t *testing.T) { spec.Run(t, "Sync", func(t *testing.T, when spec.G, it spec.S) { const installedInNamespace = "some-namespace" + const certsSecretResourceName = "some-resource-name" const certDuration = 12345678 * time.Second var r *require.Assertions @@ -122,6 +134,7 @@ func TestManagerControllerSync(t *testing.T) { // Set this at the last second to allow for injection of server override. subject = NewCertsManagerController( installedInNamespace, + certsSecretResourceName, kubeAPIClient, kubeInformers.Core().V1().Secrets(), controllerlib.WithInformer, @@ -137,7 +150,7 @@ func TestManagerControllerSync(t *testing.T) { Name: subject.Name(), Key: controllerlib.Key{ Namespace: installedInNamespace, - Name: "api-serving-cert", + Name: certsSecretResourceName, }, } @@ -160,7 +173,7 @@ func TestManagerControllerSync(t *testing.T) { timeoutContextCancel() }) - when("there is not yet an api-serving-cert Secret in the installation namespace or it was deleted", func() { + when("there is not yet a serving cert Secret in the installation namespace or it was deleted", func() { it.Before(func() { unrelatedSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -172,7 +185,7 @@ func TestManagerControllerSync(t *testing.T) { r.NoError(err) }) - it("creates the api-serving-cert Secret", func() { + it("creates the serving cert Secret", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.NoError(err) @@ -183,7 +196,7 @@ func TestManagerControllerSync(t *testing.T) { r.Equal(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"}, actualAction.GetResource()) r.Equal(installedInNamespace, actualAction.GetNamespace()) actualSecret := actualAction.GetObject().(*corev1.Secret) - r.Equal("api-serving-cert", actualSecret.Name) + r.Equal(certsSecretResourceName, actualSecret.Name) r.Equal(installedInNamespace, actualSecret.Namespace) actualCACert := actualSecret.StringData["caCertificate"] actualPrivateKey := actualSecret.StringData["tlsPrivateKey"] @@ -222,11 +235,11 @@ func TestManagerControllerSync(t *testing.T) { }) }) - when("there is an api-serving-cert Secret already in the installation namespace", func() { + when("there is a serving cert Secret already in the installation namespace", func() { it.Before(func() { apiServingCertSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "api-serving-cert", + Name: certsSecretResourceName, Namespace: installedInNamespace, }, } diff --git a/internal/controller/apicerts/certs_observer.go b/internal/controller/apicerts/certs_observer.go index 788fb0ce..6d35f5d4 100644 --- a/internal/controller/apicerts/certs_observer.go +++ b/internal/controller/apicerts/certs_observer.go @@ -16,13 +16,15 @@ import ( ) type certsObserverController struct { - namespace string - dynamicCertProvider provider.DynamicTLSServingCertProvider - secretInformer corev1informers.SecretInformer + namespace string + certsSecretResourceName string + dynamicCertProvider provider.DynamicTLSServingCertProvider + secretInformer corev1informers.SecretInformer } func NewCertsObserverController( namespace string, + certsSecretResourceName string, dynamicCertProvider provider.DynamicTLSServingCertProvider, secretInformer corev1informers.SecretInformer, withInformer pinnipedcontroller.WithInformerOptionFunc, @@ -31,14 +33,15 @@ func NewCertsObserverController( controllerlib.Config{ Name: "certs-observer-controller", Syncer: &certsObserverController{ - namespace: namespace, - dynamicCertProvider: dynamicCertProvider, - secretInformer: secretInformer, + namespace: namespace, + certsSecretResourceName: certsSecretResourceName, + dynamicCertProvider: dynamicCertProvider, + secretInformer: secretInformer, }, }, withInformer( secretInformer, - pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretName, namespace), + pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretResourceName, namespace), controllerlib.InformerOption{}, ), ) @@ -46,10 +49,10 @@ func NewCertsObserverController( func (c *certsObserverController) Sync(_ controllerlib.Context) error { // Try to get the secret from the informer cache. - certSecret, err := c.secretInformer.Lister().Secrets(c.namespace).Get(certsSecretName) + certSecret, err := c.secretInformer.Lister().Secrets(c.namespace).Get(c.certsSecretResourceName) notFound := k8serrors.IsNotFound(err) if err != nil && !notFound { - return fmt.Errorf("failed to get %s/%s secret: %w", c.namespace, certsSecretName, err) + return fmt.Errorf("failed to get %s/%s secret: %w", c.namespace, c.certsSecretResourceName, err) } if notFound { klog.Info("certsObserverController Sync found that the secret does not exist yet or was deleted") diff --git a/internal/controller/apicerts/certs_observer_test.go b/internal/controller/apicerts/certs_observer_test.go index 3286af10..b62e36bf 100644 --- a/internal/controller/apicerts/certs_observer_test.go +++ b/internal/controller/apicerts/certs_observer_test.go @@ -24,6 +24,7 @@ import ( func TestObserverControllerInformerFilters(t *testing.T) { spec.Run(t, "informer filters", func(t *testing.T, when spec.G, it spec.S) { const installedInNamespace = "some-namespace" + const certsSecretResourceName = "some-resource-name" var r *require.Assertions var observableWithInformerOption *testutil.ObservableWithInformerOption @@ -35,6 +36,7 @@ func TestObserverControllerInformerFilters(t *testing.T) { secretsInformer := kubeinformers.NewSharedInformerFactory(nil, 0).Core().V1().Secrets() _ = NewCertsObserverController( installedInNamespace, + certsSecretResourceName, nil, secretsInformer, observableWithInformerOption.WithInformer, // make it possible to observe the behavior of the Filters @@ -48,8 +50,8 @@ func TestObserverControllerInformerFilters(t *testing.T) { it.Before(func() { subject = secretsInformerFilter - target = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "api-serving-cert", Namespace: installedInNamespace}} - wrongNamespace = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "api-serving-cert", Namespace: "wrong-namespace"}} + target = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: certsSecretResourceName, Namespace: installedInNamespace}} + wrongNamespace = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: certsSecretResourceName, Namespace: "wrong-namespace"}} wrongName = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: installedInNamespace}} unrelated = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "wrong-namespace"}} }) @@ -95,6 +97,7 @@ func TestObserverControllerInformerFilters(t *testing.T) { func TestObserverControllerSync(t *testing.T) { spec.Run(t, "Sync", func(t *testing.T, when spec.G, it spec.S) { const installedInNamespace = "some-namespace" + const certsSecretResourceName = "some-resource-name" var r *require.Assertions @@ -112,6 +115,7 @@ func TestObserverControllerSync(t *testing.T) { // Set this at the last second to allow for injection of server override. subject = NewCertsObserverController( installedInNamespace, + certsSecretResourceName, dynamicCertProvider, kubeInformers.Core().V1().Secrets(), controllerlib.WithInformer, @@ -123,7 +127,7 @@ func TestObserverControllerSync(t *testing.T) { Name: subject.Name(), Key: controllerlib.Key{ Namespace: installedInNamespace, - Name: "api-serving-cert", + Name: certsSecretResourceName, }, } @@ -146,7 +150,7 @@ func TestObserverControllerSync(t *testing.T) { timeoutContextCancel() }) - when("there is not yet an api-serving-cert Secret in the installation namespace or it was deleted", func() { + when("there is not yet a serving cert Secret in the installation namespace or it was deleted", func() { it.Before(func() { unrelatedSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -171,11 +175,11 @@ func TestObserverControllerSync(t *testing.T) { }) }) - when("there is an api-serving-cert Secret with the expected keys already in the installation namespace", func() { + when("there is a serving cert Secret with the expected keys already in the installation namespace", func() { it.Before(func() { apiServingCertSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "api-serving-cert", + Name: certsSecretResourceName, Namespace: installedInNamespace, }, Data: map[string][]byte{ @@ -201,11 +205,11 @@ func TestObserverControllerSync(t *testing.T) { }) }) - when("the api-serving-cert Secret exists but is missing the expected keys", func() { + when("the serving cert Secret exists but is missing the expected keys", func() { it.Before(func() { apiServingCertSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "api-serving-cert", + Name: certsSecretResourceName, Namespace: installedInNamespace, }, Data: map[string][]byte{}, diff --git a/internal/controller/issuerconfig/create_or_update_credential_issuer_config.go b/internal/controller/issuerconfig/create_or_update_credential_issuer_config.go index 4133fb16..7418a508 100644 --- a/internal/controller/issuerconfig/create_or_update_credential_issuer_config.go +++ b/internal/controller/issuerconfig/create_or_update_credential_issuer_config.go @@ -19,6 +19,7 @@ import ( func CreateOrUpdateCredentialIssuerConfig( ctx context.Context, credentialIssuerConfigNamespace string, + credentialIssuerConfigResourceName string, pinnipedClient pinnipedclientset.Interface, applyUpdatesToCredentialIssuerConfigFunc func(configToUpdate *crdpinnipedv1alpha1.CredentialIssuerConfig), ) error { @@ -26,7 +27,7 @@ func CreateOrUpdateCredentialIssuerConfig( existingCredentialIssuerConfig, err := pinnipedClient. CrdV1alpha1(). CredentialIssuerConfigs(credentialIssuerConfigNamespace). - Get(ctx, ConfigName, metav1.GetOptions{}) + Get(ctx, credentialIssuerConfigResourceName, metav1.GetOptions{}) notFound := k8serrors.IsNotFound(err) if err != nil && !notFound { @@ -37,7 +38,7 @@ func CreateOrUpdateCredentialIssuerConfig( ctx, existingCredentialIssuerConfig, notFound, - ConfigName, + credentialIssuerConfigResourceName, credentialIssuerConfigNamespace, pinnipedClient, applyUpdatesToCredentialIssuerConfigFunc) diff --git a/internal/controller/issuerconfig/create_or_update_credential_issuer_config_test.go b/internal/controller/issuerconfig/create_or_update_credential_issuer_config_test.go index 2d6cada8..293b204e 100644 --- a/internal/controller/issuerconfig/create_or_update_credential_issuer_config_test.go +++ b/internal/controller/issuerconfig/create_or_update_credential_issuer_config_test.go @@ -31,7 +31,7 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) { var pinnipedAPIClient *pinnipedfake.Clientset var credentialIssuerConfigGVR schema.GroupVersionResource const installationNamespace = "some-namespace" - const configName = "pinniped-config" + const credentialIssuerConfigResourceName = "some-resource-name" it.Before(func() { r = require.New(t) @@ -46,7 +46,7 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) { when("the config does not exist", func() { it("creates a new config which includes only the updates made by the func parameter", func() { - err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, pinnipedAPIClient, + err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, func(configToUpdate *v1alpha1.CredentialIssuerConfig) { configToUpdate.Status.KubeConfigInfo = &crdpinnipedv1alpha1.CredentialIssuerConfigKubeConfigInfo{ CertificateAuthorityData: "some-ca-value", @@ -55,7 +55,7 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) { ) r.NoError(err) - expectedGetAction := coretesting.NewGetAction(credentialIssuerConfigGVR, installationNamespace, configName) + expectedGetAction := coretesting.NewGetAction(credentialIssuerConfigGVR, installationNamespace, credentialIssuerConfigResourceName) expectedCreateAction := coretesting.NewCreateAction( credentialIssuerConfigGVR, @@ -63,7 +63,7 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) { &crdpinnipedv1alpha1.CredentialIssuerConfig{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ - Name: configName, + Name: credentialIssuerConfigResourceName, Namespace: installationNamespace, }, Status: crdpinnipedv1alpha1.CredentialIssuerConfigStatus{ @@ -87,7 +87,7 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) { }) it("returns an error", func() { - err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, pinnipedAPIClient, + err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, func(configToUpdate *v1alpha1.CredentialIssuerConfig) {}, ) r.EqualError(err, "could not create or update credentialissuerconfig: create failed: error on create") @@ -102,7 +102,7 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) { existingConfig = &crdpinnipedv1alpha1.CredentialIssuerConfig{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ - Name: configName, + Name: credentialIssuerConfigResourceName, Namespace: installationNamespace, }, Status: crdpinnipedv1alpha1.CredentialIssuerConfigStatus{ @@ -125,14 +125,14 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) { }) it("updates the existing config to only apply the updates made by the func parameter", func() { - err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, pinnipedAPIClient, + err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, func(configToUpdate *v1alpha1.CredentialIssuerConfig) { configToUpdate.Status.KubeConfigInfo.CertificateAuthorityData = "new-ca-value" }, ) r.NoError(err) - expectedGetAction := coretesting.NewGetAction(credentialIssuerConfigGVR, installationNamespace, configName) + expectedGetAction := coretesting.NewGetAction(credentialIssuerConfigGVR, installationNamespace, credentialIssuerConfigResourceName) // Only the edited field should be changed. expectedUpdatedConfig := existingConfig.DeepCopy() @@ -143,7 +143,7 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) { }) it("avoids the cost of an update if the local updates made by the func parameter did not actually change anything", func() { - err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, pinnipedAPIClient, + err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, func(configToUpdate *v1alpha1.CredentialIssuerConfig) { configToUpdate.Status.KubeConfigInfo.CertificateAuthorityData = "initial-ca-value" @@ -155,7 +155,7 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) { ) r.NoError(err) - expectedGetAction := coretesting.NewGetAction(credentialIssuerConfigGVR, installationNamespace, configName) + expectedGetAction := coretesting.NewGetAction(credentialIssuerConfigGVR, installationNamespace, credentialIssuerConfigResourceName) r.Equal([]coretesting.Action{expectedGetAction}, pinnipedAPIClient.Actions()) }) @@ -167,7 +167,7 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) { }) it("returns an error", func() { - err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, pinnipedAPIClient, + err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, func(configToUpdate *v1alpha1.CredentialIssuerConfig) {}, ) r.EqualError(err, "could not create or update credentialissuerconfig: get failed: error on get") @@ -182,7 +182,7 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) { }) it("returns an error", func() { - err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, pinnipedAPIClient, + err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, func(configToUpdate *v1alpha1.CredentialIssuerConfig) { configToUpdate.Status.KubeConfigInfo.CertificateAuthorityData = "new-ca-value" }, @@ -216,14 +216,14 @@ func TestCreateOrUpdateCredentialIssuerConfig(t *testing.T) { }) it("retries updates on conflict", func() { - err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, pinnipedAPIClient, + err := CreateOrUpdateCredentialIssuerConfig(ctx, installationNamespace, credentialIssuerConfigResourceName, pinnipedAPIClient, func(configToUpdate *v1alpha1.CredentialIssuerConfig) { configToUpdate.Status.KubeConfigInfo.CertificateAuthorityData = "new-ca-value" }, ) r.NoError(err) - expectedGetAction := coretesting.NewGetAction(credentialIssuerConfigGVR, installationNamespace, configName) + expectedGetAction := coretesting.NewGetAction(credentialIssuerConfigGVR, installationNamespace, credentialIssuerConfigResourceName) // The first attempted update only includes its own edits. firstExpectedUpdatedConfig := existingConfig.DeepCopy() diff --git a/internal/controller/issuerconfig/publisher.go b/internal/controller/issuerconfig/publisher.go index 8d35b32b..0f81f952 100644 --- a/internal/controller/issuerconfig/publisher.go +++ b/internal/controller/issuerconfig/publisher.go @@ -20,24 +20,22 @@ import ( ) const ( - ClusterInfoNamespace = "kube-public" - - ConfigName = "pinniped-config" - + ClusterInfoNamespace = "kube-public" clusterInfoName = "cluster-info" clusterInfoConfigMapKey = "kubeconfig" ) type publisherController struct { - namespace string - serverOverride *string - pinnipedClient pinnipedclientset.Interface - configMapInformer corev1informers.ConfigMapInformer - credentialIssuerConfigInformer crdpinnipedv1alpha1informers.CredentialIssuerConfigInformer + namespace string + credentialIssuerConfigResourceName string + serverOverride *string + pinnipedClient pinnipedclientset.Interface + configMapInformer corev1informers.ConfigMapInformer + credentialIssuerConfigInformer crdpinnipedv1alpha1informers.CredentialIssuerConfigInformer } -func NewPublisherController( - namespace string, +func NewPublisherController(namespace string, + credentialIssuerConfigResourceName string, serverOverride *string, pinnipedClient pinnipedclientset.Interface, configMapInformer corev1informers.ConfigMapInformer, @@ -48,11 +46,12 @@ func NewPublisherController( controllerlib.Config{ Name: "publisher-controller", Syncer: &publisherController{ - namespace: namespace, - serverOverride: serverOverride, - pinnipedClient: pinnipedClient, - configMapInformer: configMapInformer, - credentialIssuerConfigInformer: credentialIssuerConfigInformer, + credentialIssuerConfigResourceName: credentialIssuerConfigResourceName, + namespace: namespace, + serverOverride: serverOverride, + pinnipedClient: pinnipedClient, + configMapInformer: configMapInformer, + credentialIssuerConfigInformer: credentialIssuerConfigInformer, }, }, withInformer( @@ -62,7 +61,7 @@ func NewPublisherController( ), withInformer( credentialIssuerConfigInformer, - pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(ConfigName, namespace), + pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(credentialIssuerConfigResourceName, namespace), controllerlib.InformerOption{}, ), ) @@ -112,7 +111,7 @@ func (c *publisherController) Sync(ctx controllerlib.Context) error { existingCredentialIssuerConfigFromInformerCache, err := c.credentialIssuerConfigInformer. Lister(). CredentialIssuerConfigs(c.namespace). - Get(ConfigName) + Get(c.credentialIssuerConfigResourceName) notFound = k8serrors.IsNotFound(err) if err != nil && !notFound { return fmt.Errorf("could not get credentialissuerconfig: %w", err) @@ -129,7 +128,7 @@ func (c *publisherController) Sync(ctx controllerlib.Context) error { ctx.Context, existingCredentialIssuerConfigFromInformerCache, notFound, - ConfigName, + c.credentialIssuerConfigResourceName, c.namespace, c.pinnipedClient, updateServerAndCAFunc) diff --git a/internal/controller/issuerconfig/publisher_test.go b/internal/controller/issuerconfig/publisher_test.go index 1c05a6d1..ffb12d61 100644 --- a/internal/controller/issuerconfig/publisher_test.go +++ b/internal/controller/issuerconfig/publisher_test.go @@ -30,6 +30,7 @@ import ( func TestInformerFilters(t *testing.T) { spec.Run(t, "informer filters", func(t *testing.T, when spec.G, it spec.S) { + const credentialIssuerConfigResourceName = "some-resource-name" const installedInNamespace = "some-namespace" var r *require.Assertions @@ -42,14 +43,7 @@ func TestInformerFilters(t *testing.T) { observableWithInformerOption = testutil.NewObservableWithInformerOption() configMapInformer := kubeinformers.NewSharedInformerFactory(nil, 0).Core().V1().ConfigMaps() credentialIssuerConfigInformer := pinnipedinformers.NewSharedInformerFactory(nil, 0).Crd().V1alpha1().CredentialIssuerConfigs() - _ = NewPublisherController( - installedInNamespace, - nil, - nil, - configMapInformer, - credentialIssuerConfigInformer, - observableWithInformerOption.WithInformer, // make it possible to observe the behavior of the Filters - ) + _ = NewPublisherController(installedInNamespace, credentialIssuerConfigResourceName, nil, nil, configMapInformer, credentialIssuerConfigInformer, observableWithInformerOption.WithInformer) configMapInformerFilter = observableWithInformerOption.GetFilterForInformer(configMapInformer) credentialIssuerConfigInformerFilter = observableWithInformerOption.GetFilterForInformer(credentialIssuerConfigInformer) }) @@ -109,10 +103,10 @@ func TestInformerFilters(t *testing.T) { it.Before(func() { subject = credentialIssuerConfigInformerFilter target = &crdpinnipedv1alpha1.CredentialIssuerConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "pinniped-config", Namespace: installedInNamespace}, + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerConfigResourceName, Namespace: installedInNamespace}, } wrongNamespace = &crdpinnipedv1alpha1.CredentialIssuerConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "pinniped-config", Namespace: "wrong-namespace"}, + ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerConfigResourceName, Namespace: "wrong-namespace"}, } wrongName = &crdpinnipedv1alpha1.CredentialIssuerConfig{ ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: installedInNamespace}, @@ -162,6 +156,7 @@ func TestInformerFilters(t *testing.T) { func TestSync(t *testing.T) { spec.Run(t, "Sync", func(t *testing.T, when spec.G, it spec.S) { + const credentialIssuerConfigResourceName = "some-resource-name" const installedInNamespace = "some-namespace" var r *require.Assertions @@ -185,7 +180,7 @@ func TestSync(t *testing.T) { } expectedCredentialIssuerConfig := &crdpinnipedv1alpha1.CredentialIssuerConfig{ ObjectMeta: metav1.ObjectMeta{ - Name: "pinniped-config", + Name: credentialIssuerConfigResourceName, Namespace: expectedNamespace, }, Status: crdpinnipedv1alpha1.CredentialIssuerConfigStatus{ @@ -205,6 +200,7 @@ func TestSync(t *testing.T) { // Set this at the last second to allow for injection of server override. subject = NewPublisherController( installedInNamespace, + credentialIssuerConfigResourceName, serverOverride, pinnipedAPIClient, kubeInformers.Core().V1().ConfigMaps(), diff --git a/internal/controllermanager/prepare_controllers.go b/internal/controllermanager/prepare_controllers.go index 77ff646f..7cbc7831 100644 --- a/internal/controllermanager/prepare_controllers.go +++ b/internal/controllermanager/prepare_controllers.go @@ -26,6 +26,7 @@ import ( "github.com/vmware-tanzu/pinniped/internal/controller/issuerconfig" "github.com/vmware-tanzu/pinniped/internal/controllerlib" "github.com/vmware-tanzu/pinniped/internal/provider" + "github.com/vmware-tanzu/pinniped/pkg/config/api" ) const ( @@ -36,6 +37,7 @@ const ( // Prepare the controllers and their informers and return a function that will start them when called. func PrepareControllers( serverInstallationNamespace string, + namesConfig api.NamesConfigSpec, discoveryURLOverride *string, dynamicCertProvider provider.DynamicTLSServingCertProvider, servingCertDuration time.Duration, @@ -52,15 +54,12 @@ func PrepareControllers( kubePublicNamespaceK8sInformers, installationNamespaceK8sInformers, installationNamespacePinnipedInformers := createInformers(serverInstallationNamespace, k8sClient, pinnipedClient) - // This string must match the name of the Service declared in the deployment yaml. - const serviceName = "pinniped-api" - // Create controller manager. controllerManager := controllerlib. NewManager(). WithController( - issuerconfig.NewPublisherController( - serverInstallationNamespace, + issuerconfig.NewPublisherController(serverInstallationNamespace, + namesConfig.CredentialIssuerConfig, discoveryURLOverride, pinnipedClient, kubePublicNamespaceK8sInformers.Core().V1().ConfigMaps(), @@ -72,19 +71,21 @@ func PrepareControllers( WithController( apicerts.NewCertsManagerController( serverInstallationNamespace, + namesConfig.ServingCertificateSecret, k8sClient, installationNamespaceK8sInformers.Core().V1().Secrets(), controllerlib.WithInformer, controllerlib.WithInitialEvent, servingCertDuration, "Pinniped CA", - serviceName, + namesConfig.APIService, ), singletonWorker, ). WithController( apicerts.NewAPIServiceUpdaterController( serverInstallationNamespace, + namesConfig.ServingCertificateSecret, pinnipedv1alpha1.SchemeGroupVersion.Version+"."+pinnipedv1alpha1.GroupName, aggregatorClient, installationNamespaceK8sInformers.Core().V1().Secrets(), @@ -95,6 +96,7 @@ func PrepareControllers( WithController( apicerts.NewAPIServiceUpdaterController( serverInstallationNamespace, + namesConfig.ServingCertificateSecret, loginv1alpha1.SchemeGroupVersion.Version+"."+loginv1alpha1.GroupName, aggregatorClient, installationNamespaceK8sInformers.Core().V1().Secrets(), @@ -105,6 +107,7 @@ func PrepareControllers( WithController( apicerts.NewCertsObserverController( serverInstallationNamespace, + namesConfig.ServingCertificateSecret, dynamicCertProvider, installationNamespaceK8sInformers.Core().V1().Secrets(), controllerlib.WithInformer, @@ -114,6 +117,7 @@ func PrepareControllers( WithController( apicerts.NewCertsExpirerController( serverInstallationNamespace, + namesConfig.ServingCertificateSecret, k8sClient, installationNamespaceK8sInformers.Core().V1().Secrets(), controllerlib.WithInformer, @@ -188,7 +192,7 @@ func createClients() ( return nil, nil, nil, fmt.Errorf("could not initialize pinniped client: %w", err) } - //nolint: nakedret + //nolint: nakedret // Short function. Makes the order of return values more clear. return } diff --git a/internal/server/server.go b/internal/server/server.go index c8f41c78..b25c752e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -113,7 +113,7 @@ func (a *App) runServer(ctx context.Context) error { serverInstallationNamespace := podInfo.Namespace // Load the Kubernetes cluster signing CA. - k8sClusterCA, shutdownCA, err := getClusterCASigner(ctx, serverInstallationNamespace) + k8sClusterCA, shutdownCA, err := getClusterCASigner(ctx, serverInstallationNamespace, cfg.NamesConfig.CredentialIssuerConfig) if err != nil { return err } @@ -133,6 +133,7 @@ func (a *App) runServer(ctx context.Context) error { // post start hook of the aggregated API server. startControllersFunc, err := controllermanager.PrepareControllers( serverInstallationNamespace, + cfg.NamesConfig, cfg.DiscoveryInfo.URL, dynamicCertProvider, time.Duration(*cfg.APIConfig.ServingCertificateConfig.DurationSeconds)*time.Second, @@ -164,7 +165,10 @@ func (a *App) runServer(ctx context.Context) error { return server.GenericAPIServer.PrepareRun().Run(ctx.Done()) } -func getClusterCASigner(ctx context.Context, serverInstallationNamespace string) (credentialrequest.CertIssuer, kubecertauthority.ShutdownFunc, error) { +func getClusterCASigner( + ctx context.Context, serverInstallationNamespace string, + credentialIssuerConfigResourceName string, +) (credentialrequest.CertIssuer, kubecertauthority.ShutdownFunc, error) { // Load the Kubernetes client configuration. kubeConfig, err := restclient.InClusterConfig() if err != nil { @@ -195,6 +199,7 @@ func getClusterCASigner(ctx context.Context, serverInstallationNamespace string) err = issuerconfig.CreateOrUpdateCredentialIssuerConfig( ctx, serverInstallationNamespace, + credentialIssuerConfigResourceName, pinnipedClient, func(configToUpdate *crdpinnipedv1alpha1.CredentialIssuerConfig) { configToUpdate.Status.Strategies = []crdpinnipedv1alpha1.CredentialIssuerConfigStrategy{ @@ -216,6 +221,7 @@ func getClusterCASigner(ctx context.Context, serverInstallationNamespace string) if updateErr := issuerconfig.CreateOrUpdateCredentialIssuerConfig( ctx, serverInstallationNamespace, + credentialIssuerConfigResourceName, pinnipedClient, func(configToUpdate *crdpinnipedv1alpha1.CredentialIssuerConfig) { configToUpdate.Status.Strategies = []crdpinnipedv1alpha1.CredentialIssuerConfigStrategy{ diff --git a/pkg/config/api/types.go b/pkg/config/api/types.go index 91383db1..05723842 100644 --- a/pkg/config/api/types.go +++ b/pkg/config/api/types.go @@ -3,10 +3,11 @@ package api -// Config contains knobs to setup an instance of pinniped. +// Config contains knobs to setup an instance of Pinniped. type Config struct { DiscoveryInfo DiscoveryInfoSpec `json:"discovery"` APIConfig APIConfigSpec `json:"api"` + NamesConfig NamesConfigSpec `json:"names"` } // DiscoveryInfoSpec contains configuration knobs specific to @@ -18,12 +19,19 @@ type DiscoveryInfoSpec struct { URL *string `json:"url,omitempty"` } -// APIConfigSpec contains configuration knobs for the pinniped API. +// APIConfigSpec contains configuration knobs for the Pinniped API. //nolint: golint type APIConfigSpec struct { ServingCertificateConfig ServingCertificateConfigSpec `json:"servingCertificate"` } +// NamesConfigSpec configures the names of some Kubernetes resources for Pinniped. +type NamesConfigSpec struct { + ServingCertificateSecret string `json:"servingCertificateSecret"` + CredentialIssuerConfig string `json:"credentialIssuerConfig"` + APIService string `json:"apiService"` +} + // ServingCertificateConfigSpec contains the configuration knobs for the API's // serving certificate, i.e., the x509 certificate that it uses for the server // certificate in inbound TLS connections. @@ -34,10 +42,10 @@ type ServingCertificateConfigSpec struct { // CA certificate. DurationSeconds *int64 `json:"durationSeconds,omitempty"` - // RenewBeforeSeconds is the period of time, in seconds, that pinniped will + // RenewBeforeSeconds is the period of time, in seconds, that Pinniped will // wait before rotating the serving certificate. This period of time starts // upon issuance of the serving certificate. This must be less than - // DurationSeconds. By default, pinniped begins rotation after 23328000 + // DurationSeconds. By default, Pinniped begins rotation after 23328000 // seconds (about 9 months). RenewBeforeSeconds *int64 `json:"renewBeforeSeconds,omitempty"` } diff --git a/pkg/config/config.go b/pkg/config/config.go index 68c5a669..9282d6c2 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -8,6 +8,7 @@ package config import ( "fmt" "io/ioutil" + "strings" "sigs.k8s.io/yaml" @@ -44,6 +45,10 @@ func FromPath(path string) (*api.Config, error) { return nil, fmt.Errorf("validate api: %w", err) } + if err := validateNames(&config.NamesConfig); err != nil { + return nil, fmt.Errorf("validate names: %w", err) + } + return &config, nil } @@ -57,6 +62,27 @@ func maybeSetAPIDefaults(apiConfig *api.APIConfigSpec) { } } +func validateNames(names *api.NamesConfigSpec) error { + missingNames := []string{} + if names == nil { + missingNames = append(missingNames, "servingCertificateSecret", "credentialIssuerConfig", "apiService") + } else { + if names.ServingCertificateSecret == "" { + missingNames = append(missingNames, "servingCertificateSecret") + } + if names.CredentialIssuerConfig == "" { + missingNames = append(missingNames, "credentialIssuerConfig") + } + if names.APIService == "" { + missingNames = append(missingNames, "apiService") + } + } + if len(missingNames) > 0 { + return constable.Error("missing required names: " + strings.Join(missingNames, ", ")) + } + return nil +} + func validateAPI(apiConfig *api.APIConfigSpec) error { if *apiConfig.ServingCertificateConfig.DurationSeconds < *apiConfig.ServingCertificateConfig.RenewBeforeSeconds { return constable.Error("durationSeconds cannot be smaller than renewBeforeSeconds") diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 0219ff1f..839952e9 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -4,23 +4,38 @@ package config import ( + "io/ioutil" + "os" "testing" "github.com/stretchr/testify/require" + "github.com/vmware-tanzu/pinniped/internal/here" "github.com/vmware-tanzu/pinniped/pkg/config/api" ) func TestFromPath(t *testing.T) { tests := []struct { name string - path string + yaml string wantConfig *api.Config wantError string }{ { name: "Happy", - path: "testdata/happy.yaml", + yaml: here.Doc(` + --- + discovery: + url: https://some.discovery/url + api: + servingCertificate: + durationSeconds: 3600 + renewBeforeSeconds: 2400 + names: + servingCertificateSecret: pinniped-api-tls-serving-certificate + credentialIssuerConfig: pinniped-config + apiService: pinniped-api + `), wantConfig: &api.Config{ DiscoveryInfo: api.DiscoveryInfoSpec{ URL: stringPtr("https://some.discovery/url"), @@ -31,11 +46,22 @@ func TestFromPath(t *testing.T) { RenewBeforeSeconds: int64Ptr(2400), }, }, + NamesConfig: api.NamesConfigSpec{ + ServingCertificateSecret: "pinniped-api-tls-serving-certificate", + CredentialIssuerConfig: "pinniped-config", + APIService: "pinniped-api", + }, }, }, { - name: "Default", - path: "testdata/default.yaml", + name: "When only the required fields are present, causes other fields to be defaulted", + yaml: here.Doc(` + --- + names: + servingCertificateSecret: pinniped-api-tls-serving-certificate + credentialIssuerConfig: pinniped-config + apiService: pinniped-api + `), wantConfig: &api.Config{ DiscoveryInfo: api.DiscoveryInfoSpec{ URL: nil, @@ -46,28 +72,112 @@ func TestFromPath(t *testing.T) { RenewBeforeSeconds: int64Ptr(60 * 60 * 24 * 30 * 9), // about 9 months }, }, + NamesConfig: api.NamesConfigSpec{ + ServingCertificateSecret: "pinniped-api-tls-serving-certificate", + CredentialIssuerConfig: "pinniped-config", + APIService: "pinniped-api", + }, }, }, { - name: "InvalidDurationRenewBefore", - path: "testdata/invalid-duration-renew-before.yaml", + name: "Empty", + yaml: here.Doc(``), + wantError: "validate names: missing required names: servingCertificateSecret, credentialIssuerConfig, apiService", + }, + { + name: "Missing apiService name", + yaml: here.Doc(` + --- + names: + servingCertificateSecret: pinniped-api-tls-serving-certificate + credentialIssuerConfig: pinniped-config + `), + wantError: "validate names: missing required names: apiService", + }, + { + name: "Missing credentialIssuerConfig name", + yaml: here.Doc(` + --- + names: + servingCertificateSecret: pinniped-api-tls-serving-certificate + apiService: pinniped-api + `), + wantError: "validate names: missing required names: credentialIssuerConfig", + }, + { + name: "Missing servingCertificateSecret name", + yaml: here.Doc(` + --- + names: + credentialIssuerConfig: pinniped-config + apiService: pinniped-api + `), + wantError: "validate names: missing required names: servingCertificateSecret", + }, + { + name: "InvalidDurationRenewBefore", + yaml: here.Doc(` + --- + api: + servingCertificate: + durationSeconds: 2400 + renewBeforeSeconds: 3600 + names: + servingCertificateSecret: pinniped-api-tls-serving-certificate + credentialIssuerConfig: pinniped-config + apiService: pinniped-api + `), wantError: "validate api: durationSeconds cannot be smaller than renewBeforeSeconds", }, { - name: "NegativeRenewBefore", - path: "testdata/negative-renew-before.yaml", + name: "NegativeRenewBefore", + yaml: here.Doc(` + --- + api: + servingCertificate: + durationSeconds: 2400 + renewBeforeSeconds: -10 + names: + servingCertificateSecret: pinniped-api-tls-serving-certificate + credentialIssuerConfig: pinniped-config + apiService: pinniped-api + `), wantError: "validate api: renewBefore must be positive", }, { - name: "ZeroRenewBefore", - path: "testdata/zero-renew-before.yaml", + name: "ZeroRenewBefore", + yaml: here.Doc(` + --- + api: + servingCertificate: + durationSeconds: 2400 + renewBeforeSeconds: -10 + names: + servingCertificateSecret: pinniped-api-tls-serving-certificate + credentialIssuerConfig: pinniped-config + apiService: pinniped-api + `), wantError: "validate api: renewBefore must be positive", }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - config, err := FromPath(test.path) + // Write yaml to temp file + f, err := ioutil.TempFile("", "pinniped-test-config-yaml-*") + require.NoError(t, err) + defer func() { + err := os.Remove(f.Name()) + require.NoError(t, err) + }() + _, err = f.WriteString(test.yaml) + require.NoError(t, err) + err = f.Close() + require.NoError(t, err) + + // Test FromPath() + config, err := FromPath(f.Name()) + if test.wantError != "" { require.EqualError(t, err, test.wantError) } else { diff --git a/pkg/config/testdata/default.yaml b/pkg/config/testdata/default.yaml deleted file mode 100644 index ed97d539..00000000 --- a/pkg/config/testdata/default.yaml +++ /dev/null @@ -1 +0,0 @@ ---- diff --git a/pkg/config/testdata/happy.yaml b/pkg/config/testdata/happy.yaml deleted file mode 100644 index 3ed3c18a..00000000 --- a/pkg/config/testdata/happy.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -discovery: - url: https://some.discovery/url -api: - servingCertificate: - durationSeconds: 3600 - renewBeforeSeconds: 2400 diff --git a/pkg/config/testdata/invalid-duration-renew-before.yaml b/pkg/config/testdata/invalid-duration-renew-before.yaml deleted file mode 100644 index 0c2ac255..00000000 --- a/pkg/config/testdata/invalid-duration-renew-before.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -api: - servingCertificate: - durationSeconds: 2400 - renewBeforeSeconds: 3600 diff --git a/pkg/config/testdata/negative-renew-before.yaml b/pkg/config/testdata/negative-renew-before.yaml deleted file mode 100644 index bcc483d6..00000000 --- a/pkg/config/testdata/negative-renew-before.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -api: - servingCertificate: - durationSeconds: 2400 - renewBeforeSeconds: -10 diff --git a/pkg/config/testdata/zero-renew-before.yaml b/pkg/config/testdata/zero-renew-before.yaml deleted file mode 100644 index bcc483d6..00000000 --- a/pkg/config/testdata/zero-renew-before.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -api: - servingCertificate: - durationSeconds: 2400 - renewBeforeSeconds: -10 diff --git a/test/integration/api_serving_certs_test.go b/test/integration/api_serving_certs_test.go index 4661d93f..75aa7cf7 100644 --- a/test/integration/api_serving_certs_test.go +++ b/test/integration/api_serving_certs_test.go @@ -21,6 +21,8 @@ import ( func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) { library.SkipUnlessIntegration(t) + const defaultServingCertResourceName = "pinniped-api-tls-serving-certificate" + tests := []struct { name string forceRotation func(context.Context, kubernetes.Interface, string) error @@ -36,7 +38,7 @@ func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) { return kubeClient. CoreV1(). Secrets(namespace). - Delete(ctx, "api-serving-cert", metav1.DeleteOptions{}) + Delete(ctx, defaultServingCertResourceName, metav1.DeleteOptions{}) }, }, { @@ -51,7 +53,7 @@ func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) { secret, err := kubeClient. CoreV1(). Secrets(namespace). - Get(ctx, "api-serving-cert", metav1.GetOptions{}) + Get(ctx, defaultServingCertResourceName, metav1.GetOptions{}) if err != nil { return err } @@ -83,7 +85,7 @@ func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) { const apiServiceName = "v1alpha1.pinniped.dev" // Get the initial auto-generated version of the Secret. - secret, err := kubeClient.CoreV1().Secrets(namespaceName).Get(ctx, "api-serving-cert", metav1.GetOptions{}) + secret, err := kubeClient.CoreV1().Secrets(namespaceName).Get(ctx, defaultServingCertResourceName, metav1.GetOptions{}) require.NoError(t, err) initialCACert := secret.Data["caCertificate"] initialPrivateKey := secret.Data["tlsPrivateKey"] @@ -102,7 +104,7 @@ func TestAPIServingCertificateAutoCreationAndRotation(t *testing.T) { // Expect that the Secret comes back right away with newly minted certs. secretIsRegenerated := func() bool { - secret, err = kubeClient.CoreV1().Secrets(namespaceName).Get(ctx, "api-serving-cert", metav1.GetOptions{}) + secret, err = kubeClient.CoreV1().Secrets(namespaceName).Get(ctx, defaultServingCertResourceName, metav1.GetOptions{}) return err == nil } assert.Eventually(t, secretIsRegenerated, 10*time.Second, 250*time.Millisecond) diff --git a/test/integration/cli_test.go b/test/integration/cli_test.go index 8e6a3bbb..5905efc7 100644 --- a/test/integration/cli_test.go +++ b/test/integration/cli_test.go @@ -130,7 +130,7 @@ func runKubectlCLI(t *testing.T, kubeConfig, namespaceName, username string) str err = f.Close() require.NoError(t, err) - //nolint: gosec + //nolint: gosec // It's okay that we are passing f.Name() to an exec command here. It was created above. output, err := exec.Command( "kubectl", "get",