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_postgresql_flexible_server add suport for customer_managed_key #20086

Merged
merged 3 commits into from
Jan 24, 2023
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 @@ -11,7 +11,7 @@ import (
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
"github.com/hashicorp/terraform-provider-azurerm/utils"
"github.com/tombuildsstuff/kermit/sdk/network/2022-05-01/network"
"github.com/tombuildsstuff/kermit/sdk/network/2022-07-01/network"
)

type ManagerStaticMemberModel struct {
Expand Down
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)
}
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