Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_key_vault_certificate: do not create a new certificate version when only update lifetime_action field #24755

Merged
merged 1 commit into from Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
115 changes: 76 additions & 39 deletions internal/services/keyvault/key_vault_certificate_resource.go
Expand Up @@ -15,6 +15,7 @@ import (
"time"

"github.com/Azure/go-autorest/autorest"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids"
Expand Down Expand Up @@ -620,7 +621,28 @@ func resourceKeyVaultCertificateUpdate(d *schema.ResourceData, meta interface{})
d.SetId(certificateId.ID())
}
}
if d.HasChange("certificate_policy") {

// update lifetime_action only should not recreate a certificate
var lifeTimeOld, lifeTimeNew interface{}
var policyOld, policyNew map[string]interface{}

policyOldRaw, policyNewRaw := d.GetChange("certificate_policy")
policyOldList, policyNewList := policyOldRaw.([]interface{}), policyNewRaw.([]interface{})

if len(policyOldList) > 0 {
policyOld = policyOldList[0].(map[string]interface{})
lifeTimeOld = policyOld["lifetime_action"]
delete(policyOld, "lifetime_action")
}
if len(policyNewList) > 0 {
policyNew = policyNewList[0].(map[string]interface{})
lifeTimeNew = policyNew["lifetime_action"]
delete(policyNew, "lifetime_action")
}

// do not recreate cerfiticate when only lifetime_action changes
if !cmp.Equal(policyNewList, policyOldList) {
policyNew["lifetime_action"] = lifeTimeNew
newCert, err := createCertificate(d, meta)
if err != nil {
return err
Expand All @@ -632,10 +654,18 @@ func resourceKeyVaultCertificateUpdate(d *schema.ResourceData, meta interface{})
d.SetId(certificateId.ID())
}

if d.HasChange("tags") {
if updateLifetime := !cmp.Equal(lifeTimeOld, lifeTimeNew); d.HasChange("tags") || updateLifetime {
patch := keyvault.CertificateUpdateParameters{}
if t, ok := d.GetOk("tags"); ok {
patch.Tags = tags.Expand(t.(map[string]interface{}))
if d.HasChange("tags") {
if t, ok := d.GetOk("tags"); ok {
patch.Tags = tags.Expand(t.(map[string]interface{}))
}
}

if updateLifetime {
patch.CertificatePolicy = &keyvault.CertificatePolicy{
LifetimeActions: expandKeyVaultCertificatePolicyLifetimeAction(lifeTimeNew),
}
}

if _, err = client.UpdateCertificate(ctx, id.KeyVaultBaseUrl, id.Name, "", patch); err != nil {
Expand Down Expand Up @@ -908,41 +938,7 @@ func expandKeyVaultCertificatePolicy(d *pluginsdk.ResourceData) (*keyvault.Certi
ReuseKey: utils.Bool(props["reuse_key"].(bool)),
}

lifetimeActions := make([]keyvault.LifetimeAction, 0)
actions := policyRaw["lifetime_action"].([]interface{})
for _, v := range actions {
action := v.(map[string]interface{})
lifetimeAction := keyvault.LifetimeAction{}

if v, ok := action["action"]; ok {
as := v.([]interface{})
a := as[0].(map[string]interface{})
lifetimeAction.Action = &keyvault.Action{
ActionType: keyvault.CertificatePolicyAction(a["action_type"].(string)),
}
}

if v, ok := action["trigger"]; ok {
triggers := v.([]interface{})
if triggers[0] != nil {
trigger := triggers[0].(map[string]interface{})
lifetimeAction.Trigger = &keyvault.Trigger{}

d := trigger["days_before_expiry"].(int)
if d > 0 {
lifetimeAction.Trigger.DaysBeforeExpiry = utils.Int32(int32(d))
}

p := trigger["lifetime_percentage"].(int)
if p > 0 {
lifetimeAction.Trigger.LifetimePercentage = utils.Int32(int32(p))
}
}
}

lifetimeActions = append(lifetimeActions, lifetimeAction)
}
policy.LifetimeActions = &lifetimeActions
policy.LifetimeActions = expandKeyVaultCertificatePolicyLifetimeAction(policyRaw["lifetime_action"])

secrets := policyRaw["secret_properties"].([]interface{})
secret := secrets[0].(map[string]interface{})
Expand Down Expand Up @@ -999,6 +995,47 @@ func expandKeyVaultCertificatePolicy(d *pluginsdk.ResourceData) (*keyvault.Certi
return &policy, nil
}

func expandKeyVaultCertificatePolicyLifetimeAction(actions interface{}) *[]keyvault.LifetimeAction {
lifetimeActions := make([]keyvault.LifetimeAction, 0)
if actions == nil {
return &lifetimeActions
}

for _, v := range actions.([]interface{}) {
action := v.(map[string]interface{})
lifetimeAction := keyvault.LifetimeAction{}

if v, ok := action["action"]; ok {
as := v.([]interface{})
a := as[0].(map[string]interface{})
lifetimeAction.Action = &keyvault.Action{
ActionType: keyvault.CertificatePolicyAction(a["action_type"].(string)),
}
}

if v, ok := action["trigger"]; ok {
triggers := v.([]interface{})
if triggers[0] != nil {
trigger := triggers[0].(map[string]interface{})
lifetimeAction.Trigger = &keyvault.Trigger{}

d := trigger["days_before_expiry"].(int)
if d > 0 {
lifetimeAction.Trigger.DaysBeforeExpiry = utils.Int32(int32(d))
}

p := trigger["lifetime_percentage"].(int)
if p > 0 {
lifetimeAction.Trigger.LifetimePercentage = utils.Int32(int32(p))
}
}
}

lifetimeActions = append(lifetimeActions, lifetimeAction)
}
return &lifetimeActions
}

func flattenKeyVaultCertificatePolicy(input *keyvault.CertificatePolicy, certData *[]byte) []interface{} {
if input == nil {
return []interface{}{}
Expand Down
78 changes: 78 additions & 0 deletions internal/services/keyvault/key_vault_certificate_resource_test.go
Expand Up @@ -108,6 +108,28 @@ func TestAccKeyVaultCertificate_basicGenerate(t *testing.T) {
})
}

func TestAccKeyVaultCertificate_updateLifeTime(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_key_vault_certificate", "test")
r := KeyVaultCertificateResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basicGenerate(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.basicGenerateUpdateLifetimeAction(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccKeyVaultCertificate_basicGenerateUnknownIssuer(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_key_vault_certificate", "test")
r := KeyVaultCertificateResource{}
Expand Down Expand Up @@ -713,6 +735,62 @@ resource "azurerm_key_vault_certificate" "test" {
`, r.template(data), data.RandomString)
}

func (r KeyVaultCertificateResource) basicGenerateUpdateLifetimeAction(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

%s

resource "azurerm_key_vault_certificate" "test" {
name = "acctestcert%s"
key_vault_id = azurerm_key_vault.test.id

certificate_policy {
issuer_parameters {
name = "Self"
}

key_properties {
exportable = true
key_size = 2048
key_type = "RSA"
reuse_key = true
}

lifetime_action {
action {
action_type = "EmailContacts"
}

trigger {
days_before_expiry = 30
}
}

secret_properties {
content_type = "application/x-pkcs12"
}

x509_certificate_properties {
key_usage = [
"cRLSign",
"dataEncipherment",
"digitalSignature",
"keyAgreement",
"keyEncipherment",
"keyCertSign",
]

subject = "CN=hello-world"
validity_in_months = 12
}
}
}
`, r.template(data), data.RandomString)
}

func (r KeyVaultCertificateResource) updateCertificate(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
Expand Down
2 changes: 1 addition & 1 deletion website/docs/r/key_vault_certificate.html.markdown
Expand Up @@ -238,7 +238,7 @@ The following arguments are supported:

* `certificate` - (Optional) A `certificate` block as defined below, used to Import an existing certificate. Changing this will create a new version of the Key Vault Certificate.

* `certificate_policy` - (Optional) A `certificate_policy` block as defined below. Changing this will create a new version of the Key Vault Certificate.
* `certificate_policy` - (Optional) A `certificate_policy` block as defined below. Changing this (except the `lifetime_action` field) will create a new version of the Key Vault Certificate.

~> **NOTE:** When creating a Key Vault Certificate, at least one of `certificate` or `certificate_policy` is required. Provide `certificate` to import an existing certificate, `certificate_policy` to generate a new certificate.

Expand Down