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

azurerm_mssql_managed_instance_transparent_data_encryption - add support for managed_hsm_key_id #26496

Merged
merged 6 commits into from
Jun 28, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import (
"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"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/managedhsm/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/mssql/migration"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/mssql/parse"
mssqlValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/mssql/validate"
Expand Down Expand Up @@ -56,9 +59,17 @@ func resourceMsSqlTransparentDataEncryption() *pluginsdk.Resource {
},

"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: validate.ManagedHSMDataPlaneVersionedKeyID,
ConflictsWith: []string{"key_vault_key_id"},
},

"auto_rotation_enabled": {
Expand Down Expand Up @@ -94,10 +105,8 @@ func resourceMsSqlTransparentDataEncryptionCreateUpdate(d *pluginsdk.ResourceDat
serverKeyName := ""
serverKeyType := sql.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
serverKeyType = sql.ServerKeyTypeAzureKeyVault

Expand Down Expand Up @@ -136,6 +145,38 @@ func resourceMsSqlTransparentDataEncryptionCreateUpdate(d *pluginsdk.ResourceDat
}
}

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

// Set the SQL Server Key properties z
serverKeyProperties := sql.ServerKeyProperties{
ServerKeyType: serverKeyType,
URI: &mhsmKeyId,
AutoRotationEnabled: utils.Bool(d.Get("auto_rotation_enabled").(bool)),
}
serverKey.ServerKeyProperties = &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}
serverKeyName = fmt.Sprintf("%s_%s_%s", vaultName, keyId.KeyName, keyId.KeyVersion)
}

// Service managed doesn't require a key name
encryptionProtectorProperties := sql.EncryptionProtectorProperties{
ServerKeyType: serverKeyType,
Expand Down Expand Up @@ -179,6 +220,7 @@ func resourceMsSqlTransparentDataEncryptionCreateUpdate(d *pluginsdk.ResourceDat

func resourceMsSqlTransparentDataEncryptionRead(d *pluginsdk.ResourceData, meta interface{}) error {
encryptionProtectorClient := meta.(*clients.Client).MSSQL.EncryptionProtectorClient
env := meta.(*clients.Client).Account.Environment

ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()
Expand All @@ -202,20 +244,40 @@ func resourceMsSqlTransparentDataEncryptionRead(d *pluginsdk.ResourceData, meta

log.Printf("[INFO] Encryption protector key type is %s", resp.EncryptionProtectorProperties.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.EncryptionProtectorProperties != nil && resp.EncryptionProtectorProperties.ServerKeyType == sql.ServerKeyTypeAzureKeyVault {
log.Printf("[INFO] Setting Key Vault URI to %s", *resp.EncryptionProtectorProperties.URI)

keyVaultKeyId = *resp.EncryptionProtectorProperties.URI
keyId = *resp.EncryptionProtectorProperties.URI

// autoRotation is only for AKV keys
if resp.EncryptionProtectorProperties.AutoRotationEnabled != nil {
autoRotationEnabled = *resp.EncryptionProtectorProperties.AutoRotationEnabled
}
}

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 @@ -33,6 +33,21 @@ func TestAccMsSqlServerTransparentDataEncryption_keyVault(t *testing.T) {
})
}

func TestAccMsSqlServerTransparentDataEncryption_managedHSM(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_mssql_server_transparent_data_encryption", "test")
r := MsSqlServerTransparentDataEncryptionResource{}

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

func TestAccMsSqlServerTransparentDataEncryption_autoRotate(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_mssql_server_transparent_data_encryption", "test")
r := MsSqlServerTransparentDataEncryptionResource{}
Expand Down Expand Up @@ -180,6 +195,17 @@ resource "azurerm_mssql_server_transparent_data_encryption" "test" {
`, r.baseKeyVault(data))
}

func (r MsSqlServerTransparentDataEncryptionResource) managedHSM(data acceptance.TestData) string {
return fmt.Sprintf(`
%s

resource "azurerm_mssql_server_transparent_data_encryption" "test" {
server_id = azurerm_mssql_server.test.id
managed_hsm_key_id = azurerm_key_vault_managed_hardware_security_module_key.test.versioned_id
}
`, r.withManagedHSM(data))
}

func (r MsSqlServerTransparentDataEncryptionResource) autoRotate(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
Expand Down Expand Up @@ -233,3 +259,174 @@ resource "azurerm_mssql_server" "test" {
}
`, data.RandomInteger, data.Locations.Primary)
}

func (r MsSqlServerTransparentDataEncryptionResource) withManagedHSM(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

data "azurerm_client_config" "current" {}

resource "azurerm_resource_group" "test" {
name = "acctestRG-mssql-%[2]s"
location = "%[1]s"
}

resource "azurerm_key_vault" "test" {
name = "acc%[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" {
vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri
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" {
vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri
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
}

resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "user" {
vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri
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_server" "test" {
name = "acctestsqlserver-%[2]s"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
version = "12.0"
administrator_login = "mradministrator"
administrator_login_password = "thisIsDog11"

identity {
type = "SystemAssigned, UserAssigned"
identity_ids = [
azurerm_user_assigned_identity.test.id,
]
}

primary_user_assigned_identity_id = azurerm_user_assigned_identity.test.id

lifecycle {
ignore_changes = [transparent_data_encryption_key_vault_key_id]
}
}
`, data.Locations.Primary, data.RandomStringOfLength(5))
}
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
Loading