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",