diff --git a/docs/generated/reference/output/reference/api-docs/index.html b/docs/generated/reference/output/reference/api-docs/index.html index fab686174..9981bb4d1 100755 --- a/docs/generated/reference/output/reference/api-docs/index.html +++ b/docs/generated/reference/output/reference/api-docs/index.html @@ -1865,6 +1865,10 @@ Appears In: +path
string +Where the authentication path is mounted in Vault. + + roleId
string diff --git a/docs/tutorials/vault/creating-vault-issuers.rst b/docs/tutorials/vault/creating-vault-issuers.rst index 78d452ed3..01e0b3db6 100644 --- a/docs/tutorials/vault/creating-vault-issuers.rst +++ b/docs/tutorials/vault/creating-vault-issuers.rst @@ -56,6 +56,7 @@ We can now create a cluster issuer referencing this secret: server: https://vault auth: appRole: + path: approle roleId: "291b9d21-8ff5-..." secretRef: name: cert-manager-vault-approle @@ -67,7 +68,9 @@ The Vault appRole credentials are supplied as the Vault authentication method using the appRole created in Vault. The secretRef references the Kubernetes secret created previously. More specifically, the field *name* is the Kubernetes secret name and *key* is the name given as the -key value that store the *secretId*. +key value that store the *secretId*. The optional attribute *path* specifies +where the AppRole authentication is mounted in Vault. The attribute *path* default +value is *approle*. Once we have created the above Issuer we can use it to obtain a certificate. diff --git a/pkg/apis/certmanager/v1alpha1/types.go b/pkg/apis/certmanager/v1alpha1/types.go index 8a50cd066..578e79992 100644 --- a/pkg/apis/certmanager/v1alpha1/types.go +++ b/pkg/apis/certmanager/v1alpha1/types.go @@ -112,6 +112,9 @@ type VaultAuth struct { } type VaultAppRole struct { + // Where the authentication path is mounted in Vault. + Path string `json:"path"` + RoleId string `json:"roleId"` SecretRef SecretKeySelector `json:"secretRef"` } diff --git a/pkg/issuer/vault/issue.go b/pkg/issuer/vault/issue.go index 6f6730202..8f554519f 100644 --- a/pkg/issuer/vault/issue.go +++ b/pkg/issuer/vault/issue.go @@ -110,7 +110,7 @@ func (v *Vault) initVaultClient() (*vault.Client, error) { if appRole.RoleId != "" { token, err := v.requestTokenWithAppRoleRef(client, &appRole) if err != nil { - return nil, fmt.Errorf("error reading Vault token from secret %s/%s: %s", v.issuerResourcesNamespace, appRole.SecretRef.Name, err.Error()) + return nil, fmt.Errorf("error reading Vault token from AppRole: %s", err.Error()) } client.SetToken(token) @@ -131,7 +131,12 @@ func (v *Vault) requestTokenWithAppRoleRef(client *vault.Client, appRole *v1alph "secret_id": secretId, } - url := "/v1/auth/approle/login" + authPath := appRole.Path + if authPath == "" { + authPath = "approle" + } + + url := path.Join("/v1", "auth", authPath, "login") request := client.NewRequest("POST", url) @@ -142,7 +147,7 @@ func (v *Vault) requestTokenWithAppRoleRef(client *vault.Client, appRole *v1alph resp, err := client.RawRequest(request) if err != nil { - return "", fmt.Errorf("error calling Vault server: %s", err.Error()) + return "", fmt.Errorf("error logging in to Vault server: %s", err.Error()) } defer resp.Body.Close() @@ -188,7 +193,7 @@ func (v *Vault) requestVaultCert(commonName string, altNames []string, csr []byt resp, err := client.RawRequest(request) if err != nil { - return nil, fmt.Errorf("error calling Vault server: %s", err.Error()) + return nil, fmt.Errorf("error signing certificate in Vault: %s", err.Error()) } defer resp.Body.Close() diff --git a/pkg/issuer/vault/setup.go b/pkg/issuer/vault/setup.go index 4dbb14eb0..4a89ff3ef 100644 --- a/pkg/issuer/vault/setup.go +++ b/pkg/issuer/vault/setup.go @@ -30,6 +30,7 @@ func (v *Vault) Setup(ctx context.Context) error { return fmt.Errorf(messageVaultConfigRequired) } + // check if Vault server info is specified. if v.issuer.GetSpec().Vault.Server == "" || v.issuer.GetSpec().Vault.Path == "" { glog.Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageServerAndPathRequired) @@ -37,6 +38,7 @@ func (v *Vault) Setup(ctx context.Context) error { return fmt.Errorf(messageVaultConfigRequired) } + // check if at least one auth method is specified. if v.issuer.GetSpec().Vault.Auth.TokenSecretRef.Name == "" && v.issuer.GetSpec().Vault.Auth.AppRole.RoleId == "" && v.issuer.GetSpec().Vault.Auth.AppRole.SecretRef.Name == "" { @@ -45,6 +47,7 @@ func (v *Vault) Setup(ctx context.Context) error { return fmt.Errorf(messsageAuthFieldsRequired) } + // check if only token auth method is set. if v.issuer.GetSpec().Vault.Auth.TokenSecretRef.Name != "" && (v.issuer.GetSpec().Vault.Auth.AppRole.RoleId != "" || v.issuer.GetSpec().Vault.Auth.AppRole.SecretRef.Name != "") { @@ -53,6 +56,15 @@ func (v *Vault) Setup(ctx context.Context) error { return fmt.Errorf(messageAuthFieldRequired) } + // check if all mandatory Vault appRole fields are set. + if v.issuer.GetSpec().Vault.Auth.TokenSecretRef.Name == "" && + (v.issuer.GetSpec().Vault.Auth.AppRole.RoleId == "" || + v.issuer.GetSpec().Vault.Auth.AppRole.SecretRef.Name == "") { + glog.Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageAuthFieldRequired) + v.issuer.UpdateStatusCondition(v1alpha1.IssuerConditionReady, v1alpha1.ConditionFalse, errorVault, messageAuthFieldRequired) + return fmt.Errorf(messageAuthFieldRequired) + } + client, err := v.initVaultClient() if err != nil { s := messageVaultClientInitFailed + err.Error() diff --git a/test/e2e/certificate/certificate_vault.go b/test/e2e/certificate/certificate_vault.go index dfd4ac46c..ecf2555ee 100644 --- a/test/e2e/certificate/certificate_vault.go +++ b/test/e2e/certificate/certificate_vault.go @@ -1,7 +1,7 @@ package certificate import ( - "fmt" + "path" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -23,7 +23,8 @@ var _ = framework.CertManagerDescribe("Vault Certificate (AppRole)", func() { certificateName := "test-vault-certificate" certificateSecretName := "test-vault-certificate" vaultSecretAppRoleName := "vault-role" - vaultPath := fmt.Sprintf("%s/sign/%s", intermediateMount, role) + vaultPath := path.Join(intermediateMount, "sign", role) + authPath := "approle" var vaultInit *vault.VaultInitializer var roleId string var secretId string @@ -33,7 +34,7 @@ var _ = framework.CertManagerDescribe("Vault Certificate (AppRole)", func() { podList, err := f.KubeClientSet.CoreV1().Pods("vault").List(metav1.ListOptions{}) Expect(err).NotTo(HaveOccurred()) vaultPodName := podList.Items[0].Name - vaultInit, err = vault.NewVaultInitializer(vaultPodName, rootMount, intermediateMount, role) + vaultInit, err = vault.NewVaultInitializer(vaultPodName, rootMount, intermediateMount, role, authPath) Expect(err).NotTo(HaveOccurred()) err = vaultInit.Setup() Expect(err).NotTo(HaveOccurred()) @@ -54,7 +55,69 @@ var _ = framework.CertManagerDescribe("Vault Certificate (AppRole)", func() { vaultURL := "http://vault.vault:8200" It("should generate a new valid certificate", func() { By("Creating an Issuer") - _, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName)) + _, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName, authPath)) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Issuer to become Ready") + err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name), + issuerName, + v1alpha1.IssuerCondition{ + Type: v1alpha1.IssuerConditionReady, + Status: v1alpha1.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + + By("Creating a Certificate") + cert, err := f.CertManagerClientSet.CertmanagerV1alpha1().Certificates(f.Namespace.Name).Create(util.NewCertManagerVaultCertificate(certificateName, certificateSecretName, issuerName, v1alpha1.IssuerKind)) + Expect(err).NotTo(HaveOccurred()) + + f.WaitCertificateIssuedValid(cert) + }) +}) + +var _ = framework.CertManagerDescribe("Vault Certificate (AppRole with a custom mount path)", func() { + f := framework.NewDefaultFramework("create-vault-certificate") + + rootMount := "root-ca" + intermediateMount := "intermediate-ca" + authPath := "custom/path" + role := "kubernetes-vault" + issuerName := "test-vault-issuer" + certificateName := "test-vault-certificate" + certificateSecretName := "test-vault-certificate" + vaultSecretAppRoleName := "vault-role" + vaultPath := path.Join(intermediateMount, "sign", role) + var vaultInit *vault.VaultInitializer + var roleId string + var secretId string + + BeforeEach(func() { + By("Configuring the Vault server") + podList, err := f.KubeClientSet.CoreV1().Pods("vault").List(metav1.ListOptions{}) + Expect(err).NotTo(HaveOccurred()) + vaultPodName := podList.Items[0].Name + vaultInit, err = vault.NewVaultInitializer(vaultPodName, rootMount, intermediateMount, role, authPath) + Expect(err).NotTo(HaveOccurred()) + err = vaultInit.Setup() + Expect(err).NotTo(HaveOccurred()) + roleId, secretId, err = vaultInit.CreateAppRole() + Expect(err).NotTo(HaveOccurred()) + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(vault.NewVaultAppRoleSecret(vaultSecretAppRoleName, secretId)) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + By("Cleaning up") + f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Delete(issuerName, nil) + f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(vaultSecretAppRoleName, nil) + vaultInit.CleanAppRole() + vaultInit.Clean() + }) + + vaultURL := "http://vault.vault:8200" + It("should generate a new valid certificate", func() { + By("Creating an Issuer") + _, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName, authPath)) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") diff --git a/test/e2e/issuer/issuer_vault.go b/test/e2e/issuer/issuer_vault.go index 07b22accd..8f48b5e84 100644 --- a/test/e2e/issuer/issuer_vault.go +++ b/test/e2e/issuer/issuer_vault.go @@ -1,7 +1,7 @@ package issuer import ( - "fmt" + "path" "time" . "github.com/onsi/ginkgo" @@ -23,7 +23,8 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() { role := "kubernetes-vault" vaultSecretAppRoleName := "vault-role" vaultSecretTokenName := "vault-token" - vaultPath := fmt.Sprintf("%s/sign/%s", intermediateMount, role) + vaultPath := path.Join(intermediateMount, "sign", role) + authPath := "approle" var roleId, secretId string var vaultInit *vault.VaultInitializer @@ -32,7 +33,7 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() { podList, err := f.KubeClientSet.CoreV1().Pods("vault").List(metav1.ListOptions{}) Expect(err).NotTo(HaveOccurred()) vaultPodName := podList.Items[0].Name - vaultInit, err = vault.NewVaultInitializer(vaultPodName, rootMount, intermediateMount, role) + vaultInit, err = vault.NewVaultInitializer(vaultPodName, rootMount, intermediateMount, role, authPath) Expect(err).NotTo(HaveOccurred()) err = vaultInit.Setup() Expect(err).NotTo(HaveOccurred()) @@ -56,7 +57,7 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() { Expect(err).NotTo(HaveOccurred()) By("Creating an Issuer") - _, err = f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName)) + _, err = f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName, authPath)) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") @@ -71,7 +72,7 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() { It("should fail to init with missing Vault AppRole", func() { By("Creating an Issuer") - _, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName)) + _, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(util.NewCertManagerVaultIssuerAppRole(issuerName, vaultURL, vaultPath, roleId, vaultSecretAppRoleName, authPath)) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") diff --git a/test/util/util.go b/test/util/util.go index 74c3df834..ab655f862 100644 --- a/test/util/util.go +++ b/test/util/util.go @@ -413,7 +413,7 @@ func NewCertManagerVaultIssuerToken(name, vaultURL, vaultPath, vaultSecretToken } } -func NewCertManagerVaultIssuerAppRole(name, vaultURL, vaultPath, roleId, vaultSecretAppRole string) *v1alpha1.Issuer { +func NewCertManagerVaultIssuerAppRole(name, vaultURL, vaultPath, roleId, vaultSecretAppRole, authPath string) *v1alpha1.Issuer { return &v1alpha1.Issuer{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -425,6 +425,7 @@ func NewCertManagerVaultIssuerAppRole(name, vaultURL, vaultPath, roleId, vaultSe Path: vaultPath, Auth: v1alpha1.VaultAuth{ AppRole: v1alpha1.VaultAppRole{ + Path: authPath, RoleId: roleId, SecretRef: v1alpha1.SecretKeySelector{ Key: "secretkey", diff --git a/test/util/vault/util.go b/test/util/vault/util.go index 2c3d48c90..7d5a194f5 100644 --- a/test/util/vault/util.go +++ b/test/util/vault/util.go @@ -42,9 +42,10 @@ type VaultInitializer struct { rootMount string intermediateMount string role string + authPath string } -func NewVaultInitializer(container, rootMount, intermediateMount, role string) (*VaultInitializer, error) { +func NewVaultInitializer(container, rootMount, intermediateMount, role, authPath string) (*VaultInitializer, error) { args := []string{"port-forward", "-n", "vault", container, "8200:8200"} cmd := exec.Command("kubectl", args...) err := cmd.Start() @@ -63,12 +64,17 @@ func NewVaultInitializer(container, rootMount, intermediateMount, role string) ( client.SetToken(vaultToken) + if authPath == "" { + authPath = "approle" + } + return &VaultInitializer{ proxyCmd: cmd, client: client, rootMount: rootMount, intermediateMount: intermediateMount, role: role, + authPath: authPath, }, nil } @@ -144,21 +150,22 @@ func (v *VaultInitializer) CreateAppRole() (string, string, error) { "period": "24h", "policies": v.role, } - url := path.Join("/v1/auth/approle/role", v.role) - _, err = v.callVault("POST", url, "", params) + + baseUrl := path.Join("/v1", "auth", v.authPath, "role", v.role) + _, err = v.callVault("POST", baseUrl, "", params) if err != nil { return "", "", fmt.Errorf("Error creating approle: %s", err.Error()) } // # read the role-id - url = path.Join("/v1/auth/approle/role", v.role, "role-id") + url := path.Join(baseUrl, "role-id") roleId, err := v.callVault("GET", url, "role_id", map[string]string{}) if err != nil { return "", "", fmt.Errorf("Error reading role_id: %s", err.Error()) } // # read the secret-id - url = path.Join("/v1/auth/approle/role", v.role, "secret-id") + url = path.Join(baseUrl, "secret-id") secretId, err := v.callVault("POST", url, "secret_id", map[string]string{}) if err != nil { return "", "", fmt.Errorf("Error reading secret_id: %s", err.Error()) @@ -168,7 +175,7 @@ func (v *VaultInitializer) CreateAppRole() (string, string, error) { } func (v *VaultInitializer) CleanAppRole() error { - url := path.Join("/v1/auth/approle/role", v.role) + url := path.Join("/v1", "auth", v.authPath, "role", v.role) _, err := v.callVault("DELETE", url, "", map[string]string{}) if err != nil { return fmt.Errorf("Error deleting AppRole: %s", err.Error()) @@ -280,14 +287,14 @@ func (v *VaultInitializer) setupRole() error { if err != nil { return fmt.Errorf("Error fetching auth mounts: %s", err.Error()) } - if _, ok := auths["approle/"]; !ok { + + if _, ok := auths[v.authPath+"/"]; !ok { options := &vault.EnableAuthOptions{Type: "approle"} - if err := v.client.Sys().EnableAuthWithOptions("approle", options); err != nil { + if err := v.client.Sys().EnableAuthWithOptions(v.authPath, options); err != nil { return fmt.Errorf("Error enabling approle: %s", err.Error()) } } - // vault write intermediate-ca/roles/kubernetes-vault allow_any_name=true max_ttl="2160h" params := map[string]string{ "allow_any_name": "true", "max_ttl": "2160h",