From f5837dc3e066fef801bf289b6ed7db4015684430 Mon Sep 17 00:00:00 2001 From: dkuzmenok <103177770+dkuzmenok@users.noreply.github.com> Date: Thu, 11 Jan 2024 22:25:06 +0100 Subject: [PATCH] Including #24412 * Added new fields to `azurerm_mssql_database` and `azurerm_mssql_server` * Fixed tests * Changed `auto_key_rotation_enabled` field into `transparent_data_encryption_key_automatic_rotation_enabled` * Changed comment --- .../mssql/mssql_database_data_source.go | 41 ++++++ .../mssql/mssql_database_data_source_test.go | 26 ++++ .../services/mssql/mssql_database_resource.go | 129 +++++++++++++++--- .../mssql/mssql_database_resource_test.go | 80 +++++++++++ .../mssql/mssql_server_data_source.go | 6 + website/docs/d/mssql_database.html.markdown | 16 +++ website/docs/d/mssql_server.html.markdown | 2 + website/docs/r/mssql_database.html.markdown | 129 ++++++++++++++++++ 8 files changed, 409 insertions(+), 20 deletions(-) diff --git a/internal/services/mssql/mssql_database_data_source.go b/internal/services/mssql/mssql_database_data_source.go index cd2bf9bd7fa2..adfbcd84d60a 100644 --- a/internal/services/mssql/mssql_database_data_source.go +++ b/internal/services/mssql/mssql_database_data_source.go @@ -11,8 +11,10 @@ import ( "github.com/hashicorp/go-azure-helpers/lang/response" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" + "github.com/hashicorp/go-azure-helpers/resourcemanager/identity" "github.com/hashicorp/go-azure-helpers/resourcemanager/tags" "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-02-01-preview/databases" + "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-02-01-preview/transparentdataencryptions" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" "github.com/hashicorp/terraform-provider-azurerm/internal/services/mssql/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" @@ -90,6 +92,23 @@ func dataSourceMsSqlDatabase() *pluginsdk.Resource { Computed: true, }, + "identity": commonschema.UserAssignedIdentityComputed(), + + "transparent_data_encryption_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "transparent_data_encryption_key_vault_key_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "transparent_data_encryption_key_automatic_rotation_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + "tags": commonschema.TagsDataSource(), }, } @@ -97,6 +116,7 @@ func dataSourceMsSqlDatabase() *pluginsdk.Resource { func dataSourceMsSqlDatabaseRead(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).MSSQL.DatabasesClient + transparentEncryptionClient := meta.(*clients.Client).MSSQL.TransparentDataEncryptionsClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() @@ -130,6 +150,8 @@ func dataSourceMsSqlDatabaseRead(d *pluginsdk.ResourceData, meta interface{}) er d.Set("read_replica_count", props.HighAvailabilityReplicaCount) d.Set("sku_name", props.CurrentServiceObjectiveName) d.Set("zone_redundant", props.ZoneRedundant) + d.Set("transparent_data_encryption_key_vault_key_id", props.EncryptionProtector) + d.Set("transparent_data_encryption_key_automatic_rotation_enabled", props.EncryptionProtectorAutoRotation) maxSizeGb := int64(0) if props.MaxSizeBytes != nil { @@ -156,6 +178,25 @@ func dataSourceMsSqlDatabaseRead(d *pluginsdk.ResourceData, meta interface{}) er d.Set("storage_account_type", storageAccountType) } + identity, err := identity.FlattenUserAssignedMap(model.Identity) + if err != nil { + return fmt.Errorf("setting `identity`: %+v", err) + } + + if err := d.Set("identity", identity); err != nil { + return fmt.Errorf("setting `identity`: %+v", err) + } + + tde, err := transparentEncryptionClient.Get(ctx, databaseId) + if err != nil { + return fmt.Errorf("while retrieving Transparent Data Encryption state for %s: %+v", databaseId, err) + } + if model := tde.Model; model != nil { + if props := model.Properties; props != nil { + d.Set("transparent_data_encryption_enabled", props.State == transparentdataencryptions.TransparentDataEncryptionStateEnabled) + } + } + if err := tags.FlattenAndSet(d, model.Tags); err != nil { return err } diff --git a/internal/services/mssql/mssql_database_data_source_test.go b/internal/services/mssql/mssql_database_data_source_test.go index f9683b87b2b8..f9596752f1ca 100644 --- a/internal/services/mssql/mssql_database_data_source_test.go +++ b/internal/services/mssql/mssql_database_data_source_test.go @@ -49,6 +49,21 @@ func TestAccDataSourceMsSqlDatabase_complete(t *testing.T) { }) } +func TestAccDataSourceMsSqlDatabase_transparentDataEncryptionKey(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_mssql_database", "test") + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: MsSqlDatabaseDataSource{}.transparentDataEncryptionKey(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("name").HasValue(fmt.Sprintf("acctest-db-%d", data.RandomInteger)), + check.That(data.ResourceName).Key("server_id").Exists(), + check.That(data.ResourceName).Key("identity.0.identity_ids.#").HasValue("1"), + ), + }, + }) +} + func (MsSqlDatabaseDataSource) basic(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s @@ -70,3 +85,14 @@ data "azurerm_mssql_database" "test" { } `, MsSqlDatabaseResource{}.complete(data)) } + +func (MsSqlDatabaseDataSource) transparentDataEncryptionKey(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +data "azurerm_mssql_database" "test" { + name = azurerm_mssql_database.test.name + server_id = azurerm_mssql_server.test.id +} +`, MsSqlDatabaseResource{}.transparentDataEncryptionKey(data)) +} diff --git a/internal/services/mssql/mssql_database_resource.go b/internal/services/mssql/mssql_database_resource.go index 382ac8c28dec..c8d0a4a7a711 100644 --- a/internal/services/mssql/mssql_database_resource.go +++ b/internal/services/mssql/mssql_database_resource.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/go-azure-helpers/lang/response" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" + "github.com/hashicorp/go-azure-helpers/resourcemanager/identity" "github.com/hashicorp/go-azure-helpers/resourcemanager/tags" "github.com/hashicorp/go-azure-sdk/resource-manager/maintenance/2022-07-01-preview/publicmaintenanceconfigurations" "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-02-01-preview/backupshorttermretentionpolicies" @@ -29,6 +30,8 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" "github.com/hashicorp/terraform-provider-azurerm/internal/locks" + keyVaultParser "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" + keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/services/mssql/helper" "github.com/hashicorp/terraform-provider-azurerm/internal/services/mssql/migration" "github.com/hashicorp/terraform-provider-azurerm/internal/services/mssql/validate" @@ -280,6 +283,7 @@ func resourceMsSqlDatabaseCreate(d *pluginsdk.ResourceData, meta interface{}) er RequestedBackupStorageRedundancy: pointer.To(databases.BackupStorageRedundancy(d.Get("storage_account_type").(string))), ZoneRedundant: pointer.To(d.Get("zone_redundant").(bool)), IsLedgerOn: pointer.To(ledgerEnabled), + EncryptionProtectorAutoRotation: pointer.To(d.Get("transparent_data_encryption_key_automatic_rotation_enabled").(bool)), }, Tags: tags.Expand(d.Get("tags").(map[string]interface{})), @@ -377,6 +381,25 @@ func resourceMsSqlDatabaseCreate(d *pluginsdk.ResourceData, meta interface{}) er input.Properties.RestorableDroppedDatabaseId = pointer.To(v.(string)) } + if v, ok := d.GetOk("identity"); ok { + expandedIdentity, err := identity.ExpandUserAssignedMap(v.([]interface{})) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + input.Identity = expandedIdentity + } + + if v, ok := d.GetOk("transparent_data_encryption_key_vault_key_id"); ok { + keyVaultKeyId := v.(string) + + keyId, err := keyVaultParser.ParseNestedItemID(keyVaultKeyId) + if err != nil { + return fmt.Errorf("unable to parse key: %q: %+v", keyVaultKeyId, err) + } + + input.Properties.EncryptionProtector = pointer.To(keyId.ID()) + } + err = client.CreateOrUpdateThenPoll(ctx, id, input) if err != nil { return fmt.Errorf("creating %s: %+v", id, err) @@ -431,34 +454,51 @@ func resourceMsSqlDatabaseCreate(d *pluginsdk.ResourceData, meta interface{}) er state = transparentdataencryptions.TransparentDataEncryptionStateEnabled } - input := transparentdataencryptions.LogicalDatabaseTransparentDataEncryption{ - Properties: &transparentdataencryptions.TransparentDataEncryptionProperties{ - State: state, - }, + tde, err := transparentEncryptionClient.Get(ctx, id) + if err != nil { + return fmt.Errorf("while retrieving Transparent Data Encryption state for %s: %+v", id, err) } - err := transparentEncryptionClient.CreateOrUpdateThenPoll(ctx, id, input) - if err != nil { - return fmt.Errorf("while enabling Transparent Data Encryption for %q: %+v", id.String(), err) + currentState := transparentdataencryptions.TransparentDataEncryptionStateDisabled + if model := tde.Model; model != nil { + if props := model.Properties; props != nil { + currentState = props.State + } } - // NOTE: Internal x-ref, this is another case of hashicorp/go-azure-sdk#307 so this can be removed once that's fixed - if err = pluginsdk.Retry(d.Timeout(pluginsdk.TimeoutCreate), func() *pluginsdk.RetryError { - c, err := client.Get(ctx, id, databases.DefaultGetOperationOptions()) + // Submit TDE state only when state is being changed, otherwise it can cause unwanted detection of state changes from the cloud side + if !strings.EqualFold(string(currentState), string(state)) { + input := transparentdataencryptions.LogicalDatabaseTransparentDataEncryption{ + Properties: &transparentdataencryptions.TransparentDataEncryptionProperties{ + State: state, + }, + } + + err := transparentEncryptionClient.CreateOrUpdateThenPoll(ctx, id, input) if err != nil { - return pluginsdk.NonRetryableError(fmt.Errorf("while polling %s for status: %+v", id.String(), err)) + return fmt.Errorf("while enabling Transparent Data Encryption for %q: %+v", id.String(), err) } - if c.Model != nil && c.Model.Properties != nil && c.Model.Properties.Status != nil { - if c.Model.Properties.Status == pointer.To(databases.DatabaseStatusScaling) { - return pluginsdk.RetryableError(fmt.Errorf("database %s is still scaling", id.String())) + + // NOTE: Internal x-ref, this is another case of hashicorp/go-azure-sdk#307 so this can be removed once that's fixed + if err = pluginsdk.Retry(d.Timeout(pluginsdk.TimeoutCreate), func() *pluginsdk.RetryError { + c, err := client.Get(ctx, id, databases.DefaultGetOperationOptions()) + if err != nil { + return pluginsdk.NonRetryableError(fmt.Errorf("while polling %s for status: %+v", id.String(), err)) + } + if c.Model != nil && c.Model.Properties != nil && c.Model.Properties.Status != nil { + if c.Model.Properties.Status == pointer.To(databases.DatabaseStatusScaling) { + return pluginsdk.RetryableError(fmt.Errorf("database %s is still scaling", id.String())) + } + } else { + return pluginsdk.RetryableError(fmt.Errorf("retrieving database status %s: Model, Properties or Status is nil", id.String())) } - } else { - return pluginsdk.RetryableError(fmt.Errorf("retrieving database status %s: Model, Properties or Status is nil", id.String())) - } - return nil - }); err != nil { - return nil + return nil + }); err != nil { + return nil + } + } else { + log.Print("[DEBUG] Skipping re-writing of Transparent Data Encryption, since encryption state is not changing ...") } } @@ -639,6 +679,17 @@ func resourceMsSqlDatabaseRead(d *pluginsdk.ResourceData, meta interface{}) erro d.Set("maintenance_configuration_name", configurationName) d.Set("ledger_enabled", ledgerEnabled) d.Set("enclave_type", enclaveType) + d.Set("transparent_data_encryption_key_vault_key_id", props.EncryptionProtector) + d.Set("transparent_data_encryption_key_automatic_rotation_enabled", pointer.From(props.EncryptionProtectorAutoRotation)) + + identity, err := identity.FlattenUserAssignedMap(model.Identity) + if err != nil { + return fmt.Errorf("setting `identity`: %+v", err) + } + + if err := d.Set("identity", identity); err != nil { + return fmt.Errorf("setting `identity`: %+v", err) + } if err := tags.FlattenAndSet(d, model.Tags); err != nil { return err @@ -961,6 +1012,29 @@ func resourceMsSqlDatabaseUpdate(d *pluginsdk.ResourceData, meta interface{}) er payload.Tags = tags.Expand(d.Get("tags").(map[string]interface{})) } + if d.HasChange("identity") { + expanded, err := identity.ExpandUserAssignedMap(d.Get("identity").([]interface{})) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + payload.Identity = expanded + } + + if d.HasChange("transparent_data_encryption_key_vault_key_id") { + keyVaultKeyId := d.Get(("transparent_data_encryption_key_vault_key_id")).(string) + + keyId, err := keyVaultParser.ParseNestedItemID(keyVaultKeyId) + if err != nil { + return fmt.Errorf("unable to parse key: %q: %+v", keyVaultKeyId, err) + } + + props.EncryptionProtector = pointer.To(keyId.ID()) + } + + if d.HasChange("transparent_data_encryption_key_automatic_rotation_enabled") { + props.EncryptionProtectorAutoRotation = pointer.To(d.Get("transparent_data_encryption_key_automatic_rotation_enabled").(bool)) + } + payload.Properties = pointer.To(props) err = client.UpdateThenPoll(ctx, id, payload) if err != nil { @@ -1601,12 +1675,27 @@ func resourceMsSqlDatabaseSchema() map[string]*pluginsdk.Schema { ForceNew: true, }, + "identity": commonschema.UserAssignedIdentityOptional(), + "transparent_data_encryption_enabled": { Type: pluginsdk.TypeBool, Optional: true, Default: true, }, + "transparent_data_encryption_key_vault_key_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: keyVaultValidate.NestedItemId, + }, + + "transparent_data_encryption_key_automatic_rotation_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + RequiredWith: []string{"transparent_data_encryption_key_vault_key_id"}, + }, + "tags": commonschema.Tags(), } } diff --git a/internal/services/mssql/mssql_database_resource_test.go b/internal/services/mssql/mssql_database_resource_test.go index c2f3df48de8d..52476f59d3ad 100644 --- a/internal/services/mssql/mssql_database_resource_test.go +++ b/internal/services/mssql/mssql_database_resource_test.go @@ -889,6 +889,21 @@ func TestAccMsSqlDatabase_elasticPoolEnclaveTypeError(t *testing.T) { }) } +func TestAccMsSqlDatabase_transparentDataEncryptionKey(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_mssql_database", "test") + r := MsSqlDatabaseResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.transparentDataEncryptionKey(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (MsSqlDatabaseResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := commonids.ParseSqlDatabaseID(state.ID) if err != nil { @@ -1963,3 +1978,68 @@ resource "azurerm_mssql_database" "test" { } `, r.template(data), data.RandomInteger, enclaveType) } + +func (r MsSqlDatabaseResource) transparentDataEncryptionKey(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +data "azurerm_client_config" "test" {} + +resource "azurerm_user_assigned_identity" "test" { + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + name = "test_identity" +} + +resource "azurerm_key_vault" "test" { + name = "vault%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + enabled_for_disk_encryption = true + tenant_id = azurerm_user_assigned_identity.test.tenant_id + soft_delete_retention_days = 7 + purge_protection_enabled = true + + sku_name = "standard" + + access_policy { + tenant_id = data.azurerm_client_config.test.tenant_id + object_id = data.azurerm_client_config.test.object_id + + key_permissions = ["Get", "List", "Create", "Delete", "Update", "Recover", "Purge", "GetRotationPolicy"] + } + + access_policy { + tenant_id = azurerm_user_assigned_identity.test.tenant_id + object_id = azurerm_user_assigned_identity.test.principal_id + + key_permissions = ["Get", "WrapKey", "UnwrapKey"] + } +} + +resource "azurerm_key_vault_key" "test" { + depends_on = [azurerm_key_vault.test] + + name = "key-%[3]s" + key_vault_id = azurerm_key_vault.test.id + key_type = "RSA" + key_size = 2048 + + key_opts = ["unwrapKey", "wrapKey"] +} + +resource "azurerm_mssql_database" "test" { + name = "acctest-db-%[2]d" + server_id = azurerm_mssql_server.test.id + sku_name = "S0" + transparent_data_encryption_enabled = true + transparent_data_encryption_key_vault_key_id = azurerm_key_vault_key.test.id + transparent_data_encryption_key_automatic_rotation_enabled = true + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.test.id] + } +} +`, r.template(data), data.RandomInteger, data.RandomString) +} diff --git a/internal/services/mssql/mssql_server_data_source.go b/internal/services/mssql/mssql_server_data_source.go index 38c495cc5bc3..109ba98b64bd 100644 --- a/internal/services/mssql/mssql_server_data_source.go +++ b/internal/services/mssql/mssql_server_data_source.go @@ -62,6 +62,11 @@ func dataSourceMsSqlServer() *pluginsdk.Resource { }, }, + "transparent_data_encryption_key_vault_key_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "tags": commonschema.TagsDataSource(), }, } @@ -94,6 +99,7 @@ func dataSourceMsSqlServerRead(d *pluginsdk.ResourceData, meta interface{}) erro d.Set("version", props.Version) d.Set("administrator_login", props.AdministratorLogin) d.Set("fully_qualified_domain_name", props.FullyQualifiedDomainName) + d.Set("transparent_data_encryption_key_vault_key_id", props.KeyId) } identity, err := identity.FlattenLegacySystemAndUserAssignedMap(model.Identity) diff --git a/website/docs/d/mssql_database.html.markdown b/website/docs/d/mssql_database.html.markdown index 1195e517f69e..d379fe32ed7c 100644 --- a/website/docs/d/mssql_database.html.markdown +++ b/website/docs/d/mssql_database.html.markdown @@ -67,8 +67,24 @@ output "database_id" { * `zone_redundant` - Whether or not this database is zone redundant, which means the replicas of this database will be spread across multiple availability zones. +* `identity` - A `identity` block as defined below. + +* `transparent_data_encryption_enabled` - Whether or not Transparent Data Encryption is enabled. + +* `transparent_data_encryption_key_vault_key_id` - The Key Vault key URI to be used as the `Customer Managed Key`(CMK/BYOK) for the `Transparent Data Encryption`(TDE) layer. + +* `transparent_data_encryption_key_automatic_rotation_enabled` - Whether or not TDE automatically rotates the encryption Key to latest version. + * `tags` - A mapping of tags to assign to the resource. +--- + +An `identity` block exports the following: + +* `type` - The type of Managed Service Identity that is configured on this Microsoft SQL Database. + +* `identity_ids` - The list of User Assigned Managed Identity IDs assigned to this Microsoft SQL Database. + ## Timeouts The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: diff --git a/website/docs/d/mssql_server.html.markdown b/website/docs/d/mssql_server.html.markdown index eced7ca75c97..00e7f65b921e 100644 --- a/website/docs/d/mssql_server.html.markdown +++ b/website/docs/d/mssql_server.html.markdown @@ -47,6 +47,8 @@ In addition to the Arguments listed above - the following Attributes are exporte * `restorable_dropped_database_ids` - A list of dropped restorable database IDs on the server. +* `transparent_data_encryption_key_vault_key_id` - The Key Vault key URI to be used as the `Customer Managed Key`(CMK/BYOK) for the `Transparent Data Encryption`(TDE) layer. + * `tags` - A mapping of tags assigned to this Microsoft SQL Server. * `version` - This servers MS SQL version. diff --git a/website/docs/r/mssql_database.html.markdown b/website/docs/r/mssql_database.html.markdown index 343f53039d00..5a6f802e8bf3 100644 --- a/website/docs/r/mssql_database.html.markdown +++ b/website/docs/r/mssql_database.html.markdown @@ -63,6 +63,109 @@ resource "azurerm_mssql_database" "example" { } ``` +## Example Usage for Transparent Data Encryption(TDE) with a Customer Managed Key(CMK) during Create +```hcl +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_user_assigned_identity" "example" { + name = "example-admin" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name +} + +resource "azurerm_storage_account" "example" { + name = "examplesa" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_mssql_server" "example" { + name = "example-sqlserver" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + version = "12.0" + administrator_login = "4dm1n157r470r" + administrator_login_password = "4-v3ry-53cr37-p455w0rd" +} + +resource "azurerm_mssql_database" "example" { + name = "example-db" + server_id = azurerm_mssql_server.example.id + collation = "SQL_Latin1_General_CP1_CI_AS" + license_type = "LicenseIncluded" + max_size_gb = 4 + read_scale = true + sku_name = "S0" + zone_redundant = true + enclave_type = "VBS" + + tags = { + foo = "bar" + } + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.example.id] + } + + transparent_data_encryption_key_vault_key_id = azurerm_key_vault_key.example.id + + # prevent the possibility of accidental data loss + lifecycle { + prevent_destroy = true + } +} + +# Create a key vault with access policies which allow for the current user to get, list, create, delete, update, recover, purge and getRotationPolicy for the key vault key and also add a key vault access policy for the Microsoft Sql Server instance User Managed Identity to get, wrap, and unwrap key(s) +resource "azurerm_key_vault" "example" { + name = "mssqltdeexample" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + enabled_for_disk_encryption = true + tenant_id = azurerm_user_assigned_identity.example.tenant_id + soft_delete_retention_days = 7 + purge_protection_enabled = true + + sku_name = "standard" + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = ["Get", "List", "Create", "Delete", "Update", "Recover", "Purge", "GetRotationPolicy"] + } + + access_policy { + tenant_id = azurerm_user_assigned_identity.example.tenant_id + object_id = azurerm_user_assigned_identity.example.principal_id + + key_permissions = ["Get", "WrapKey", "UnwrapKey"] + } +} + +resource "azurerm_key_vault_key" "example" { + depends_on = [azurerm_key_vault.example] + + name = "example-key" + key_vault_id = azurerm_key_vault.example.id + key_type = "RSA" + key_size = 2048 + + key_opts = ["unwrapKey", "wrapKey"] +} + +``` + + ## Argument Reference The following arguments are supported: @@ -135,10 +238,18 @@ The following arguments are supported: * `threat_detection_policy` - (Optional) Threat detection policy configuration. The `threat_detection_policy` block supports fields documented below. +* `identity` - (Optional) An `identity` block as defined below. + * `transparent_data_encryption_enabled` - (Optional) If set to true, Transparent Data Encryption will be enabled on the database. Defaults to `true`. -> **NOTE:** `transparent_data_encryption_enabled` can only be set to `false` on DW (e.g, DataWarehouse) server SKUs. +* `transparent_data_encryption_key_vault_key_id` - (Optional) The fully versioned `Key Vault` `Key` URL (e.g. `'https://.vault.azure.net/keys//`) to be used as the `Customer Managed Key`(CMK/BYOK) for the `Transparent Data Encryption`(TDE) layer. + +~> **NOTE:** To successfully deploy a `Microsoft SQL Database` in CMK/BYOK TDE the `Key Vault` must have `Soft-delete` and `purge protection` enabled to protect from data loss due to accidental key and/or key vault deletion. The `Key Vault` and the `Microsoft SQL Server` `User Managed Identity Instance` must belong to the same `Azure Active Directory` `tenant`. + +* `transparent_data_encryption_key_automatic_rotation_enabled` - (Optional) Boolean flag to specify whether TDE automatically rotates the encryption Key to latest version or not. Possible values are `true` or `false`. Defaults to `false`. + * `zone_redundant` - (Optional) Whether or not this database is zone redundant, which means the replicas of this database will be spread across multiple availability zones. This property is only settable for Premium and Business Critical databases. * `tags` - (Optional) A mapping of tags to assign to the resource. @@ -181,12 +292,30 @@ A `short_term_retention_policy` block supports the following: * `retention_days` - (Required) Point In Time Restore configuration. Value has to be between `1` and `35`. * `backup_interval_in_hours` - (Optional) The hours between each differential backup. This is only applicable to live databases but not dropped databases. Value has to be `12` or `24`. Defaults to `12` hours. +--- + +An `identity` block supports the following: + +* `type` - (Required) Specifies the type of Managed Service Identity that should be configured on this SQL Database. Possible value is `UserAssigned`. + +* `identity_ids` - (Required) Specifies a list of User Assigned Managed Identity IDs to be assigned to this SQL Database. + ## Attributes Reference In addition to the Arguments listed above - the following Attributes are exported: * `id` - The ID of the MS SQL Database. +--- + +A `identity` block exports the following: + +* `principal_id` - The Principal ID for the Service Principal associated with the Identity of this SQL Database. + +* `tenant_id` - The Tenant ID for the Service Principal associated with the Identity of this SQL Database. + +-> You can access the Principal ID via `azurerm_mssql_database.example.identity.0.principal_id` and the Tenant ID via `azurerm_mssql_database.example.identity.0.tenant_id` + ## Timeouts The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: