Skip to content

Commit

Permalink
support for managed_hsm_key_id
Browse files Browse the repository at this point in the history
  • Loading branch information
catriona-m committed Jan 10, 2025
1 parent 76e8d80 commit b16a809
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ import (
"strings"
"time"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids"
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/managedinstanceencryptionprotectors"
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/managedinstancekeys"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
keyVaultParser "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse"
keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate"
managedHsmHelpers "github.com/hashicorp/terraform-provider-azurerm/internal/services/managedhsm/helpers"
mhsmParser "github.com/hashicorp/terraform-provider-azurerm/internal/services/managedhsm/parse"
managedhsmValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/managedhsm/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/mssqlmanagedinstance/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/mssqlmanagedinstance/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
Expand Down Expand Up @@ -52,9 +56,17 @@ func resourceMsSqlManagedInstanceTransparentDataEncryption() *pluginsdk.Resource
ValidateFunc: validate.ManagedInstanceID,
},
"key_vault_key_id": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: keyVaultValidate.NestedItemId,
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: keyVaultValidate.NestedItemId,
ConflictsWith: []string{"managed_hsm_key_id"},
},

"managed_hsm_key_id": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: managedhsmValidate.ManagedHSMDataPlaneVersionedKeyID,
ConflictsWith: []string{"key_vault_key_id"},
},
"auto_rotation_enabled": {
Type: pluginsdk.TypeBool,
Expand Down Expand Up @@ -89,18 +101,17 @@ func resourceMsSqlManagedInstanceTransparentDataEncryptionCreateUpdate(d *plugin
managedInstanceKeyName := ""
managedInstanceKeyType := managedinstancekeys.ServerKeyTypeServiceManaged

keyVaultKeyId := strings.TrimSpace(d.Get("key_vault_key_id").(string))

// If it has content, then we assume it's a key vault key id
if keyVaultKeyId != "" {
if v, ok := d.GetOk("key_vault_key_id"); ok {
keyVaultKeyId := strings.TrimSpace(v.(string))
// Update the server key type to AKV
managedInstanceKeyType = managedinstancekeys.ServerKeyTypeAzureKeyVault

// Set the SQL Managed Instance Key properties
managedInstanceKeyProperties := managedinstancekeys.ManagedInstanceKeyProperties{
ServerKeyType: managedInstanceKeyType,
Uri: &keyVaultKeyId,
AutoRotationEnabled: utils.Bool(d.Get("auto_rotation_enabled").(bool)),
AutoRotationEnabled: pointer.To(d.Get("auto_rotation_enabled").(bool)),
}
managedInstanceKey.Properties = &managedInstanceKeyProperties

Expand Down Expand Up @@ -131,6 +142,38 @@ func resourceMsSqlManagedInstanceTransparentDataEncryptionCreateUpdate(d *plugin
}
}

if v, ok := d.GetOk("managed_hsm_key_id"); ok {
mhsmKeyId := strings.TrimSpace(v.(string))
// Update the server key type to AKV
managedInstanceKeyType = managedinstancekeys.ServerKeyTypeAzureKeyVault

// Set the SQL Server Key properties z
serverKeyProperties := managedinstancekeys.ManagedInstanceKeyProperties{
ServerKeyType: managedInstanceKeyType,
Uri: &mhsmKeyId,
AutoRotationEnabled: pointer.To(d.Get("auto_rotation_enabled").(bool)),
}
managedInstanceKey.Properties = &serverKeyProperties

// Make sure it's a key, if not, throw an error
keyId, err := mhsmParser.ManagedHSMDataPlaneVersionedKeyID(mhsmKeyId, nil)
if err != nil {
return fmt.Errorf("failed to parse '%s' as HSM key ID", mhsmKeyId)
}

// Extract the vault name from the keyvault base url
idURL, err := url.ParseRequestURI(keyId.BaseUri())
if err != nil {
return fmt.Errorf("unable to parse key vault hostname: %s", keyId.BaseUri())
}

hostParts := strings.Split(idURL.Host, ".")
vaultName := hostParts[0]

// Create the key path for the Encryption Protector. Format is: {vaultname}_{key}_{key_version}
managedInstanceKeyName = fmt.Sprintf("%s_%s_%s", vaultName, keyId.KeyName, keyId.KeyVersion)
}

keyType := managedinstanceencryptionprotectors.ServerKeyTypeServiceManaged
if managedInstanceKeyType == managedinstancekeys.ServerKeyTypeAzureKeyVault {
keyType = managedinstanceencryptionprotectors.ServerKeyTypeAzureKeyVault
Expand Down Expand Up @@ -171,6 +214,7 @@ func resourceMsSqlManagedInstanceTransparentDataEncryptionCreateUpdate(d *plugin

func resourceMsSqlManagedInstanceTransparentDataEncryptionRead(d *pluginsdk.ResourceData, meta interface{}) error {
encryptionProtectorClient := meta.(*clients.Client).MSSQLManagedInstance.ManagedInstanceEncryptionProtectorClient
env := meta.(*clients.Client).Account.Environment

ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()
Expand All @@ -196,14 +240,14 @@ func resourceMsSqlManagedInstanceTransparentDataEncryptionRead(d *pluginsdk.Reso
if resp.Model != nil && resp.Model.Properties != nil {
log.Printf("[INFO] Encryption protector key type is %s", resp.Model.Properties.ServerKeyType)

keyVaultKeyId := ""
keyId := ""
autoRotationEnabled := false

// Only set the key type if it's an AKV key. For service managed, we can omit the setting the key_vault_key_id
if resp.Model.Properties.ServerKeyType == managedinstanceencryptionprotectors.ServerKeyTypeAzureKeyVault {
if resp.Model.Properties.Uri != nil {
log.Printf("[INFO] Setting Key Vault URI to %s", *resp.Model.Properties.Uri)
keyVaultKeyId = *resp.Model.Properties.Uri
keyId = *resp.Model.Properties.Uri
}

// autoRotation is only for AKV keys
Expand All @@ -212,6 +256,25 @@ func resourceMsSqlManagedInstanceTransparentDataEncryptionRead(d *pluginsdk.Reso
}
}

hsmKey := ""
keyVaultKeyId := ""
if keyId != "" {
isHSMURI, err, _, _ := managedHsmHelpers.IsManagedHSMURI(env, keyId)
if err != nil {
return err
}

if isHSMURI {
hsmKey = keyId
} else {
keyVaultKeyId = keyId
}
}

if err := d.Set("managed_hsm_key_id", hsmKey); err != nil {
return fmt.Errorf("setting `managed_hsm_key_id`: %+v", err)
}

if err := d.Set("key_vault_key_id", keyVaultKeyId); err != nil {
return fmt.Errorf("setting `key_vault_key_id`: %+v", err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ func TestAccMsSqlManagedInstanceTransparentDataEncryption_systemManaged(t *testi
})
}

func TestAccMsSqlManagedInstanceTransparentDataEncryption_managedHSM(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_mssql_managed_instance_transparent_data_encryption", "test")
r := MsSqlManagedInstanceTransparentDataEncryptionResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.managedHSM(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccMsSqlManagedInstanceTransparentDataEncryption_update(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_mssql_managed_instance_transparent_data_encryption", "test")
r := MsSqlManagedInstanceTransparentDataEncryptionResource{}
Expand Down Expand Up @@ -372,10 +387,6 @@ resource "azurerm_mssql_managed_instance" "test" {
database = "test"
}
}
`, db.template(data, data.Locations.Primary), data.RandomInteger)
}

Expand Down Expand Up @@ -440,13 +451,202 @@ resource "azurerm_mssql_managed_instance" "test" {
database = "test"
}
}
`, db.template(data, data.Locations.Primary), data.RandomInteger)
}

func (r MsSqlManagedInstanceTransparentDataEncryptionResource) managedHSM(data acceptance.TestData) string {
db := MsSqlManagedInstanceResource{}
return fmt.Sprintf(`
%s
provider "azurerm" {
features {
key_vault {
purge_soft_delete_on_destroy = false
purge_soft_deleted_keys_on_destroy = false
}
resource_group {
/* Due to the creation of unmanaged Microsoft.Network/networkIntentPolicies in this service,
prevent_deletion_if_contains_resources has been added here to allow the test resources to be
deleted until this can be properly investigated
*/
prevent_deletion_if_contains_resources = false
}
}
}
data "azurerm_client_config" "current" {}
resource "azurerm_key_vault" "test" {
name = "acctest%[2]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 = "standard"
soft_delete_retention_days = 7
access_policy {
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
key_permissions = [
"Create",
"Delete",
"Get",
"Purge",
"Recover",
"Update",
"GetRotationPolicy",
]
secret_permissions = [
"Delete",
"Get",
"Set",
]
certificate_permissions = [
"Create",
"Delete",
"DeleteIssuers",
"Get",
"Purge",
"Update"
]
}
tags = {
environment = "Production"
}
}
resource "azurerm_key_vault_certificate" "cert" {
count = 3
name = "acchsmcert${count.index}"
key_vault_id = azurerm_key_vault.test.id
certificate_policy {
issuer_parameters {
name = "Self"
}
key_properties {
exportable = true
key_size = 2048
key_type = "RSA"
reuse_key = true
}
lifetime_action {
action {
action_type = "AutoRenew"
}
trigger {
days_before_expiry = 30
}
}
secret_properties {
content_type = "application/x-pkcs12"
}
x509_certificate_properties {
extended_key_usage = []
key_usage = [
"cRLSign",
"dataEncipherment",
"digitalSignature",
"keyAgreement",
"keyCertSign",
"keyEncipherment",
]
subject = "CN=hello-world"
validity_in_months = 12
}
}
}
resource "azurerm_key_vault_managed_hardware_security_module" "test" {
name = "kvHsm%[2]s"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.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
security_domain_key_vault_certificate_ids = [for cert in azurerm_key_vault_certificate.cert : cert.id]
security_domain_quorum = 3
}
resource "azurerm_user_assigned_identity" "test" {
name = "acctestmi%[2]s"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
}
resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "test" {
managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id
name = "1e243909-064c-6ac3-84e9-1c8bf8d6ad22"
scope = "/keys"
role_definition_id = "/Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/21dbd100-6940-42c2-9190-5d6cb909625b"
principal_id = data.azurerm_client_config.current.object_id
}
resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "test1" {
managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id
name = "1e243909-064c-6ac3-84e9-1c8bf8d6ad23"
scope = "/keys"
role_definition_id = "/Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/515eb02d-2335-4d2d-92f2-b1cbdf9c3778"
principal_id = data.azurerm_client_config.current.object_id
}
`, db.template(data, data.Locations.Primary), data.RandomInteger)
resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "user" {
managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id
name = "1e243909-064c-6ac3-84e9-1c8bf8d6ad20"
scope = "/keys"
role_definition_id = "/Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/21dbd100-6940-42c2-9190-5d6cb909625b"
principal_id = azurerm_user_assigned_identity.test.principal_id
}
resource "azurerm_key_vault_managed_hardware_security_module_key" "test" {
name = "acctestHSMK-%[2]s"
managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id
key_type = "RSA-HSM"
key_size = 2048
key_opts = ["unwrapKey", "wrapKey"]
depends_on = [
azurerm_key_vault_managed_hardware_security_module_role_assignment.test,
azurerm_key_vault_managed_hardware_security_module_role_assignment.test1
]
}
resource "azurerm_mssql_managed_instance" "test" {
name = "acctestsqlserver%[3]d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
license_type = "BasePrice"
sku_name = "GP_Gen5"
storage_size_in_gb = 32
subnet_id = azurerm_subnet.test.id
vcores = 4
administrator_login = "missadministrator"
administrator_login_password = "NCC-1701-D"
depends_on = [
azurerm_subnet_network_security_group_association.test,
azurerm_subnet_route_table_association.test,
]
identity {
type = "UserAssigned"
identity_ids = [
azurerm_user_assigned_identity.test.id
]
}
tags = {
environment = "staging"
database = "test"
}
}
resource "azurerm_mssql_managed_instance_transparent_data_encryption" "test" {
managed_instance_id = azurerm_mssql_managed_instance.test.id
managed_hsm_key_id = azurerm_key_vault_managed_hardware_security_module_key.test.versioned_id
}
`, db.template(data, data.Locations.Primary), data.RandomStringOfLength(5), data.RandomInteger)
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ The following arguments are supported:

* `key_vault_key_id` - (Optional) To use customer managed keys from Azure Key Vault, provide the AKV Key ID. To use service managed keys, omit this field.

* `managed_hsm_key_id` - (Optional) To use customer managed keys from a managed HSM, provide the Managed HSM Key ID. To use service managed keys, omit this field.

~> **NOTE:** In order to use customer managed keys, the identity of the MSSQL Managed Instance must have the following permissions on the key vault: 'get', 'wrapKey' and 'unwrapKey'

~> **NOTE:** If `managed_instance_id` denotes a secondary instance deployed for disaster recovery purposes, then the `key_vault_key_id` should be the same key used for the primary instance's transparent data encryption. Both primary and secondary instances should be encrypted with same key material.
Expand Down

0 comments on commit b16a809

Please sign in to comment.