Skip to content

Commit

Permalink
New Resource: `azurerm_key_vault_managed_hardware_security_module_rol…
Browse files Browse the repository at this point in the history
…e_definition` and `azurerm_key_vault_managed_hardware_security_module_role_assignment` for managed HSM (#22332)
  • Loading branch information
wuxu92 authored Dec 7, 2023
1 parent c76e7bc commit d6e5c62
Show file tree
Hide file tree
Showing 16 changed files with 1,540 additions and 11 deletions.
19 changes: 12 additions & 7 deletions internal/services/keyvault/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ func resourceKeyVaultManagedHardwareSecurityModule() *pluginsdk.Resource {
"public_network_access_enabled": {
Type: pluginsdk.TypeBool,
Optional: true,
//Computed: true,
Default: true,
ForceNew: true,
},
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
})
}
Expand Down Expand Up @@ -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 := KeyVaultManagedHSMRoleAssignmentResource{}

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{}
Expand Down Expand Up @@ -261,6 +307,7 @@ resource "azurerm_key_vault_managed_hardware_security_module" "test" {
}
`, template, data.RandomInteger, certCount, activateConfig)
}

func (r KeyVaultManagedHardwareSecurityModuleResource) complete(data acceptance.TestData) string {
template := r.template(data)
return fmt.Sprintf(`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package keyvault

import (
"context"
"fmt"
"regexp"
"strings"
"time"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-sdk/resource-manager/authorization/2022-04-01/roledefinitions"
"github.com/hashicorp/terraform-provider-azurerm/internal/locks"
"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 KeyVaultManagedHSMRoleAssignmentModel 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 KeyVaultManagedHSMRoleAssignmentResource struct{}

var _ sdk.Resource = KeyVaultManagedHSMRoleAssignmentResource{}

func (m KeyVaultManagedHSMRoleAssignmentResource) 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/<key_name>`"),
},

"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 KeyVaultManagedHSMRoleAssignmentResource) Attributes() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"resource_id": {
Type: pluginsdk.TypeString,
Computed: true,
},
}
}

func (m KeyVaultManagedHSMRoleAssignmentResource) ModelObject() interface{} {
return &KeyVaultManagedHSMRoleAssignmentModel{}
}

func (m KeyVaultManagedHSMRoleAssignmentResource) ResourceType() string {
return "azurerm_key_vault_managed_hardware_security_module_role_assignment"
}

func (m KeyVaultManagedHSMRoleAssignmentResource) 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 KeyVaultManagedHSMRoleAssignmentModel
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),
// the role definition id may has '/' prefix, but the api doesn't accept it
RoleDefinitionID: pointer.FromString(strings.TrimPrefix(model.RoleDefinitionId, "/")),
}
if _, err = client.Create(ctx, model.VaultBaseUrl, model.Scope, model.Name, param); err != nil {
return fmt.Errorf("creating %s: %v", id.ID(), err)
}

meta.SetID(id)
return nil
},
}
}

func (m KeyVaultManagedHSMRoleAssignmentResource) 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 KeyVaultManagedHSMRoleAssignmentModel
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.PrincipalId = pointer.ToString(prop.PrincipalID)
model.ResourceId = pointer.ToString(result.ID)
if roleID, err := roledefinitions.ParseScopedRoleDefinitionIDInsensitively(pointer.ToString(prop.RoleDefinitionID)); err != nil {
return fmt.Errorf("parsing role definition id: %v", err)
} else {
model.RoleDefinitionId = roleID.ID()
}

return meta.Encode(&model)
},
}
}

func (m KeyVaultManagedHSMRoleAssignmentResource) 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 KeyVaultManagedHSMRoleAssignmentResource) IDValidationFunc() pluginsdk.SchemaValidateFunc {
return validate.MHSMNestedItemId
}
Original file line number Diff line number Diff line change
@@ -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 KeyVaultManagedHSMRoleAssignmentResource struct{}

// real test nested in TestAccKeyVaultManagedHardwareSecurityModule, only provide Exists logic here
func (k KeyVaultManagedHSMRoleAssignmentResource) 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 KeyVaultManagedHSMRoleAssignmentResource) 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 KeyVaultManagedHSMRoleAssignmentResource) 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)
}
Loading

0 comments on commit d6e5c62

Please sign in to comment.