From 2995cc90a3073152493660f0d8a58fe5045cfa3a Mon Sep 17 00:00:00 2001 From: Vincent Desjardins Date: Mon, 4 Jun 2018 01:59:48 +0000 Subject: [PATCH] Vault: configurable appRole authentication path --- .../vault/creating-vault-issuers.rst | 5 +- pkg/apis/certmanager/v1alpha1/types.go | 2 + pkg/issuer/vault/issue.go | 7 +- pkg/issuer/vault/setup.go | 8 +++ test/e2e/certificate/certificate_vault.go | 66 ++++++++++++++++++- test/e2e/issuer/issuer_vault.go | 6 +- test/util/util.go | 3 +- test/util/vault/util.go | 25 ++++--- 8 files changed, 105 insertions(+), 17 deletions(-) diff --git a/docs/tutorials/vault/creating-vault-issuers.rst b/docs/tutorials/vault/creating-vault-issuers.rst index 78d452ed3..97f1797d4 100644 --- a/docs/tutorials/vault/creating-vault-issuers.rst +++ b/docs/tutorials/vault/creating-vault-issuers.rst @@ -55,6 +55,7 @@ We can now create a cluster issuer referencing this secret: path: pki_int/sign/example-dot-com server: https://vault auth: + authPath: approle appRole: roleId: "291b9d21-8ff5-..." secretRef: @@ -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 *authPath* specifies +where the AppRole authentication is mounted in Vault. The *authPath* 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..0d5f996c0 100644 --- a/pkg/apis/certmanager/v1alpha1/types.go +++ b/pkg/apis/certmanager/v1alpha1/types.go @@ -109,6 +109,8 @@ type VaultAuth struct { TokenSecretRef SecretKeySelector `json:"tokenSecretRef,omitempty"` // This Secret contains a AppRole and Secret AppRole VaultAppRole `json:"appRole,omitempty"` + // Where the authentication path is mounted in Vault. + AuthPath string `json:"authPath,omitempty"` } type VaultAppRole struct { diff --git a/pkg/issuer/vault/issue.go b/pkg/issuer/vault/issue.go index 6f6730202..db48a9771 100644 --- a/pkg/issuer/vault/issue.go +++ b/pkg/issuer/vault/issue.go @@ -131,7 +131,12 @@ func (v *Vault) requestTokenWithAppRoleRef(client *vault.Client, appRole *v1alph "secret_id": secretId, } - url := "/v1/auth/approle/login" + authPath := v.issuer.GetSpec().Vault.Auth.AuthPath + if authPath == "" { + authPath = "approle" + } + + url := path.Join("/v1/auth", authPath, "login") request := client.NewRequest("POST", url) diff --git a/pkg/issuer/vault/setup.go b/pkg/issuer/vault/setup.go index 4dbb14eb0..71be54dd9 100644 --- a/pkg/issuer/vault/setup.go +++ b/pkg/issuer/vault/setup.go @@ -21,6 +21,7 @@ const ( messageServerAndPathRequired = "Vault server and path are required fields" messsageAuthFieldsRequired = "Vault tokenSecretRef or appRole is required" messageAuthFieldRequired = "Vault tokenSecretRef and appRole cannot be set on the same issuer" + messageAuthPathInvalid = "Vault authPath cannot be used with a tokenSecretRef" ) func (v *Vault) Setup(ctx context.Context) error { @@ -53,6 +54,13 @@ func (v *Vault) Setup(ctx context.Context) error { return fmt.Errorf(messageAuthFieldRequired) } + if v.issuer.GetSpec().Vault.Auth.TokenSecretRef.Name != "" && + v.issuer.GetSpec().Vault.Auth.AuthPath != "" { + glog.Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageAuthPathInvalid) + v.issuer.UpdateStatusCondition(v1alpha1.IssuerConditionReady, v1alpha1.ConditionFalse, errorVault, messageAuthPathInvalid) + return fmt.Errorf(messageAuthPathInvalid) + } + 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..b2f78e58e 100644 --- a/test/e2e/certificate/certificate_vault.go +++ b/test/e2e/certificate/certificate_vault.go @@ -33,7 +33,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, "") Expect(err).NotTo(HaveOccurred()) err = vaultInit.Setup() Expect(err).NotTo(HaveOccurred()) @@ -54,7 +54,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, "")) + 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" + roleMount := "custom/path" + role := "kubernetes-vault" + issuerName := "test-vault-issuer" + certificateName := "test-vault-certificate" + certificateSecretName := "test-vault-certificate" + vaultSecretAppRoleName := "vault-role" + vaultPath := fmt.Sprintf("%s/sign/%s", intermediateMount, 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, roleMount) + 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, roleMount)) 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..c10778468 100644 --- a/test/e2e/issuer/issuer_vault.go +++ b/test/e2e/issuer/issuer_vault.go @@ -32,7 +32,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, "") Expect(err).NotTo(HaveOccurred()) err = vaultInit.Setup() Expect(err).NotTo(HaveOccurred()) @@ -56,7 +56,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, "")) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") @@ -71,7 +71,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, "")) 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..7e6601abb 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, @@ -424,6 +424,7 @@ func NewCertManagerVaultIssuerAppRole(name, vaultURL, vaultPath, roleId, vaultSe Server: vaultURL, Path: vaultPath, Auth: v1alpha1.VaultAuth{ + AuthPath: authPath, AppRole: v1alpha1.VaultAppRole{ RoleId: roleId, SecretRef: v1alpha1.SecretKeySelector{ diff --git a/test/util/vault/util.go b/test/util/vault/util.go index 2c3d48c90..d9e9d91d9 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",