Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Resource: azurerm_key_vault_managed_hardware_security_module_role_definition and azurerm_key_vault_managed_hardware_security_module_role_assignment for managed HSM #22332

Merged
merged 5 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading