diff --git a/azurerm/internal/services/compute/client/client.go b/azurerm/internal/services/compute/client/client.go index 64b03a4b6496..c3484a722bd2 100644 --- a/azurerm/internal/services/compute/client/client.go +++ b/azurerm/internal/services/compute/client/client.go @@ -9,6 +9,7 @@ import ( type Client struct { AvailabilitySetsClient *compute.AvailabilitySetsClient DisksClient *compute.DisksClient + DiskEncryptionSetsClient *compute.DiskEncryptionSetsClient GalleriesClient *compute.GalleriesClient GalleryImagesClient *compute.GalleryImagesClient GalleryImageVersionsClient *compute.GalleryImageVersionsClient @@ -33,6 +34,9 @@ func NewClient(o *common.ClientOptions) *Client { disksClient := compute.NewDisksClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&disksClient.Client, o.ResourceManagerAuthorizer) + diskEncryptionSetsClient := compute.NewDiskEncryptionSetsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&diskEncryptionSetsClient.Client, o.ResourceManagerAuthorizer) + galleriesClient := compute.NewGalleriesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&galleriesClient.Client, o.ResourceManagerAuthorizer) @@ -81,6 +85,7 @@ func NewClient(o *common.ClientOptions) *Client { return &Client{ AvailabilitySetsClient: &availabilitySetsClient, DisksClient: &disksClient, + DiskEncryptionSetsClient: &diskEncryptionSetsClient, GalleriesClient: &galleriesClient, GalleryImagesClient: &galleryImagesClient, GalleryImageVersionsClient: &galleryImageVersionsClient, diff --git a/azurerm/internal/services/compute/data_source_disk_encryption_set.go b/azurerm/internal/services/compute/data_source_disk_encryption_set.go new file mode 100644 index 000000000000..970aaad76591 --- /dev/null +++ b/azurerm/internal/services/compute/data_source_disk_encryption_set.go @@ -0,0 +1,137 @@ +package compute + +import ( + "fmt" + "time" + + "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/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func dataSourceArmDiskEncryptionSet() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArmDiskEncryptionSetRead, + + 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{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "location": azure.SchemaLocationForDataSource(), + + "resource_group_name": azure.SchemaResourceGroupNameForDataSource(), + + "active_key": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key_url": { + Type: schema.TypeString, + Computed: true, + }, + "source_vault_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "identity": { + Type: schema.TypeList, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Computed: true, + }, + "principal_id": { + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "previous_keys": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key_url": { + Type: schema.TypeString, + Computed: true, + }, + "source_vault_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "tags": tags.SchemaDataSource(), + }, + } +} + +func dataSourceArmDiskEncryptionSetRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Compute.DiskEncryptionSetsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error: Disk Encryption Set %q (Resource Group %q) was not found", name, resourceGroup) + } + return fmt.Errorf("Error reading Disk Encryption Set %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.SetId(*resp.ID) + + d.Set("name", name) + d.Set("resource_group_name", resourceGroup) + if location := resp.Location; location != nil { + d.Set("location", azure.NormalizeLocation(*location)) + } + if encryptionSetProperties := resp.EncryptionSetProperties; encryptionSetProperties != nil { + if err := d.Set("active_key", flattenArmDiskEncryptionSetKeyVaultAndKeyReference(encryptionSetProperties.ActiveKey)); err != nil { + return fmt.Errorf("Error setting `active_key`: %+v", err) + } + if err := d.Set("previous_keys", flattenArmDiskEncryptionSetKeyVaultAndKeyReferenceArray(encryptionSetProperties.PreviousKeys)); err != nil { + return fmt.Errorf("Error setting `previous_keys`: %+v", err) + } + } + if identity := resp.Identity; identity != nil { + if err := d.Set("identity", flattenArmDiskEncryptionSetIdentity(identity)); err != nil { + return fmt.Errorf("Error setting `identity`: %+v", err) + } + } + + return nil +} diff --git a/azurerm/internal/services/compute/registration.go b/azurerm/internal/services/compute/registration.go index bddae3dec966..d77a4693dacd 100644 --- a/azurerm/internal/services/compute/registration.go +++ b/azurerm/internal/services/compute/registration.go @@ -16,6 +16,7 @@ func (r Registration) Name() string { func (r Registration) SupportedDataSources() map[string]*schema.Resource { return map[string]*schema.Resource{ "azurerm_availability_set": dataSourceArmAvailabilitySet(), + "azurerm_disk_encryption_set": dataSourceArmDiskEncryptionSet(), "azurerm_managed_disk": dataSourceArmManagedDisk(), "azurerm_image": dataSourceArmImage(), "azurerm_platform_image": dataSourceArmPlatformImage(), @@ -31,6 +32,7 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource { func (r Registration) SupportedResources() map[string]*schema.Resource { resources := map[string]*schema.Resource{ "azurerm_availability_set": resourceArmAvailabilitySet(), + "azurerm_disk_encryption_set": resourceArmDiskEncryptionSet(), "azurerm_image": resourceArmImage(), "azurerm_managed_disk": resourceArmManagedDisk(), "azurerm_marketplace_agreement": resourceArmMarketplaceAgreement(), diff --git a/azurerm/internal/services/compute/resource_arm_disk_encryption_set.go b/azurerm/internal/services/compute/resource_arm_disk_encryption_set.go new file mode 100644 index 000000000000..ee99beabe978 --- /dev/null +++ b/azurerm/internal/services/compute/resource_arm_disk_encryption_set.go @@ -0,0 +1,387 @@ +package compute + +import ( + "context" + "fmt" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute" + "github.com/hashicorp/go-azure-helpers/response" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "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/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmDiskEncryptionSet() *schema.Resource { + return &schema.Resource{ + Create: resourceArmDiskEncryptionSetCreateUpdate, + Read: resourceArmDiskEncryptionSetRead, + Update: resourceArmDiskEncryptionSetCreateUpdate, + Delete: resourceArmDiskEncryptionSetDelete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(60 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(60 * time.Minute), + Delete: schema.DefaultTimeout(60 * time.Minute), + }, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: ValidateDiskEncryptionSetName, + }, + + "location": azure.SchemaLocation(), + + "resource_group_name": azure.SchemaResourceGroupName(), + + "active_key": { + Type: schema.TypeList, + Required: true, + // the forceNew is enabled because currently key rotation is not supported, you cannot change the key vault and key associated with disk encryption set. + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key_url": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "source_vault_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: azure.ValidateResourceID, + }, + }, + }, + }, + + "identity": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + string(compute.SystemAssigned), + }, false), + Default: string(compute.SystemAssigned), + }, + "principal_id": { + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "previous_keys": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key_url": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "source_vault_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + }, + }, + }, + + "tags": tags.Schema(), + }, + } +} + +func resourceArmDiskEncryptionSetCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Compute.DiskEncryptionSetsClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for present of existing Disk Encryption Set %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_disk_encryption_set", *existing.ID) + } + } + + location := azure.NormalizeLocation(d.Get("location").(string)) + activeKey := d.Get("active_key").([]interface{}) + t := d.Get("tags").(map[string]interface{}) + + diskEncryptionSet := compute.DiskEncryptionSet{ + Location: utils.String(location), + EncryptionSetProperties: &compute.EncryptionSetProperties{ + ActiveKey: expandArmDiskEncryptionSetKeyVaultAndKeyReference(activeKey), + }, + Tags: tags.Expand(t), + } + + if v, ok := d.GetOk("identity"); ok { + diskEncryptionSet.Identity = expandArmDiskEncryptionSetIdentity(v.([]interface{})) + } else { + diskEncryptionSet.Identity = &compute.EncryptionSetIdentity{ + Type: compute.SystemAssigned, + } + } + + // validate whether the keyvault has soft-delete and purge-protection enabled + err := validateKeyVaultAndKey(ctx, meta, resourceGroup, diskEncryptionSet.ActiveKey) + if err != nil { + return fmt.Errorf("Error creating Disk Encryption Set %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + future, err := client.CreateOrUpdate(ctx, resourceGroup, name, diskEncryptionSet) + if err != nil { + return fmt.Errorf("Error creating Disk Encryption Set %q (Resource Group %q): %+v", name, resourceGroup, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for creation of Disk Encryption Set %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Error retrieving Disk Encryption Set %q (Resource Group %q): %+v", name, resourceGroup, err) + } + if resp.ID == nil { + return fmt.Errorf("Cannot read Disk Encryption Set %q (Resource Group %q) ID", name, resourceGroup) + } + d.SetId(*resp.ID) + + return resourceArmDiskEncryptionSetRead(d, meta) +} + +func validateKeyVaultAndKey(ctx context.Context, meta interface{}, resourceGroup string, keyVaultAndKey *compute.KeyVaultAndKeyReference) error { + armClient := meta.(*clients.Client) + if keyVaultAndKey == nil { + return nil + } + if keyVault := keyVaultAndKey.SourceVault; keyVault != nil { + if keyVaultId := keyVault.ID; keyVaultId != nil { + client := armClient.KeyVault.VaultsClient + parsedId, err := azure.ParseAzureResourceID(*keyVaultId) + if err != nil { + return fmt.Errorf("Error parsing ID for keyvault in Disk Encryption Set: %+v", err) + } + keyVaultName := parsedId.Path["name"] + log.Printf("[INFO] Keyvault name input in Disk Encryption Set: %s", keyVaultName) + resp, err := client.Get(ctx, resourceGroup, keyVaultName) + if err != nil { + return fmt.Errorf("Error reading keyvault %q (Resource Group %q): %+v", keyVaultName, resourceGroup, err) + } + if props := resp.Properties; props != nil { + if softDelete := props.EnableSoftDelete; softDelete != nil { + if !*softDelete { + return fmt.Errorf("the keyvault in Disk Encryption Set must enable soft delete") + } + } + if purgeProtection := props.EnablePurgeProtection; purgeProtection != nil { + if !*purgeProtection { + return fmt.Errorf("the keyvault in Disk Encryption Set must enable purge protection") + } + } + } + } + } + return nil +} + +func resourceArmDiskEncryptionSetRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Compute.DiskEncryptionSetsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + name := id.Path["diskEncryptionSets"] + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Disk Encryption Set %q does not exist - removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error reading Disk Encryption Set %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.Set("name", name) + d.Set("resource_group_name", resourceGroup) + if location := resp.Location; location != nil { + d.Set("location", azure.NormalizeLocation(*location)) + } + if encryptionSetProperties := resp.EncryptionSetProperties; encryptionSetProperties != nil { + if err := d.Set("active_key", flattenArmDiskEncryptionSetKeyVaultAndKeyReference(encryptionSetProperties.ActiveKey)); err != nil { + return fmt.Errorf("Error setting `active_key`: %+v", err) + } + if err := d.Set("previous_keys", flattenArmDiskEncryptionSetKeyVaultAndKeyReferenceArray(encryptionSetProperties.PreviousKeys)); err != nil { + return fmt.Errorf("Error setting `previous_keys`: %+v", err) + } + } + if identity := resp.Identity; identity != nil { + if err := d.Set("identity", flattenArmDiskEncryptionSetIdentity(identity)); err != nil { + return fmt.Errorf("Error setting `identity`: %+v", err) + } + } + + return tags.FlattenAndSet(d, resp.Tags) +} + +func resourceArmDiskEncryptionSetDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Compute.DiskEncryptionSetsClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + name := id.Path["diskEncryptionSets"] + + future, err := client.Delete(ctx, resourceGroup, name) + if err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + return fmt.Errorf("Error deleting Disk Encryption Set %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + if !response.WasNotFound(future.Response()) { + return fmt.Errorf("Error waiting for deleting Disk Encryption Set %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + return nil +} + +func expandArmDiskEncryptionSetKeyVaultAndKeyReference(input []interface{}) *compute.KeyVaultAndKeyReference { + if len(input) == 0 { + return nil + } + v := input[0].(map[string]interface{}) + + sourceVaultId := v["source_vault_id"].(string) + keyUrl := v["key_url"].(string) + + result := compute.KeyVaultAndKeyReference{ + KeyURL: utils.String(keyUrl), + SourceVault: &compute.SourceVault{ + ID: utils.String(sourceVaultId), + }, + } + return &result +} + +func expandArmDiskEncryptionSetIdentity(input []interface{}) *compute.EncryptionSetIdentity { + if len(input) == 0 { + return nil + } + v := input[0].(map[string]interface{}) + + t := v["type"].(string) + result := compute.EncryptionSetIdentity{ + Type: compute.DiskEncryptionSetIdentityType(t), + } + + return &result +} + +func flattenArmDiskEncryptionSetKeyVaultAndKeyReference(input *compute.KeyVaultAndKeyReference) []interface{} { + if input == nil { + return make([]interface{}, 0) + } + + result := make(map[string]interface{}) + + if keyUrl := input.KeyURL; keyUrl != nil { + result["key_url"] = *keyUrl + } + if sourceVault := input.SourceVault; sourceVault != nil { + if sourceVaultId := sourceVault.ID; sourceVaultId != nil { + result["source_vault_id"] = *sourceVaultId + } + } + + return []interface{}{result} +} + +func flattenArmDiskEncryptionSetKeyVaultAndKeyReferenceArray(input *[]compute.KeyVaultAndKeyReference) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + for _, item := range *input { + v := make(map[string]interface{}) + + if sourceVault := item.SourceVault; sourceVault != nil { + if sourceVaultId := sourceVault.ID; sourceVaultId != nil { + v["source_vault_id"] = *sourceVaultId + } + } + + results = append(results, v) + } + + return results +} + +func flattenArmDiskEncryptionSetIdentity(input *compute.EncryptionSetIdentity) []interface{} { + if input == nil { + return make([]interface{}, 0) + } + + result := make(map[string]interface{}) + + result["type"] = string(input.Type) + if principalId := input.PrincipalID; principalId != nil { + result["principal_id"] = *principalId + } + if tenantId := input.TenantID; tenantId != nil { + result["tenant_id"] = *tenantId + } + return []interface{}{result} +} diff --git a/azurerm/internal/services/compute/tests/data_source_disk_encryption_set_test.go b/azurerm/internal/services/compute/tests/data_source_disk_encryption_set_test.go new file mode 100644 index 000000000000..2bb02678266c --- /dev/null +++ b/azurerm/internal/services/compute/tests/data_source_disk_encryption_set_test.go @@ -0,0 +1,65 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" +) + +func TestAccDataSourceAzureRMDiskEncryptionSet_basic(t *testing.T) { + dataSourceName := "data.azurerm_disk_encryption_set.test" + ri := tf.AccRandTimeInt() + rs := acctest.RandString(6) + resourceGroup := fmt.Sprintf("acctestRG-%d", ri) + vaultName := fmt.Sprintf("vault%d", ri) + keyName := fmt.Sprintf("key-%s", rs) + desName := fmt.Sprintf("acctestdes-%d", ri) + location := acceptance.Location() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMDiskEncryptionSetDestroy, + Steps: []resource.TestStep{ + // These two steps are used for setting up a valid keyVault, which enables soft-delete and purge protection. + { + Config: testAccPrepareKeyvaultAndKey(resourceGroup, location, vaultName, keyName), + Destroy: false, + Check: resource.ComposeTestCheckFunc(), + }, + // This step is not negligible, without this step, the final step will fail on refresh complaining `Disk Encryption Set does not exist` + { + PreConfig: func() { enableSoftDeleteAndPurgeProtectionForKeyvault(resourceGroup, vaultName) }, + Config: testAccAzureRMDiskEncryptionSet_basic(resourceGroup, location, vaultName, keyName, desName), + }, + { + Config: testAccDataSourceDiskEncryptionSet_basic(resourceGroup, location, vaultName, keyName, desName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "identity.#", "1"), + resource.TestCheckResourceAttr(dataSourceName, "identity.0.type", "SystemAssigned"), + resource.TestCheckResourceAttrSet(dataSourceName, "identity.0.principal_id"), + resource.TestCheckResourceAttrSet(dataSourceName, "identity.0.tenant_id"), + resource.TestCheckResourceAttr(dataSourceName, "active_key.#", "1"), + resource.TestCheckResourceAttrSet(dataSourceName, "active_key.0.source_vault_id"), + resource.TestCheckResourceAttrSet(dataSourceName, "active_key.0.key_url"), + ), + }, + }, + }) +} + +func testAccDataSourceDiskEncryptionSet_basic(resourceGroup, location, vaultName, keyName, desName string) string { + config := testAccAzureRMDiskEncryptionSet_basic(resourceGroup, location, vaultName, keyName, desName) + return fmt.Sprintf(` +%s + +data "azurerm_disk_encryption_set" "test" { + resource_group_name = "${azurerm_disk_encryption_set.test.resource_group_name}" + name = "${azurerm_disk_encryption_set.test.name}" +} +`, config) +} diff --git a/azurerm/internal/services/compute/tests/resource_arm_disk_encryption_set_test.go b/azurerm/internal/services/compute/tests/resource_arm_disk_encryption_set_test.go new file mode 100644 index 000000000000..8382261c9580 --- /dev/null +++ b/azurerm/internal/services/compute/tests/resource_arm_disk_encryption_set_test.go @@ -0,0 +1,243 @@ +package tests + +import ( + "fmt" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "log" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2018-02-14/keyvault" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMDiskEncryptionSet_basic(t *testing.T) { + resourceName := "azurerm_disk_encryption_set.test" + ri := tf.AccRandTimeInt() + rs := acctest.RandString(6) + resourceGroup := fmt.Sprintf("acctestRG-%d", ri) + vaultName := fmt.Sprintf("vault%d", ri) + keyName := fmt.Sprintf("key-%s", rs) + desName := fmt.Sprintf("acctestdes-%d", ri) + location := acceptance.Location() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMDiskEncryptionSetDestroy, + Steps: []resource.TestStep{ + // This test step is temporary due to freezing of functions in keyVault. + // After applying soft-delete and purge-protection in keyVault, this extra step can be removed. + { + Config: testAccPrepareKeyvaultAndKey(resourceGroup, location, vaultName, keyName), + Destroy: false, + Check: resource.ComposeTestCheckFunc(), + }, + { + PreConfig: func() { enableSoftDeleteAndPurgeProtectionForKeyvault(resourceGroup, vaultName) }, + Config: testAccAzureRMDiskEncryptionSet_basic(resourceGroup, location, vaultName, keyName, desName), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDiskEncryptionSetExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "active_key.0.source_vault_id"), + resource.TestCheckResourceAttrSet(resourceName, "active_key.0.key_url"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testCheckAzureRMDiskEncryptionSetExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Disk Encryption Set not found: %s", resourceName) + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + client := acceptance.AzureProvider.Meta().(*clients.Client).Compute.DiskEncryptionSetsClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + if resp, err := client.Get(ctx, resourceGroup, name); err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Disk Encryption Set %q (Resource Group %q) does not exist", name, resourceGroup) + } + return fmt.Errorf("Bad: Get on Compute.DiskEncryptionSetsClient: %+v", err) + } + + return nil + } +} + +func testCheckAzureRMDiskEncryptionSetDestroy(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Compute.DiskEncryptionSetsClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_disk_encryption_set" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + if resp, err := client.Get(ctx, resourceGroup, name); err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Get on Compute.DiskEncryptionSetsClient: %+v", err) + } + } + + return nil + } + + return nil +} + +func enableSoftDeleteAndPurgeProtectionForKeyvault(resourceGroup, vaultName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + armClient := acceptance.AzureProvider.Meta().(*clients.Client) + client := armClient.KeyVault.VaultsClient + ctx := armClient.StopContext + vaultPatch := keyvault.VaultPatchParameters{ + Properties: &keyvault.VaultPatchProperties{ + EnableSoftDelete: utils.Bool(true), + EnablePurgeProtection: utils.Bool(true), + }, + } + log.Printf("[LOG] Updating") + _, err := client.Update(ctx, resourceGroup, vaultName, vaultPatch) + if err != nil { + return fmt.Errorf("Bad: error when updating Keyvault %q (Resource Group %q): %+v", vaultName, resourceGroup, err) + } + return nil + } +} + +func testAccPrepareKeyvaultAndKey(resourceGroup, location, vaultName, keyName string) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "%s" + location = "%s" +} + +resource "azurerm_key_vault" "test" { + name = "%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 = "premium" + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.service_principal_object_id + + key_permissions = [ + "create", + "delete", + "get", + "update", + ] + + secret_permissions = [ + "get", + "delete", + "set", + ] + } +} + +resource "azurerm_key_vault_key" "test" { + name = "%s" + key_vault_id = azurerm_key_vault.test.id + key_type = "RSA" + key_size = 2048 + + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] +} +`, resourceGroup, location, vaultName, keyName) +} + +func testAccAzureRMDiskEncryptionSet_basic(resourceGroup, location, vaultName, keyName, desName string) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "%s" + location = "%s" +} + +resource "azurerm_key_vault" "test" { + name = "%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 = "premium" + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.service_principal_object_id + + key_permissions = [ + "create", + "delete", + "get", + "update", + ] + + secret_permissions = [ + "get", + "delete", + "set", + ] + } +} + +resource "azurerm_key_vault_key" "test" { + name = "%s" + key_vault_id = azurerm_key_vault.test.id + key_type = "RSA" + key_size = 2048 + + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] +} + +resource "azurerm_disk_encryption_set" "test" { + name = "%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + + active_key { + source_vault_id = azurerm_key_vault.test.id + key_url = azurerm_key_vault_key.test.id + } +} +`, resourceGroup, location, vaultName, keyName, desName) +} diff --git a/azurerm/internal/services/compute/validation.go b/azurerm/internal/services/compute/validation.go index c0b3309b716c..c4e42be3cdba 100644 --- a/azurerm/internal/services/compute/validation.go +++ b/azurerm/internal/services/compute/validation.go @@ -76,3 +76,18 @@ func validateName(maxLength int) func(i interface{}, k string) (warnings []strin return } } + +func ValidateDiskEncryptionSetName(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %s to be string", k)) + return + } + + // Supported characters for the name are a-z, A-Z, 0-9 and _. The maximum name length is 80 characters. + // despite the swagger says the constraint above, but a name that contains hyphens will also pass. + if matched := regexp.MustCompile(`^[a-zA-Z0-9_-]{1,80}$`).Match([]byte(v)); !matched { + errors = append(errors, fmt.Errorf("%s must be between 1 - 80 characters long, and contains only a-z, A-Z, 0-9 and _", k)) + } + return +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 42c01b5ff468..f9a1552186f7 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -178,6 +178,10 @@ azurerm_dev_test_virtual_network +
  • + azurerm_disk_encryption_set +
  • +
  • azurerm_eventhub_namespace
  • @@ -824,6 +828,10 @@ azurerm_availability_set +
  • + azurerm_disk_encryption_set +
  • +
  • azurerm_image
  • diff --git a/website/docs/d/disk_encryption_set.html.markdown b/website/docs/d/disk_encryption_set.html.markdown new file mode 100644 index 000000000000..ca00bfc0a2d0 --- /dev/null +++ b/website/docs/d/disk_encryption_set.html.markdown @@ -0,0 +1,66 @@ +--- +subcategory: "Compute" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_disk_encryption_set" +sidebar_current: "docs-azurerm-datasource-disk-encryption-set" +description: |- + Gets information about an existing Disk Encryption Set +--- + +# Data Source: azurerm_disk_encryption_set + +Use this data source to access information about an existing Disk Encryption Set. + + + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the Disk Encryption Set exists. + +* `resource_group_name` - (Required) The name of the Resource Group where the Disk Encryption Set exists. + + +## Attributes Reference + +The following attributes are exported: + +* `id` - Resource Id + +* `location` - Resource location + +* `active_key` - One `active_key` block defined below. + +* `identity` - A `identity` block defined below. + +* `previous_keys` - One or more `previous_key` block defined below. + +* `tags` - Resource tags + + +--- + +The `active_key` block contains the following: + +* `source_vault_id` - The resource id of the KeyVault containing the key or secret which the Disk Encryption Set is using. + +* `key_url` - The URL pointing to a key or secret in KeyVault. + +--- + +The `identity` block contains the following: + +* `type` - The type of Managed Service Identity used by this Disk Encryption Set. Only SystemAssigned is supported. + +* `principal_id` - The object ID of the Managed Service Identity created by Azure. + +* `tenant_id` - The tenant ID of the Managed Service Identity created by Azure. + +--- + +The `previous_key` block contains the following: + +* `source_vault_id` - The resource id of the KeyVault containing the key or secret which the Disk Encryption Set is using. + +* `key_url` - The URL pointing to a key or secret in KeyVault. diff --git a/website/docs/r/disk_encryption_set.html.markdown b/website/docs/r/disk_encryption_set.html.markdown new file mode 100644 index 000000000000..585bb8e3050b --- /dev/null +++ b/website/docs/r/disk_encryption_set.html.markdown @@ -0,0 +1,140 @@ +--- +subcategory: "Compute" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_disk_encryption_set" +sidebar_current: "docs-azurerm-resource-disk-encryption-set" +description: |- + Manage Azure DiskEncryptionSet instance. +--- + +# azurerm_disk_encryption_set + +Manage an Azure DiskEncryptionSet instance. + +-> **NOTE:** The DiskEncryptionSet service is currently in public preview and are only available in a limited set of regions. + +## Example Usage + +```hcl +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "des-example-rg" + location = "westus2" +} + +resource "azurerm_key_vault" "test" { + name = "des-example-keyvault" + 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.service_principal_object_id + + key_permissions = [ + "create", + "get", + "delete", + "list", + "wrapkey", + "unwrapkey", + "get", + ] + + secret_permissions = [ + "get", + "delete", + "set", + ] + } +} + +resource "azurerm_key_vault_key" "test" { + name = "des-example-key" + key_vault_id = azurerm_key_vault.test.id + key_type = "RSA" + key_size = 2048 + + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] +} + +resource "azurerm_disk_encryption_set" "test" { + name = "des" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + + active_key { + source_vault_id = azurerm_key_vault.test.id + key_url = azurerm_key_vault_key.test.id + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the disk encryption set that is being created. The name can't be changed after the disk encryption set is created. Supported characters for the name are a-z, A-Z, 0-9, _ and -. The maximum name length is 80 characters. Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) Specifies the name of the Resource Group in which the Disk Encryption Set should exist. Changing this forces a new resource to be created. + +* `location` - (Required) Specifies the Azure Region where the Disk Encryption Set exists. Changing this forces a new resource to be created. + +* `active_key` - (Required) One `active_key` block defined below. + +* `identity` - (Optional) A `identity` block defined below. + +* `tags` - (Optional) A mapping of tags to assign to the Disk Encryption Set. + +--- + +The `active_key` block supports the following: + +* `source_vault_id` - (Required) Specifies the resource ID of the KeyVault containing the key or secret. + +* `key_url` - (Required) Specifies the URL pointing to a key or secret in KeyVault. + +-> **NOTE** Access to the KeyVault must be granted for this Disk Encryption Set, if you want to further use this Disk Encryption Set in a Managed Disk or Virtual Machine, or Virtual Machine Scale Set. For instructions, please refer to the doc of [Server side encryption of Azure managed disks](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/disk-encryption). + +## Attributes Reference + +The following attributes are exported: + +* `previous_keys` - One or more `previous_key` block defined below. + +* `id` - The ID of the Disk Encryption Set. + +--- + +A `identity` block supports the following: + +* `type` - (Required) The Managed Service Identity Type of this Disk Encryption Set. The possible value is `SystemAssigned` (where Azure will generate a Service Principal for you). + +~> **NOTE:** When `type` is set to `SystemAssigned`, identity the Principal ID can be retrieved after the Disk Encryption Set has been created. See [documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview) for additional information. + +--- + +The `previous_key` block contains the following: + +* `source_vault_id` - (Required) Specifies the resource ID of the KeyVault containing the key or secret. + +* `key_url` - (Required) Specifies the URL pointing to a key or secret in KeyVault. + +## Import + +Disk Encryption Set can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_disk_encryption_set.test /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Compute/diskEncryptionSets/des1 +``` \ No newline at end of file