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_database / azurerm_mssql_server - Added fields for database-level TDE #24412

Merged
merged 4 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
41 changes: 41 additions & 0 deletions internal/services/mssql/mssql_database_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -90,13 +92,31 @@ 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,
},

"auto_key_rotation_enabled": {
dkuzmenok marked this conversation as resolved.
Show resolved Hide resolved
Type: pluginsdk.TypeBool,
Computed: true,
},

"tags": commonschema.TagsDataSource(),
},
}
}

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()

Expand Down Expand Up @@ -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("auto_key_rotation_enabled", props.EncryptionProtectorAutoRotation)

maxSizeGb := int64(0)
if props.MaxSizeBytes != nil {
Expand All @@ -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
}
Expand Down
26 changes: 26 additions & 0 deletions internal/services/mssql/mssql_database_data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
}
129 changes: 109 additions & 20 deletions internal/services/mssql/mssql_database_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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("auto_key_rotation_enabled").(bool)),
},

Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 selector 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 ...")
}
}

Expand Down Expand Up @@ -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("auto_key_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
Expand Down Expand Up @@ -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("auto_key_rotation_enabled") {
props.EncryptionProtectorAutoRotation = pointer.To(d.Get("auto_key_rotation_enabled").(bool))
}

payload.Properties = pointer.To(props)
err = client.UpdateThenPoll(ctx, id, payload)
if err != nil {
Expand Down Expand Up @@ -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,
},

"auto_key_rotation_enabled": {
Type: pluginsdk.TypeBool,
Optional: true,
Default: false,
RequiredWith: []string{"transparent_data_encryption_key_vault_key_id"},
},

"tags": commonschema.Tags(),
}
}
Loading
Loading