diff --git a/azurerm/internal/acceptance/data.go b/azurerm/internal/acceptance/data.go index 3e79ea88bee1..2a7ad06e102e 100644 --- a/azurerm/internal/acceptance/data.go +++ b/azurerm/internal/acceptance/data.go @@ -24,7 +24,7 @@ type TestData struct { // Locations is a set of Azure Regions which should be used for this Test Locations Regions - // RandomString is a random integer which is unique to this test case + // RandomInteger is a random integer which is unique to this test case RandomInteger int // RandomString is a random 5 character string is unique to this test case @@ -90,6 +90,7 @@ func BuildTestData(t *testing.T, resourceType string, resourceLabel string) Test return testData } +// RandomIntOfLength is a random 8 to 18 digit integer which is unique to this test case func (td *TestData) RandomIntOfLength(len int) int { // len should not be // - greater then 18, longest a int can represent @@ -116,3 +117,13 @@ func (td *TestData) RandomIntOfLength(len int) int { return i } + +// RandomStringOfLength is a random 1 to 1024 character string which is unique to this test case +func (td *TestData) RandomStringOfLength(len int) string { + // len should not be less then 1 or greater than 1024 + if 1 > len || len > 1024 { + panic(fmt.Sprintf("Invalid Test: RandomStringOfLength: length argument must be between 1 and 1024 characters")) + } + + return acctest.RandString(len) +} diff --git a/azurerm/internal/services/iothub/resource_arm_iothub.go b/azurerm/internal/services/iothub/resource_arm_iothub.go index 270efa6c597c..f0587350b78b 100644 --- a/azurerm/internal/services/iothub/resource_arm_iothub.go +++ b/azurerm/internal/services/iothub/resource_arm_iothub.go @@ -26,6 +26,8 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) +// TODO: outside of this pr make this private + var IothubResourceName = "azurerm_iothub" func suppressIfTypeIsNot(t string) schema.SchemaDiffSuppressFunc { diff --git a/azurerm/internal/services/keyvault/parse/key_vault_id.go b/azurerm/internal/services/keyvault/parse/key_vault_id.go new file mode 100644 index 000000000000..65944f189241 --- /dev/null +++ b/azurerm/internal/services/keyvault/parse/key_vault_id.go @@ -0,0 +1,31 @@ +package parse + +import ( + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type KeyVaultId struct { + Name string + ResourceGroup string +} + +func KeyVaultID(input string) (*KeyVaultId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + account := KeyVaultId{ + ResourceGroup: id.ResourceGroup, + } + + if account.Name, err = id.PopSegment("vaults"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &account, nil +} diff --git a/azurerm/internal/services/keyvault/parse/key_vault_id_test.go b/azurerm/internal/services/keyvault/parse/key_vault_id_test.go new file mode 100644 index 000000000000..9c73991d5cf2 --- /dev/null +++ b/azurerm/internal/services/keyvault/parse/key_vault_id_test.go @@ -0,0 +1,73 @@ +package parse + +import ( + "testing" +) + +func TestKeyVaultID(t *testing.T) { + testData := []struct { + Name string + Input string + Expected *KeyVaultId + }{ + { + Name: "Empty", + Input: "", + Expected: nil, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Expected: nil, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Expected: nil, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", + Expected: nil, + }, + { + Name: "Missing Vaults Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.KeyVault/vaults/", + Expected: nil, + }, + { + Name: "Key Vault ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.KeyVault/vaults/vault1", + Expected: &KeyVaultId{ + Name: "vault1", + ResourceGroup: "resGroup1", + }, + }, + { + Name: "Wrong Casing", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.KeyVault/Vaults/vault1", + Expected: nil, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := KeyVaultID(v.Input) + if err != nil { + if v.Expected == nil { + continue + } + + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expected.ResourceGroup, actual.ResourceGroup) + } + } +} diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault_key.go b/azurerm/internal/services/keyvault/resource_arm_key_vault_key.go index 3e1ca2060988..74f87464b3a8 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault_key.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault_key.go @@ -339,6 +339,7 @@ func resourceArmKeyVaultKeyRead(d *schema.ResourceData, meta interface{}) error } d.Set("name", id.Name) + if key := resp.Key; key != nil { d.Set("key_type", string(key.Kty)) diff --git a/azurerm/internal/services/keyvault/validate/key_vault_id.go b/azurerm/internal/services/keyvault/validate/key_vault_id.go new file mode 100644 index 000000000000..5b365e71ce47 --- /dev/null +++ b/azurerm/internal/services/keyvault/validate/key_vault_id.go @@ -0,0 +1,22 @@ +package validate + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/keyvault/parse" +) + +func KeyVaultID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + if _, err := parse.KeyVaultID(v); err != nil { + errors = append(errors, fmt.Errorf("Can not parse %q as a resource id: %v", k, err)) + return + } + + return warnings, errors +} diff --git a/azurerm/internal/services/storage/data_source_storage_account.go b/azurerm/internal/services/storage/data_source_storage_account.go index 3bf211ee7a72..6be24f9baa03 100644 --- a/azurerm/internal/services/storage/data_source_storage_account.go +++ b/azurerm/internal/services/storage/data_source_storage_account.go @@ -54,11 +54,6 @@ func dataSourceArmStorageAccount() *schema.Resource { Computed: true, }, - "account_encryption_source": { - Type: schema.TypeString, - Computed: true, - }, - "custom_domain": { Type: schema.TypeList, Computed: true, @@ -72,16 +67,6 @@ func dataSourceArmStorageAccount() *schema.Resource { }, }, - "enable_blob_encryption": { - Type: schema.TypeBool, - Computed: true, - }, - - "enable_file_encryption": { - Type: schema.TypeBool, - Computed: true, - }, - "enable_https_traffic_only": { Type: schema.TypeBool, Computed: true, @@ -329,18 +314,6 @@ func dataSourceArmStorageAccountRead(d *schema.ResourceData, meta interface{}) e } } - if encryption := props.Encryption; encryption != nil { - if services := encryption.Services; services != nil { - if blob := services.Blob; blob != nil { - d.Set("enable_blob_encryption", blob.Enabled) - } - if file := services.File; file != nil { - d.Set("enable_file_encryption", file.Enabled) - } - } - d.Set("account_encryption_source", string(encryption.KeySource)) - } - // Computed d.Set("primary_location", props.PrimaryLocation) d.Set("secondary_location", props.SecondaryLocation) diff --git a/azurerm/internal/services/storage/registration.go b/azurerm/internal/services/storage/registration.go index d69a696c5d87..12f7f66b9df3 100644 --- a/azurerm/internal/services/storage/registration.go +++ b/azurerm/internal/services/storage/registration.go @@ -32,16 +32,17 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource { // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "azurerm_storage_account": resourceArmStorageAccount(), - "azurerm_storage_account_network_rules": resourceArmStorageAccountNetworkRules(), - "azurerm_storage_blob": resourceArmStorageBlob(), - "azurerm_storage_container": resourceArmStorageContainer(), - "azurerm_storage_data_lake_gen2_filesystem": resourceArmStorageDataLakeGen2FileSystem(), - "azurerm_storage_management_policy": resourceArmStorageManagementPolicy(), - "azurerm_storage_queue": resourceArmStorageQueue(), - "azurerm_storage_share": resourceArmStorageShare(), - "azurerm_storage_share_directory": resourceArmStorageShareDirectory(), - "azurerm_storage_table": resourceArmStorageTable(), - "azurerm_storage_table_entity": resourceArmStorageTableEntity(), + "azurerm_storage_account": resourceArmStorageAccount(), + "azurerm_storage_account_customer_managed_key": resourceArmStorageAccountCustomerManagedKey(), + "azurerm_storage_account_network_rules": resourceArmStorageAccountNetworkRules(), + "azurerm_storage_blob": resourceArmStorageBlob(), + "azurerm_storage_container": resourceArmStorageContainer(), + "azurerm_storage_data_lake_gen2_filesystem": resourceArmStorageDataLakeGen2FileSystem(), + "azurerm_storage_management_policy": resourceArmStorageManagementPolicy(), + "azurerm_storage_queue": resourceArmStorageQueue(), + "azurerm_storage_share": resourceArmStorageShare(), + "azurerm_storage_share_directory": resourceArmStorageShareDirectory(), + "azurerm_storage_table": resourceArmStorageTable(), + "azurerm_storage_table_entity": resourceArmStorageTableEntity(), } } diff --git a/azurerm/internal/services/storage/resource_arm_storage_account.go b/azurerm/internal/services/storage/resource_arm_storage_account.go index e7faa9321201..3a924d8395df 100644 --- a/azurerm/internal/services/storage/resource_arm_storage_account.go +++ b/azurerm/internal/services/storage/resource_arm_storage_account.go @@ -20,7 +20,6 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/iothub" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" @@ -31,6 +30,8 @@ import ( const blobStorageAccountDefaultAccessTier = "Hot" +var storageAccountResourceName = "azurerm_storage_account" + func resourceArmStorageAccount() *schema.Resource { return &schema.Resource{ Create: resourceArmStorageAccountCreate, @@ -112,17 +113,6 @@ func resourceArmStorageAccount() *schema.Resource { }, true), }, - "account_encryption_source": { - Type: schema.TypeString, - Optional: true, - Default: string(storage.MicrosoftStorage), - ValidateFunc: validation.StringInSlice([]string{ - string(storage.MicrosoftKeyvault), - string(storage.MicrosoftStorage), - }, true), - DiffSuppressFunc: suppress.CaseDifference, - }, - "custom_domain": { Type: schema.TypeList, Optional: true, @@ -143,18 +133,6 @@ func resourceArmStorageAccount() *schema.Resource { }, }, - "enable_blob_encryption": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - - "enable_file_encryption": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - "enable_https_traffic_only": { Type: schema.TypeBool, Optional: true, @@ -619,15 +597,12 @@ func resourceArmStorageAccountCreate(d *schema.ResourceData, meta interface{}) e accountKind := d.Get("account_kind").(string) location := azure.NormalizeLocation(d.Get("location").(string)) t := d.Get("tags").(map[string]interface{}) - enableBlobEncryption := d.Get("enable_blob_encryption").(bool) - enableFileEncryption := d.Get("enable_file_encryption").(bool) enableHTTPSTrafficOnly := d.Get("enable_https_traffic_only").(bool) isHnsEnabled := d.Get("is_hns_enabled").(bool) accountTier := d.Get("account_tier").(string) replicationType := d.Get("account_replication_type").(string) storageType := fmt.Sprintf("%s_%s", accountTier, replicationType) - storageAccountEncryptionSource := d.Get("account_encryption_source").(string) parameters := storage.AccountCreateParameters{ Location: &location, @@ -637,16 +612,6 @@ func resourceArmStorageAccountCreate(d *schema.ResourceData, meta interface{}) e Tags: tags.Expand(t), Kind: storage.Kind(accountKind), AccountPropertiesCreateParameters: &storage.AccountPropertiesCreateParameters{ - Encryption: &storage.Encryption{ - Services: &storage.EncryptionServices{ - Blob: &storage.EncryptionService{ - Enabled: utils.Bool(enableBlobEncryption), - }, - File: &storage.EncryptionService{ - Enabled: utils.Bool(enableFileEncryption), - }}, - KeySource: storage.KeySource(storageAccountEncryptionSource), - }, EnableHTTPSTrafficOnly: &enableHTTPSTrafficOnly, NetworkRuleSet: expandStorageAccountNetworkRules(d), IsHnsEnabled: &isHnsEnabled, @@ -785,8 +750,8 @@ func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) e storageAccountName := id.Path["storageAccounts"] resourceGroupName := id.ResourceGroup - locks.ByName(storageAccountName, iothub.IothubResourceName) - defer locks.UnlockByName(storageAccountName, iothub.IothubResourceName) + locks.ByName(storageAccountName, storageAccountResourceName) + defer locks.UnlockByName(storageAccountName, storageAccountResourceName) accountTier := d.Get("account_tier").(string) replicationType := d.Get("account_replication_type").(string) @@ -847,40 +812,6 @@ func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) e d.SetPartial("tags") } - if d.HasChange("enable_blob_encryption") || d.HasChange("enable_file_encryption") { - encryptionSource := d.Get("account_encryption_source").(string) - - opts := storage.AccountUpdateParameters{ - AccountPropertiesUpdateParameters: &storage.AccountPropertiesUpdateParameters{ - Encryption: &storage.Encryption{ - Services: &storage.EncryptionServices{}, - KeySource: storage.KeySource(encryptionSource), - }, - }, - } - - if d.HasChange("enable_blob_encryption") { - enableEncryption := d.Get("enable_blob_encryption").(bool) - opts.Encryption.Services.Blob = &storage.EncryptionService{ - Enabled: utils.Bool(enableEncryption), - } - - d.SetPartial("enable_blob_encryption") - } - - if d.HasChange("enable_file_encryption") { - enableEncryption := d.Get("enable_file_encryption").(bool) - opts.Encryption.Services.File = &storage.EncryptionService{ - Enabled: utils.Bool(enableEncryption), - } - d.SetPartial("enable_file_encryption") - } - - if _, err := client.Update(ctx, resourceGroupName, storageAccountName, opts); err != nil { - return fmt.Errorf("Error updating Azure Storage Account Encryption %q: %+v", storageAccountName, err) - } - } - if d.HasChange("custom_domain") { opts := storage.AccountUpdateParameters{ AccountPropertiesUpdateParameters: &storage.AccountPropertiesUpdateParameters{ @@ -1066,18 +997,6 @@ func resourceArmStorageAccountRead(d *schema.ResourceData, meta interface{}) err } } - if encryption := props.Encryption; encryption != nil { - if services := encryption.Services; services != nil { - if blob := services.Blob; blob != nil { - d.Set("enable_blob_encryption", blob.Enabled) - } - if file := services.File; file != nil { - d.Set("enable_file_encryption", file.Enabled) - } - } - d.Set("account_encryption_source", string(encryption.KeySource)) - } - // Computed d.Set("primary_location", props.PrimaryLocation) d.Set("secondary_location", props.SecondaryLocation) diff --git a/azurerm/internal/services/storage/resource_arm_storage_account_customer_managed_key.go b/azurerm/internal/services/storage/resource_arm_storage_account_customer_managed_key.go new file mode 100644 index 000000000000..5eecd10114ed --- /dev/null +++ b/azurerm/internal/services/storage/resource_arm_storage_account_customer_managed_key.go @@ -0,0 +1,282 @@ +package storage + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-04-01/storage" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" + keyVaultParse "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/keyvault/parse" + keyVaultValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/keyvault/validate" + storageParse "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/storage/parsers" + storageValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/storage/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmStorageAccountCustomerManagedKey() *schema.Resource { + return &schema.Resource{ + Create: resourceArmStorageAccountCustomerManagedKeyCreateUpdate, + Read: resourceArmStorageAccountCustomerManagedKeyRead, + Update: resourceArmStorageAccountCustomerManagedKeyCreateUpdate, + Delete: resourceArmStorageAccountCustomerManagedKeyDelete, + + // TODO: this needs a custom ID validating importer + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "storage_account_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: storageValidate.StorageAccountID, + }, + + "key_vault_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: keyVaultValidate.KeyVaultID, + }, + + "key_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "key_version": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + }, + } +} + +func resourceArmStorageAccountCustomerManagedKeyCreateUpdate(d *schema.ResourceData, meta interface{}) error { + storageClient := meta.(*clients.Client).Storage.AccountsClient + vaultsClient := meta.(*clients.Client).KeyVault.VaultsClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + storageAccountIdRaw := d.Get("storage_account_id").(string) + storageAccountId, err := storageParse.ParseAccountID(storageAccountIdRaw) + if err != nil { + return err + } + + locks.ByName(storageAccountId.Name, storageAccountResourceName) + defer locks.UnlockByName(storageAccountId.Name, storageAccountResourceName) + + storageAccount, err := storageClient.GetProperties(ctx, storageAccountId.ResourceGroup, storageAccountId.Name, "") + if err != nil { + return fmt.Errorf("Error retrieving Storage Account %q (Resource Group %q): %+v", storageAccountId.Name, storageAccountId.ResourceGroup, err) + } + if storageAccount.AccountProperties == nil { + return fmt.Errorf("Error retrieving Storage Account %q (Resource Group %q): `properties` was nil", storageAccountId.Name, storageAccountId.ResourceGroup) + } + + // since we're mutating the storage account here, we can use that as the ID + resourceId := storageAccountIdRaw + + if d.IsNewResource() { + // whilst this looks superflurious given encryption is enabled by default, due to the way + // the Azure API works this technically can be nil + if storageAccount.AccountProperties.Encryption != nil { + if storageAccount.AccountProperties.Encryption.KeySource == storage.MicrosoftKeyvault { + return tf.ImportAsExistsError("azurerm_storage_account_customer_managed_key", resourceId) + } + } + } + + keyVaultIdRaw := d.Get("key_vault_id").(string) + keyVaultId, err := keyVaultParse.KeyVaultID(keyVaultIdRaw) + if err != nil { + return err + } + + keyVault, err := vaultsClient.Get(ctx, keyVaultId.ResourceGroup, keyVaultId.Name) + if err != nil { + return fmt.Errorf("Error retrieving Key Vault %q (Resource Group %q): %+v", keyVaultId.Name, keyVaultId.ResourceGroup, err) + } + + softDeleteEnabled := false + purgeProtectionEnabled := false + if props := keyVault.Properties; props != nil { + if esd := props.EnableSoftDelete; esd != nil { + softDeleteEnabled = *esd + } + if epp := props.EnablePurgeProtection; epp != nil { + purgeProtectionEnabled = *epp + } + } + if !softDeleteEnabled || !purgeProtectionEnabled { + return fmt.Errorf("Key Vault %q (Resource Group %q) must be configured for both Purge Protection and Soft Delete", keyVaultId.Name, keyVaultId.ResourceGroup) + } + + keyVaultBaseURL, err := azure.GetKeyVaultBaseUrlFromID(ctx, vaultsClient, keyVaultIdRaw) + if err != nil { + return fmt.Errorf("Error looking up Key Vault URI from Key Vault %q (Resource Group %q): %+v", keyVaultId.Name, keyVaultId.ResourceGroup, err) + } + + keyName := d.Get("key_name").(string) + keyVersion := d.Get("key_version").(string) + props := storage.AccountUpdateParameters{ + AccountPropertiesUpdateParameters: &storage.AccountPropertiesUpdateParameters{ + Encryption: &storage.Encryption{ + Services: &storage.EncryptionServices{ + Blob: &storage.EncryptionService{ + Enabled: utils.Bool(true), + }, + File: &storage.EncryptionService{ + Enabled: utils.Bool(true), + }, + }, + KeySource: storage.MicrosoftKeyvault, + KeyVaultProperties: &storage.KeyVaultProperties{ + KeyName: utils.String(keyName), + KeyVersion: utils.String(keyVersion), + KeyVaultURI: utils.String(keyVaultBaseURL), + }, + }, + }, + } + + if _, err = storageClient.Update(ctx, storageAccountId.ResourceGroup, storageAccountId.Name, props); err != nil { + return fmt.Errorf("Error updating Customer Managed Key for Storage Account %q (Resource Group %q): %+v", storageAccountId.Name, storageAccountId.ResourceGroup, err) + } + + d.SetId(resourceId) + return resourceArmStorageAccountCustomerManagedKeyRead(d, meta) +} + +func resourceArmStorageAccountCustomerManagedKeyRead(d *schema.ResourceData, meta interface{}) error { + storageClient := meta.(*clients.Client).Storage.AccountsClient + vaultsClient := meta.(*clients.Client).KeyVault.VaultsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + storageAccountId, err := storageParse.ParseAccountID(d.Id()) + if err != nil { + return err + } + + storageAccount, err := storageClient.GetProperties(ctx, storageAccountId.ResourceGroup, storageAccountId.Name, "") + if err != nil { + if utils.ResponseWasNotFound(storageAccount.Response) { + log.Printf("[DEBUG] Storage Account %q could not be found in Resource Group %q - removing from state!", storageAccountId.Name, storageAccountId.ResourceGroup) + d.SetId("") + return nil + } + + return fmt.Errorf("Error retrieving Storage Account %q (Resource Group %q): %+v", storageAccountId.Name, storageAccountId.ResourceGroup, err) + } + if storageAccount.AccountProperties == nil { + return fmt.Errorf("Error retrieving Storage Account %q (Resource Group %q): `properties` was nil", storageAccountId.Name, storageAccountId.ResourceGroup) + } + if storageAccount.AccountProperties.Encryption == nil || storageAccount.AccountProperties.Encryption.KeySource != storage.MicrosoftKeyvault { + log.Printf("[DEBUG] Customer Managed Key was not defined for Storage Account %q (Resource Group %q) - removing from state!", storageAccountId.Name, storageAccountId.ResourceGroup) + d.SetId("") + return nil + } + + encryption := *storageAccount.AccountProperties.Encryption + + keyName := "" + keyVaultUri := "" + keyVersion := "" + if props := encryption.KeyVaultProperties; props != nil { + if props.KeyName != nil { + keyName = *props.KeyName + } + if props.KeyVaultURI != nil { + keyVaultUri = *props.KeyVaultURI + } + if props.KeyVersion != nil { + keyVersion = *props.KeyVersion + } + } + + if keyVaultUri == "" { + return fmt.Errorf("Error retrieving Storage Account %q (Resource Group %q): `properties.encryption.keyVaultProperties.keyVaultUri` was nil", storageAccountId.Name, storageAccountId.ResourceGroup) + } + + keyVaultId, err := azure.GetKeyVaultIDFromBaseUrl(ctx, vaultsClient, keyVaultUri) + if err != nil { + return fmt.Errorf("Error retrieving Key Vault ID from the Base URI %q: %+v", keyVaultUri, err) + } + + // now we have the key vault uri we can look up the ID + + d.Set("storage_account_id", d.Id()) + d.Set("key_vault_id", keyVaultId) + d.Set("key_name", keyName) + d.Set("key_version", keyVersion) + + return nil +} + +func resourceArmStorageAccountCustomerManagedKeyDelete(d *schema.ResourceData, meta interface{}) error { + storageClient := meta.(*clients.Client).Storage.AccountsClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + storageAccountId, err := storageParse.ParseAccountID(d.Id()) + if err != nil { + return err + } + + locks.ByName(storageAccountId.Name, storageAccountResourceName) + defer locks.UnlockByName(storageAccountId.Name, storageAccountResourceName) + + // confirm it still exists prior to trying to update it, else we'll get an error + storageAccount, err := storageClient.GetProperties(ctx, storageAccountId.ResourceGroup, storageAccountId.Name, "") + if err != nil { + if utils.ResponseWasNotFound(storageAccount.Response) { + return nil + } + + return fmt.Errorf("Error retrieving Storage Account %q (Resource Group %q): %+v", storageAccountId.Name, storageAccountId.ResourceGroup, err) + } + + // Since this isn't a real object, just modifying an existing object + // "Delete" doesn't really make sense it should really be a "Revert to Default" + // So instead of the Delete func actually deleting the Storage Account I am + // making it reset the Storage Account to it's default state + props := storage.AccountUpdateParameters{ + AccountPropertiesUpdateParameters: &storage.AccountPropertiesUpdateParameters{ + Encryption: &storage.Encryption{ + Services: &storage.EncryptionServices{ + Blob: &storage.EncryptionService{ + Enabled: utils.Bool(true), + }, + File: &storage.EncryptionService{ + Enabled: utils.Bool(true), + }, + }, + KeySource: storage.MicrosoftStorage, + }, + }, + } + + if _, err = storageClient.Update(ctx, storageAccountId.ResourceGroup, storageAccountId.Name, props); err != nil { + return fmt.Errorf("Error removing Customer Managed Key for Storage Account %q (Resource Group %q): %+v", storageAccountId.Name, storageAccountId.ResourceGroup, err) + } + + return nil +} diff --git a/azurerm/internal/services/storage/resource_arm_storage_account_network_rules.go b/azurerm/internal/services/storage/resource_arm_storage_account_network_rules.go index 7bbfda7558ce..2d8f676f3c88 100644 --- a/azurerm/internal/services/storage/resource_arm_storage_account_network_rules.go +++ b/azurerm/internal/services/storage/resource_arm_storage_account_network_rules.go @@ -17,8 +17,6 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) -var storageAccountResourceName = "azurerm_storage_account" - func resourceArmStorageAccountNetworkRules() *schema.Resource { return &schema.Resource{ Create: resourceArmStorageAccountNetworkRulesCreateUpdate, diff --git a/azurerm/internal/services/storage/tests/resource_arm_storage_account_customer_managed_key_test.go b/azurerm/internal/services/storage/tests/resource_arm_storage_account_customer_managed_key_test.go new file mode 100644 index 000000000000..8d4e45edc18e --- /dev/null +++ b/azurerm/internal/services/storage/tests/resource_arm_storage_account_customer_managed_key_test.go @@ -0,0 +1,278 @@ +package tests + +import ( + "fmt" + "net/http" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-04-01/storage" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccAzureRMStorageAccountCustomerManagedKey_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_storage_account_customer_managed_key", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMStorageAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageAccountCustomerManagedKey_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageAccountCustomerManagedKeyExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + // Delete the encryption settings resource and verify it is gone + Config: testAccAzureRMStorageAccountCustomerManagedKey_template(data), + Check: resource.ComposeTestCheckFunc( + // Then ensure the encryption settings on the storage account + // have been reverted to their default state + testCheckAzureRMStorageAccountExistsWithDefaultSettings("azurerm_storage_account.test"), + ), + }, + }, + }) +} + +func TestAccAzureRMStorageAccountCustomerManagedKey_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_storage_account_customer_managed_key", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMStorageAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageAccountCustomerManagedKey_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageAccountCustomerManagedKeyExists(data.ResourceName), + ), + }, + data.RequiresImportErrorStep(testAccAzureRMStorageAccountCustomerManagedKey_requiresImport), + }, + }) +} + +func TestAccAzureRMStorageAccountCustomerManagedKey_updateKey(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_storage_account_customer_managed_key", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMStorageAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageAccountCustomerManagedKey_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageAccountCustomerManagedKeyExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMStorageAccountCustomerManagedKey_updated(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageAccountCustomerManagedKeyExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func testCheckAzureRMStorageAccountExistsWithDefaultSettings(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + storageAccount := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + // Ensure resource group exists in API + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + conn := acceptance.AzureProvider.Meta().(*clients.Client).Storage.AccountsClient + + resp, err := conn.GetProperties(ctx, resourceGroup, storageAccount, "") + if err != nil { + return fmt.Errorf("Bad: Get on storageServiceClient: %+v", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: StorageAccount %q (resource group: %q) does not exist", storageAccount, resourceGroup) + } + + if props := resp.AccountProperties; props != nil { + if encryption := props.Encryption; encryption != nil { + if services := encryption.Services; services != nil { + if !*services.Blob.Enabled { + return fmt.Errorf("enable_blob_encryption not set to default: %s", resourceName) + } + if !*services.File.Enabled { + return fmt.Errorf("enable_file_encryption not set to default: %s", resourceName) + } + } + + if encryption.KeySource != storage.MicrosoftStorage { + return fmt.Errorf("%s keySource not set to default(storage.MicrosoftStorage): %s", resourceName, encryption.KeySource) + } + } else { + return fmt.Errorf("storage account encryption properties not found: %s", resourceName) + } + } + + return nil + } +} + +func testCheckAzureRMStorageAccountCustomerManagedKeyExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + if storageAccountId := rs.Primary.Attributes["storage_account_id"]; storageAccountId == "" { + return fmt.Errorf("Unable to read storageAccountId: %s", resourceName) + } + + return nil + } +} + +func testAccAzureRMStorageAccountCustomerManagedKey_basic(data acceptance.TestData) string { + template := testAccAzureRMStorageAccountCustomerManagedKey_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_storage_account_customer_managed_key" "test" { + storage_account_id = azurerm_storage_account.test.id + key_vault_id = azurerm_key_vault.test.id + key_name = azurerm_key_vault_key.first.name + key_version = azurerm_key_vault_key.first.version +} +`, template) +} + +func testAccAzureRMStorageAccountCustomerManagedKey_requiresImport(data acceptance.TestData) string { + template := testAccAzureRMStorageAccountCustomerManagedKey_basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_storage_account_customer_managed_key" "import" { + storage_account_id = azurerm_storage_account_customer_managed_key.test.storage_account_id + key_vault_id = azurerm_storage_account_customer_managed_key.test.key_vault_id + key_name = azurerm_storage_account_customer_managed_key.test.key_name + key_version = azurerm_storage_account_customer_managed_key.test.key_version +} +`, template) +} + +func testAccAzureRMStorageAccountCustomerManagedKey_updated(data acceptance.TestData) string { + template := testAccAzureRMStorageAccountCustomerManagedKey_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_key_vault_key" "second" { + name = "second" + key_vault_id = azurerm_key_vault.test.id + key_type = "RSA" + key_size = 2048 + key_opts = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"] + + depends_on = [ + azurerm_key_vault_access_policy.client, + azurerm_key_vault_access_policy.storage, + ] +} + +resource "azurerm_storage_account_customer_managed_key" "test" { + storage_account_id = azurerm_storage_account.test.id + key_vault_id = azurerm_key_vault.test.id + key_name = azurerm_key_vault_key.second.name + key_version = azurerm_key_vault_key.second.version +} +`, template) +} + +func testAccAzureRMStorageAccountCustomerManagedKey_template(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + } + } +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_key_vault" "test" { + name = "acctestkv%s" + 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 = "standard" + soft_delete_enabled = true + purge_protection_enabled = true +} + +resource "azurerm_key_vault_access_policy" "storage" { + key_vault_id = azurerm_key_vault.test.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = azurerm_storage_account.test.identity.0.principal_id + + key_permissions = ["get", "create", "list", "restore", "recover", "unwrapkey", "wrapkey", "purge", "encrypt", "decrypt", "sign", "verify"] + secret_permissions = ["get"] +} + +resource "azurerm_key_vault_access_policy" "client" { + key_vault_id = azurerm_key_vault.test.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = ["get", "create", "delete", "list", "restore", "recover", "unwrapkey", "wrapkey", "purge", "encrypt", "decrypt", "sign", "verify"] + secret_permissions = ["get"] +} + +resource "azurerm_key_vault_key" "first" { + name = "first" + key_vault_id = azurerm_key_vault.test.id + key_type = "RSA" + key_size = 2048 + key_opts = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"] + + depends_on = [ + azurerm_key_vault_access_policy.client, + azurerm_key_vault_access_policy.storage, + ] +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" + + identity { + type = "SystemAssigned" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomString) +} diff --git a/azurerm/internal/services/storage/tests/resource_arm_storage_account_test.go b/azurerm/internal/services/storage/tests/resource_arm_storage_account_test.go index 5e21e4e2761e..23811ddb0629 100644 --- a/azurerm/internal/services/storage/tests/resource_arm_storage_account_test.go +++ b/azurerm/internal/services/storage/tests/resource_arm_storage_account_test.go @@ -3,7 +3,6 @@ package tests import ( "fmt" "net/http" - "os" "regexp" "testing" @@ -194,68 +193,6 @@ func TestAccAzureRMStorageAccount_blobConnectionString(t *testing.T) { }) } -func TestAccAzureRMStorageAccount_blobEncryption(t *testing.T) { - _, exists := os.LookupEnv("TF_ACC_STORAGE_ENCRYPTION_DISABLE") - if !exists { - t.Skip("`TF_ACC_STORAGE_ENCRYPTION_DISABLE` isn't specified - skipping since disabling encryption is generally disabled") - } - data := acceptance.BuildTestData(t, "azurerm_storage_account", "test") - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acceptance.PreCheck(t) }, - Providers: acceptance.SupportedProviders, - CheckDestroy: testCheckAzureRMStorageAccountDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAzureRMStorageAccount_blobEncryption(data), - Check: resource.ComposeTestCheckFunc( - testCheckAzureRMStorageAccountExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "enable_blob_encryption", "true"), - ), - }, - data.ImportStep(), - { - Config: testAccAzureRMStorageAccount_blobEncryptionDisabled(data), - Check: resource.ComposeTestCheckFunc( - testCheckAzureRMStorageAccountExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "enable_blob_encryption", "false"), - ), - }, - }, - }) -} - -func TestAccAzureRMStorageAccount_fileEncryption(t *testing.T) { - _, exists := os.LookupEnv("TF_ACC_STORAGE_ENCRYPTION_DISABLE") - if !exists { - t.Skip("`TF_ACC_STORAGE_ENCRYPTION_DISABLE` isn't specified - skipping since disabling encryption is generally disabled") - } - data := acceptance.BuildTestData(t, "azurerm_storage_account", "test") - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acceptance.PreCheck(t) }, - Providers: acceptance.SupportedProviders, - CheckDestroy: testCheckAzureRMStorageAccountDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAzureRMStorageAccount_fileEncryption(data), - Check: resource.ComposeTestCheckFunc( - testCheckAzureRMStorageAccountExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "enable_file_encryption", "true"), - ), - }, - data.ImportStep(), - { - Config: testAccAzureRMStorageAccount_fileEncryptionDisabled(data), - Check: resource.ComposeTestCheckFunc( - testCheckAzureRMStorageAccountExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "enable_file_encryption", "false"), - ), - }, - }, - }) -} - func TestAccAzureRMStorageAccount_enableHttpsTrafficOnly(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_storage_account", "test") @@ -388,6 +325,7 @@ func TestAccAzureRMStorageAccount_fileStorageWithUpdate(t *testing.T) { }, }) } + func TestAccAzureRMStorageAccount_storageV2WithUpdate(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_storage_account", "test") @@ -805,98 +743,6 @@ resource "azurerm_storage_account" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString) } -func testAccAzureRMStorageAccount_blobEncryption(data acceptance.TestData) string { - return fmt.Sprintf(` -resource "azurerm_resource_group" "test" { - name = "acctestRG-storage-%d" - location = "%s" -} - -resource "azurerm_storage_account" "test" { - name = "unlikely23exst2acct%s" - resource_group_name = "${azurerm_resource_group.test.name}" - - location = "${azurerm_resource_group.test.location}" - account_tier = "Standard" - account_replication_type = "LRS" - enable_blob_encryption = true - - tags = { - environment = "production" - } -} -`, data.RandomInteger, data.Locations.Primary, data.RandomString) -} - -func testAccAzureRMStorageAccount_blobEncryptionDisabled(data acceptance.TestData) string { - return fmt.Sprintf(` -resource "azurerm_resource_group" "test" { - name = "acctestRG-storage-%d" - location = "%s" -} - -resource "azurerm_storage_account" "test" { - name = "unlikely23exst2acct%s" - resource_group_name = "${azurerm_resource_group.test.name}" - - location = "${azurerm_resource_group.test.location}" - account_tier = "Standard" - account_replication_type = "LRS" - enable_blob_encryption = false - - tags = { - environment = "production" - } -} -`, data.RandomInteger, data.Locations.Primary, data.RandomString) -} - -func testAccAzureRMStorageAccount_fileEncryption(data acceptance.TestData) string { - return fmt.Sprintf(` -resource "azurerm_resource_group" "test" { - name = "acctestRG-storage-%d" - location = "%s" -} - -resource "azurerm_storage_account" "test" { - name = "unlikely23exst2acct%s" - resource_group_name = "${azurerm_resource_group.test.name}" - - location = "${azurerm_resource_group.test.location}" - account_tier = "Standard" - account_replication_type = "LRS" - enable_file_encryption = true - - tags = { - environment = "production" - } -} -`, data.RandomInteger, data.Locations.Primary, data.RandomString) -} - -func testAccAzureRMStorageAccount_fileEncryptionDisabled(data acceptance.TestData) string { - return fmt.Sprintf(` -resource "azurerm_resource_group" "test" { - name = "acctestRG-storage-%d" - location = "%s" -} - -resource "azurerm_storage_account" "test" { - name = "unlikely23exst2acct%s" - resource_group_name = "${azurerm_resource_group.test.name}" - - location = "${azurerm_resource_group.test.location}" - account_tier = "Standard" - account_replication_type = "LRS" - enable_file_encryption = false - - tags = { - environment = "production" - } -} -`, data.RandomInteger, data.Locations.Primary, data.RandomString) -} - func testAccAzureRMStorageAccount_enableHttpsTrafficOnly(data acceptance.TestData) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/internal/services/storage/validate/storage_account.go b/azurerm/internal/services/storage/validate/storage_account.go new file mode 100644 index 000000000000..3fe3b6d2710b --- /dev/null +++ b/azurerm/internal/services/storage/validate/storage_account.go @@ -0,0 +1,22 @@ +package validate + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/storage/parsers" +) + +func StorageAccountID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + if _, err := parsers.ParseAccountID(v); err != nil { + errors = append(errors, fmt.Errorf("Can not parse %q as a resource id: %v", k, err)) + return + } + + return warnings, errors +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 13e50d7b1078..87265350dafe 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -2185,6 +2185,10 @@ azurerm_storage_account +
  • + azurerm_storage_account_customer_managed_key +
  • +
  • azurerm_storage_account_network_rules
  • diff --git a/website/docs/d/key_vault_key.html.markdown b/website/docs/d/key_vault_key.html.markdown index 8e9128f2ba81..d02dcc30b050 100644 --- a/website/docs/d/key_vault_key.html.markdown +++ b/website/docs/d/key_vault_key.html.markdown @@ -23,7 +23,7 @@ data "azurerm_key_vault_key" "example" { } output "key_type" { - value = data.azurerm_key_vault_secret.example.key_type + value = data.azurerm_key_vault_key.example.key_type } ``` diff --git a/website/docs/d/storage_account.html.markdown b/website/docs/d/storage_account.html.markdown index eec3c0a3686c..f20cdc38a725 100644 --- a/website/docs/d/storage_account.html.markdown +++ b/website/docs/d/storage_account.html.markdown @@ -43,18 +43,10 @@ output "storage_account_tier" { * `access_tier` - The access tier for `BlobStorage` accounts. -* `enable_blob_encryption` - Are Encryption Services are enabled for Blob storage? See [here](https://azure.microsoft.com/en-us/documentation/articles/storage-service-encryption/) - for more information. - -* `enable_file_encryption` - Are Encryption Services are enabled for File storage? See [here](https://azure.microsoft.com/en-us/documentation/articles/storage-service-encryption/) - for more information. - * `enable_https_traffic_only` - Is traffic only allowed via HTTPS? See [here](https://docs.microsoft.com/en-us/azure/storage/storage-require-secure-transfer/) for more information. - -* `is_hns_enabled` - Is Hierarchical Namespace enabled? -* `account_encryption_source` - The Encryption Source for this Storage Account. +* `is_hns_enabled` - Is Hierarchical Namespace enabled? * `custom_domain` - A `custom_domain` block as documented below. diff --git a/website/docs/r/key_vault.html.markdown b/website/docs/r/key_vault.html.markdown index e5ab3ed5c6e0..63353965902b 100644 --- a/website/docs/r/key_vault.html.markdown +++ b/website/docs/r/key_vault.html.markdown @@ -36,8 +36,8 @@ resource "azurerm_key_vault" "example" { sku_name = "standard" access_policy { - tenant_id = "d6e396d0-5584-41dc-9fc0-268df99bc610" - object_id = "d746815a-0433-4a21-b95d-fc437d2d475b" + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.service_principal_object_id key_permissions = [ "get", @@ -58,7 +58,7 @@ resource "azurerm_key_vault" "example" { } tags = { - environment = "Production" + environment = "Testing" } } ``` @@ -73,7 +73,7 @@ The following arguments are supported: * `resource_group_name` - (Required) The name of the resource group in which to create the Key Vault. Changing this forces a new resource to be created. -* `sku_name` - (Required) The Name of the SKU used for this Key Vault. Possible values are `standard` and `premium`. +* `sku_name` - (Optional) The Name of the SKU used for this Key Vault. Possible values are `standard` and `premium`. * `tenant_id` - (Required) The Azure Active Directory tenant ID that should be used for authenticating requests to the key vault. diff --git a/website/docs/r/storage_account.html.markdown b/website/docs/r/storage_account.html.markdown index 50da869e31bd..cc5b75f232f0 100644 --- a/website/docs/r/storage_account.html.markdown +++ b/website/docs/r/storage_account.html.markdown @@ -92,17 +92,11 @@ The following arguments are supported: * `access_tier` - (Optional) Defines the access tier for `BlobStorage`, `FileStorage` and `StorageV2` accounts. Valid options are `Hot` and `Cool`, defaults to `Hot`. -* `enable_blob_encryption` - (Optional) Boolean flag which controls if Encryption Services are enabled for Blob storage, see [here](https://azure.microsoft.com/en-us/documentation/articles/storage-service-encryption/) for more information. Defaults to `true`. - -* `enable_file_encryption` - (Optional) Boolean flag which controls if Encryption Services are enabled for File storage, see [here](https://azure.microsoft.com/en-us/documentation/articles/storage-service-encryption/) for more information. Defaults to `true`. - * `enable_https_traffic_only` - (Optional) Boolean flag which forces HTTPS if enabled, see [here](https://docs.microsoft.com/en-us/azure/storage/storage-require-secure-transfer/) for more information. Defaults to `true`. * `is_hns_enabled` - (Optional) Is Hierarchical Namespace enabled? This can be used with Azure Data Lake Storage Gen 2 ([see here for more information](https://docs.microsoft.com/en-us/azure/storage/blobs/data-lake-storage-quickstart-create-account/)). Changing this forces a new resource to be created. -* `account_encryption_source` - (Optional) The Encryption Source for this Storage Account. Possible values are `Microsoft.Keyvault` and `Microsoft.Storage`. Defaults to `Microsoft.Storage`. - * `custom_domain` - (Optional) A `custom_domain` block as documented below. * `identity` - (Optional) A `identity` block as defined below. diff --git a/website/docs/r/storage_account_customer_managed_key.html.markdown b/website/docs/r/storage_account_customer_managed_key.html.markdown new file mode 100644 index 000000000000..4ae7721d8e44 --- /dev/null +++ b/website/docs/r/storage_account_customer_managed_key.html.markdown @@ -0,0 +1,114 @@ +--- +subcategory: "Storage" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_storage_account_customer_managed_key" +description: |- + Manages a Customer Managed Key for a Storage Account. +--- + +# azurerm_storage_account_customer_managed_key + +Manages a Customer Managed Key for a Storage Account. + +## Example Usage + +```hcl + +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_key_vault" "example" { + name = "examplekv" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" + + soft_delete_enabled = true + purge_protection_enabled = true +} + +resource "azurerm_key_vault_access_policy" "storage" { + key_vault_id = azurerm_key_vault.example.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = azurerm_storage_account.example.identity.0.principal_id + + key_permissions = ["get", "create", "list", "restore", "recover", "unwrapkey", "wrapkey", "purge", "encrypt", "decrypt", "sign", "verify"] + secret_permissions = ["get"] +} + +resource "azurerm_key_vault_access_policy" "client" { + key_vault_id = azurerm_key_vault.example.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = ["get", "create", "delete", "list", "restore", "recover", "unwrapkey", "wrapkey", "purge", "encrypt", "decrypt", "sign", "verify"] + secret_permissions = ["get"] +} + + +resource "azurerm_key_vault_key" "example" { + name = "tfex-key" + key_vault_id = azurerm_key_vault.example.id + key_type = "RSA" + key_size = 2048 + key_opts = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"] + + depends_on = [ + azurerm_key_vault_access_policy.client, + azurerm_key_vault_access_policy.storage, + ] +} + + +resource "azurerm_storage_account" "example" { + name = "examplestor" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "GRS" + + identity { + type = "SystemAssigned" + } +} + +resource "azurerm_storage_account_customer_managed_key" "example" { + storage_account_id = azurerm_storage_account.example.id + key_vault_id = azurerm_key_vault.example.id + key_name = azurerm_key_vault_key.example.name + key_version = azurerm_key_vault_key.example.version +} +``` + +## Argument Reference + +The following arguments are supported: + +* `storage_account_id` - (Required) The ID of the Storage Account. Changing this forces a new resource to be created. + +* `key_vault_id` - (Required) The ID of the Key Vault. Changing this forces a new resource to be created. + +* `key_name` - (Required) The name of Key Vault Key. + +* `key_version` - (Required) The version of Key Vault Key. + +## Attributes Reference + +The following attributes are exported in addition to the arguments listed above: + +* `id` - The ID of the Storage Account. + +--- + +## Import + +Customer Managed Keys for a Storage Account can be imported using the `resource id` of the Storage Account, e.g. + +```shell +terraform import azurerm_storage_account_customer_managed_key.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myresourcegroup/providers/Microsoft.Storage/storageAccounts/myaccount +``` \ No newline at end of file