From f4839ebcd20277e539cc6a52dfee44042695e3f4 Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Boll Date: Tue, 21 Aug 2018 16:18:08 +0200 Subject: [PATCH] Add softdelete feature --- azurerm/data_source_key_vault.go | 13 +++ azurerm/data_source_key_vault_test.go | 35 +++++++ azurerm/helpers/validate/validate.go | 16 +++ azurerm/helpers/validate/validate_test.go | 34 +++++++ azurerm/resource_arm_key_vault.go | 69 ++++++++++--- azurerm/resource_arm_key_vault_test.go | 113 ++++++++++++++++++++++ website/docs/d/key_vault.html.markdown | 4 + website/docs/r/key_vault.html.markdown | 9 ++ 8 files changed, 282 insertions(+), 11 deletions(-) diff --git a/azurerm/data_source_key_vault.go b/azurerm/data_source_key_vault.go index 41c1a8f14165..1be07dce4b68 100644 --- a/azurerm/data_source_key_vault.go +++ b/azurerm/data_source_key_vault.go @@ -105,6 +105,16 @@ func dataSourceArmKeyVault() *schema.Resource { Computed: true, }, + "enabled_for_soft_delete": { + Type: schema.TypeBool, + Computed: true, + }, + + "enabled_for_purge_protection": { + Type: schema.TypeBool, + Computed: true, + }, + "tags": tagsForDataSourceSchema(), }, } @@ -138,6 +148,9 @@ func dataSourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error { d.Set("enabled_for_deployment", props.EnabledForDeployment) d.Set("enabled_for_disk_encryption", props.EnabledForDiskEncryption) d.Set("enabled_for_template_deployment", props.EnabledForTemplateDeployment) + d.Set("enabled_for_soft_delete", props.EnableSoftDelete) + d.Set("enabled_for_purge_protection", props.EnablePurgeProtection) + if err := d.Set("sku", flattenKeyVaultDataSourceSku(props.Sku)); err != nil { return fmt.Errorf("Error flattening `sku` for KeyVault %q: %+v", *resp.Name, err) } diff --git a/azurerm/data_source_key_vault_test.go b/azurerm/data_source_key_vault_test.go index d2881e3e799c..ed9ddbc8d83c 100644 --- a/azurerm/data_source_key_vault_test.go +++ b/azurerm/data_source_key_vault_test.go @@ -65,6 +65,29 @@ func TestAccDataSourceAzureRMKeyVault_complete(t *testing.T) { }) } +func TestAccDataSourceAzureRMKeyVault_enable_soft_delete(t *testing.T) { + dataSourceName := "data.azurerm_key_vault.test" + ri := acctest.RandInt() + location := testLocation() + config := testAccDataSourceAzureRMKeyVault_enable_soft_delete(ri, location) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(dataSourceName), + resource.TestCheckResourceAttr(dataSourceName, "enabled_for_soft_delete", "true"), + resource.TestCheckResourceAttr(dataSourceName, "enabled_for_purge_protection", "false"), + ), + }, + }, + }) +} + func testAccDataSourceAzureRMKeyVault_basic(rInt int, location string) string { resource := testAccAzureRMKeyVault_basic(rInt, location) return fmt.Sprintf(` @@ -88,3 +111,15 @@ data "azurerm_key_vault" "test" { } `, resource) } + +func testAccDataSourceAzureRMKeyVault_enable_soft_delete(rInt int, location string) string { + resource := testAccAzureRMKeyVault_enable_soft_delete(rInt, location) + return fmt.Sprintf(` +%s + +data "azurerm_key_vault" "test" { + name = "${azurerm_key_vault.test.name}" + resource_group_name = "${azurerm_key_vault.test.resource_group_name}" +} +`, resource) +} diff --git a/azurerm/helpers/validate/validate.go b/azurerm/helpers/validate/validate.go index 1fde2d3a9e01..ecbaee13f7ca 100644 --- a/azurerm/helpers/validate/validate.go +++ b/azurerm/helpers/validate/validate.go @@ -69,3 +69,19 @@ func UrlWithScheme(validSchemes []string) schema.SchemaValidateFunc { return } } + +func BoolIsTrue() schema.SchemaValidateFunc { + return func(i interface{}, k string) (_ []string, errors []error) { + v, ok := i.(bool) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be bool", k)) + return + } + + if !v { + errors = append(errors, fmt.Errorf("%q can only be set to true, if not required remove key", k)) + return + } + return + } +} diff --git a/azurerm/helpers/validate/validate_test.go b/azurerm/helpers/validate/validate_test.go index 4011884c879f..085299674b0f 100644 --- a/azurerm/helpers/validate/validate_test.go +++ b/azurerm/helpers/validate/validate_test.go @@ -43,3 +43,37 @@ func TestUrlWithScheme(t *testing.T) { } }) } + +func TestBoolIsTrue(t *testing.T) { + + testCases := []struct { + Value bool + ShouldHaveError bool + }{ + { + Value: true, + ShouldHaveError: false, + }, { + Value: false, + ShouldHaveError: true, + }, + } + + t.Run("TestBoolIsTrue", func(t *testing.T) { + for _, value := range testCases { + _, errors := BoolIsTrue()(value.Value, "dummy") + hasErrors := len(errors) > 0 + + if value.ShouldHaveError && !hasErrors { + t.Fatalf("Expected an error but didn't get one for %t", value.Value) + return + } + + if !value.ShouldHaveError && hasErrors { + t.Fatalf("Expected %t to return no errors, but got some %+v", value.Value, errors) + return + } + + } + }) +} diff --git a/azurerm/resource_arm_key_vault.go b/azurerm/resource_arm_key_vault.go index f7179cda8d24..c492cc5a5dbb 100644 --- a/azurerm/resource_arm_key_vault.go +++ b/azurerm/resource_arm_key_vault.go @@ -11,8 +11,9 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" - "github.com/satori/go.uuid" + uuid "github.com/satori/go.uuid" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -119,6 +120,25 @@ func resourceArmKeyVault() *schema.Resource { Optional: true, }, + "enabled_for_soft_delete": { + Type: schema.TypeBool, + Optional: true, + ValidateFunc: validate.BoolIsTrue(), + }, + + "enabled_for_purge_protection": { + Type: schema.TypeBool, + Optional: true, + ValidateFunc: validate.BoolIsTrue(), + }, + + "purge_on_delete": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ConflictsWith: []string{"enabled_for_purge_protection"}, + }, + "tags": tagsSchema(), }, } @@ -136,6 +156,8 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e enabledForDeployment := d.Get("enabled_for_deployment").(bool) enabledForDiskEncryption := d.Get("enabled_for_disk_encryption").(bool) enabledForTemplateDeployment := d.Get("enabled_for_template_deployment").(bool) + enabledForSoftDelete := d.Get("enabled_for_soft_delete").(bool) + enabledForPurgeProtection := d.Get("enabled_for_purge_protection").(bool) tags := d.Get("tags").(map[string]interface{}) policies := d.Get("access_policy").([]interface{}) @@ -144,17 +166,29 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error expanding `access_policy`: %+v", policies) } + vaultProperties := keyvault.VaultProperties{ + TenantID: &tenantUUID, + Sku: expandKeyVaultSku(d), + AccessPolicies: accessPolicies, + EnabledForDeployment: &enabledForDeployment, + EnabledForDiskEncryption: &enabledForDiskEncryption, + EnabledForTemplateDeployment: &enabledForTemplateDeployment, + } + + // This setting can only be set if it is true, if set when value is false API returns errors + if enabledForSoftDelete { + vaultProperties.EnableSoftDelete = &enabledForSoftDelete + } + + // This setting can only be set if it is true, if set when value is false API returns errors + if enabledForPurgeProtection { + vaultProperties.EnablePurgeProtection = &enabledForPurgeProtection + } + parameters := keyvault.VaultCreateOrUpdateParameters{ - Location: &location, - Properties: &keyvault.VaultProperties{ - TenantID: &tenantUUID, - Sku: expandKeyVaultSku(d), - AccessPolicies: accessPolicies, - EnabledForDeployment: &enabledForDeployment, - EnabledForDiskEncryption: &enabledForDiskEncryption, - EnabledForTemplateDeployment: &enabledForTemplateDeployment, - }, - Tags: expandTags(tags), + Location: &location, + Properties: &vaultProperties, + Tags: expandTags(tags), } // Locking this resource so we don't make modifications to it at the same time if there is a @@ -232,6 +266,8 @@ func resourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error { d.Set("enabled_for_deployment", props.EnabledForDeployment) d.Set("enabled_for_disk_encryption", props.EnabledForDiskEncryption) d.Set("enabled_for_template_deployment", props.EnabledForTemplateDeployment) + d.Set("enabled_for_soft_delete", props.EnableSoftDelete) + d.Set("enabled_for_purge_protection", props.EnablePurgeProtection) if err := d.Set("sku", flattenKeyVaultSku(props.Sku)); err != nil { return fmt.Errorf("Error flattening `sku` for KeyVault %s: %+v", *resp.Name, err) } @@ -263,6 +299,17 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { _, err = client.Delete(ctx, resGroup, name) + if d.Get("purge_on_delete").(bool) && d.Get("enabled_for_soft_delete").(bool) && err == nil { + log.Printf("[DEBUG] KeyVault %s marked for purge, executing purge", name) + location := d.Get("location").(string) + future, err := client.PurgeDeleted(ctx, name, location) + if err != nil { + return err + } + log.Printf("[DEBUG] Waiting for purge of KeyVault %s", name) + err = future.WaitForCompletion(ctx, client.Client) + } + return err } diff --git a/azurerm/resource_arm_key_vault_test.go b/azurerm/resource_arm_key_vault_test.go index c8d589e1cc8f..d208d472e5e3 100644 --- a/azurerm/resource_arm_key_vault_test.go +++ b/azurerm/resource_arm_key_vault_test.go @@ -2,6 +2,7 @@ package azurerm import ( "fmt" + "regexp" "testing" "github.com/hashicorp/terraform/helper/acctest" @@ -166,6 +167,61 @@ func TestAccAzureRMKeyVault_update(t *testing.T) { }) } +func TestAccAzureRMKeyVault_soft_delete(t *testing.T) { + ri := acctest.RandInt() + resourceName := "azurerm_key_vault.test" + preConfig := testAccAzureRMKeyVault_basic(ri, testLocation()) + postConfig := testAccAzureRMKeyVault_enable_soft_delete(ri, testLocation()) + purgeConfig := testAccAzureRMKeyVault_purge_vault(ri, testLocation()) + + expectedError := fmt.Sprintf("^Check failed: Check 1/1 error: Not found: %s", resourceName) + + errorRegEx, _ := regexp.Compile(expectedError) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "access_policy.0.key_permissions.0", "create"), + resource.TestCheckResourceAttr(resourceName, "access_policy.0.secret_permissions.0", "set"), + resource.TestCheckResourceAttr(resourceName, "tags.environment", "Production"), + ), + }, + { + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "access_policy.0.key_permissions.0", "get"), + resource.TestCheckResourceAttr(resourceName, "access_policy.0.secret_permissions.0", "get"), + resource.TestCheckResourceAttr(resourceName, "enabled_for_soft_delete", "true"), + resource.TestCheckResourceAttr(resourceName, "enabled_for_purge_protection", "false"), + resource.TestCheckResourceAttr(resourceName, "tags.environment", "Production"), + ), + }, + { + Config: purgeConfig, + ExpectError: errorRegEx, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(resourceName), + ), + }, + { + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "access_policy.0.key_permissions.0", "create"), + resource.TestCheckResourceAttr(resourceName, "access_policy.0.secret_permissions.0", "set"), + resource.TestCheckResourceAttr(resourceName, "tags.environment", "Production"), + ), + }, + }, + }) +} + func testCheckAzureRMKeyVaultDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*ArmClient).keyVaultClient ctx := testAccProvider.Meta().(*ArmClient).StopContext @@ -283,6 +339,10 @@ resource "azurerm_key_vault" "test" { "set", ] } + + tags { + environment = "Production" + } } `, rInt, location, rInt) } @@ -330,6 +390,59 @@ resource "azurerm_key_vault" "test" { `, rInt, location, rInt) } +func testAccAzureRMKeyVault_enable_soft_delete(rInt int, location string) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_key_vault" "test" { + name = "vault%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + + sku { + name = "premium" + } + + access_policy { + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + object_id = "${data.azurerm_client_config.current.client_id}" + + key_permissions = [ + "get", + ] + + secret_permissions = [ + "get", + ] + } + + enabled_for_soft_delete = true + purge_on_delete = true + + tags { + environment = "Production" + } +} +`, rInt, location, rInt) +} + +func testAccAzureRMKeyVault_purge_vault(rInt int, location string) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} +`, rInt, location) +} + func testAccAzureRMKeyVault_complete(rInt int, location string) string { return fmt.Sprintf(` data "azurerm_client_config" "current" {} diff --git a/website/docs/d/key_vault.html.markdown b/website/docs/d/key_vault.html.markdown index 0c9a73e88b47..cd8c2cc01ea7 100644 --- a/website/docs/d/key_vault.html.markdown +++ b/website/docs/d/key_vault.html.markdown @@ -53,6 +53,10 @@ The following attributes are exported: * `enabled_for_template_deployment` - Can Azure Resource Manager retrieve secrets from the Key Vault? +* `enabled_for_soft_delete` - Is soft delete enabled on this Key Vault? + +* `enabled_for_purge_protection` - Is purge protection enabled on this Key Vault? + * `tags` - A mapping of tags assigned to the Key Vault. A `sku` block exports the following: diff --git a/website/docs/r/key_vault.html.markdown b/website/docs/r/key_vault.html.markdown index b672109071f0..5a1ce8edbe09 100644 --- a/website/docs/r/key_vault.html.markdown +++ b/website/docs/r/key_vault.html.markdown @@ -87,6 +87,15 @@ The following arguments are supported: Azure Resource Manager is permitted to retrieve secrets from the key vault. Defaults to false. +* `enabled_for_soft_delete` - (Optional) Boolean flag to specify whether + the key vault is enabled for soft delete. Once enabled you can not disable this setting anymore! + +* `purge_on_delete` - (Optional) Boolean flag to specify if the KeyVault should be purged on delete. This purges KeyVaults enabled for soft delete on resource deletition! + +* `enabled_for_purge_protection` - (Optional) Boolean flag to specify whether + the key vault is enabled for purge protection, this conflicts with `purge_on_delete`. Once enabled you can not disable this setting anymore! + + * `tags` - (Optional) A mapping of tags to assign to the resource. `sku` supports the following: