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

Key Vault: support for soft delete #1805

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions azurerm/data_source_key_vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
},
}
Expand Down Expand Up @@ -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)
}
Expand Down
35 changes: 35 additions & 0 deletions azurerm/data_source_key_vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(`
Expand All @@ -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)
}
16 changes: 16 additions & 0 deletions azurerm/helpers/validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
34 changes: 34 additions & 0 deletions azurerm/helpers/validate/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

}
})
}
69 changes: 58 additions & 11 deletions azurerm/resource_arm_key_vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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(),
},
}
Expand All @@ -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{})
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}

Expand Down
113 changes: 113 additions & 0 deletions azurerm/resource_arm_key_vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package azurerm

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -283,6 +339,10 @@ resource "azurerm_key_vault" "test" {
"set",
]
}

tags {
environment = "Production"
}
}
`, rInt, location, rInt)
}
Expand Down Expand Up @@ -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" {}
Expand Down
4 changes: 4 additions & 0 deletions website/docs/d/key_vault.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading