diff --git a/CHANGELOG.md b/CHANGELOG.md index 71d2fdb49..1dd501f68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ FEATURES: * Update `vault_pki_secret_backend_role` to support the `cn_validations` role field ([#1820](https://github.com/hashicorp/terraform-provider-vault/pull/1820)). * Add new resource `vault_pki_secret_backend_acme_eab` to manage PKI ACME external account binding tokens. Requires Vault 1.14+. ([#2367](https://github.com/hashicorp/terraform-provider-vault/pull/2367)) * Add new data source and resource `vault_pki_secret_backend_config_cmpv2`. Requires Vault 1.18+. *Available only for Vault Enterprise* ([#2330](https://github.com/hashicorp/terraform-provider-vault/pull/2330)) +* Add support for certificate revocation with `revoke_with_key` in `vault_pki_secret_backend_cert` IMPROVEMENTS: diff --git a/internal/consts/consts.go b/internal/consts/consts.go index 8f33f0a5e..54c2b939b 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -325,6 +325,7 @@ const ( FieldUseCSRValues = "use_csr_values" FieldCertificateBundle = "certificate_bundle" FieldRevoke = "revoke" + FieldRevokeWithKey = "revoke_with_key" FieldPrivateKeyType = "private_key_type" FieldAddBasicConstraints = "add_basic_constraints" FieldExported = "exported" diff --git a/vault/resource_pki_secret_backend_cert.go b/vault/resource_pki_secret_backend_cert.go index 421f19ec5..c81083c7b 100644 --- a/vault/resource_pki_secret_backend_cert.go +++ b/vault/resource_pki_secret_backend_cert.go @@ -166,10 +166,18 @@ func pkiSecretBackendCertResource() *schema.Resource { "the expiration is less than min_seconds_remaining in the future.", }, consts.FieldRevoke: { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: "Revoke the certificate upon resource destruction.", + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Revoke the certificate upon resource destruction.", + ConflictsWith: []string{consts.FieldRevokeWithKey}, + }, + consts.FieldRevokeWithKey: { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Revoke the certificate with private key method upon resource destruction.", + ConflictsWith: []string{consts.FieldRevoke}, }, consts.FieldIssuerRef: { Type: schema.TypeString, @@ -357,28 +365,41 @@ func pkiSecretBackendCertUpdate(ctx context.Context, d *schema.ResourceData, m i } func pkiSecretBackendCertDelete(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - if d.Get(consts.FieldRevoke).(bool) { + var revokeWithKey bool + if d.Get(consts.FieldRevokeWithKey) != nil { + revokeWithKey = d.Get(consts.FieldRevokeWithKey).(bool) + } else { + revokeWithKey = false + } + if d.Get(consts.FieldRevoke).(bool) || revokeWithKey { client, e := provider.GetClient(d, meta) if e != nil { return diag.FromErr(e) } backend := d.Get(consts.FieldBackend).(string) - path := strings.Trim(backend, "/") + "/revoke" - serialNumber := d.Get(consts.FieldSerialNumber).(string) commonName := d.Get(consts.FieldCommonName).(string) data := map[string]interface{}{ consts.FieldSerialNumber: serialNumber, } + var path string + if revokeWithKey { + data["private_key"] = d.Get(consts.FieldPrivateKey).(string) + path = strings.Trim(backend, "/") + "/revoke-with-key" + } else { + path = strings.Trim(backend, "/") + "/revoke" + } log.Printf("[DEBUG] Revoking certificate %q with serial number %q on PKI secret backend %q", commonName, serialNumber, backend) _, err := client.Logical().Write(path, data) + if err != nil { return diag.Errorf("error revoking certificate %q with serial number %q for PKI secret backend %q: %s", commonName, serialNumber, backend, err) } + log.Printf("[DEBUG] Successfully revoked certificate %q with serial number %q on PKI secret backend %q", commonName, serialNumber, backend) diff --git a/vault/resource_pki_secret_backend_cert_test.go b/vault/resource_pki_secret_backend_cert_test.go index ae482bf5b..932a9ca3e 100644 --- a/vault/resource_pki_secret_backend_cert_test.go +++ b/vault/resource_pki_secret_backend_cert_test.go @@ -30,6 +30,7 @@ type testPKICertStore struct { expiration int64 expirationWindow int64 expectRevoked bool + revokeWithKey bool } func TestPkiSecretBackendCert_basic(t *testing.T) { @@ -54,7 +55,7 @@ func TestPkiSecretBackendCert_basic(t *testing.T) { CheckDestroy: testCheckMountDestroyed("vault_mount", consts.MountTypePKI, consts.FieldPath), Steps: []resource.TestStep{ { - Config: testPkiSecretBackendCertConfig_basic(rootPath, intermediatePath, true, false), + Config: testPkiSecretBackendCertConfig_basic(rootPath, intermediatePath, true, false, false), Check: resource.ComposeTestCheckFunc( append(checks, resource.TestCheckResourceAttr(resourceName, "revoke", "false"), @@ -65,7 +66,7 @@ func TestPkiSecretBackendCert_basic(t *testing.T) { }, { // revoke the cert, expect a new one is re-issued - Config: testPkiSecretBackendCertConfig_basic(rootPath, intermediatePath, true, true), + Config: testPkiSecretBackendCertConfig_basic(rootPath, intermediatePath, true, true, false), Check: resource.ComposeTestCheckFunc( append(checks, resource.TestCheckResourceAttr(resourceName, "revoke", "true"), @@ -76,7 +77,7 @@ func TestPkiSecretBackendCert_basic(t *testing.T) { }, { // remove the cert to test revocation flow (expect no revocation) - Config: testPkiSecretBackendCertConfig_basic(rootPath, intermediatePath, false, false), + Config: testPkiSecretBackendCertConfig_basic(rootPath, intermediatePath, false, false, false), Check: resource.ComposeTestCheckFunc( testPKICertRevocation(intermediatePath, store), ), @@ -86,17 +87,28 @@ func TestPkiSecretBackendCert_basic(t *testing.T) { meta := testProvider.Meta().(*provider.ProviderMeta) return !meta.IsAPISupported(provider.VaultVersion113), nil }, - Config: testPkiSecretBackendCertConfig_basic(rootPath, intermediatePath, true, false), + Config: testPkiSecretBackendCertConfig_basic(rootPath, intermediatePath, true, false, false), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "user_ids.0", "foo"), resource.TestCheckResourceAttr(resourceName, "user_ids.1", "bar"), ), }, + { + // revoke the cert with key + Config: testPkiSecretBackendCertConfig_basic(rootPath, intermediatePath, true, true, true), + Check: resource.ComposeTestCheckFunc( + append(checks, + resource.TestCheckResourceAttr(resourceName, "revoke_with_key", "true"), + testPKICertRevocation(intermediatePath, store), + testCapturePKICert(resourceName, store), + )..., + ), + }, }, }) } -func testPkiSecretBackendCertConfig_basic(rootPath, intermediatePath string, withCert, revoke bool) string { +func testPkiSecretBackendCertConfig_basic(rootPath, intermediatePath string, withCert, revoke bool, revokeWithKey bool) string { fragments := []string{ fmt.Sprintf(` resource "vault_mount" "test-root" { @@ -167,20 +179,34 @@ resource "vault_pki_secret_backend_role" "test" { `, rootPath, intermediatePath), } - if withCert { + if withCert && !revokeWithKey { fragments = append(fragments, fmt.Sprintf(` resource "vault_pki_secret_backend_cert" "test" { backend = vault_pki_secret_backend_role.test.backend name = vault_pki_secret_backend_role.test.name common_name = "cert.test.my.domain" uri_sans = ["spiffe://test.my.domain"] - user_ids = ["foo", "bar"] + user_ids = ["foo", "bar"] ttl = "720h" min_seconds_remaining = 60 revoke = %t } `, revoke)) } + if revokeWithKey && withCert { + fragments = append(fragments, ` +resource "vault_pki_secret_backend_cert" "test" { + backend = vault_pki_secret_backend_role.test.backend + name = vault_pki_secret_backend_role.test.name + common_name = "cert.test.my.domain" + uri_sans = ["spiffe://test.my.domain"] + user_ids = ["foo", "bar"] + ttl = "720h" + min_seconds_remaining = 60 + revoke_with_key = true +} +`) + } return strings.Join(fragments, "\n") } diff --git a/website/docs/r/pki_secret_backend_cert.html.md b/website/docs/r/pki_secret_backend_cert.html.md index b97bd314e..95d644334 100755 --- a/website/docs/r/pki_secret_backend_cert.html.md +++ b/website/docs/r/pki_secret_backend_cert.html.md @@ -67,7 +67,11 @@ The following arguments are supported: * `auto_renew` - (Optional) If set to `true`, certs will be renewed if the expiration is within `min_seconds_remaining`. Default `false` -* `revoke` - If set to `true`, the certificate will be revoked on resource destruction. +* `revoke` - If set to `true`, the certificate will be revoked on resource destruction using the `revoke` PKI API. Conflicts with `revoke_with_key`. Default `false`. + +* `revoke_with_key` - If set to `true`, the certificate will be revoked on resource destruction using the `revoke-with-key` PKI API. Conflicts with `revoke`. Default `false` + +* `revoke_with_key` - If set to `true`, use method `revoke-with-key` to revoke the certificate on resource destruction. Used to revoke certificate without using privileged operation. Conflicts with `revoke`. Default `false` ## Attributes Reference