Skip to content

Commit

Permalink
azurerm_postgresql_flexible_server add suport for customer_managed_…
Browse files Browse the repository at this point in the history
…key (#20086)

fixes #19215
  • Loading branch information
bwilczynski authored Jan 24, 2023
1 parent 5cc6a18 commit 5bbe761
Show file tree
Hide file tree
Showing 3 changed files with 271 additions and 0 deletions.
138 changes: 138 additions & 0 deletions internal/services/postgres/postgresql_flexible_server_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ import (
"time"

"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/location"
"github.com/hashicorp/go-azure-helpers/resourcemanager/tags"
"github.com/hashicorp/go-azure-sdk/resource-manager/postgresql/2021-06-01/serverrestart"
"github.com/hashicorp/go-azure-sdk/resource-manager/postgresql/2022-12-01/servers"
"github.com/hashicorp/go-azure-sdk/resource-manager/privatedns/2018-09-01/privatezones"
"github.com/hashicorp/terraform-provider-azurerm/helpers/tf"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate"
networkValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/network/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/postgres/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
Expand Down Expand Up @@ -242,6 +245,33 @@ func resourcePostgresqlFlexibleServer() *pluginsdk.Resource {
Computed: true,
},

"identity": commonschema.SystemAssignedUserAssignedIdentityOptional(),

"customer_managed_key": {
Type: pluginsdk.TypeList,
Optional: true,
MaxItems: 1,
ForceNew: true,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"key_vault_key_id": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: keyVaultValidate.NestedItemIdWithOptionalVersion,
RequiredWith: []string{
"identity",
"customer_managed_key.0.primary_user_assigned_identity_id",
},
},
"primary_user_assigned_identity_id": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: commonids.ValidateUserAssignedIdentityID,
},
},
},
},

"tags": commonschema.Tags(),
},
}
Expand Down Expand Up @@ -309,6 +339,7 @@ func resourcePostgresqlFlexibleServerCreate(d *pluginsdk.ResourceData, meta inte
Storage: expandArmServerStorage(d),
HighAvailability: expandFlexibleServerHighAvailability(d.Get("high_availability").([]interface{}), true),
Backup: expandArmServerBackup(d),
DataEncryption: expandFlexibleServerDataEncryption(d.Get("customer_managed_key").([]interface{})),
},
Sku: sku,
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
Expand Down Expand Up @@ -358,6 +389,12 @@ func resourcePostgresqlFlexibleServerCreate(d *pluginsdk.ResourceData, meta inte
parameters.Properties.AuthConfig = authConfig
}

identity, err := expandFlexibleServerIdentity(d.Get("identity").([]interface{}))
if err != nil {
return fmt.Errorf("expanding `identity`")
}
parameters.Identity = identity

if err = client.CreateThenPoll(ctx, id, parameters); err != nil {
return fmt.Errorf("creating %s: %+v", id, err)
}
Expand Down Expand Up @@ -459,6 +496,22 @@ func resourcePostgresqlFlexibleServerRead(d *pluginsdk.ResourceData, meta interf
if props.AuthConfig != nil {
d.Set("authentication", flattenFlexibleServerAuthConfig(props.AuthConfig))
}

cmk, err := flattenFlexibleServerDataEncryption(props.DataEncryption)
if err != nil {
return fmt.Errorf("flattening `customer_managed_key`: %+v", err)
}
if err := d.Set("customer_managed_key", cmk); err != nil {
return fmt.Errorf("setting `customer_managed_key`: %+v", err)
}

id, err := flattenFlexibleServerIdentity(model.Identity)
if err != nil {
return fmt.Errorf("flattening `identity`: %+v", err)
}
if err := d.Set("identity", id); err != nil {
return fmt.Errorf("setting `identity`: %+v", err)
}
}

sku, err := flattenFlexibleServerSku(model.Sku)
Expand Down Expand Up @@ -564,6 +617,18 @@ func resourcePostgresqlFlexibleServerUpdate(d *pluginsdk.ResourceData, meta inte
parameters.Properties.HighAvailability = expandFlexibleServerHighAvailability(d.Get("high_availability").([]interface{}), false)
}

if d.HasChange("customer_managed_key") {
parameters.Properties.DataEncryption = expandFlexibleServerDataEncryption(d.Get("customer_managed_key").([]interface{}))
}

if d.HasChange("identity") {
identity, err := expandFlexibleServerIdentity(d.Get("identity").([]interface{}))
if err != nil {
return fmt.Errorf("expanding `identity` for Mysql Flexible Server %s (Resource Group %q): %v", id.ServerName, id.ResourceGroupName, err)
}
parameters.Identity = identity
}

if err = client.UpdateThenPoll(ctx, *id, parameters); err != nil {
return fmt.Errorf("updating %s: %+v", id, err)
}
Expand Down Expand Up @@ -830,3 +895,76 @@ func flattenFlexibleServerAuthConfig(ac *servers.AuthConfig) interface{} {
result = append(result, out)
return result
}

func expandFlexibleServerDataEncryption(input []interface{}) *servers.DataEncryption {
if len(input) == 0 {
return nil
}
v := input[0].(map[string]interface{})

det := servers.ArmServerKeyTypeAzureKeyVault
dataEncryption := servers.DataEncryption{
Type: &det,
PrimaryKeyURI: utils.String(v["key_vault_key_id"].(string)),
PrimaryUserAssignedIdentityId: utils.String(v["primary_user_assigned_identity_id"].(string)),
}

return &dataEncryption
}

func flattenFlexibleServerDataEncryption(de *servers.DataEncryption) ([]interface{}, error) {
if de == nil || *de.Type != servers.ArmServerKeyTypeAzureKeyVault {
return []interface{}{}, nil
}

item := map[string]interface{}{}
if de.PrimaryKeyURI != nil {
item["key_vault_key_id"] = *de.PrimaryKeyURI
}
if identity := de.PrimaryUserAssignedIdentityId; identity != nil {
parsed, err := commonids.ParseUserAssignedIdentityIDInsensitively(*identity)
if err != nil {
return nil, fmt.Errorf("parsing %q: %+v", *identity, err)
}
item["primary_user_assigned_identity_id"] = parsed.ID()
}

return []interface{}{item}, nil
}

func expandFlexibleServerIdentity(input []interface{}) (*servers.UserAssignedIdentity, error) {
expanded, err := identity.ExpandUserAssignedMap(input)
if err != nil || expanded.Type != identity.TypeUserAssigned {
return nil, err
}

idUserAssigned := servers.IdentityTypeUserAssigned
out := servers.UserAssignedIdentity{
Type: idUserAssigned,
}
if expanded.Type == identity.TypeUserAssigned {
ids := make(map[string]servers.UserIdentity)
for k := range expanded.IdentityIds {
ids[k] = servers.UserIdentity{}
}
out.UserAssignedIdentities = &ids
}

return &out, nil
}

func flattenFlexibleServerIdentity(input *servers.UserAssignedIdentity) (*[]interface{}, error) {
var transform *identity.UserAssignedMap

if input != nil {
transform = &identity.UserAssignedMap{
Type: identity.Type(string(input.Type)),
IdentityIds: make(map[string]identity.UserAssignedIdentityDetails),
}
for k := range *input.UserAssignedIdentities {
transform.IdentityIds[k] = identity.UserAssignedIdentityDetails{}
}
}

return identity.FlattenUserAssignedMap(transform)
}
109 changes: 109 additions & 0 deletions internal/services/postgres/postgresql_flexible_server_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,23 @@ func TestAccPostgresqlFlexibleServer_authConfig(t *testing.T) {
})
}

func TestAccPostgresqlFlexibleServer_createWithCustomerManagedKey(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_postgresql_flexible_server", "test")
r := PostgresqlFlexibleServerResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.withCustomerManagedKey(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That("azurerm_postgresql_flexible_server.test").Key("customer_managed_key.0.key_vault_key_id").Exists(),
check.That("azurerm_postgresql_flexible_server.test").Key("customer_managed_key.0.primary_user_assigned_identity_id").Exists(),
),
},
data.ImportStep("administrator_password", "create_mode"),
})
}

func (PostgresqlFlexibleServerResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := servers.ParseFlexibleServerID(state.ID)
if err != nil {
Expand Down Expand Up @@ -668,3 +685,95 @@ resource "azurerm_postgresql_flexible_server" "test" {
}
`, r.template(data), data.RandomInteger, aadEnabled, pwdEnabled, tenantIdBlock)
}

func (r PostgresqlFlexibleServerResource) cmkTemplate(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {
key_vault {
purge_soft_delete_on_destroy = false
purge_soft_deleted_keys_on_destroy = false
}
}
}
data "azurerm_client_config" "current" {}
resource "azurerm_resource_group" "test" {
name = "acctestRG-postgresql-%d"
location = "%s"
}
resource "azurerm_user_assigned_identity" "test" {
name = "acctestmi%s"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
}
resource "azurerm_key_vault" "test" {
name = "acctestkv%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"
purge_protection_enabled = true
}
resource "azurerm_key_vault_access_policy" "server" {
key_vault_id = azurerm_key_vault.test.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = azurerm_user_assigned_identity.test.principal_id
key_permissions = ["Get", "List", "WrapKey", "UnwrapKey"]
}
resource "azurerm_key_vault_access_policy" "client" {
key_vault_id = azurerm_key_vault.test.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
key_permissions = ["Get", "Create", "Delete", "List", "Restore", "Recover", "UnwrapKey", "WrapKey", "Purge", "Encrypt", "Decrypt", "Sign", "Verify"]
}
resource "azurerm_key_vault_key" "test" {
name = "test"
key_vault_id = azurerm_key_vault.test.id
key_type = "RSA"
key_size = 2048
key_opts = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"]
depends_on = [
azurerm_key_vault_access_policy.client,
azurerm_key_vault_access_policy.server,
]
}
`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomString)
}

func (r PostgresqlFlexibleServerResource) withCustomerManagedKey(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
resource "azurerm_postgresql_flexible_server" "test" {
name = "acctest-fs-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
administrator_login = "adminTerraform"
administrator_password = "QAZwsx123"
storage_mb = 32768
version = "12"
sku_name = "B_Standard_B1ms"
zone = "1"
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.test.id]
}
customer_managed_key {
key_vault_key_id = azurerm_key_vault_key.test.id
primary_user_assigned_identity_id = azurerm_user_assigned_identity.test.id
}
}
`, r.cmkTemplate(data), data.RandomInteger)
}
24 changes: 24 additions & 0 deletions website/docs/r/postgresql_flexible_server.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ The following arguments are supported:

* `backup_retention_days` - (Optional) The backup retention days for the PostgreSQL Flexible Server. Possible values are between `7` and `35` days.

* `customer_managed_key` - (Optional) A `customer_managed_key` block as defined below.

* `geo_redundant_backup_enabled` - (Optional) Is Geo-Redundant backup enabled on the PostgreSQL Flexible Server. Defaults to `false`. Changing this forces a new PostgreSQL Flexible Server to be created.

* `create_mode` - (Optional) The creation mode which can be used to restore or replicate existing servers. Possible values are `Default` and `PointInTimeRestore`. Changing this forces a new PostgreSQL Flexible Server to be created.
Expand All @@ -108,6 +110,8 @@ The following arguments are supported:

* `high_availability` - (Optional) A `high_availability` block as defined below.

* `identity` - (Optional) An `identity` block as defined below.

* `maintenance_window` - (Optional) A `maintenance_window` block as defined below.

* `point_in_time_restore_time_in_utc` - (Optional) The point in time to restore from `source_server_id` when `create_mode` is `PointInTimeRestore`. Changing this forces a new PostgreSQL Flexible Server to be created.
Expand Down Expand Up @@ -144,6 +148,26 @@ An `authentication` block supports the following:

---

A `customer_managed_key` block supports the following:

* `key_vault_key_id` - (Required) The ID of the Key Vault Key.

* `primary_user_assigned_identity_id` - (Required) Specifies the primary user managed identity id for a Customer Managed Key. Should be added with `identity_ids`.

~> **NOTE:** This is required when `type` is set to `UserAssigned` or `SystemAssigned, UserAssigned`.

---

An `identity` block supports the following:

* `type` - (Required) Specifies the type of Managed Service Identity that should be configured on this API Management Service. Should be set to `UserAssigned`, `SystemAssigned, UserAssigned` (to enable both).

* `identity_ids` - (Optional) A list of User Assigned Managed Identity IDs to be assigned to this API Management Service. Required if used together with `customer_managed_key` block.

~> **NOTE:** This is required when `type` is set to `UserAssigned` or `SystemAssigned, UserAssigned`.

---

A `maintenance_window` block supports the following:

* `day_of_week` - (Optional) The day of week for maintenance window, where the week starts on a Sunday, i.e. Sunday = `0`, Monday = `1`. Defaults to `0`.
Expand Down

0 comments on commit 5bbe761

Please sign in to comment.