diff --git a/internal/services/automation/automation_account_resource.go b/internal/services/automation/automation_account_resource.go index 7f300f2f5a4e..4ed67afa2db9 100644 --- a/internal/services/automation/automation_account_resource.go +++ b/internal/services/automation/automation_account_resource.go @@ -6,13 +6,17 @@ import ( "time" "github.com/hashicorp/go-azure-helpers/lang/response" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" "github.com/hashicorp/go-azure-helpers/resourcemanager/identity" "github.com/hashicorp/go-azure-helpers/resourcemanager/location" "github.com/hashicorp/go-azure-sdk/resource-manager/automation/2021-06-22/automationaccount" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" "github.com/hashicorp/terraform-provider-azurerm/internal/services/automation/validate" + keyVaultParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" + keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tags" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" @@ -58,6 +62,43 @@ func resourceAutomationAccount() *pluginsdk.Resource { "identity": commonschema.SystemAssignedUserAssignedIdentityOptional(), + "encryption": { + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*schema.Schema{ + "user_assigned_identity_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: commonids.ValidateUserAssignedIdentityID, + }, + + "key_source": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice( + automationaccount.PossibleValuesForEncryptionKeySourceType(), + false, + ), + }, + + "key_vault_key_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: keyVaultValidate.NestedItemIdWithOptionalVersion, + }, + }, + }, + }, + + "local_authentication_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + "tags": tags.Schema(), "dsc_server_endpoint": { @@ -114,6 +155,17 @@ func resourceAutomationAccountCreate(d *pluginsdk.ResourceData, meta interface{} }, Location: utils.String(location.Normalize(d.Get("location").(string))), } + + if localAuth := d.Get("local_authentication_enabled").(bool); localAuth == false { + parameters.Properties.DisableLocalAuth = utils.Bool(true) + } + if encryption := d.Get("encryption").([]interface{}); len(encryption) > 0 { + enc, err := expandEncryption(encryption[0].(map[string]interface{})) + if err != nil { + return fmt.Errorf("expanding `encryption`: %v", err) + } + parameters.Properties.Encryption = enc + } // for create account do not set identity property (even TypeNone is not allowed), or api will response error if identityVal.Type != identity.TypeNone { parameters.Identity = identityVal @@ -154,6 +206,18 @@ func resourceAutomationAccountUpdate(d *pluginsdk.ResourceData, meta interface{} Identity: identity, } + if localAuth := d.Get("local_authentication_enabled").(bool); localAuth == false { + parameters.Properties.DisableLocalAuth = utils.Bool(true) + } + + if encryption := d.Get("encryption").([]interface{}); len(encryption) > 0 { + enc, err := expandEncryption(encryption[0].(map[string]interface{})) + if err != nil { + return fmt.Errorf("expanding `encryption`: %v", err) + } + parameters.Properties.Encryption = enc + } + if tagsVal := expandTags(d.Get("tags").(map[string]interface{})); tagsVal != nil { parameters.Tags = &tagsVal } @@ -217,6 +281,16 @@ func resourceAutomationAccountRead(d *pluginsdk.ResourceData, meta interface{}) } d.Set("sku_name", skuName) + localAuthEnabled := true + if val := prop.DisableLocalAuth; val != nil && *val == true { + localAuthEnabled = false + } + d.Set("local_authentication_enabled", localAuthEnabled) + + if err := d.Set("encryption", flattenEncryption(prop.Encryption)); err != nil { + return fmt.Errorf("setting `encryption`: %+v", err) + } + d.Set("dsc_server_endpoint", keysResp.Endpoint) if keys := keysResp.Keys; keys != nil { d.Set("dsc_primary_access_key", keys.Primary) @@ -258,3 +332,51 @@ func resourceAutomationAccountDelete(d *pluginsdk.ResourceData, meta interface{} return nil } + +func expandEncryption(encMap map[string]interface{}) (*automationaccount.EncryptionProperties, error) { + var id interface{} + id, ok := encMap["user_assigned_identity_id"].(string) + if !ok { + return nil, fmt.Errorf("read encryption user identity id error") + } + prop := &automationaccount.EncryptionProperties{ + Identity: &automationaccount.EncryptionPropertiesIdentity{ + UserAssignedIdentity: &id, + }, + } + if val, ok := encMap["key_source"].(string); ok && val != "" { + prop.KeySource = (*automationaccount.EncryptionKeySourceType)(&val) + } + if keyIdStr := encMap["key_vault_key_id"].(string); keyIdStr != "" { + keyId, err := keyVaultParse.ParseOptionallyVersionedNestedItemID(keyIdStr) + if err != nil { + return nil, err + } + prop.KeyVaultProperties = &automationaccount.KeyVaultProperties{ + KeyName: utils.String(keyId.Name), + KeyVersion: utils.String(keyId.Version), + KeyvaultUri: utils.String(keyId.KeyVaultBaseUrl), + } + } + return prop, nil +} + +func flattenEncryption(encryption *automationaccount.EncryptionProperties) (res []interface{}) { + if encryption == nil { + return + } + item := map[string]interface{}{} + if encryption.KeySource != nil { + item["key_source"] = (string)(*encryption.KeySource) + } + if encryption.Identity != nil && encryption.Identity.UserAssignedIdentity != nil { + item["user_assigned_identity_id"] = (*encryption.Identity.UserAssignedIdentity).(string) + } + if keyProp := encryption.KeyVaultProperties; keyProp != nil { + keyVaultKeyId, err := keyVaultParse.NewNestedItemID(*keyProp.KeyvaultUri, "keys", *keyProp.KeyName, *keyProp.KeyVersion) + if err == nil { + item["key_vault_key_id"] = keyVaultKeyId.ID() + } + } + return []interface{}{item} +} diff --git a/internal/services/automation/automation_account_resource_test.go b/internal/services/automation/automation_account_resource_test.go index 7a71212ace81..bbf395aefceb 100644 --- a/internal/services/automation/automation_account_resource_test.go +++ b/internal/services/automation/automation_account_resource_test.go @@ -70,6 +70,24 @@ func TestAccAutomationAccount_complete(t *testing.T) { }) } +func TestAccAutomationAccount_encryption(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_automation_account", "test") + r := AutomationAccountResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.encryption(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("sku_name").HasValue("Basic"), + check.That(data.ResourceName).Key("local_authentication_enabled").HasValue("false"), + check.That(data.ResourceName).Key("encryption.0.key_source").HasValue("Microsoft.Keyvault"), + ), + }, + data.ImportStep(), + }) +} + func TestAccAutomationAccount_identityUpdate(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_automation_account", "test") r := AutomationAccountResource{} @@ -257,6 +275,123 @@ resource "azurerm_automation_account" "test" { `, data.RandomInteger, data.Locations.Primary) } +func (AutomationAccountResource) encryption(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + } +} + +data "azurerm_client_config" "current" { +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-auto-%[1]d" + location = "%[2]s" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acctestUAI-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_key_vault" "test" { + name = "vault%[1]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 = "standard" + soft_delete_retention_days = 7 + purge_protection_enabled = true + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + certificate_permissions = [ + "ManageContacts", + ] + + key_permissions = [ + "Create", + "Get", + "List", + "Delete", + "Purge", + ] + + secret_permissions = [ + "Set", + ] + } + + access_policy { + tenant_id = azurerm_user_assigned_identity.test.tenant_id + object_id = azurerm_user_assigned_identity.test.principal_id + + certificate_permissions = [] + + key_permissions = [ + "Get", + "Recover", + "WrapKey", + "UnwrapKey", + ] + + secret_permissions = [] + } +} + +data "azurerm_key_vault" "test" { + name = azurerm_key_vault.test.name + resource_group_name = azurerm_key_vault.test.resource_group_name +} + +resource "azurerm_key_vault_key" "test" { + name = "acckvkey-%[1]d" + key_vault_id = azurerm_key_vault.test.id + key_type = "RSA" + key_size = 2048 + + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] +} + +resource "azurerm_automation_account" "test" { + name = "acctest-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku_name = "Basic" + + identity { + type = "UserAssigned" + identity_ids = [ + azurerm_user_assigned_identity.test.id + ] + } + + local_authentication_enabled = false + + encryption { + key_source = "Microsoft.Keyvault" + user_assigned_identity_id = azurerm_user_assigned_identity.test.id + key_vault_key_id = azurerm_key_vault_key.test.id + } +} +`, data.RandomInteger, data.Locations.Primary) +} + func (AutomationAccountResource) userAssignedIdentity(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/website/docs/r/automation_account.html.markdown b/website/docs/r/automation_account.html.markdown index b4443cd50a53..33e99f95f572 100644 --- a/website/docs/r/automation_account.html.markdown +++ b/website/docs/r/automation_account.html.markdown @@ -44,12 +44,16 @@ The following arguments are supported: * `sku_name` - (Required) The SKU of the account - only `Basic` is supported at this time. +* `local_authentication_enabled` - (Optional) Whether requests using non-AAD authentication are blocked. + --- * `identity` - (Optional) An `identity` block as defined below. * `tags` - (Optional) A mapping of tags to assign to the resource. +* `encryption` - (Optional) An `encryption` block as defined below. + --- An `identity` block supports the following: @@ -60,6 +64,16 @@ An `identity` block supports the following: -> **Note:** `identity_ids` is required when `type` is set to `UserAssigned` or `SystemAssigned, UserAssigned`. +-- + +An `encryption` block supports the following: + +* `user_assigned_identity_id` - (Optional) The User Assigned Managed Identity ID to be used for accessing the Customer Managed Key for encryption. + +* `key_source` - (Optional) The source of the encryption key. Possible values are `Microsoft.Keyvault` and `Microsoft.Storage`. + +* `key_vault_key_id` - (Required) The ID of the Key Vault Key which should be used to Encrypt the data in this Automation Account. + --- ## Attributes Reference