diff --git a/go.mod b/go.mod index 6ae3908f5..abb61b0c4 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/denisenkom/go-mssqldb v0.12.0 github.com/docker/distribution v2.8.1+incompatible // indirect github.com/go-sql-driver/mysql v1.6.0 + github.com/google/uuid v1.3.0 github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect github.com/gosimple/slug v1.11.0 github.com/hashicorp/errwrap v1.1.0 diff --git a/util/util.go b/util/util.go index 19954494b..4dd55c959 100644 --- a/util/util.go +++ b/util/util.go @@ -51,6 +51,10 @@ func ToStringArray(input []interface{}) []string { return output } +func Is500(err error) bool { + return ErrorContainsHTTPCode(err, http.StatusInternalServerError) +} + func Is404(err error) bool { return ErrorContainsHTTPCode(err, http.StatusNotFound) } @@ -64,6 +68,10 @@ func ErrorContainsHTTPCode(err error, codes ...int) bool { return false } +func ErrorContainsString(err error, s string) bool { + return strings.Contains(err.Error(), s) +} + // CalculateConflictsWith returns a slice of field names that conflict with // a single field (self). func CalculateConflictsWith(self string, group []string) []string { diff --git a/vault/resource_pki_secret_backend_intermediate_cert_request.go b/vault/resource_pki_secret_backend_intermediate_cert_request.go index fc9a06144..d92fd864f 100644 --- a/vault/resource_pki_secret_backend_intermediate_cert_request.go +++ b/vault/resource_pki_secret_backend_intermediate_cert_request.go @@ -5,9 +5,11 @@ package vault import ( "context" + "fmt" "log" "strings" + "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -231,7 +233,8 @@ func pkiSecretBackendIntermediateCertRequestCreate(ctx context.Context, d *schem backend := d.Get(consts.FieldBackend).(string) intermediateType := d.Get(consts.FieldType).(string) - path := pkiSecretBackendIntermediateGeneratePath(backend, intermediateType) + + path := pkiSecretBackendIntermediateGeneratePath(backend, intermediateType, provider.IsAPISupported(meta, provider.VaultVersion111)) intermediateCertAPIFields := []string{ consts.FieldCommonName, @@ -263,14 +266,18 @@ func pkiSecretBackendIntermediateCertRequestCreate(ctx context.Context, d *schem // add multi-issuer write API fields if supported isIssuerAPISupported := provider.IsAPISupported(meta, provider.VaultVersion111) + // Fields only used when we are generating a key if !(intermediateType == keyTypeKMS || intermediateType == consts.FieldExisting) { - // if kms or existing type, intermediateCertAPIFields = append(intermediateCertAPIFields, consts.FieldKeyType, consts.FieldKeyBits) - if isIssuerAPISupported { + } + + if isIssuerAPISupported { + // Note: CSR generation does not persist an issuer, just a key, so consts.FieldIssuerName is not supported + if intermediateType == consts.FieldExisting { intermediateCertAPIFields = append(intermediateCertAPIFields, consts.FieldKeyRef) + } else { + intermediateCertAPIFields = append(intermediateCertAPIFields, consts.FieldKeyName) } - } else if isIssuerAPISupported { - intermediateCertAPIFields = append(intermediateCertAPIFields, consts.FieldKeyName) } data := map[string]interface{}{} @@ -329,7 +336,15 @@ func pkiSecretBackendIntermediateCertRequestCreate(ctx context.Context, d *schem } - d.SetId(path) + id := path + if provider.IsAPISupported(meta, provider.VaultVersion111) { + // multiple CSRs can be generated + // ensure unique IDs + uniqueSuffix := uuid.New() + id = fmt.Sprintf("%s/%s", path, uniqueSuffix) + } + + d.SetId(id) return pkiSecretBackendIntermediateCertRequestRead(ctx, d, meta) } @@ -341,6 +356,9 @@ func pkiSecretBackendIntermediateCertRequestDelete(ctx context.Context, d *schem return nil } -func pkiSecretBackendIntermediateGeneratePath(backend string, intermediateType string) string { +func pkiSecretBackendIntermediateGeneratePath(backend, intermediateType string, isMultiIssuerSupported bool) string { + if isMultiIssuerSupported { + return strings.Trim(backend, "/") + "/issuers/generate/intermediate/" + strings.Trim(intermediateType, "/") + } return strings.Trim(backend, "/") + "/intermediate/generate/" + strings.Trim(intermediateType, "/") } diff --git a/vault/resource_pki_secret_backend_intermediate_cert_request_test.go b/vault/resource_pki_secret_backend_intermediate_cert_request_test.go index 05b1c523f..6db518635 100644 --- a/vault/resource_pki_secret_backend_intermediate_cert_request_test.go +++ b/vault/resource_pki_secret_backend_intermediate_cert_request_test.go @@ -80,14 +80,27 @@ func TestPkiSecretBackendIntermediateCertificate_multiIssuer(t *testing.T) { resourceName := "vault_pki_secret_backend_intermediate_cert_request.test" keyName := acctest.RandomWithPrefix("test-pki-key") - checks := []resource.TestCheckFunc{ + // used to test existing key flow + store := &testPKIKeyStore{} + keyResourceName := "vault_pki_secret_backend_key.test" + updatedKeyName := acctest.RandomWithPrefix("test-pki-key-updated") + + commonChecks := []resource.TestCheckFunc{ resource.TestCheckResourceAttr(resourceName, consts.FieldBackend, path), - resource.TestCheckResourceAttr(resourceName, consts.FieldType, "internal"), + resource.TestCheckResourceAttrSet(resourceName, consts.FieldKeyID), resource.TestCheckResourceAttr(resourceName, consts.FieldCommonName, "test Intermediate CA"), + } + + internalChecks := append(commonChecks, + resource.TestCheckResourceAttr(resourceName, consts.FieldType, "internal"), + // keyName is only set on internal if it is passed by user resource.TestCheckResourceAttr(resourceName, consts.FieldKeyName, keyName), - resource.TestCheckResourceAttrSet(resourceName, consts.FieldKeyID), + ) + + existingChecks := append(commonChecks, + resource.TestCheckResourceAttr(resourceName, consts.FieldType, "existing"), resource.TestCheckResourceAttrSet(resourceName, consts.FieldKeyRef), - } + ) resource.Test(t, resource.TestCase{ ProviderFactories: providerFactories, @@ -97,11 +110,27 @@ func TestPkiSecretBackendIntermediateCertificate_multiIssuer(t *testing.T) { }, CheckDestroy: testCheckMountDestroyed("vault_mount", consts.MountTypePKI, consts.FieldPath), Steps: []resource.TestStep{ - // @TODO add a test step with a key_ref { - Config: testPkiSecretBackendIntermediateCertRequestConfig_multiIssuer(path, keyName), + Config: testPkiSecretBackendIntermediateCertRequestConfig_multiIssuerInternal(path, keyName), Check: resource.ComposeTestCheckFunc( - append(checks)..., + append(internalChecks)..., + ), + }, + { + // Create and capture key ID + Config: testAccPKISecretBackendKey_basic(path, updatedKeyName, "rsa", "2048"), + Check: resource.ComposeTestCheckFunc( + testCapturePKIKeyID(keyResourceName, store), + ), + }, + { + Config: testPkiSecretBackendIntermediateCertRequestConfig_multiIssuerExisting(path, updatedKeyName), + Check: resource.ComposeTestCheckFunc( + append(existingChecks, + // confirm that root cert key ID is same as the key + // created in step 2; thereby confirming key_ref is passed + testPKIKeyUpdate(resourceName, store, true), + )..., ), }, }, @@ -128,7 +157,26 @@ resource "vault_pki_secret_backend_intermediate_cert_request" "test" { `, path, addConstraints) } -func testPkiSecretBackendIntermediateCertRequestConfig_multiIssuer(path, keyName string) string { +func testPkiSecretBackendIntermediateCertRequestConfig_multiIssuerInternal(path, keyName string) string { + return fmt.Sprintf(` +resource "vault_mount" "test" { + path = "%s" + type = "pki" + description = "test" + default_lease_ttl_seconds = 86400 + max_lease_ttl_seconds = 86400 +} + +resource "vault_pki_secret_backend_intermediate_cert_request" "test" { + backend = vault_mount.test.path + type = "internal" + common_name = "test Intermediate CA" + key_name = "%s" +} +`, path, keyName) +} + +func testPkiSecretBackendIntermediateCertRequestConfig_multiIssuerExisting(path, keyName string) string { return fmt.Sprintf(` resource "vault_mount" "test" { path = "%s" @@ -141,17 +189,16 @@ resource "vault_mount" "test" { resource "vault_pki_secret_backend_key" "test" { backend = vault_mount.test.path type = "exported" - key_name = "test" + key_name = "%s" key_type = "rsa" - key_bits = "4096" + key_bits = "2048" } resource "vault_pki_secret_backend_intermediate_cert_request" "test" { backend = vault_mount.test.path - type = "internal" + type = "existing" common_name = "test Intermediate CA" - key_ref = vault_pki_secret_backend_key.test.id - key_name = "%s" + key_ref = vault_pki_secret_backend_key.test.key_id } `, path, keyName) } diff --git a/vault/resource_pki_secret_backend_issuer.go b/vault/resource_pki_secret_backend_issuer.go index 41823a4f4..2a5c3e4c3 100644 --- a/vault/resource_pki_secret_backend_issuer.go +++ b/vault/resource_pki_secret_backend_issuer.go @@ -205,6 +205,11 @@ func pkiSecretBackendIssuerRead(ctx context.Context, d *schema.ResourceData, met log.Printf("[DEBUG] Reading %s from Vault", path) resp, err := client.Logical().ReadWithContext(ctx, path) + if resp == nil { + d.SetId("") + return nil + } + if err != nil { return diag.Errorf("error reading from Vault: %s", err) } @@ -232,9 +237,11 @@ func pkiSecretBackendIssuerRead(ctx context.Context, d *schema.ResourceData, met } for _, k := range fields { - if err := d.Set(k, resp.Data[k]); err != nil { - return diag.Errorf("error setting state key %q for issuer, err=%s", - k, err) + if v, ok := resp.Data[k]; ok { + if err := d.Set(k, v); err != nil { + return diag.Errorf("error setting state key %q for issuer, err=%s", + k, err) + } } } diff --git a/vault/resource_pki_secret_backend_key_test.go b/vault/resource_pki_secret_backend_key_test.go index c70a2a462..6220d9a04 100644 --- a/vault/resource_pki_secret_backend_key_test.go +++ b/vault/resource_pki_secret_backend_key_test.go @@ -86,14 +86,16 @@ func TestAccPKISecretBackendKey_basic(t *testing.T) { func testAccPKISecretBackendKey_basic(path, keyName, keyType, keyBits string) string { return fmt.Sprintf(` -resource "vault_mount" "pki" { - path = "%s" - type = "pki" - description = "PKI secret engine mount" +resource "vault_mount" "test" { + path = "%s" + type = "pki" + description = "test" + default_lease_ttl_seconds = "86400" + max_lease_ttl_seconds = "86400" } resource "vault_pki_secret_backend_key" "test" { - backend = vault_mount.pki.path + backend = vault_mount.test.path type = "exported" key_name = "%s" key_type = "%s" diff --git a/vault/resource_pki_secret_backend_root_cert.go b/vault/resource_pki_secret_backend_root_cert.go index 297b136df..094b9ee7c 100644 --- a/vault/resource_pki_secret_backend_root_cert.go +++ b/vault/resource_pki_secret_backend_root_cert.go @@ -24,6 +24,10 @@ import ( "github.com/hashicorp/terraform-provider-vault/util" ) +const ( + issuerNotFoundErr = "unable to find PKI issuer for reference" +) + func pkiSecretBackendRootCertResource() *schema.Resource { return &schema.Resource{ CreateContext: pkiSecretBackendRootCertCreate, @@ -53,9 +57,38 @@ func pkiSecretBackendRootCertResource() *schema.Resource { return e } - cert, err := getCACertificate(client, d.Get(consts.FieldBackend).(string)) - if err != nil { - return err + var cert *x509.Certificate + isIssuerAPISupported := provider.IsAPISupported(meta, provider.VaultVersion111) + if isIssuerAPISupported { + // get the specific certificate for issuer with issuer_id + cert, e = getIssuerPEM(client, d.Get(consts.FieldBackend).(string), d.Get(consts.FieldIssuerID).(string)) + if e != nil { + // Check if this is an out-of-band change on the issuer + if util.Is500(e) && util.ErrorContainsString(e, issuerNotFoundErr) { + log.Printf("[WARN] issuer deleted out-of-band. re-creating root cert") + // Force a change on the issuer ID field since + // it no longer exists and must be re-created + if e = d.SetNewComputed(consts.FieldIssuerID); e != nil { + return e + } + + if e := d.ForceNew(consts.FieldIssuerID); e != nil { + return e + } + + return nil + } + + // not an out-of-band issuer error + return e + } + } else { + // get the 'default' issuer's certificate + // default behavior for non multi-issuer API + cert, e = getDefaultCAPEM(client, d.Get(consts.FieldBackend).(string)) + if e != nil { + return e + } } if cert != nil { @@ -316,7 +349,7 @@ func pkiSecretBackendRootCertCreate(_ context.Context, d *schema.ResourceData, m backend := d.Get(consts.FieldBackend).(string) rootType := d.Get(consts.FieldType).(string) - path := pkiSecretBackendIntermediateSetSignedReadPath(backend, rootType) + path := pkiSecretBackendGenerateRootPath(backend, rootType, provider.IsAPISupported(meta, provider.VaultVersion111)) rootCertAPIFields := []string{ consts.FieldCommonName, @@ -350,13 +383,20 @@ func pkiSecretBackendRootCertCreate(_ context.Context, d *schema.ResourceData, m // add multi-issuer write API fields if supported isIssuerAPISupported := provider.IsAPISupported(meta, provider.VaultVersion111) + // Fields only used when we are generating a key if !(rootType == keyTypeKMS || rootType == consts.FieldExisting) { rootCertAPIFields = append(rootCertAPIFields, consts.FieldKeyType, consts.FieldKeyBits) - if isIssuerAPISupported { - rootCertAPIFields = append(rootCertAPIFields, consts.FieldKeyRef, consts.FieldIssuerName) + } + + if isIssuerAPISupported { + // We always can specify the issuer name we are generating root certs + rootCertAPIFields = append(rootCertAPIFields, consts.FieldIssuerName) + + if rootType == consts.FieldExisting { + rootCertAPIFields = append(rootCertAPIFields, consts.FieldKeyRef) + } else { + rootCertAPIFields = append(rootCertAPIFields, consts.FieldKeyName) } - } else if isIssuerAPISupported { - rootCertAPIFields = append(rootCertAPIFields, consts.FieldKeyName, consts.FieldIssuerName) } data := map[string]interface{}{} @@ -416,13 +456,20 @@ func pkiSecretBackendRootCertCreate(_ context.Context, d *schema.ResourceData, m } } - d.SetId(path) + id := path + if isIssuerAPISupported { + // multiple root certs can be issued + // ensure ID for each root_cert resource is unique + issuerID := resp.Data[consts.FieldIssuerID] + id = fmt.Sprintf("%s/issuer/%s", backend, issuerID) + } + + d.SetId(id) return nil } -func getCACertificate(client *api.Client, mount string) (*x509.Certificate, error) { - path := fmt.Sprintf("/v1/%s/ca/pem", mount) +func getCACertificate(client *api.Client, path string) (*x509.Certificate, error) { req := client.NewRequest(http.MethodGet, path) req.ClientToken = "" resp, err := client.RawRequest(req) @@ -455,6 +502,16 @@ func getCACertificate(client *api.Client, mount string) (*x509.Certificate, erro return nil, nil } +func getDefaultCAPEM(client *api.Client, mount string) (*x509.Certificate, error) { + path := fmt.Sprintf("/v1/%s/ca/pem", mount) + return getCACertificate(client, path) +} + +func getIssuerPEM(client *api.Client, mount, issuerID string) (*x509.Certificate, error) { + path := fmt.Sprintf("/v1/%s/issuer/%s/pem", mount, issuerID) + return getCACertificate(client, path) +} + func pkiSecretBackendRootCertDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client, e := provider.GetClient(d, meta) if e != nil { @@ -463,7 +520,16 @@ func pkiSecretBackendRootCertDelete(ctx context.Context, d *schema.ResourceData, backend := d.Get(consts.FieldBackend).(string) - path := pkiSecretBackendIntermediateSetSignedDeletePath(backend) + path := pkiSecretBackendDeleteRootPath(backend) + + if provider.IsAPISupported(meta, provider.VaultVersion111) { + // @TODO can be removed in future versions of the Provider + // this is added to allow a seamless upgrade for users + // from v3.18.0/v3.19.0 of the Provider + if !strings.Contains(d.Id(), "/root/generate/") { + path = d.Id() + } + } log.Printf("[DEBUG] Deleting root cert from PKI secret backend %q", path) if _, err := client.Logical().Delete(path); err != nil { @@ -473,11 +539,14 @@ func pkiSecretBackendRootCertDelete(ctx context.Context, d *schema.ResourceData, return nil } -func pkiSecretBackendIntermediateSetSignedReadPath(backend string, rootType string) string { +func pkiSecretBackendGenerateRootPath(backend, rootType string, isMultiIssuerSupported bool) string { + if isMultiIssuerSupported { + return strings.Trim(backend, "/") + "/issuers/generate/root/" + strings.Trim(rootType, "/") + } return strings.Trim(backend, "/") + "/root/generate/" + strings.Trim(rootType, "/") } -func pkiSecretBackendIntermediateSetSignedDeletePath(backend string) string { +func pkiSecretBackendDeleteRootPath(backend string) string { return strings.Trim(backend, "/") + "/root" } diff --git a/vault/resource_pki_secret_backend_root_cert_test.go b/vault/resource_pki_secret_backend_root_cert_test.go index 780f55c9d..0d85149b0 100644 --- a/vault/resource_pki_secret_backend_root_cert_test.go +++ b/vault/resource_pki_secret_backend_root_cert_test.go @@ -84,7 +84,9 @@ func TestPkiSecretBackendRootCertificate_basic(t *testing.T) { if err != nil { t.Fatal(err) } - genPath := pkiSecretBackendIntermediateSetSignedReadPath(path, "internal") + + isMultiIssuerSupported := testProvider.Meta().(*provider.ProviderMeta).IsAPISupported(provider.VaultVersion111) + genPath := pkiSecretBackendGenerateRootPath(path, "internal", isMultiIssuerSupported) resp, err := client.Logical().Write(genPath, map[string]interface{}{ consts.FieldCommonName: "out-of-band", @@ -116,16 +118,30 @@ func TestPkiSecretBackendRootCertificate_multiIssuer(t *testing.T) { keyName := acctest.RandomWithPrefix("test-pki-key") issuerName := acctest.RandomWithPrefix("test-pki-issuer") - checks := []resource.TestCheckFunc{ + // used to test existing key flow + store := &testPKIKeyStore{} + keyResourceName := "vault_pki_secret_backend_key.test" + updatedKeyName := acctest.RandomWithPrefix("test-pki-key-updated") + + commonChecks := []resource.TestCheckFunc{ resource.TestCheckResourceAttr(resourceName, consts.FieldBackend, path), - resource.TestCheckResourceAttr(resourceName, consts.FieldType, "internal"), - resource.TestCheckResourceAttr(resourceName, consts.FieldCommonName, "test Root CA"), - resource.TestCheckResourceAttr(resourceName, consts.FieldIssuerName, issuerName), - resource.TestCheckResourceAttr(resourceName, consts.FieldKeyName, keyName), resource.TestCheckResourceAttrSet(resourceName, consts.FieldKeyID), resource.TestCheckResourceAttrSet(resourceName, consts.FieldIssuerID), + resource.TestCheckResourceAttr(resourceName, consts.FieldIssuerName, issuerName), + resource.TestCheckResourceAttr(resourceName, consts.FieldCommonName, "test Root CA"), } + internalChecks := append(commonChecks, + resource.TestCheckResourceAttr(resourceName, consts.FieldType, "internal"), + // keyName is only set on internal if it is passed by user + resource.TestCheckResourceAttr(resourceName, consts.FieldKeyName, keyName), + ) + + existingChecks := append(commonChecks, + resource.TestCheckResourceAttr(resourceName, consts.FieldType, "existing"), + resource.TestCheckResourceAttrSet(resourceName, consts.FieldKeyRef), + ) + resource.Test(t, resource.TestCase{ ProviderFactories: providerFactories, PreCheck: func() { @@ -134,11 +150,59 @@ func TestPkiSecretBackendRootCertificate_multiIssuer(t *testing.T) { }, CheckDestroy: testCheckMountDestroyed("vault_mount", consts.MountTypePKI, consts.FieldPath), Steps: []resource.TestStep{ - // @TODO add a test step with a key_ref { - Config: testPkiSecretBackendRootCertificateConfig_multiIssuer(path, issuerName, keyName), + Config: testPkiSecretBackendRootCertificateConfig_multiIssuerInternal(path, issuerName, keyName), Check: resource.ComposeTestCheckFunc( - append(checks)..., + append(internalChecks)..., + ), + }, + { + // Create and capture key ID + Config: testAccPKISecretBackendKey_basic(path, updatedKeyName, "rsa", "2048"), + Check: resource.ComposeTestCheckFunc( + testCapturePKIKeyID(keyResourceName, store), + ), + }, + { + Config: testPkiSecretBackendRootCertificateConfig_multiIssuerExisting(path, issuerName, updatedKeyName), + Check: resource.ComposeTestCheckFunc( + append(existingChecks, + // confirm that root cert key ID is same as the key + // created in step 2; thereby confirming key_ref is passed + testPKIKeyUpdate(resourceName, store, true), + )..., + ), + }, + }, + }) +} + +// Ensures that TF state is cleanly resolved whenever +// multiple root certs are generated +func TestAccPKISecretBackendRootCert_multipleRootCerts(t *testing.T) { + backend := acctest.RandomWithPrefix("tf-test-pki") + resourceType := "vault_pki_secret_backend_root_cert" + resourceCurrentIssuer := resourceType + ".current" + resourceNextIssuer := resourceType + ".next" + + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { + testutil.TestAccPreCheck(t) + SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion111) + }, + CheckDestroy: testCheckMountDestroyed(resourceType, consts.MountTypePKI, consts.FieldBackend), + Steps: []resource.TestStep{ + { + Config: testAccPKISecretBackendRootCert_multipleRootCerts(backend), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceCurrentIssuer, consts.FieldBackend, backend), + resource.TestCheckResourceAttr(resourceCurrentIssuer, consts.FieldType, "internal"), + resource.TestCheckResourceAttrSet(resourceCurrentIssuer, consts.FieldIssuerID), + + resource.TestCheckResourceAttr(resourceNextIssuer, consts.FieldBackend, backend), + resource.TestCheckResourceAttr(resourceNextIssuer, consts.FieldType, "internal"), + resource.TestCheckResourceAttrSet(resourceNextIssuer, consts.FieldIssuerID), ), }, }, @@ -206,7 +270,7 @@ resource "vault_pki_secret_backend_root_cert" "test" { return config } -func testPkiSecretBackendRootCertificateConfig_multiIssuer(path, issuer, key string) string { +func testPkiSecretBackendRootCertificateConfig_multiIssuerInternal(path, issuer, key string) string { config := fmt.Sprintf(` resource "vault_mount" "test" { path = "%s" @@ -228,6 +292,59 @@ resource "vault_pki_secret_backend_root_cert" "test" { return config } +func testPkiSecretBackendRootCertificateConfig_multiIssuerExisting(path, issuer, key string) string { + config := fmt.Sprintf(` +resource "vault_mount" "test" { + path = "%s" + type = "pki" + description = "test" + default_lease_ttl_seconds = "86400" + max_lease_ttl_seconds = "86400" +} + +resource "vault_pki_secret_backend_key" "test" { + backend = vault_mount.test.path + type = "exported" + key_name = "%s" + key_type = "rsa" + key_bits = "2048" +} + +resource "vault_pki_secret_backend_root_cert" "test" { + backend = vault_mount.test.path + type = "existing" + common_name = "test Root CA" + issuer_name = "%s" + key_ref = vault_pki_secret_backend_key.test.key_id +} +`, path, key, issuer) + + return config +} + +func testAccPKISecretBackendRootCert_multipleRootCerts(path string) string { + return fmt.Sprintf(` +resource "vault_mount" "test" { + path = "%s" + type = "pki" + description = "PKI secret engine mount" +} + +resource "vault_pki_secret_backend_root_cert" "current" { + backend = vault_mount.test.path + type = "internal" + common_name = "test" + ttl = "86400" +} + +resource "vault_pki_secret_backend_root_cert" "next" { + backend = vault_mount.test.path + type = "internal" + common_name = "test" + ttl = "86400" +}`, path) +} + func testPkiSecretBackendRootCertificateConfig_managedKeys(path, managedKeyName, accessKey, secretKey string) string { config := fmt.Sprintf(` resource "vault_managed_keys" "test" {