From 172036274e7cd2f212a587d14376b6f40ab3bf53 Mon Sep 17 00:00:00 2001 From: xuwu1 Date: Mon, 13 Mar 2023 18:05:57 +0800 Subject: [PATCH] add new role definition and role assignment resource --- internal/services/keyvault/client/client.go | 19 +- ...naged_hardware_security_module_resource.go | 3 +- ..._hardware_security_module_resource_test.go | 46 +++ ...ecurity_module_role_assignment_resource.go | 196 ++++++++++ ...ty_module_role_assignment_resource_test.go | 82 ++++ ...rity_module_role_definition_data_source.go | 170 +++++++++ ...ecurity_module_role_definition_resource.go | 359 ++++++++++++++++++ ...ty_module_role_definition_resource_test.go | 91 +++++ .../keyvault/parse/mhsm_nested_item.go | 95 +++++ .../keyvault/parse/mhsm_nested_item_test.go | 153 ++++++++ internal/services/keyvault/registration.go | 3 + .../keyvault/validate/mhsm_nested_item_id.go | 27 ++ .../validate/mhsm_nested_item_id_test.go | 55 +++ ...urity_module_role_definition.html.markdown | 71 ++++ ...urity_module_role_assignment.html.markdown | 67 ++++ ...urity_module_role_definition.html.markdown | 106 ++++++ 16 files changed, 1534 insertions(+), 9 deletions(-) create mode 100644 internal/services/keyvault/key_vault_managed_hardware_security_module_role_assignment_resource.go create mode 100644 internal/services/keyvault/key_vault_managed_hardware_security_module_role_assignment_resource_test.go create mode 100644 internal/services/keyvault/key_vault_managed_hardware_security_module_role_definition_data_source.go create mode 100644 internal/services/keyvault/key_vault_managed_hardware_security_module_role_definition_resource.go create mode 100644 internal/services/keyvault/key_vault_managed_hardware_security_module_role_definition_resource_test.go create mode 100644 internal/services/keyvault/parse/mhsm_nested_item.go create mode 100644 internal/services/keyvault/parse/mhsm_nested_item_test.go create mode 100644 internal/services/keyvault/validate/mhsm_nested_item_id.go create mode 100644 internal/services/keyvault/validate/mhsm_nested_item_id_test.go create mode 100644 website/docs/d/key_vault_managed_hardware_security_module_role_definition.html.markdown create mode 100644 website/docs/r/key_vault_managed_hardware_security_module_role_assignment.html.markdown create mode 100644 website/docs/r/key_vault_managed_hardware_security_module_role_definition.html.markdown diff --git a/internal/services/keyvault/client/client.go b/internal/services/keyvault/client/client.go index e709d12f1008..b693c570acb7 100644 --- a/internal/services/keyvault/client/client.go +++ b/internal/services/keyvault/client/client.go @@ -15,8 +15,9 @@ type Client struct { ManagementClient *dataplane.BaseClient VaultsClient *vaults.VaultsClient - MHSMSDClient *dataplane.HSMSecurityDomainClient - MHSMRoleClient *dataplane.RoleDefinitionsClient + MHSMSDClient *dataplane.HSMSecurityDomainClient + MHSMRoleClient *dataplane.RoleDefinitionsClient + MHSMRoleAssignmentsClient *dataplane.RoleAssignmentsClient } func NewClient(o *common.ClientOptions) *Client { @@ -36,11 +37,15 @@ func NewClient(o *common.ClientOptions) *Client { o.ConfigureClient(&vaultsClient.Client, o.ResourceManagerAuthorizer) + mhsmRoleAssignClient := dataplane.NewRoleAssignmentsClient() + o.ConfigureClient(&mhsmRoleAssignClient.Client, o.ManagedHSMAuthorizer) + return &Client{ - ManagedHsmClient: &managedHsmClient, - ManagementClient: &managementClient, - VaultsClient: &vaultsClient, - MHSMSDClient: &sdClient, - MHSMRoleClient: &mhsmRoleDefineClient, + ManagedHsmClient: &managedHsmClient, + ManagementClient: &managementClient, + VaultsClient: &vaultsClient, + MHSMSDClient: &sdClient, + MHSMRoleClient: &mhsmRoleDefineClient, + MHSMRoleAssignmentsClient: &mhsmRoleAssignClient, } } diff --git a/internal/services/keyvault/key_vault_managed_hardware_security_module_resource.go b/internal/services/keyvault/key_vault_managed_hardware_security_module_resource.go index d6364f9eeac3..eba7fd15de3a 100644 --- a/internal/services/keyvault/key_vault_managed_hardware_security_module_resource.go +++ b/internal/services/keyvault/key_vault_managed_hardware_security_module_resource.go @@ -115,7 +115,6 @@ func resourceKeyVaultManagedHardwareSecurityModule() *pluginsdk.Resource { "public_network_access_enabled": { Type: pluginsdk.TypeBool, Optional: true, - //Computed: true, Default: true, ForceNew: true, }, @@ -435,7 +434,7 @@ func securityDomainDownload(ctx context.Context, cli *client.Client, vaultBaseUr keyID, _ := parse.ParseNestedItemID(certIDStr) certRes, err := keyClient.GetCertificate(ctx, keyID.KeyVaultBaseUrl, keyID.Name, keyID.Version) if err != nil { - return "", fmt.Errorf("retreiving key %s: %v", certID, err) + return "", fmt.Errorf("retrieving key %s: %v", certID, err) } if certRes.Cer == nil { return "", fmt.Errorf("got nil key for %s", certID) diff --git a/internal/services/keyvault/key_vault_managed_hardware_security_module_resource_test.go b/internal/services/keyvault/key_vault_managed_hardware_security_module_resource_test.go index 3f963ad449ea..aeda7c655f8e 100644 --- a/internal/services/keyvault/key_vault_managed_hardware_security_module_resource_test.go +++ b/internal/services/keyvault/key_vault_managed_hardware_security_module_resource_test.go @@ -28,6 +28,8 @@ func TestAccKeyVaultManagedHardwareSecurityModule(t *testing.T) { "update": testAccKeyVaultManagedHardwareSecurityModule_requiresImport, "complete": testAccKeyVaultManagedHardwareSecurityModule_complete, "download": testAccKeyVaultManagedHardwareSecurityModule_download, + "role_define": testAccKeyVaultManagedHardwareSecurityModule_roleDefinition, + "role_assign": testAccKeyVaultManagedHardwareSecurityModule_roleAssignment, }, }) } @@ -76,6 +78,50 @@ func testAccKeyVaultManagedHardwareSecurityModule_download(t *testing.T) { }) } +func testAccKeyVaultManagedHardwareSecurityModule_roleDefinition(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault_managed_hardware_security_module_role_definition", "test") + r := KeyVaultMHSMRoleDefinitionResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.withRoleDefinition(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withRoleDefinitionUpdate(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func testAccKeyVaultManagedHardwareSecurityModule_roleAssignment(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault_managed_hardware_security_module_role_assignment", "test") + r := KeyVaultRoleAssignmentResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.withRoleAssignment(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInRoleAssignment(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func testAccKeyVaultManagedHardwareSecurityModule_requiresImport(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_key_vault_managed_hardware_security_module", "test") r := KeyVaultManagedHardwareSecurityModuleResource{} diff --git a/internal/services/keyvault/key_vault_managed_hardware_security_module_role_assignment_resource.go b/internal/services/keyvault/key_vault_managed_hardware_security_module_role_assignment_resource.go new file mode 100644 index 000000000000..ac5551b507e0 --- /dev/null +++ b/internal/services/keyvault/key_vault_managed_hardware_security_module_role_assignment_resource.go @@ -0,0 +1,196 @@ +package keyvault + +import ( + "context" + "fmt" + "regexp" + "time" + + "github.com/hashicorp/go-azure-sdk/resource-manager/authorization/2022-04-01/roledefinitions" + + "github.com/hashicorp/terraform-provider-azurerm/internal/locks" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/utils" + "github.com/tombuildsstuff/kermit/sdk/keyvault/7.4/keyvault" +) + +type ManagedHSMRoleAssignmentModel struct { + VaultBaseUrl string `tfschema:"vault_base_url"` + Name string `tfschema:"name"` + Scope string `tfschema:"scope"` + RoleDefinitionId string `tfschema:"role_definition_id"` + PrincipalId string `tfschema:"principal_id"` + ResourceId string `tfschema:"resource_id"` +} + +type KeyVaultRoleAssignmentResource struct{} + +var _ sdk.Resource = (*KeyVaultRoleAssignmentResource)(nil) + +func (m KeyVaultRoleAssignmentResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "vault_base_url": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "scope": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(/|/keys|/keys/.+)$`), "scope should be one of `/`, `/keys', `/keys/`"), + }, + + "role_definition_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: roledefinitions.ValidateScopedRoleDefinitionID, + }, + + "principal_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + } +} + +func (m KeyVaultRoleAssignmentResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "resource_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + } +} + +func (m KeyVaultRoleAssignmentResource) ModelObject() interface{} { + return &ManagedHSMRoleAssignmentModel{} +} + +func (m KeyVaultRoleAssignmentResource) ResourceType() string { + return "azurerm_key_vault_managed_hardware_security_module_role_assignment" +} + +func (m KeyVaultRoleAssignmentResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, meta sdk.ResourceMetaData) (err error) { + client := meta.Client.KeyVault.MHSMRoleAssignmentsClient + + var model ManagedHSMRoleAssignmentModel + if err := meta.Decode(&model); err != nil { + return err + } + + locks.ByName(model.VaultBaseUrl, "azurerm_key_vault_managed_hardware_security_module") + defer locks.UnlockByName(model.VaultBaseUrl, "azurerm_key_vault_managed_hardware_security_module") + + id, err := parse.NewMHSMNestedItemID(model.VaultBaseUrl, model.Scope, parse.RoleAssignmentType, model.Name) + if err != nil { + return err + } + + existing, err := client.Get(ctx, model.VaultBaseUrl, model.Scope, model.Name) + if !utils.ResponseWasNotFound(existing.Response) { + if err != nil { + return fmt.Errorf("retrieving %s: %v", id.ID(), err) + } + return meta.ResourceRequiresImport(m.ResourceType(), id) + } + + var param keyvault.RoleAssignmentCreateParameters + param.Properties = &keyvault.RoleAssignmentProperties{ + PrincipalID: pointer.FromString(model.PrincipalId), + RoleDefinitionID: pointer.FromString(model.RoleDefinitionId), + } + _, err = client.Create(ctx, model.VaultBaseUrl, model.Scope, model.Name, param) + if err != nil { + return fmt.Errorf("creating %s: %v", id.ID(), err) + } + + meta.SetID(id) + return nil + }, + } +} + +func (m KeyVaultRoleAssignmentResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, meta sdk.ResourceMetaData) error { + client := meta.Client.KeyVault.MHSMRoleAssignmentsClient + + id, err := parse.MHSMNestedItemID(meta.ResourceData.Id()) + if err != nil { + return err + } + + result, err := client.Get(ctx, id.VaultBaseUrl, id.Scope, id.Name) + if err != nil { + if utils.ResponseWasNotFound(result.Response) { + return meta.MarkAsGone(id) + } + return err + } + + var model ManagedHSMRoleAssignmentModel + if err := meta.Decode(&model); err != nil { + return err + } + + prop := result.Properties + model.Name = pointer.From(result.Name) + model.VaultBaseUrl = id.VaultBaseUrl + model.Scope = id.Scope + + model.RoleDefinitionId = pointer.ToString(prop.RoleDefinitionID) + model.PrincipalId = pointer.ToString(prop.PrincipalID) + model.ResourceId = pointer.ToString(result.ID) + + return meta.Encode(&model) + }, + } +} + +func (m KeyVaultRoleAssignmentResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 10 * time.Minute, + Func: func(ctx context.Context, meta sdk.ResourceMetaData) error { + id, err := parse.MHSMNestedItemID(meta.ResourceData.Id()) + if err != nil { + return err + } + + meta.Logger.Infof("deleting %s", id) + + locks.ByName(id.VaultBaseUrl, "azurerm_key_vault_managed_hardware_security_module") + defer locks.UnlockByName(id.VaultBaseUrl, "azurerm_key_vault_managed_hardware_security_module") + if _, err := meta.Client.KeyVault.MHSMRoleAssignmentsClient.Delete(ctx, id.VaultBaseUrl, id.Scope, id.Name); err != nil { + return fmt.Errorf("deleting %s: %v", id.ID(), err) + } + return nil + }, + } +} + +func (m KeyVaultRoleAssignmentResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.MHSMNestedItemId +} diff --git a/internal/services/keyvault/key_vault_managed_hardware_security_module_role_assignment_resource_test.go b/internal/services/keyvault/key_vault_managed_hardware_security_module_role_assignment_resource_test.go new file mode 100644 index 000000000000..c2922279ff5f --- /dev/null +++ b/internal/services/keyvault/key_vault_managed_hardware_security_module_role_assignment_resource_test.go @@ -0,0 +1,82 @@ +package keyvault_test + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type KeyVaultRoleAssignmentResource struct{} + +// real test nested in TestAccKeyVaultManagedHardwareSecurityModule, only provide Exists logic here +func (k KeyVaultRoleAssignmentResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.MHSMNestedItemID(state.ID) + if err != nil { + return nil, err + } + resp, err := client.KeyVault.MHSMRoleAssignmentsClient.Get(ctx, id.VaultBaseUrl, id.Scope, id.Name) + if err != nil { + return nil, fmt.Errorf("retrieving Type %s: %+v", id, err) + } + return utils.Bool(resp.Properties != nil), nil +} + +func (k KeyVaultRoleAssignmentResource) withRoleAssignment(data acceptance.TestData) string { + roleDef := KeyVaultMHSMRoleDefinitionResource{}.withRoleDefinition(data) + + return fmt.Sprintf(` + + +%s + +locals { + assignmentTestName = "1e243909-064c-6ac3-84e9-1c8bf8d6ad52" +} + +data "azurerm_key_vault_managed_hardware_security_module_role_definition" "test" { + name = azurerm_key_vault_managed_hardware_security_module_role_definition.test.name + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri +} + +resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "test" { + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + name = local.assignmentTestName + scope = "/keys" + role_definition_id = data.azurerm_key_vault_managed_hardware_security_module_role_definition.test.resource_manager_id + principal_id = data.azurerm_client_config.current.object_id +} +`, roleDef) +} + +func (k KeyVaultRoleAssignmentResource) withBuiltInRoleAssignment(data acceptance.TestData) string { + roleDef := k.withRoleAssignment(data) + + return fmt.Sprintf(` + + +%s + +locals { + assignmentOfficerName = "706c03c7-69ad-33e5-2796-b3380d3a6e1a" +} + +data "azurerm_key_vault_managed_hardware_security_module_role_definition" "officer" { + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + name = "515eb02d-2335-4d2d-92f2-b1cbdf9c3778" +} + +resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "officer" { + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + name = local.assignmentOfficerName + scope = "/keys" + role_definition_id = data.azurerm_key_vault_managed_hardware_security_module_role_definition.officer.resource_manager_id + principal_id = data.azurerm_client_config.current.object_id +} +`, roleDef) +} diff --git a/internal/services/keyvault/key_vault_managed_hardware_security_module_role_definition_data_source.go b/internal/services/keyvault/key_vault_managed_hardware_security_module_role_definition_data_source.go new file mode 100644 index 000000000000..b961ab5df3f6 --- /dev/null +++ b/internal/services/keyvault/key_vault_managed_hardware_security_module_role_definition_data_source.go @@ -0,0 +1,170 @@ +package keyvault + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/terraform-provider-azurerm/utils" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +type KeyVaultMHSMRoleDefinitionDataSourceModel struct { + Name string `tfschema:"name"` + RoleName string `tfschema:"role_name"` + VaultBaseUrl string `tfschema:"vault_base_url"` + Description string `tfschema:"description"` + AssignableScopes []string `tfschema:"assignable_scopes"` + Permission []Permission `tfschema:"permission"` + RoleType string `tfschema:"role_type"` + ResourceManagerId string `tfschema:"resource_manager_id"` +} + +type KeyvaultMHSMRoleDefinitionDataSource struct{} + +var _ sdk.DataSource = KeyvaultMHSMRoleDefinitionDataSource{} + +func (k KeyvaultMHSMRoleDefinitionDataSource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.IsUUID, + }, + + "vault_base_url": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.IsURLWithHTTPorHTTPS, + }, + } +} + +func (k KeyvaultMHSMRoleDefinitionDataSource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "role_type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "role_name": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "resource_manager_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "description": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "assignable_scopes": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "permission": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "actions": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "not_actions": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "data_actions": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "not_data_actions": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, + }, + }, + } +} + +func (k KeyvaultMHSMRoleDefinitionDataSource) ModelObject() interface{} { + return &KeyVaultMHSMRoleDefinitionDataSourceModel{} +} + +func (k KeyvaultMHSMRoleDefinitionDataSource) ResourceType() string { + return "azurerm_key_vault_managed_hardware_security_module_role_definition" +} + +func (k KeyvaultMHSMRoleDefinitionDataSource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, meta sdk.ResourceMetaData) error { + var model KeyVaultMHSMRoleDefinitionDataSourceModel + if err := meta.Decode(&model); err != nil { + return err + } + + id, err := parse.NewMHSMNestedItemID(model.VaultBaseUrl, roleDefinitionScope, parse.RoleDefinitionType, model.Name) + if err != nil { + return err + } + + client := meta.Client.KeyVault.MHSMRoleClient + result, err := client.Get(ctx, id.VaultBaseUrl, roleDefinitionScope, id.Name) + if err != nil { + if utils.ResponseWasNotFound(result.Response) { + return fmt.Errorf("%s does not exist", id) + } + return err + } + + model.ResourceManagerId = pointer.From(result.ID) + if prop := result.RoleDefinitionProperties; prop != nil { + model.Description = pointer.ToString(prop.Description) + model.RoleType = string(prop.RoleType) + model.RoleName = pointer.From(prop.RoleName) + + if prop.AssignableScopes != nil { + for _, r := range *prop.AssignableScopes { + model.AssignableScopes = append(model.AssignableScopes, string(r)) + } + } + + model.Permission = flattenKeyVaultMHSMRolePermission(prop.Permissions) + } + + meta.SetID(id) + return meta.Encode(&model) + }, + } +} diff --git a/internal/services/keyvault/key_vault_managed_hardware_security_module_role_definition_resource.go b/internal/services/keyvault/key_vault_managed_hardware_security_module_role_definition_resource.go new file mode 100644 index 000000000000..179398fc591b --- /dev/null +++ b/internal/services/keyvault/key_vault_managed_hardware_security_module_role_definition_resource.go @@ -0,0 +1,359 @@ +package keyvault + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/go-azure-helpers/lang/response" + + "github.com/hashicorp/terraform-provider-azurerm/internal/locks" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/utils" + "github.com/tombuildsstuff/kermit/sdk/keyvault/7.4/keyvault" +) + +const roleDefinitionScope = "/" + +type Permission struct { + Actions []string `tfschema:"actions"` + NotActions []string `tfschema:"not_actions"` + DataActions []string `tfschema:"data_actions"` + NotDataActions []string `tfschema:"not_data_actions"` +} + +type KeyVaultMHSMRoleDefinitionModel struct { + Name string `tfschema:"name"` + RoleName string `tfschema:"role_name"` + VaultBaseUrl string `tfschema:"vault_base_url"` + Description string `tfschema:"description"` + Permission []Permission `tfschema:"permission"` + RoleType string `tfschema:"role_type"` + ResourceManagerId string `tfschema:"resource_manager_id"` +} + +type KeyVaultMHSMRoleDefinitionResource struct{} + +var _ sdk.ResourceWithUpdate = KeyVaultMHSMRoleDefinitionResource{} + +// Arguments ... +// skip `assignable_scopes` field support as https://github.com/Azure/azure-rest-api-specs/issues/23045 +func (k KeyVaultMHSMRoleDefinitionResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.IsUUID, + }, + + "role_name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "vault_base_url": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.IsURLWithHTTPorHTTPS, + }, + + "permission": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "actions": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + + "not_actions": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + + "data_actions": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringInSlice(func() (res []string) { + for _, v := range keyvault.PossibleDataActionValues() { + res = append(res, string(v)) + } + return + }(), false), + }, + Set: pluginsdk.HashString, + }, + + "not_data_actions": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringInSlice(func() (res []string) { + for _, v := range keyvault.PossibleDataActionValues() { + res = append(res, string(v)) + } + return + }(), false), + }, + Set: pluginsdk.HashString, + }, + }, + }, + }, + + "description": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + } +} + +func (k KeyVaultMHSMRoleDefinitionResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "role_type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "resource_manager_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + } +} + +func (k KeyVaultMHSMRoleDefinitionResource) ModelObject() interface{} { + return &KeyVaultMHSMRoleDefinitionModel{} +} + +func (k KeyVaultMHSMRoleDefinitionResource) ResourceType() string { + return "azurerm_key_vault_managed_hardware_security_module_role_definition" +} + +func (k KeyVaultMHSMRoleDefinitionResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, meta sdk.ResourceMetaData) (err error) { + client := meta.Client.KeyVault.MHSMRoleClient + + var model KeyVaultMHSMRoleDefinitionModel + if err = meta.Decode(&model); err != nil { + return err + } + + // need a lock for hsm subresource create/update/delete, or API may respond error as below + // Status=409 Code="Conflict" Message="There was a conflict while trying to delete the role assignment. + locks.ByName(model.VaultBaseUrl, "azurerm_key_vault_managed_hardware_security_module") + defer locks.UnlockByName(model.VaultBaseUrl, "azurerm_key_vault_managed_hardware_security_module") + + id, err := parse.NewMHSMNestedItemID(model.VaultBaseUrl, roleDefinitionScope, parse.RoleDefinitionType, model.Name) + if err != nil { + return err + } + + existing, err := client.Get(ctx, id.VaultBaseUrl, id.Scope, id.Name) + if !utils.ResponseWasNotFound(existing.Response) { + if err != nil { + return fmt.Errorf("retrieving role definition by name %s: %v", model.Name, err) + } + return meta.ResourceRequiresImport(k.ResourceType(), id) + } + + var param keyvault.RoleDefinitionCreateParameters + param.Properties = &keyvault.RoleDefinitionProperties{} + prop := param.Properties + prop.RoleName = pointer.To(model.RoleName) + prop.Description = pointer.To(model.Description) + prop.RoleType = keyvault.RoleTypeCustomRole + prop.Permissions = expandKeyVaultMHSMRolePermissions(model.Permission) + prop.AssignableScopes = pointer.To([]keyvault.RoleScope{"/"}) + + _, err = client.CreateOrUpdate(ctx, model.VaultBaseUrl, roleDefinitionScope, model.Name, param) + if err != nil { + return fmt.Errorf("creating %s: %v", id.ID(), err) + } + + meta.SetID(id) + return nil + }, + } +} + +func (k KeyVaultMHSMRoleDefinitionResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, meta sdk.ResourceMetaData) error { + // import has no model data but only id + id, err := parse.MHSMNestedItemID(meta.ResourceData.Id()) + if err != nil { + return err + } + + var model KeyVaultMHSMRoleDefinitionModel + if err = meta.Decode(&model); err != nil { + return err + } + + client := meta.Client.KeyVault.MHSMRoleClient + result, err := client.Get(ctx, id.VaultBaseUrl, id.Scope, id.Name) + if err != nil { + if response.WasNotFound(result.Response.Response) { + return meta.MarkAsGone(id) + } + return err + } + model.Name = pointer.From(result.Name) + model.VaultBaseUrl = id.VaultBaseUrl + model.ResourceManagerId = pointer.From(result.ID) + + if prop := result.RoleDefinitionProperties; prop != nil { + model.Description = pointer.ToString(prop.Description) + model.RoleType = string(prop.RoleType) + model.RoleName = pointer.From(prop.RoleName) + + model.Permission = flattenKeyVaultMHSMRolePermission(prop.Permissions) + } + + meta.SetID(id) + return meta.Encode(&model) + }, + } +} + +func (k KeyVaultMHSMRoleDefinitionResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: time.Minute * 10, + Func: func(ctx context.Context, meta sdk.ResourceMetaData) (err error) { + client := meta.Client.KeyVault.MHSMRoleClient + + var model KeyVaultMHSMRoleDefinitionModel + if err = meta.Decode(&model); err != nil { + return err + } + + id, err := parse.NewMHSMNestedItemID(model.VaultBaseUrl, roleDefinitionScope, parse.RoleDefinitionType, model.Name) + if err != nil { + return err + } + + locks.ByName(model.VaultBaseUrl, "azurerm_key_vault_managed_hardware_security_module") + defer locks.UnlockByName(model.VaultBaseUrl, "azurerm_key_vault_managed_hardware_security_module") + + existing, err := client.Get(ctx, id.VaultBaseUrl, id.Scope, id.Name) + if err != nil { + if response.WasNotFound(existing.Response.Response) { + return fmt.Errorf("not found resource to update: %s", id) + } + return fmt.Errorf("retrieving role definition by name %s: %v", model.Name, err) + } + + var param keyvault.RoleDefinitionCreateParameters + param.Properties = &keyvault.RoleDefinitionProperties{} + prop := param.Properties + prop.RoleName = pointer.To(model.RoleName) + prop.Description = pointer.To(model.Description) + prop.RoleType = keyvault.RoleTypeCustomRole + prop.Permissions = expandKeyVaultMHSMRolePermissions(model.Permission) + + _, err = client.CreateOrUpdate(ctx, model.VaultBaseUrl, roleDefinitionScope, model.Name, param) + if err != nil { + return fmt.Errorf("creating %s: %v", id.ID(), err) + } + + meta.SetID(id) + return nil + }, + } +} + +func (k KeyVaultMHSMRoleDefinitionResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 10 * time.Minute, + Func: func(ctx context.Context, meta sdk.ResourceMetaData) error { + id, err := parse.MHSMNestedItemID(meta.ResourceData.Id()) + if err != nil { + return err + } + meta.Logger.Infof("deleting %s", id.ID()) + + locks.ByName(id.VaultBaseUrl, "azurerm_key_vault_managed_hardware_security_module") + defer locks.UnlockByName(id.VaultBaseUrl, "azurerm_key_vault_managed_hardware_security_module") + if _, err = meta.Client.KeyVault.MHSMRoleClient.Delete(ctx, id.VaultBaseUrl, id.Scope, id.Name); err != nil { + return fmt.Errorf("deleting %+v: %v", id, err) + } + return nil + }, + } +} + +func (k KeyVaultMHSMRoleDefinitionResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.MHSMNestedItemId +} + +func expandKeyVaultMHSMRolePermissions(perms []Permission) *[]keyvault.Permission { + var res []keyvault.Permission + for _, perm := range perms { + var dataActions, notDataActions []keyvault.DataAction + for _, data := range perm.DataActions { + dataActions = append(dataActions, keyvault.DataAction(data)) + } + for _, notData := range perm.NotDataActions { + notDataActions = append(notDataActions, keyvault.DataAction(notData)) + } + + res = append(res, keyvault.Permission{ + Actions: pointer.To(perm.Actions), + NotActions: pointer.To(perm.NotActions), + DataActions: pointer.To(dataActions), + NotDataActions: pointer.To(notDataActions), + }) + } + return &res +} + +func flattenKeyVaultMHSMRolePermission(perms *[]keyvault.Permission) []Permission { + if perms == nil { + return make([]Permission, 0) + } + + var res []Permission + for _, perm := range *perms { + var data, notData []string + for _, item := range pointer.From(perm.DataActions) { + data = append(data, string(item)) + } + for _, item := range pointer.From(perm.NotDataActions) { + notData = append(notData, string(item)) + } + + res = append(res, Permission{ + Actions: pointer.From(perm.Actions), + NotActions: pointer.From(perm.NotActions), + DataActions: data, + NotDataActions: notData, + }) + } + return res +} diff --git a/internal/services/keyvault/key_vault_managed_hardware_security_module_role_definition_resource_test.go b/internal/services/keyvault/key_vault_managed_hardware_security_module_role_definition_resource_test.go new file mode 100644 index 000000000000..e503b064b1d3 --- /dev/null +++ b/internal/services/keyvault/key_vault_managed_hardware_security_module_role_definition_resource_test.go @@ -0,0 +1,91 @@ +package keyvault_test + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type KeyVaultMHSMRoleDefinitionResource struct{} + +// real test nested in TestAccKeyVaultManagedHardwareSecurityModule, only provide Exists logic here +func (k KeyVaultMHSMRoleDefinitionResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + baseURL := state.Attributes["vault_base_url"] + id, err := parse.MHSMNestedItemID(state.ID) + if err != nil { + return nil, err + } + resp, err := client.KeyVault.MHSMRoleClient.Get(ctx, baseURL, "/", id.Name) + if err != nil { + return nil, fmt.Errorf("retrieving Type %s: %+v", id, err) + } + return utils.Bool(resp.RoleDefinitionProperties != nil), nil +} + +func (k KeyVaultMHSMRoleDefinitionResource) withRoleDefinition(data acceptance.TestData) string { + hsm := KeyVaultManagedHardwareSecurityModuleResource{}.download(data, 3) + return fmt.Sprintf(` + + +%s + +locals { + roleTestName = "c9562a52-2bd9-2671-3d89-cea5b4798a6b" +} + +resource "azurerm_key_vault_managed_hardware_security_module_role_definition" "test" { + name = local.roleTestName + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + description = "desc foo" + permission { + data_actions = [ + "Microsoft.KeyVault/managedHsm/keys/read/action", + "Microsoft.KeyVault/managedHsm/keys/write/action", + "Microsoft.KeyVault/managedHsm/keys/encrypt/action", + "Microsoft.KeyVault/managedHsm/keys/create", + "Microsoft.KeyVault/managedHsm/keys/delete", + ] + not_data_actions = [ + "Microsoft.KeyVault/managedHsm/roleAssignments/read/action", + ] + } +} +`, hsm) +} + +func (k KeyVaultMHSMRoleDefinitionResource) withRoleDefinitionUpdate(data acceptance.TestData) string { + hsm := KeyVaultManagedHardwareSecurityModuleResource{}.download(data, 3) + return fmt.Sprintf(` + + +%s + +locals { + roleTestName = "c9562a52-2bd9-2671-3d89-cea5b4798a6b" +} + +resource "azurerm_key_vault_managed_hardware_security_module_role_definition" "test" { + name = local.roleTestName + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + description = "desc foo2" + permission { + data_actions = [ + "Microsoft.KeyVault/managedHsm/keys/read/action", + "Microsoft.KeyVault/managedHsm/keys/write/action", + "Microsoft.KeyVault/managedHsm/keys/encrypt/action", + "Microsoft.KeyVault/managedHsm/keys/create", + ] + not_data_actions = [ + "Microsoft.KeyVault/managedHsm/roleAssignments/read/action", + "Microsoft.KeyVault/managedHsm/keys/delete", + ] + } +} +`, hsm) +} diff --git a/internal/services/keyvault/parse/mhsm_nested_item.go b/internal/services/keyvault/parse/mhsm_nested_item.go new file mode 100644 index 000000000000..42db8590e07f --- /dev/null +++ b/internal/services/keyvault/parse/mhsm_nested_item.go @@ -0,0 +1,95 @@ +package parse + +import ( + "fmt" + "net/url" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +var _ resourceids.Id = MHSMNestedItemId{} + +type MHSMResourceType string + +const ( + RoleDefinitionType MHSMResourceType = "RoleDefinition" + RoleAssignmentType MHSMResourceType = "RoleAssignment" +) + +type MHSMNestedItemId struct { + VaultBaseUrl string + Scope string + Type MHSMResourceType + Name string +} + +func NewMHSMNestedItemID(hsmBaseUrl, scope string, typ MHSMResourceType, name string) (*MHSMNestedItemId, error) { + keyVaultUrl, err := url.Parse(hsmBaseUrl) + if err != nil || hsmBaseUrl == "" { + return nil, fmt.Errorf("parsing managedHSM nested itemID %q: %+v", hsmBaseUrl, err) + } + // (@jackofallops) - Log Analytics service adds the port number to the API returns, so we strip it here + if hostParts := strings.Split(keyVaultUrl.Host, ":"); len(hostParts) > 1 { + keyVaultUrl.Host = hostParts[0] + } + + return &MHSMNestedItemId{ + VaultBaseUrl: keyVaultUrl.String(), + Scope: scope, + Type: typ, + Name: name, + }, nil +} + +func (n MHSMNestedItemId) ID() string { + // example: https://tharvey-keyvault.managedhsm.azure.net///uuid-idshifds-fks + segments := []string{ + strings.TrimSuffix(n.VaultBaseUrl, "/"), + n.Scope, + string(n.Type), + n.Name, + } + return strings.TrimSuffix(strings.Join(segments, "/"), "/") +} + +func (n MHSMNestedItemId) String() string { + return n.ID() +} + +func MHSMNestedItemID(input string) (*MHSMNestedItemId, error) { + return parseMHSMNestedItemId(input) +} + +func parseMHSMNestedItemId(id string) (*MHSMNestedItemId, error) { + idURL, err := url.ParseRequestURI(id) + if err != nil { + return nil, fmt.Errorf("Cannot parse Azure KeyVault Child Id: %s", err) + } + + path := idURL.Path + + path = strings.TrimPrefix(path, "/") + path = strings.TrimSuffix(path, "/") + + nameSep := strings.LastIndex(path, "/") + if nameSep <= 0 { + return nil, fmt.Errorf("no name speparate exist in %s", id) + } + scope, name := path[:nameSep], path[nameSep+1:] + + typeSep := strings.LastIndex(scope, "/") + if typeSep <= 0 { + return nil, fmt.Errorf("no type speparate exist in %s", id) + } + scope, typ := scope[:typeSep], scope[typeSep+1:] + + childId := MHSMNestedItemId{ + VaultBaseUrl: fmt.Sprintf("%s://%s/", idURL.Scheme, idURL.Host), + Scope: scope, + Type: MHSMResourceType(typ), + Name: name, + } + + return &childId, nil +} diff --git a/internal/services/keyvault/parse/mhsm_nested_item_test.go b/internal/services/keyvault/parse/mhsm_nested_item_test.go new file mode 100644 index 000000000000..66772e13f2d2 --- /dev/null +++ b/internal/services/keyvault/parse/mhsm_nested_item_test.go @@ -0,0 +1,153 @@ +package parse + +import ( + "fmt" + "testing" +) + +func TestNewMHSMNestedItemID(t *testing.T) { + mhsmType := RoleDefinitionType + cases := []struct { + Scenario string + keyVaultBaseUrl string + Expected string + Scope string + Name string + ExpectError bool + }{ + { + Scenario: "empty values", + keyVaultBaseUrl: "", + Expected: "", + ExpectError: true, + }, + { + Scenario: "valid, no port", + keyVaultBaseUrl: "https://test.managedhsm.azure.net", + Scope: "/", + Name: "test", + Expected: fmt.Sprintf("https://test.managedhsm.azure.net///%s/test", mhsmType), + ExpectError: false, + }, + { + Scenario: "valid, with port", + keyVaultBaseUrl: "https://test.managedhsm.azure.net:443", + Scope: "/keys", + Name: "test", + Expected: fmt.Sprintf("https://test.managedhsm.azure.net//keys/%s/test", mhsmType), + ExpectError: false, + }, + } + for idx, tc := range cases { + id, err := NewMHSMNestedItemID(tc.keyVaultBaseUrl, tc.Scope, mhsmType, tc.Name) + if err != nil { + if !tc.ExpectError { + t.Fatalf("Got error for New Resource ID '%s': %+v", tc.keyVaultBaseUrl, err) + return + } + continue + } + if id.ID() != tc.Expected { + t.Fatalf("Expected %d id for %q to be %q, got %q", idx, tc.keyVaultBaseUrl, tc.Expected, id) + } + } +} + +func TestParseMHSMNestedItemID(t *testing.T) { + typ := RoleDefinitionType + cases := []struct { + Input string + Expected MHSMNestedItemId + ExpectError bool + }{ + { + Input: "", + ExpectError: true, + }, + { + Input: fmt.Sprintf("https://my-keyvault.managedhsm.azure.net///%s/test", typ), + ExpectError: true, + Expected: MHSMNestedItemId{ + Name: "test", + VaultBaseUrl: "https://my-keyvault.managedhsm.azure.net/", + Scope: "/", + }, + }, + { + Input: fmt.Sprintf("https://my-keyvault.managedhsm.azure.net///%s/bird", typ), + ExpectError: true, + Expected: MHSMNestedItemId{ + Name: "bird", + VaultBaseUrl: "https://my-keyvault.managedhsm.azure.net/", + Scope: "/", + }, + }, + { + Input: fmt.Sprintf("https://my-keyvault.managedhsm.azure.net///%s/bird", typ), + ExpectError: false, + Expected: MHSMNestedItemId{ + Name: "bird", + VaultBaseUrl: "https://my-keyvault.managedhsm.azure.net/", + Scope: "/", + }, + }, + { + Input: fmt.Sprintf("https://my-keyvault.managedhsm.azure.net//keys/%s/world", typ), + ExpectError: false, + Expected: MHSMNestedItemId{ + Name: "world", + VaultBaseUrl: "https://my-keyvault.managedhsm.azure.net/", + Scope: "/keys", + }, + }, + { + Input: fmt.Sprintf("https://my-keyvault.managedhsm.azure.net//keys/%s/fdf067c93bbb4b22bff4d8b7a9a56217", typ), + ExpectError: true, + Expected: MHSMNestedItemId{ + Name: "fdf067c93bbb4b22bff4d8b7a9a56217", + VaultBaseUrl: "https://my-keyvault.managedhsm.azure.net/", + Scope: "/keys", + }, + }, + { + Input: "https://kvhsm23030816100222.managedhsm.azure.net///RoleDefinition/862d4d5e-bf01-11ed-a49d-00155d61ee9e", + ExpectError: true, + Expected: MHSMNestedItemId{ + Name: "862d4d5e-bf01-11ed-a49d-00155d61ee9e", + VaultBaseUrl: "https://kvhsm23030816100222.managedhsm.azure.net/", + Scope: "/", + }, + }, + } + + for idx, tc := range cases { + secretId, err := MHSMNestedItemID(tc.Input) + if err != nil { + if tc.ExpectError { + continue + } + + t.Fatalf("Got error for ID '%s': %+v", tc.Input, err) + } + + if secretId == nil { + t.Fatalf("Expected a SecretID to be parsed for ID '%s', got nil.", tc.Input) + } + + if tc.Expected.VaultBaseUrl != secretId.VaultBaseUrl { + t.Fatalf("Expected %d 'KeyVaultBaseUrl' to be '%s', got '%s' for ID '%s'", idx, tc.Expected.VaultBaseUrl, secretId.VaultBaseUrl, tc.Input) + } + + if tc.Expected.Name != secretId.Name { + t.Fatalf("Expected 'Name' to be '%s', got '%s' for ID '%s'", tc.Expected.Name, secretId.Name, tc.Input) + } + + if tc.Expected.Scope != secretId.Scope { + t.Fatalf("Expected 'Scope' to be '%s', got '%s' for ID '%s'", tc.Expected.Scope, secretId.Scope, tc.Input) + } + + if tc.Input != secretId.ID() { + t.Fatalf("Expected 'ID()' to be '%s', got '%s'", tc.Input, secretId.ID()) + } + } +} diff --git a/internal/services/keyvault/registration.go b/internal/services/keyvault/registration.go index 712891f0e8c4..764977559258 100644 --- a/internal/services/keyvault/registration.go +++ b/internal/services/keyvault/registration.go @@ -63,11 +63,14 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { func (r Registration) DataSources() []sdk.DataSource { return []sdk.DataSource{ EncryptedValueDataSource{}, + KeyvaultMHSMRoleDefinitionDataSource{}, } } func (r Registration) Resources() []sdk.Resource { return []sdk.Resource{ KeyVaultCertificateContactsResource{}, + KeyVaultMHSMRoleDefinitionResource{}, + KeyVaultRoleAssignmentResource{}, } } diff --git a/internal/services/keyvault/validate/mhsm_nested_item_id.go b/internal/services/keyvault/validate/mhsm_nested_item_id.go new file mode 100644 index 000000000000..c3d65f06af9c --- /dev/null +++ b/internal/services/keyvault/validate/mhsm_nested_item_id.go @@ -0,0 +1,27 @@ +package validate + +import ( + "fmt" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +func MHSMNestedItemId(i interface{}, k string) (warnings []string, errors []error) { + if warnings, errors = validation.StringIsNotEmpty(i, k); len(errors) > 0 { + return warnings, errors + } + + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("Expected %s to be a string!", k)) + return warnings, errors + } + + if _, err := parse.MHSMNestedItemID(v); err != nil { + errors = append(errors, fmt.Errorf("parsing %q: %s", v, err)) + return warnings, errors + } + + return warnings, errors +} diff --git a/internal/services/keyvault/validate/mhsm_nested_item_id_test.go b/internal/services/keyvault/validate/mhsm_nested_item_id_test.go new file mode 100644 index 000000000000..95db11b31c44 --- /dev/null +++ b/internal/services/keyvault/validate/mhsm_nested_item_id_test.go @@ -0,0 +1,55 @@ +package validate + +import ( + "testing" +) + +func TestMHSMNestedItemId(t *testing.T) { + cases := []struct { + Input string + ExpectError bool + }{ + { + Input: "", + ExpectError: true, + }, + { + Input: "https://my-keyvault.managedhsm.azure.net///test", + ExpectError: false, + }, + { + Input: "https://my-keyvault.managedhsm.azure.net//keys/test", + ExpectError: false, + }, + + { + Input: "https://my-keyvault.managedhsm.azure.net/certificates/hello/world", + ExpectError: true, + }, + { + Input: "https://my-keyvault.managedhsm.azure.net/keys/castle/1492", + ExpectError: false, + }, + { + Input: "https://my-keyvault.managedhsm.azure.net/secrets/bird/fdf067c93bbb4b22bff4d8b7a9a56217/XXX", + ExpectError: true, + }, + } + + for _, tc := range cases { + warnings, err := MHSMNestedItemId(tc.Input, "example") + if err != nil { + if !tc.ExpectError { + t.Fatalf("Got error for input %q: %+v", tc.Input, err) + } + + return + } + + if tc.ExpectError && len(warnings) == 0 { + t.Fatalf("Got no errors for input %q but expected some", tc.Input) + } else if !tc.ExpectError && len(warnings) > 0 { + t.Fatalf("Got %d errors for input %q when didn't expect any", len(warnings), tc.Input) + } + } +} diff --git a/website/docs/d/key_vault_managed_hardware_security_module_role_definition.html.markdown b/website/docs/d/key_vault_managed_hardware_security_module_role_definition.html.markdown new file mode 100644 index 000000000000..8deb4a149f28 --- /dev/null +++ b/website/docs/d/key_vault_managed_hardware_security_module_role_definition.html.markdown @@ -0,0 +1,71 @@ +--- +subcategory: "Key Vault" +layout: "azurerm" +page_title: "Azure Resource Manager: Data Source: azurerm_key_vault_managed_hardware_security_module_role_definition" +description: |- + Gets information about an existing KeyVault Role Definition. +--- + +# Data Source: azurerm_key_vault_managed_hardware_security_module_role_definition + +Use this data source to access information about an existing KeyVault Role Definition. + +## Example Usage + +```hcl +data "azurerm_key_vault_managed_hardware_security_module_role_definition" "example" { + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + name = "21dbd100-6940-42c2-9190-5d6cb909625b" + scope = "/" +} + +output "id" { + value = data.azurerm_key_vault_managed_hardware_security_module_role_definition.example.resource_manager_id +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name in UUID notation of this KeyVault Role Definition. + +* `vault_base_url` - (Required) Specify the base URL of the Managed HSM resource. + +* `scope` - (Optional) Specify the scope to retrieve the role definition. Defaults to `/`. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the KeyVault Role Definition. + +* `assignable_scopes` - A list of assignable role scope. Possible values are `/` and `/keys`. + +* `description` - A text description of this role definition. + +* `permission` - A `permission` block as defined below. + +* `resource_manager_id` - The ID of the role definition resource without base url. + +* `role_name` - The role name of the role definition. + +* `role_type` - The type of the role definition. Possible values are `AKVBuiltInRole` and `CustomRole`. + +--- + +A `permission` block exports the following: + +* `actions` - A list of action permission granted. + +* `not_actions` - A list of action permission excluded (but not denied). + +* `data_actions` - A list of data action permission granted. + +* `not_data_actions` - (Optional) A list of data action permission granted. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `read` - (Defaults to 5 minutes) Used when retrieving the KeyVault. diff --git a/website/docs/r/key_vault_managed_hardware_security_module_role_assignment.html.markdown b/website/docs/r/key_vault_managed_hardware_security_module_role_assignment.html.markdown new file mode 100644 index 000000000000..f2f2ac86d320 --- /dev/null +++ b/website/docs/r/key_vault_managed_hardware_security_module_role_assignment.html.markdown @@ -0,0 +1,67 @@ +--- +subcategory: "Key Vault" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_key_vault_role_assignment" +description: |- + Manages a KeyVault Role Assignment. +--- + +# azurerm_key_vault_role_assignment + +Manages a KeyVault Role Assignment. + +## Example Usage + +```hcl +data "azurerm_key_vault_role_definition" "user" { + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + name = "21dbd100-6940-42c2-9190-5d6cb909625b" + scope = "/" +} + +resource "azurerm_key_vault_role_assignment" "example" { + name = "a9dbe818-56e7-5878-c0ce-a1477692c1d6" + vault_base_url = azurerm_key_vault_managed_hardware_security_module.example.hsm_uri + scope = "${data.azurerm_key_vault_role_definition.user.scope}" + role_definition_id = "${data.azurerm_key_vault_role_definition.user.resource_id}" + principal_id = "${data.azurerm_client_config.current.object_id}" +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name in GUID notation which should be used for this KeyVault Role Assignment. Changing this forces a new KeyVault to be created. + +* `principal_id` - (Required) The principal ID to be assigned to this role. It can point to a user, service principal, or security group. Changing this forces a new KeyVault to be created. + +* `role_definition_id` - (Required) The resource ID of the role definition to assign. Changing this forces a new KeyVault to be created. + +* `scope` - (Required) Specifies the scope to create the role assignment. Changing this forces a new KeyVault to be created. + +* `vault_base_url` - (Required) The HSM URI of a Managed Hardware Security Module resource. Changing this forces a new KeyVault to be created. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the KeyVault Role Assignment with Vault Base URL. + +* `resource_id` - The resource id of created assignment resource. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the KeyVault. +* `read` - (Defaults to 5 minutes) Used when retrieving the KeyVault. +* `delete` - (Defaults to 10 minutes) Used when deleting the KeyVault. + +## Import + +KeyVaults can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_key_vault_role_assignment.example https://0000.managedhsm.azure.net///RoleAssignment/00000000-0000-0000-0000-000000000000 +``` diff --git a/website/docs/r/key_vault_managed_hardware_security_module_role_definition.html.markdown b/website/docs/r/key_vault_managed_hardware_security_module_role_definition.html.markdown new file mode 100644 index 000000000000..7c34108e8a26 --- /dev/null +++ b/website/docs/r/key_vault_managed_hardware_security_module_role_definition.html.markdown @@ -0,0 +1,106 @@ +--- +subcategory: "Key Vault" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_key_vault_managed_hardware_security_module_role_definition" +description: |- + Manages a KeyVault Managed Hardware Security Module Role Definition. +--- + +# azurerm_key_vault_managed_hardware_security_module_role_definition + +Manages a KeyVault Managed Hardware Security Module Role Definition. This resource works together with [Managed hardware security module resource](./key_vault_managed_hardware_security_module). + +## Example Usage + +```hcl +resource "azurerm_key_vault_managed_hardware_security_module" "example" { + name = "example" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + sku_name = "Standard_B1" + tenant_id = data.azurerm_client_config.current.tenant_id + admin_object_ids = [data.azurerm_client_config.current.object_id] + purge_protection_enabled = false + + active_config { + security_domain_certificate = [ + azurerm_key_vault_certificate.cert[0].id, + azurerm_key_vault_certificate.cert[1].id, + azurerm_key_vault_certificate.cert[2].id, + ] + security_domain_quorum = 2 + } +} + +resource "azurerm_key_vault_managed_hardware_security_module_role_definition" "example" { + name = "7d206142-bf01-11ed-80bc-00155d61ee9e" + scope = "/" + vault_base_url = azurerm_key_vault_managed_hardware_security_module.example.hsm_uri + description = "desc foo" + assignable_scopes = ["/"] + permission { + data_actions = [ + "Microsoft.KeyVault/managedHsm/keys/read/action", + ] + } +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this KeyVault Role Definition. Changing this forces a new KeyVault Role Definition to be created. + +* `vault_base_url` - (Required) The base URL of the managed hardware security module resource. Changing this forces a new KeyVault Role Definition to be created. + +--- + +* `assignable_scopes` - (Optional) Specifies a list of scopes allowed to assign. Possible value is `/`. + +* `description` - (Optional) Specifies a text description about this KeyVault Role Definition. + +* `permission` - (Optional) One or more `permission` blocks as defined below. + +* `role_name` - (Optional) Specify a name for this KeyVault Role Definition. + +* `scope` - (Optional) Specify the scope of this role definition. Possible value is `/`. Defaults to `/`. Changing this forces a new KeyVault to be created. + +--- + +A `permission` block supports the following, more details about permission see [permitted-operations](https://learn.microsoft.com/en-us/azure/key-vault/managed-hsm/built-in-roles#permitted-operations): + +* `actions` - (Optional) One or more Allowed Actions, such as `*`, `Microsoft.Resources/subscriptions/resourceGroups/read`. See ['Azure Resource Manager resource provider operations'](https://docs.microsoft.com/azure/role-based-access-control/resource-provider-operations) for details. + +* `not_actions` - (Optional) One or more Disallowed Actions, such as `*`, `Microsoft.Resources/subscriptions/resourceGroups/read`. See ['Azure Resource Manager resource provider operations'](https://docs.microsoft.com/azure/role-based-access-control/resource-provider-operations) for details. + +* `data_actions` - (Optional) Specifies a list of data action permission to grant. Possible values are `Microsoft.KeyVault/managedHsm/keys/read/action`, `Microsoft.KeyVault/managedHsm/keys/write/action`, `Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action`, `Microsoft.KeyVault/managedHsm/keys/deletedKeys/recover/action`, `Microsoft.KeyVault/managedHsm/keys/backup/action`, `Microsoft.KeyVault/managedHsm/keys/restore/action`, `Microsoft.KeyVault/managedHsm/roleAssignments/delete/action`, `Microsoft.KeyVault/managedHsm/roleAssignments/read/action`, `Microsoft.KeyVault/managedHsm/roleAssignments/write/action`, `Microsoft.KeyVault/managedHsm/roleDefinitions/read/action`, `Microsoft.KeyVault/managedHsm/roleDefinitions/write/action`, `Microsoft.KeyVault/managedHsm/roleDefinitions/delete/action`, `Microsoft.KeyVault/managedHsm/keys/encrypt/action`, `Microsoft.KeyVault/managedHsm/keys/decrypt/action`, `Microsoft.KeyVault/managedHsm/keys/wrap/action`, `Microsoft.KeyVault/managedHsm/keys/unwrap/action`, `Microsoft.KeyVault/managedHsm/keys/sign/action`, `Microsoft.KeyVault/managedHsm/keys/verify/action`, `Microsoft.KeyVault/managedHsm/keys/create`, `Microsoft.KeyVault/managedHsm/keys/delete`, `Microsoft.KeyVault/managedHsm/keys/export/action`, `Microsoft.KeyVault/managedHsm/keys/release/action`, `Microsoft.KeyVault/managedHsm/keys/import/action`, `Microsoft.KeyVault/managedHsm/keys/deletedKeys/delete`, `Microsoft.KeyVault/managedHsm/securitydomain/download/action`, `Microsoft.KeyVault/managedHsm/securitydomain/download/read`, `Microsoft.KeyVault/managedHsm/securitydomain/upload/action`, `Microsoft.KeyVault/managedHsm/securitydomain/upload/read`, `Microsoft.KeyVault/managedHsm/securitydomain/transferkey/read`, `Microsoft.KeyVault/managedHsm/backup/start/action`, `Microsoft.KeyVault/managedHsm/restore/start/action`, `Microsoft.KeyVault/managedHsm/backup/status/action`, `Microsoft.KeyVault/managedHsm/restore/status/action` and `Microsoft.KeyVault/managedHsm/rng/action`. + +* `not_data_actions` - (Optional) Specifies a list of data action permission not to grant. Possible values are `Microsoft.KeyVault/managedHsm/keys/read/action`, `Microsoft.KeyVault/managedHsm/keys/write/action`, `Microsoft.KeyVault/managedHsm/keys/deletedKeys/read/action`, `Microsoft.KeyVault/managedHsm/keys/deletedKeys/recover/action`, `Microsoft.KeyVault/managedHsm/keys/backup/action`, `Microsoft.KeyVault/managedHsm/keys/restore/action`, `Microsoft.KeyVault/managedHsm/roleAssignments/delete/action`, `Microsoft.KeyVault/managedHsm/roleAssignments/read/action`, `Microsoft.KeyVault/managedHsm/roleAssignments/write/action`, `Microsoft.KeyVault/managedHsm/roleDefinitions/read/action`, `Microsoft.KeyVault/managedHsm/roleDefinitions/write/action`, `Microsoft.KeyVault/managedHsm/roleDefinitions/delete/action`, `Microsoft.KeyVault/managedHsm/keys/encrypt/action`, `Microsoft.KeyVault/managedHsm/keys/decrypt/action`, `Microsoft.KeyVault/managedHsm/keys/wrap/action`, `Microsoft.KeyVault/managedHsm/keys/unwrap/action`, `Microsoft.KeyVault/managedHsm/keys/sign/action`, `Microsoft.KeyVault/managedHsm/keys/verify/action`, `Microsoft.KeyVault/managedHsm/keys/create`, `Microsoft.KeyVault/managedHsm/keys/delete`, `Microsoft.KeyVault/managedHsm/keys/export/action`, `Microsoft.KeyVault/managedHsm/keys/release/action`, `Microsoft.KeyVault/managedHsm/keys/import/action`, `Microsoft.KeyVault/managedHsm/keys/deletedKeys/delete`, `Microsoft.KeyVault/managedHsm/securitydomain/download/action`, `Microsoft.KeyVault/managedHsm/securitydomain/download/read`, `Microsoft.KeyVault/managedHsm/securitydomain/upload/action`, `Microsoft.KeyVault/managedHsm/securitydomain/upload/read`, `Microsoft.KeyVault/managedHsm/securitydomain/transferkey/read`, `Microsoft.KeyVault/managedHsm/backup/start/action`, `Microsoft.KeyVault/managedHsm/restore/start/action`, `Microsoft.KeyVault/managedHsm/backup/status/action`, `Microsoft.KeyVault/managedHsm/restore/status/action` and `Microsoft.KeyVault/managedHsm/rng/action`. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the KeyVault Role Definition. + +* `resource_id` - The ID of the role definition resource without Key Vault base URL. + +* `role_type` - The type of the role definition. Possible values are `AKVBuiltInRole` and `CustomRole`. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the KeyVault. +* `read` - (Defaults to 5 minutes) Used when retrieving the KeyVault. +* `update` - (Defaults to 10 minutes) Used when updating the KeyVault. +* `delete` - (Defaults to 10 minutes) Used when deleting the KeyVault. + +## Import + +KeyVaults can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_key_vault_managed_hardware_security_module_role_definition.example https://0000.managedhsm.azure.net///RoleDefinition/00000000-0000-0000-0000-000000000000 +```