diff --git a/test/e2e/framework/addon/globals.go b/test/e2e/framework/addon/globals.go index 53caa6b25..411dd1a29 100644 --- a/test/e2e/framework/addon/globals.go +++ b/test/e2e/framework/addon/globals.go @@ -38,8 +38,9 @@ type AddonTransferableData = internal.AddonTransferableData var ( // Base is a base addon containing Kubernetes clients - Base = &base.Base{} - Vault = &vault.Vault{} + Base = &base.Base{} + Vault = &vault.Vault{} + VaultEnforceMtls = &vault.Vault{} // allAddons is populated by InitGlobals and defines the order in which // addons will be provisioned @@ -65,9 +66,16 @@ func InitGlobals(cfg *config.Config) { Namespace: "e2e-vault", Name: "vault", } + *VaultEnforceMtls = vault.Vault{ + Base: Base, + Namespace: "e2e-vault-mtls", + Name: "vault-mtls", + EnforceMtls: true, + } allAddons = []Addon{ Base, Vault, + VaultEnforceMtls, } } diff --git a/test/e2e/framework/addon/vault/setup.go b/test/e2e/framework/addon/vault/setup.go index bd590fca0..ab0a4017e 100644 --- a/test/e2e/framework/addon/vault/setup.go +++ b/test/e2e/framework/addon/vault/setup.go @@ -18,6 +18,7 @@ package vault import ( "context" + "crypto/tls" "crypto/x509" "fmt" "net" @@ -183,6 +184,19 @@ func NewVaultKubernetesSecret(secretName, serviceAccountName string) *corev1.Sec } } +func NewVaultClientCertificateSecret(secretName string, certificate, key []byte) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + }, + Data: map[string][]byte{ + corev1.TLSCertKey: certificate, + corev1.TLSPrivateKeyKey: key, + }, + Type: corev1.SecretTypeTLS, + } +} + // Set up a new Vault client, port-forward to the Vault instance. func (v *VaultInitializer) Init() error { cfg := vault.DefaultConfiguration() @@ -193,6 +207,13 @@ func (v *VaultInitializer) Init() error { return fmt.Errorf("error loading Vault CA bundle: %s", v.details.VaultCA) } cfg.HTTPClient.Transport.(*http.Transport).TLSClientConfig.RootCAs = caCertPool + if v.details.EnforceMtls { + clientCertificate, err := tls.X509KeyPair(v.details.VaultClientCertificate, v.details.VaultClientPrivateKey) + if err != nil { + return fmt.Errorf("unable to read vault client certificate: %s", err) + } + cfg.HTTPClient.Transport.(*http.Transport).TLSClientConfig.Certificates = []tls.Certificate{clientCertificate} + } client, err := vault.New(vault.WithConfiguration(cfg)) if err != nil { @@ -211,7 +232,10 @@ func (v *VaultInitializer) Init() error { return fmt.Errorf("error parsing proxy URL: %s", err.Error()) } var lastError error - err = wait.PollUntilContextTimeout(context.TODO(), time.Second, 20*time.Second, true, func(ctx context.Context) (bool, error) { + // The timeout below must be aligned with the time taken by the Vault addons to start, + // each addon safely takes about 20 seconds to start and two addons are started one after another, + // one for without mTLS enforced and another with mTLS enforced + err = wait.PollUntilContextTimeout(context.TODO(), time.Second, 45*time.Second, true, func(ctx context.Context) (bool, error) { conn, err := net.DialTimeout("tcp", proxyUrl.Host, time.Second) if err != nil { lastError = err diff --git a/test/e2e/framework/addon/vault/vault.go b/test/e2e/framework/addon/vault/vault.go index 885c835c6..956fa94d3 100644 --- a/test/e2e/framework/addon/vault/vault.go +++ b/test/e2e/framework/addon/vault/vault.go @@ -30,6 +30,7 @@ import ( "math/big" "net" "os" + "strconv" "strings" "time" @@ -63,6 +64,10 @@ type Vault struct { // Namespace is the namespace to deploy Vault into Namespace string + // EnforceMtls defines if mTLS is enforced in the vault server + // and clients must provide client certificates + EnforceMtls bool + // Proxy is the proxy that can be used to connect to Vault proxy *proxy @@ -84,6 +89,16 @@ type Details struct { // VaultCA is the CA used to sign the vault serving certificate VaultCA []byte + + // VaultClientCertificate is the certificate used by clients when connecting to vault + VaultClientCertificate []byte + + // VaultClientPrivateKey is the private key used by clients when connecting to vault + VaultClientPrivateKey []byte + + // EnforceMtls defines if mTLS is enforced in the vault server + // and clients must provide client certificates + EnforceMtls bool } func convertInterfaceToDetails(unmarshalled interface{}) (Details, error) { @@ -149,6 +164,15 @@ func (v *Vault) Setup(cfg *config.Config, leaderData ...internal.AddonTransferab Key: "server.extraEnvironmentVars.VAULT_DEV_ROOT_TOKEN_ID", Value: "vault-root-token", }, + // configure client certificates used in the readiness/liveness probes exec commands + { + Key: "server.extraEnvironmentVars.VAULT_CLIENT_CERT", + Value: "/vault/tls/client.crt", + }, + { + Key: "server.extraEnvironmentVars.VAULT_CLIENT_KEY", + Value: "/vault/tls/client.key", + }, // configure tls certificate { Key: "global.tlsDisable", @@ -156,15 +180,16 @@ func (v *Vault) Setup(cfg *config.Config, leaderData ...internal.AddonTransferab }, { Key: "server.standalone.config", - Value: ` + Value: fmt.Sprintf(` listener "tcp" { - tls_disable = false address = "[::]:8200" cluster_address = "[::]:8201" tls_disable = false + tls_client_ca_file = "/vault/tls/ca.crt" tls_cert_file = "/vault/tls/server.crt" tls_key_file = "/vault/tls/server.key" - }`, + tls_require_and_verify_client_cert = %s + }`, strconv.FormatBool(v.EnforceMtls)), }, { Key: "server.volumes[0].name", @@ -267,6 +292,14 @@ func (v *Vault) Setup(cfg *config.Config, leaderData ...internal.AddonTransferab return nil, err } + vaultClientCertificate, vaultClientPrivateKey, err := generateVaultClientCert(vaultCA, vaultCAPrivateKey) + if err != nil { + return nil, err + } + v.details.VaultClientCertificate = vaultClientCertificate + v.details.VaultClientPrivateKey = vaultClientPrivateKey + v.details.EnforceMtls = v.EnforceMtls + if cfg.Kubectl == "" { return nil, fmt.Errorf("path to kubectl must be specified") } @@ -310,8 +343,11 @@ func (v *Vault) Provision() error { Namespace: v.Namespace, }, StringData: map[string]string{ + "ca.crt": string(v.details.VaultCA), "server.crt": string(v.vaultCert), "server.key": string(v.vaultCertPrivateKey), + "client.crt": string(v.details.VaultClientCertificate), + "client.key": string(v.details.VaultClientPrivateKey), }, } _, err = kubeClient.CoreV1().Secrets(v.Namespace).Create(context.TODO(), tlsSecret, metav1.CreateOptions{}) @@ -438,6 +474,30 @@ func generateVaultServingCert(vaultCA []byte, vaultCAPrivateKey []byte, dnsName return encodePublicKey(certBytes), encodePrivateKey(privateKey), nil } +func generateVaultClientCert(vaultCA []byte, vaultCAPrivateKey []byte) ([]byte, []byte, error) { + catls, _ := tls.X509KeyPair(vaultCA, vaultCAPrivateKey) + ca, _ := x509.ParseCertificate(catls.Certificate[0]) + + cert := &x509.Certificate{ + Version: 3, + SerialNumber: big.NewInt(1658), + Subject: pkix.Name{ + CommonName: "cert-manager vault client", + Organization: []string{"cert-manager"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + } + + privateKey, _ := rsa.GenerateKey(rand.Reader, 2048) + certBytes, _ := x509.CreateCertificate(rand.Reader, cert, ca, &privateKey.PublicKey, catls.PrivateKey) + + return encodePublicKey(certBytes), encodePrivateKey(privateKey), nil +} + func GenerateCA() ([]byte, []byte, error) { ca := &x509.Certificate{ Version: 3, diff --git a/test/e2e/suite/issuers/vault/mtls.go b/test/e2e/suite/issuers/vault/mtls.go new file mode 100644 index 000000000..e706b9531 --- /dev/null +++ b/test/e2e/suite/issuers/vault/mtls.go @@ -0,0 +1,468 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vault + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon" + vaultaddon "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/vault" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/test/unit/gen" +) + +var _ = framework.CertManagerDescribe("Vault Issuer [mtls]", func() { + f := framework.NewDefaultFramework("create-vault-issuer") + + issuerName := "test-vault-issuer" + vaultSecretServiceAccount := "vault-serviceaccount" + vaultClientCertificateSecretName := "vault-client-cert-secret-" + rand.String(5) + var roleId, secretId, vaultSecretName string + + appRoleSecretGeneratorName := "vault-approle-secret-" + var setup *vaultaddon.VaultInitializer + + details := addon.VaultEnforceMtls.Details() + + BeforeEach(func() { + By("Configuring the Vault server") + + setup = vaultaddon.NewVaultInitializerAllAuth( + addon.Base.Details().KubeClient, + *details, + false, + "https://kubernetes.default.svc.cluster.local", + ) + Expect(setup.Init()).NotTo(HaveOccurred(), "failed to init vault") + Expect(setup.Setup()).NotTo(HaveOccurred(), "failed to setup vault") + + var err error + roleId, secretId, err = setup.CreateAppRole() + Expect(err).NotTo(HaveOccurred()) + + By("creating a service account for Vault authentication") + err = setup.CreateKubernetesRole(f.KubeClientSet, f.Namespace.Name, vaultSecretServiceAccount) + Expect(err).NotTo(HaveOccurred()) + + By("creating a client certificate for Vault mTLS") + secret := vaultaddon.NewVaultClientCertificateSecret(vaultClientCertificateSecretName, details.VaultClientCertificate, details.VaultClientPrivateKey) + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), secret, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + }) + + JustAfterEach(func() { + By("Cleaning up AppRole") + f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuerName, metav1.DeleteOptions{}) + f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(context.TODO(), vaultSecretName, metav1.DeleteOptions{}) + setup.CleanAppRole() + + By("Cleaning up Kubernetes") + setup.CleanKubernetesRole(f.KubeClientSet, f.Namespace.Name, vaultSecretServiceAccount) + + By("Cleaning up Vault") + Expect(setup.Clean()).NotTo(HaveOccurred()) + }) + + It("should be ready with a valid AppRole", func() { + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vaultaddon.NewVaultAppRoleSecret(appRoleSecretGeneratorName, secretId), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultSecretName = sec.Name + + vaultIssuer := gen.IssuerWithRandomName(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + iss.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should fail to init with missing client certificates", func() { + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vaultaddon.NewVaultAppRoleSecret(appRoleSecretGeneratorName, secretId), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultSecretName = sec.Name + + By("Creating an Issuer") + vaultIssuer := gen.IssuerWithRandomName(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + iss.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should fail to init with missing Vault AppRole", func() { + By("Creating an Issuer") + vaultIssuer := gen.IssuerWithRandomName(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultAppRoleAuth("secretkey", roleId, setup.Role(), setup.AppRoleAuthPath())) + iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + iss.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should fail to init with missing Vault Token", func() { + By("Creating an Issuer") + vaultIssuer := gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultTokenAuth("secretkey", "vault-token")) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should be ready with a valid Kubernetes Role and ServiceAccount Secret", func() { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vaultaddon.NewVaultKubernetesSecret(saTokenSecretName, vaultSecretServiceAccount), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultIssuer := gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + _, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should fail to init with missing Kubernetes Role", func() { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + // we test without creating the secret + + By("Creating an Issuer") + vaultIssuer := gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should fail to init when both caBundle and caBundleSecretRef are set", func() { + By("Creating an Issuer") + vaultIssuer := gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt")) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring( + "spec.vault.caBundle: Invalid value: \"\": specified caBundle and caBundleSecretRef cannot be used together", + )) + Expect(err.Error()).To(ContainSubstring("spec.vault.caBundleSecretRef: Invalid value: \"ca-bundle\": specified caBundleSecretRef and caBundle cannot be used together")) + }) + + It("should be ready with a caBundle from a Kubernetes Secret", func() { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vaultaddon.NewVaultKubernetesSecret(saTokenSecretName, vaultSecretServiceAccount), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca-bundle", + }, + Type: "Opaque", + Data: map[string][]byte{ + "ca.crt": details.VaultCA, + }, + }, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultIssuer := gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt"), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + _, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should be eventually ready when the CA bundle secret gets created after the Issuer", func() { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vaultaddon.NewVaultKubernetesSecret(saTokenSecretName, vaultSecretServiceAccount), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultIssuer := gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt"), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + _, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Validate that the Issuer is not ready yet") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca-bundle", + }, + Type: "Opaque", + Data: map[string][]byte{ + "ca.crt": details.VaultCA, + }, + }, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should be eventually ready when the Vault client certificate secret gets created after the Issuer", func() { + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vaultaddon.NewVaultAppRoleSecret(appRoleSecretGeneratorName, secretId), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultSecretName = sec.Name + customVaultClientCertificateSecretName := "vault-client-cert-secret-custom-" + rand.String(5) + + vaultIssuer := gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(customVaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(customVaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Validate that the Issuer is not ready yet") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + + By("creating a client certificate for Vault mTLS") + secret := vaultaddon.NewVaultClientCertificateSecret(customVaultClientCertificateSecretName, details.VaultClientCertificate, details.VaultClientPrivateKey) + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), secret, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + iss.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("it should become not ready when the CA certificate in the secret changes and doesn't match Vault's CA anymore", func() { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vaultaddon.NewVaultKubernetesSecret(saTokenSecretName, vaultSecretServiceAccount), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca-bundle", + }, + Type: "Opaque", + Data: map[string][]byte{ + "ca.crt": details.VaultCA, + }, + }, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultIssuer := gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt"), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + _, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + + By("Updating CA bundle") + public, _, err := vaultaddon.GenerateCA() + Expect(err).NotTo(HaveOccurred()) + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.TODO(), &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca-bundle", + }, + Data: map[string][]byte{ + "ca.crt": public, + }, + }, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Validate that the issuer isn't ready anymore due to Vault still using the old certificate") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + }) + It("should be ready with a valid serviceAccountRef", func() { + // Note that we reuse the same service account as for the Kubernetes + // auth based on secretRef. There should be no problem doing so. + By("Creating the Role and RoleBinding to let cert-manager use TokenRequest for the ServiceAccount") + vaultaddon.CreateKubernetesRoleForServiceAccountRefAuth(f.KubeClientSet, setup.Role(), f.Namespace.Name, vaultSecretServiceAccount) + defer vaultaddon.CleanKubernetesRoleForServiceAccountRefAuth(f.KubeClientSet, setup.Role(), f.Namespace.Name, vaultSecretServiceAccount) + + By("Creating an Issuer") + vaultIssuer := gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultKubernetesAuthServiceAccount(vaultSecretServiceAccount, setup.Role(), setup.KubernetesAuthPath())) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) +}) diff --git a/test/unit/gen/issuer.go b/test/unit/gen/issuer.go index 1ab46f34f..eb74eff82 100644 --- a/test/unit/gen/issuer.go +++ b/test/unit/gen/issuer.go @@ -302,6 +302,36 @@ func SetIssuerVaultCABundleSecretRef(name, namespace, key string) IssuerModifier } } +func SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, key string) IssuerModifier { + return func(iss v1.GenericIssuer) { + spec := iss.GetSpec() + if spec.Vault == nil { + spec.Vault = &v1.VaultIssuer{} + } + spec.Vault.ClientCertSecretRef = &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: vaultClientCertificateSecretName, + }, + Key: key, + } + } +} + +func SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, key string) IssuerModifier { + return func(iss v1.GenericIssuer) { + spec := iss.GetSpec() + if spec.Vault == nil { + spec.Vault = &v1.VaultIssuer{} + } + spec.Vault.ClientKeySecretRef = &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: vaultClientCertificateSecretName, + }, + Key: key, + } + } +} + func SetIssuerVaultTokenAuth(keyName, tokenName string) IssuerModifier { return func(iss v1.GenericIssuer) { spec := iss.GetSpec()