diff --git a/azurerm/data_source_key_vault.go b/azurerm/data_source_key_vault.go index 3107545aba0e..b066ca762b4e 100644 --- a/azurerm/data_source_key_vault.go +++ b/azurerm/data_source_key_vault.go @@ -5,6 +5,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2016-10-01/keyvault" "github.com/hashicorp/terraform/helper/schema" + kvschema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -140,7 +141,9 @@ func dataSourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error { if err := d.Set("sku", flattenKeyVaultDataSourceSku(props.Sku)); err != nil { return fmt.Errorf("Error flattening `sku` for KeyVault %q: %+v", resp.Name, err) } - if err := d.Set("access_policy", flattenKeyVaultDataSourceAccessPolicies(props.AccessPolicies)); err != nil { + + flattenedPolicies := kvschema.FlattenKeyVaultAccessPolicies(props.AccessPolicies) + if err := d.Set("access_policy", flattenedPolicies); err != nil { return fmt.Errorf("Error flattening `access_policy` for KeyVault %q: %+v", resp.Name, err) } d.Set("vault_uri", props.VaultURI) @@ -158,53 +161,3 @@ func flattenKeyVaultDataSourceSku(sku *keyvault.Sku) []interface{} { return []interface{}{result} } - -func flattenKeyVaultDataSourceAccessPolicies(policies *[]keyvault.AccessPolicyEntry) []interface{} { - result := make([]interface{}, 0, len(*policies)) - - if policies == nil { - return result - } - - for _, policy := range *policies { - policyRaw := make(map[string]interface{}) - - keyPermissionsRaw := make([]interface{}, 0) - secretPermissionsRaw := make([]interface{}, 0) - certificatePermissionsRaw := make([]interface{}, 0) - - if permissions := policy.Permissions; permissions != nil { - if keys := permissions.Keys; keys != nil { - for _, keyPermission := range *keys { - keyPermissionsRaw = append(keyPermissionsRaw, string(keyPermission)) - } - } - if secrets := permissions.Secrets; secrets != nil { - for _, secretPermission := range *secrets { - secretPermissionsRaw = append(secretPermissionsRaw, string(secretPermission)) - } - } - - if certificates := permissions.Certificates; certificates != nil { - for _, certificatePermission := range *certificates { - certificatePermissionsRaw = append(certificatePermissionsRaw, string(certificatePermission)) - } - } - } - - policyRaw["tenant_id"] = policy.TenantID.String() - if policy.ObjectID != nil { - policyRaw["object_id"] = *policy.ObjectID - } - if policy.ApplicationID != nil { - policyRaw["application_id"] = policy.ApplicationID.String() - } - policyRaw["key_permissions"] = keyPermissionsRaw - policyRaw["secret_permissions"] = secretPermissionsRaw - policyRaw["certificate_permissions"] = certificatePermissionsRaw - - result = append(result, policyRaw) - } - - return result -} diff --git a/azurerm/helpers/schema/key_vault_access_policy.go b/azurerm/helpers/schema/key_vault_access_policy.go new file mode 100644 index 000000000000..b73612f788ab --- /dev/null +++ b/azurerm/helpers/schema/key_vault_access_policy.go @@ -0,0 +1,224 @@ +package schema + +import ( + "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2016-10-01/keyvault" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/satori/go.uuid" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" +) + +func KeyVaultCertificatePermissionsSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(keyvault.Create), + string(keyvault.Delete), + string(keyvault.Deleteissuers), + string(keyvault.Get), + string(keyvault.Getissuers), + string(keyvault.Import), + string(keyvault.List), + string(keyvault.Listissuers), + string(keyvault.Managecontacts), + string(keyvault.Manageissuers), + string(keyvault.Purge), + string(keyvault.Recover), + string(keyvault.Setissuers), + string(keyvault.Update), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + } +} + +func KeyVaultKeyPermissionsSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(keyvault.KeyPermissionsBackup), + string(keyvault.KeyPermissionsCreate), + string(keyvault.KeyPermissionsDecrypt), + string(keyvault.KeyPermissionsDelete), + string(keyvault.KeyPermissionsEncrypt), + string(keyvault.KeyPermissionsGet), + string(keyvault.KeyPermissionsImport), + string(keyvault.KeyPermissionsList), + string(keyvault.KeyPermissionsPurge), + string(keyvault.KeyPermissionsRecover), + string(keyvault.KeyPermissionsRestore), + string(keyvault.KeyPermissionsSign), + string(keyvault.KeyPermissionsUnwrapKey), + string(keyvault.KeyPermissionsUpdate), + string(keyvault.KeyPermissionsVerify), + string(keyvault.KeyPermissionsWrapKey), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + } +} + +func KeyVaultSecretPermissionsSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(keyvault.SecretPermissionsBackup), + string(keyvault.SecretPermissionsDelete), + string(keyvault.SecretPermissionsGet), + string(keyvault.SecretPermissionsList), + string(keyvault.SecretPermissionsPurge), + string(keyvault.SecretPermissionsRecover), + string(keyvault.SecretPermissionsRestore), + string(keyvault.SecretPermissionsSet), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + } +} + +func ExpandKeyVaultAccessPolicies(input []interface{}) (*[]keyvault.AccessPolicyEntry, error) { + output := make([]keyvault.AccessPolicyEntry, 0) + + for _, policySet := range input { + policyRaw := policySet.(map[string]interface{}) + + certificatePermissionsRaw := policyRaw["certificate_permissions"].([]interface{}) + keyPermissionsRaw := policyRaw["key_permissions"].([]interface{}) + secretPermissionsRaw := policyRaw["secret_permissions"].([]interface{}) + + policy := keyvault.AccessPolicyEntry{ + Permissions: &keyvault.Permissions{ + Certificates: ExpandCertificatePermissions(certificatePermissionsRaw), + Keys: ExpandKeyPermissions(keyPermissionsRaw), + Secrets: ExpandSecretPermissions(secretPermissionsRaw), + }, + } + + tenantUUID := uuid.FromStringOrNil(policyRaw["tenant_id"].(string)) + policy.TenantID = &tenantUUID + objectUUID := policyRaw["object_id"].(string) + policy.ObjectID = &objectUUID + + if v := policyRaw["application_id"]; v != "" { + applicationUUID := uuid.FromStringOrNil(v.(string)) + policy.ApplicationID = &applicationUUID + } + + output = append(output, policy) + } + + return &output, nil +} + +func FlattenKeyVaultAccessPolicies(policies *[]keyvault.AccessPolicyEntry) []map[string]interface{} { + result := make([]map[string]interface{}, 0) + + if policies == nil { + return result + } + + for _, policy := range *policies { + policyRaw := make(map[string]interface{}) + + if tenantId := policy.TenantID; tenantId != nil { + policyRaw["tenant_id"] = tenantId.String() + } + + if objectId := policy.ObjectID; objectId != nil { + policyRaw["object_id"] = *objectId + } + + if appId := policy.ApplicationID; appId != nil { + policyRaw["application_id"] = appId.String() + } + + if permissions := policy.Permissions; permissions != nil { + certs := FlattenCertificatePermissions(permissions.Certificates) + policyRaw["certificate_permissions"] = certs + + keys := FlattenKeyPermissions(permissions.Keys) + policyRaw["key_permissions"] = keys + + secrets := FlattenSecretPermissions(permissions.Secrets) + policyRaw["secret_permissions"] = secrets + } + + result = append(result, policyRaw) + } + + return result +} + +func ExpandCertificatePermissions(input []interface{}) *[]keyvault.CertificatePermissions { + output := make([]keyvault.CertificatePermissions, 0) + + for _, permission := range input { + output = append(output, keyvault.CertificatePermissions(permission.(string))) + } + + return &output +} + +func FlattenCertificatePermissions(input *[]keyvault.CertificatePermissions) []interface{} { + output := make([]interface{}, 0) + + if input != nil { + for _, certificatePermission := range *input { + output = append(output, string(certificatePermission)) + } + } + + return output +} + +func ExpandKeyPermissions(keyPermissionsRaw []interface{}) *[]keyvault.KeyPermissions { + output := make([]keyvault.KeyPermissions, 0) + + for _, permission := range keyPermissionsRaw { + output = append(output, keyvault.KeyPermissions(permission.(string))) + } + return &output +} + +func FlattenKeyPermissions(input *[]keyvault.KeyPermissions) []interface{} { + output := make([]interface{}, 0) + + if input != nil { + for _, keyPermission := range *input { + output = append(output, string(keyPermission)) + } + } + + return output +} + +func ExpandSecretPermissions(input []interface{}) *[]keyvault.SecretPermissions { + output := make([]keyvault.SecretPermissions, 0) + + for _, permission := range input { + output = append(output, keyvault.SecretPermissions(permission.(string))) + } + + return &output +} + +func FlattenSecretPermissions(input *[]keyvault.SecretPermissions) []interface{} { + output := make([]interface{}, 0) + + if input != nil { + for _, secretPermission := range *input { + output = append(output, string(secretPermission)) + } + } + + return output +} diff --git a/azurerm/import_arm_key_vault_access_policy_test.go b/azurerm/import_arm_key_vault_access_policy_test.go new file mode 100644 index 000000000000..c5920b466a56 --- /dev/null +++ b/azurerm/import_arm_key_vault_access_policy_test.go @@ -0,0 +1,57 @@ +package azurerm + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAzureRMKeyVaultAccessPolicy_importBasic(t *testing.T) { + resourceName := "azurerm_key_vault_access_policy.test" + + rs := acctest.RandString(5) + config := testAccAzureRMKeyVaultAccessPolicy_basic(rs, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: config, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMKeyVaultAccessPolicy_importMultiple(t *testing.T) { + rs := acctest.RandString(5) + config := testAccAzureRMKeyVaultAccessPolicy_multiple(rs, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: config, + }, + { + ResourceName: "azurerm_key_vault_access_policy.test_with_application_id", + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "azurerm_key_vault_access_policy.test_no_application_id", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/azurerm/provider.go b/azurerm/provider.go index c9ad8f102de9..9ee126a6bdaf 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -158,6 +158,7 @@ func Provider() terraform.ResourceProvider { "azurerm_image": resourceArmImage(), "azurerm_iothub": resourceArmIotHub(), "azurerm_key_vault": resourceArmKeyVault(), + "azurerm_key_vault_access_policy": resourceArmKeyVaultAccessPolicy(), "azurerm_key_vault_certificate": resourceArmKeyVaultCertificate(), "azurerm_key_vault_key": resourceArmKeyVaultKey(), "azurerm_key_vault_secret": resourceArmKeyVaultSecret(), diff --git a/azurerm/resource_arm_key_vault.go b/azurerm/resource_arm_key_vault.go index ef9e1b3a8819..3be569735fff 100644 --- a/azurerm/resource_arm_key_vault.go +++ b/azurerm/resource_arm_key_vault.go @@ -11,7 +11,8 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" - "github.com/satori/go.uuid" + uuid "github.com/satori/go.uuid" + azschema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -20,11 +21,13 @@ import ( // https://github.com/Azure/azure-rest-api-specs/blob/master/arm-keyvault/2015-06-01/swagger/keyvault.json#L239 var armKeyVaultSkuFamily = "A" +var keyVaultResourceName = "azurerm_key_vault" + func resourceArmKeyVault() *schema.Resource { return &schema.Resource{ - Create: resourceArmKeyVaultCreate, + Create: resourceArmKeyVaultCreateUpdate, Read: resourceArmKeyVaultRead, - Update: resourceArmKeyVaultCreate, + Update: resourceArmKeyVaultCreateUpdate, Delete: resourceArmKeyVaultDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, @@ -75,7 +78,7 @@ func resourceArmKeyVault() *schema.Resource { "access_policy": { Type: schema.TypeList, Optional: true, - MinItems: 1, + Computed: true, MaxItems: 16, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -94,74 +97,9 @@ func resourceArmKeyVault() *schema.Resource { Optional: true, ValidateFunc: validateUUID, }, - "certificate_permissions": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice([]string{ - string(keyvault.Create), - string(keyvault.Delete), - string(keyvault.Deleteissuers), - string(keyvault.Get), - string(keyvault.Getissuers), - string(keyvault.Import), - string(keyvault.List), - string(keyvault.Listissuers), - string(keyvault.Managecontacts), - string(keyvault.Manageissuers), - string(keyvault.Purge), - string(keyvault.Recover), - string(keyvault.Setissuers), - string(keyvault.Update), - }, true), - DiffSuppressFunc: ignoreCaseDiffSuppressFunc, - }, - }, - "key_permissions": { - Type: schema.TypeList, - Required: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice([]string{ - string(keyvault.KeyPermissionsBackup), - string(keyvault.KeyPermissionsCreate), - string(keyvault.KeyPermissionsDecrypt), - string(keyvault.KeyPermissionsDelete), - string(keyvault.KeyPermissionsEncrypt), - string(keyvault.KeyPermissionsGet), - string(keyvault.KeyPermissionsImport), - string(keyvault.KeyPermissionsList), - string(keyvault.KeyPermissionsPurge), - string(keyvault.KeyPermissionsRecover), - string(keyvault.KeyPermissionsRestore), - string(keyvault.KeyPermissionsSign), - string(keyvault.KeyPermissionsUnwrapKey), - string(keyvault.KeyPermissionsUpdate), - string(keyvault.KeyPermissionsVerify), - string(keyvault.KeyPermissionsWrapKey), - }, true), - DiffSuppressFunc: ignoreCaseDiffSuppressFunc, - }, - }, - "secret_permissions": { - Type: schema.TypeList, - Required: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice([]string{ - string(keyvault.SecretPermissionsBackup), - string(keyvault.SecretPermissionsDelete), - string(keyvault.SecretPermissionsGet), - string(keyvault.SecretPermissionsList), - string(keyvault.SecretPermissionsPurge), - string(keyvault.SecretPermissionsRecover), - string(keyvault.SecretPermissionsRestore), - string(keyvault.SecretPermissionsSet), - }, true), - DiffSuppressFunc: ignoreCaseDiffSuppressFunc, - }, - }, + "certificate_permissions": azschema.KeyVaultCertificatePermissionsSchema(), + "key_permissions": azschema.KeyVaultKeyPermissionsSchema(), + "secret_permissions": azschema.KeyVaultSecretPermissionsSchema(), }, }, }, @@ -186,7 +124,7 @@ func resourceArmKeyVault() *schema.Resource { } } -func resourceArmKeyVaultCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).keyVaultClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for Azure ARM KeyVault creation.") @@ -200,12 +138,18 @@ func resourceArmKeyVaultCreate(d *schema.ResourceData, meta interface{}) error { enabledForTemplateDeployment := d.Get("enabled_for_template_deployment").(bool) tags := d.Get("tags").(map[string]interface{}) + policies := d.Get("access_policy").([]interface{}) + accessPolicies, err := azschema.ExpandKeyVaultAccessPolicies(policies) + if err != nil { + return fmt.Errorf("Error expanding `access_policy`: %+v", policies) + } + parameters := keyvault.VaultCreateOrUpdateParameters{ Location: &location, Properties: &keyvault.VaultProperties{ TenantID: &tenantUUID, Sku: expandKeyVaultSku(d), - AccessPolicies: expandKeyVaultAccessPolicies(d), + AccessPolicies: accessPolicies, EnabledForDeployment: &enabledForDeployment, EnabledForDiskEncryption: &enabledForDiskEncryption, EnabledForTemplateDeployment: &enabledForTemplateDeployment, @@ -213,17 +157,22 @@ func resourceArmKeyVaultCreate(d *schema.ResourceData, meta interface{}) error { Tags: expandTags(tags), } - _, err := client.CreateOrUpdate(ctx, resGroup, name, parameters) + // Locking this resource so we don't make modifications to it at the same time if there is a + // key vault access policy trying to update it as well + azureRMLockByName(name, keyVaultResourceName) + defer azureRMUnlockByName(name, keyVaultResourceName) + + _, err = client.CreateOrUpdate(ctx, resGroup, name, parameters) if err != nil { - return err + return fmt.Errorf("Error updating Key Vault %q (Resource Group %q): %+v", name, resGroup, err) } read, err := client.Get(ctx, resGroup, name) if err != nil { - return err + return fmt.Errorf("Error retrieving Key Vault %q (Resource Group %q): %+v", name, resGroup, err) } if read.ID == nil { - return fmt.Errorf("Cannot read KeyVault %s (resource group %s) ID", name, resGroup) + return fmt.Errorf("Cannot read KeyVault %s (resource Group %q) ID", name, resGroup) } d.SetId(*read.ID) @@ -269,7 +218,7 @@ func resourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error { d.SetId("") return nil } - return fmt.Errorf("Error making Read request on Azure KeyVault %s: %+v", name, err) + return fmt.Errorf("Error making Read request on KeyVault %q (Resource Group %q): %+v", name, resGroup, err) } d.Set("name", resp.Name) @@ -286,7 +235,9 @@ func resourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error { if err := d.Set("sku", flattenKeyVaultSku(props.Sku)); err != nil { return fmt.Errorf("Error flattening `sku` for KeyVault %q: %+v", resp.Name, err) } - if err := d.Set("access_policy", flattenKeyVaultAccessPolicies(props.AccessPolicies)); err != nil { + + flattenedPolicies := azschema.FlattenKeyVaultAccessPolicies(props.AccessPolicies) + if err := d.Set("access_policy", flattenedPolicies); err != nil { return fmt.Errorf("Error flattening `access_policy` for KeyVault %q: %+v", resp.Name, err) } d.Set("vault_uri", props.VaultURI) @@ -307,6 +258,9 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { resGroup := id.ResourceGroup name := id.Path["vaults"] + azureRMLockByName(name, keyVaultResourceName) + defer azureRMUnlockByName(name, keyVaultResourceName) + _, err = client.Delete(ctx, resGroup, name) return err @@ -322,58 +276,6 @@ func expandKeyVaultSku(d *schema.ResourceData) *keyvault.Sku { } } -func expandKeyVaultAccessPolicies(d *schema.ResourceData) *[]keyvault.AccessPolicyEntry { - policies := d.Get("access_policy").([]interface{}) - result := make([]keyvault.AccessPolicyEntry, 0, len(policies)) - - for _, policySet := range policies { - policyRaw := policySet.(map[string]interface{}) - - certificatePermissionsRaw := policyRaw["certificate_permissions"].([]interface{}) - certificatePermissions := make([]keyvault.CertificatePermissions, 0) - for _, v := range certificatePermissionsRaw { - permission := keyvault.CertificatePermissions(v.(string)) - certificatePermissions = append(certificatePermissions, permission) - } - - keyPermissionsRaw := policyRaw["key_permissions"].([]interface{}) - keyPermissions := make([]keyvault.KeyPermissions, 0) - for _, v := range keyPermissionsRaw { - permission := keyvault.KeyPermissions(v.(string)) - keyPermissions = append(keyPermissions, permission) - } - - secretPermissionsRaw := policyRaw["secret_permissions"].([]interface{}) - secretPermissions := make([]keyvault.SecretPermissions, 0) - for _, v := range secretPermissionsRaw { - permission := keyvault.SecretPermissions(v.(string)) - secretPermissions = append(secretPermissions, permission) - } - - policy := keyvault.AccessPolicyEntry{ - Permissions: &keyvault.Permissions{ - Certificates: &certificatePermissions, - Keys: &keyPermissions, - Secrets: &secretPermissions, - }, - } - - tenantUUID := uuid.FromStringOrNil(policyRaw["tenant_id"].(string)) - policy.TenantID = &tenantUUID - objectUUID := policyRaw["object_id"].(string) - policy.ObjectID = &objectUUID - - if v := policyRaw["application_id"]; v != "" { - applicationUUID := uuid.FromStringOrNil(v.(string)) - policy.ApplicationID = &applicationUUID - } - - result = append(result, policy) - } - - return &result -} - func flattenKeyVaultSku(sku *keyvault.Sku) []interface{} { result := map[string]interface{}{ "name": string(sku.Name), @@ -382,56 +284,6 @@ func flattenKeyVaultSku(sku *keyvault.Sku) []interface{} { return []interface{}{result} } -func flattenKeyVaultAccessPolicies(policies *[]keyvault.AccessPolicyEntry) []interface{} { - result := make([]interface{}, 0, len(*policies)) - - if policies == nil { - return result - } - - for _, policy := range *policies { - policyRaw := make(map[string]interface{}) - - keyPermissionsRaw := make([]interface{}, 0) - secretPermissionsRaw := make([]interface{}, 0) - certificatePermissionsRaw := make([]interface{}, 0) - - if permissions := policy.Permissions; permissions != nil { - if keys := permissions.Keys; keys != nil { - for _, keyPermission := range *keys { - keyPermissionsRaw = append(keyPermissionsRaw, string(keyPermission)) - } - } - if secrets := permissions.Secrets; secrets != nil { - for _, secretPermission := range *secrets { - secretPermissionsRaw = append(secretPermissionsRaw, string(secretPermission)) - } - } - - if certificates := permissions.Certificates; certificates != nil { - for _, certificatePermission := range *certificates { - certificatePermissionsRaw = append(certificatePermissionsRaw, string(certificatePermission)) - } - } - } - - policyRaw["tenant_id"] = policy.TenantID.String() - if policy.ObjectID != nil { - policyRaw["object_id"] = *policy.ObjectID - } - if policy.ApplicationID != nil { - policyRaw["application_id"] = policy.ApplicationID.String() - } - policyRaw["key_permissions"] = keyPermissionsRaw - policyRaw["secret_permissions"] = secretPermissionsRaw - policyRaw["certificate_permissions"] = certificatePermissionsRaw - - result = append(result, policyRaw) - } - - return result -} - func validateKeyVaultName(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if matched := regexp.MustCompile(`^[a-zA-Z0-9-]{3,24}$`).Match([]byte(value)); !matched { diff --git a/azurerm/resource_arm_key_vault_access_policy.go b/azurerm/resource_arm_key_vault_access_policy.go new file mode 100644 index 000000000000..530d165d3dbe --- /dev/null +++ b/azurerm/resource_arm_key_vault_access_policy.go @@ -0,0 +1,252 @@ +package azurerm + +import ( + "fmt" + "log" + + "strings" + + "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2016-10-01/keyvault" + "github.com/hashicorp/terraform/helper/schema" + uuid "github.com/satori/go.uuid" + azschema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmKeyVaultAccessPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceArmKeyVaultAccessPolicyCreate, + Read: resourceArmKeyVaultAccessPolicyRead, + Update: resourceArmKeyVaultAccessPolicyUpdate, + Delete: resourceArmKeyVaultAccessPolicyDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "vault_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "resource_group_name": resourceGroupNameSchema(), + + "tenant_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateUUID, + }, + + "object_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateUUID, + }, + + "application_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateUUID, + }, + + "certificate_permissions": azschema.KeyVaultCertificatePermissionsSchema(), + + "key_permissions": azschema.KeyVaultKeyPermissionsSchema(), + + "secret_permissions": azschema.KeyVaultSecretPermissionsSchema(), + }, + } +} + +func resourceArmKeyVaultAccessPolicyCreateOrDelete(d *schema.ResourceData, meta interface{}, action keyvault.AccessPolicyUpdateKind) error { + client := meta.(*ArmClient).keyVaultClient + ctx := meta.(*ArmClient).StopContext + log.Printf("[INFO] Preparing arguments for Key Vault Access Policy: %s.", action) + + vaultName := d.Get("vault_name").(string) + resGroup := d.Get("resource_group_name").(string) + + tenantIdRaw := d.Get("tenant_id").(string) + tenantId, err := uuid.FromString(tenantIdRaw) + if err != nil { + return fmt.Errorf("Error parsing Tenant ID %q as a UUID: %+v", tenantIdRaw, err) + } + + objectId := d.Get("object_id").(string) + + certPermissionsRaw := d.Get("certificate_permissions").([]interface{}) + certPermissions := azschema.ExpandCertificatePermissions(certPermissionsRaw) + + keyPermissionsRaw := d.Get("key_permissions").([]interface{}) + keyPermissions := azschema.ExpandKeyPermissions(keyPermissionsRaw) + + secretPermissionsRaw := d.Get("secret_permissions").([]interface{}) + secretPermissions := azschema.ExpandSecretPermissions(secretPermissionsRaw) + + accessPolicy := keyvault.AccessPolicyEntry{ + ObjectID: utils.String(objectId), + TenantID: &tenantId, + Permissions: &keyvault.Permissions{ + Certificates: certPermissions, + Keys: keyPermissions, + Secrets: secretPermissions, + }, + } + + applicationIdRaw := d.Get("application_id").(string) + if applicationIdRaw != "" { + applicationId, err := uuid.FromString(applicationIdRaw) + if err != nil { + return fmt.Errorf("Error parsing Appliciation ID %q as a UUID: %+v", applicationIdRaw, err) + } + + accessPolicy.ApplicationID = &applicationId + } + + accessPolicies := []keyvault.AccessPolicyEntry{accessPolicy} + + parameters := keyvault.VaultAccessPolicyParameters{ + Name: &vaultName, + Properties: &keyvault.VaultAccessPolicyProperties{ + AccessPolicies: &accessPolicies, + }, + } + + // Locking to prevent parallel changes causing issues + azureRMLockByName(vaultName, keyVaultResourceName) + defer azureRMUnlockByName(vaultName, keyVaultResourceName) + + _, err = client.UpdateAccessPolicy(ctx, resGroup, vaultName, action, parameters) + if err != nil { + return fmt.Errorf("Error updating Access Policy (Object ID %q / Application ID %q) for Key Vault %q (Resource Group %q): %+v", objectId, applicationIdRaw, vaultName, resGroup, err) + } + + read, err := client.Get(ctx, resGroup, vaultName) + if err != nil { + return fmt.Errorf("Error retrieving Key Vault %q (Resource Group %q): %+v", vaultName, resGroup, err) + } + + if read.ID == nil { + return fmt.Errorf("Cannot read KeyVault %q (Resource Group %q) ID", vaultName, resGroup) + } + + if d.IsNewResource() { + // This is because azure doesn't have an 'id' for a keyvault access policy + // In order to compensate for this and allow importing of this resource we are artificially + // creating an identity for a key vault policy object + resourceId := fmt.Sprintf("%s/objectId/%s", *read.ID, objectId) + if applicationIdRaw != "" { + resourceId = fmt.Sprintf("%s/applicationId/%s", resourceId, applicationIdRaw) + } + d.SetId(resourceId) + } + + return nil +} + +func resourceArmKeyVaultAccessPolicyCreate(d *schema.ResourceData, meta interface{}) error { + return resourceArmKeyVaultAccessPolicyCreateOrDelete(d, meta, keyvault.Add) +} + +func resourceArmKeyVaultAccessPolicyDelete(d *schema.ResourceData, meta interface{}) error { + return resourceArmKeyVaultAccessPolicyCreateOrDelete(d, meta, keyvault.Remove) +} + +func resourceArmKeyVaultAccessPolicyUpdate(d *schema.ResourceData, meta interface{}) error { + return resourceArmKeyVaultAccessPolicyCreateOrDelete(d, meta, keyvault.Replace) +} + +func resourceArmKeyVaultAccessPolicyRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).keyVaultClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + + if err != nil { + return err + } + resGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + objectId := id.Path["objectId"] + applicationId := id.Path["applicationId"] + + resp, err := client.Get(ctx, resGroup, vaultName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[ERROR] Key Vault %q (Resource Group %q) was not found - removing from state", vaultName, resGroup) + d.SetId("") + return nil + } + + return fmt.Errorf("Error making Read request on Azure KeyVault %q (Resource Group %q): %+v", vaultName, resGroup, err) + } + + policy, err := findKeyVaultAccessPolicy(resp.Properties.AccessPolicies, objectId, applicationId) + if err != nil { + return fmt.Errorf("Error locating Access Policy (Object ID %q / Application ID %q) in Key Vault %q (Resource Group %q)", objectId, applicationId, vaultName, resGroup) + } + + if policy == nil { + log.Printf("[ERROR] Access Policy (Object ID %q / Application ID %q) was not found in Key Vault %q (Resource Group %q) - removing from state", objectId, applicationId, vaultName, resGroup) + d.SetId("") + return nil + } + + d.Set("vault_name", resp.Name) + d.Set("resource_group_name", resGroup) + d.Set("object_id", objectId) + + if tid := policy.TenantID; tid != nil { + d.Set("tenant_id", tid.String()) + } + + if aid := policy.ApplicationID; aid != nil { + d.Set("application_id", aid.String()) + } + + if permissions := policy.Permissions; permissions != nil { + certificatePermissions := azschema.FlattenCertificatePermissions(permissions.Certificates) + if err := d.Set("certificate_permissions", certificatePermissions); err != nil { + return fmt.Errorf("Error flattening `certificate_permissions`: %+v", err) + } + + keyPermissions := azschema.FlattenKeyPermissions(permissions.Keys) + if err := d.Set("key_permissions", keyPermissions); err != nil { + return fmt.Errorf("Error flattening `key_permissions`: %+v", err) + } + + secretPermissions := azschema.FlattenSecretPermissions(permissions.Secrets) + if err := d.Set("secret_permissions", secretPermissions); err != nil { + return fmt.Errorf("Error flattening `secret_permissions`: %+v", err) + } + } + + return nil +} + +func findKeyVaultAccessPolicy(policies *[]keyvault.AccessPolicyEntry, objectId string, applicationId string) (*keyvault.AccessPolicyEntry, error) { + if policies == nil { + return nil, nil + } + + for _, policy := range *policies { + if id := policy.ObjectID; id != nil { + if strings.EqualFold(*id, objectId) { + aid := "" + if policy.ApplicationID != nil { + aid = policy.ApplicationID.String() + } + + if strings.EqualFold(aid, applicationId) { + return &policy, nil + } + } + } + } + + return nil, nil +} diff --git a/azurerm/resource_arm_key_vault_access_policy_test.go b/azurerm/resource_arm_key_vault_access_policy_test.go new file mode 100644 index 000000000000..d9b1976ba4b1 --- /dev/null +++ b/azurerm/resource_arm_key_vault_access_policy_test.go @@ -0,0 +1,262 @@ +package azurerm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMKeyVaultAccessPolicy_basic(t *testing.T) { + resourceName := "azurerm_key_vault_access_policy.test" + rs := acctest.RandString(6) + config := testAccAzureRMKeyVaultAccessPolicy_basic(rs, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultAccessPolicyExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "key_permissions.0", "get"), + resource.TestCheckResourceAttr(resourceName, "secret_permissions.0", "get"), + resource.TestCheckResourceAttr(resourceName, "secret_permissions.1", "set"), + ), + }, + }, + }) +} + +func TestAccAzureRMKeyVaultAccessPolicy_multiple(t *testing.T) { + resourceName1 := "azurerm_key_vault_access_policy.test_with_application_id" + resourceName2 := "azurerm_key_vault_access_policy.test_no_application_id" + rs := acctest.RandString(6) + config := testAccAzureRMKeyVaultAccessPolicy_multiple(rs, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultAccessPolicyExists(resourceName1), + resource.TestCheckResourceAttr(resourceName1, "key_permissions.0", "create"), + resource.TestCheckResourceAttr(resourceName1, "key_permissions.1", "get"), + resource.TestCheckResourceAttr(resourceName1, "secret_permissions.0", "get"), + resource.TestCheckResourceAttr(resourceName1, "secret_permissions.1", "delete"), + resource.TestCheckResourceAttr(resourceName1, "certificate_permissions.0", "create"), + resource.TestCheckResourceAttr(resourceName1, "certificate_permissions.1", "delete"), + + testCheckAzureRMKeyVaultAccessPolicyExists(resourceName2), + resource.TestCheckResourceAttr(resourceName2, "key_permissions.0", "list"), + resource.TestCheckResourceAttr(resourceName2, "key_permissions.1", "encrypt"), + resource.TestCheckResourceAttr(resourceName2, "secret_permissions.0", "list"), + resource.TestCheckResourceAttr(resourceName2, "secret_permissions.1", "delete"), + resource.TestCheckResourceAttr(resourceName2, "certificate_permissions.0", "list"), + resource.TestCheckResourceAttr(resourceName2, "certificate_permissions.1", "delete"), + ), + }, + }, + }) +} + +func TestAccAzureRMKeyVaultAccessPolicy_update(t *testing.T) { + rs := acctest.RandString(6) + resourceName := "azurerm_key_vault_access_policy.test" + preConfig := testAccAzureRMKeyVaultAccessPolicy_basic(rs, testLocation()) + postConfig := testAccAzureRMKeyVaultAccessPolicy_update(rs, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultAccessPolicyExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "key_permissions.0", "get"), + resource.TestCheckResourceAttr(resourceName, "secret_permissions.0", "get"), + resource.TestCheckResourceAttr(resourceName, "secret_permissions.1", "set"), + ), + }, + { + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultAccessPolicyExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "key_permissions.0", "list"), + resource.TestCheckResourceAttr(resourceName, "key_permissions.1", "encrypt"), + ), + }, + }, + }) +} + +func testCheckAzureRMKeyVaultAccessPolicyExists(name 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[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + vaultName := rs.Primary.Attributes["vault_name"] + resGroup := rs.Primary.Attributes["resource_group_name"] + objectId := rs.Primary.Attributes["object_id"] + applicationId := rs.Primary.Attributes["application_id"] + + client := testAccProvider.Meta().(*ArmClient).keyVaultClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resGroup, vaultName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Key Vault %q (resource group: %q) does not exist", vaultName, resGroup) + } + + return fmt.Errorf("Bad: Get on keyVaultClient: %+v", err) + } + + policy, err := findKeyVaultAccessPolicy(resp.Properties.AccessPolicies, objectId, applicationId) + if policy == nil { + return fmt.Errorf("Bad: Key Vault Policy %q (resource group: %q, object_id: %s) does not exist", vaultName, resGroup, objectId) + } + + return nil + } +} + +func testAccAzureRMKeyVaultAccessPolicy_basic(rString string, location string) string { + template := testAccAzureRMKeyVaultAccessPolicy_template(rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_key_vault_access_policy" "test" { + vault_name = "${azurerm_key_vault.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + + key_permissions = [ + "get", + ] + + secret_permissions = [ + "get", + "set", + ] + + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + object_id = "${data.azurerm_client_config.current.service_principal_object_id}" +} +`, template) +} + +func testAccAzureRMKeyVaultAccessPolicy_multiple(rString string, location string) string { + template := testAccAzureRMKeyVaultAccessPolicy_template(rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_key_vault_access_policy" "test_with_application_id" { + vault_name = "${azurerm_key_vault.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + + key_permissions = [ + "create", + "get", + ] + + secret_permissions = [ + "get", + "delete", + ] + + certificate_permissions = [ + "create", + "delete", + ] + + application_id = "${data.azurerm_client_config.current.service_principal_application_id}" + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + object_id = "${data.azurerm_client_config.current.service_principal_object_id}" +} + +resource "azurerm_key_vault_access_policy" "test_no_application_id" { + vault_name = "${azurerm_key_vault.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + + key_permissions = [ + "list", + "encrypt", + ] + + secret_permissions = [ + "list", + "delete", + ] + + certificate_permissions = [ + "list", + "delete", + ] + + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + object_id = "${data.azurerm_client_config.current.service_principal_object_id}" +} +`, template) +} + +func testAccAzureRMKeyVaultAccessPolicy_update(rString string, location string) string { + template := testAccAzureRMKeyVaultAccessPolicy_template(rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_key_vault_access_policy" "test" { + vault_name = "${azurerm_key_vault.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + + key_permissions = [ + "list", + "encrypt", + ] + + secret_permissions = [] + + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + object_id = "${data.azurerm_client_config.current.service_principal_object_id}" +} + +`, template) +} + +func testAccAzureRMKeyVaultAccessPolicy_template(rString string, location string) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%s" + 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 = "premium" + } + + tags { + environment = "Production" + } +} +`, rString, location, rString) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 63e393803a17..c2320ab3b6b4 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -471,6 +471,10 @@ azurerm_key_vault +