From 9bc0a5ab2165d5bfaae7c3007be5dccd0e31abab Mon Sep 17 00:00:00 2001 From: Peter Souter Date: Wed, 11 Nov 2020 10:21:32 +0000 Subject: [PATCH] Adds CMK for CosmosDB Account (#8919) Co-authored-by: kt Co-authored-by: jackofallops --- .../cosmos/cosmosdb_account_data_source.go | 9 ++ .../cosmos/cosmosdb_account_resource.go | 39 +++++- .../cosmos/cosmosdb_account_resource_test.go | 127 ++++++++++++++++++ .../cosmos-db/customer-managed-key/README.md | 3 + .../cosmos-db/customer-managed-key/main.tf | 83 ++++++++++++ .../cosmos-db/customer-managed-key/outputs.tf | 23 ++++ .../customer-managed-key/variables.tf | 7 + website/docs/d/cosmosdb_account.html.markdown | 4 + website/docs/r/cosmosdb_account.html.markdown | 4 + 9 files changed, 296 insertions(+), 3 deletions(-) create mode 100644 examples/cosmos-db/customer-managed-key/README.md create mode 100644 examples/cosmos-db/customer-managed-key/main.tf create mode 100644 examples/cosmos-db/customer-managed-key/outputs.tf create mode 100644 examples/cosmos-db/customer-managed-key/variables.tf diff --git a/azurerm/internal/services/cosmos/cosmosdb_account_data_source.go b/azurerm/internal/services/cosmos/cosmosdb_account_data_source.go index f18e86376b3e..1321c514430a 100644 --- a/azurerm/internal/services/cosmos/cosmosdb_account_data_source.go +++ b/azurerm/internal/services/cosmos/cosmosdb_account_data_source.go @@ -138,6 +138,11 @@ func dataSourceArmCosmosDbAccount() *schema.Resource { }, }, + "key_vault_key_id": { + Type: schema.TypeString, + Computed: true, + }, + "enable_multiple_write_locations": { Type: schema.TypeBool, Computed: true, @@ -253,6 +258,10 @@ func dataSourceArmCosmosDbAccountRead(d *schema.ResourceData, meta interface{}) d.Set("enable_free_tier", resp.EnableFreeTier) d.Set("enable_automatic_failover", resp.EnableAutomaticFailover) + if v := props.KeyVaultKeyURI; v != nil { + d.Set("key_vault_key_id", resp.KeyVaultKeyURI) + } + if err = d.Set("consistency_policy", flattenAzureRmCosmosDBAccountConsistencyPolicy(resp.ConsistencyPolicy)); err != nil { return fmt.Errorf("Error setting `consistency_policy`: %+v", err) } diff --git a/azurerm/internal/services/cosmos/cosmosdb_account_resource.go b/azurerm/internal/services/cosmos/cosmosdb_account_resource.go index a7b7fb9dbed0..96bf8269b095 100644 --- a/azurerm/internal/services/cosmos/cosmosdb_account_resource.go +++ b/azurerm/internal/services/cosmos/cosmosdb_account_resource.go @@ -10,9 +10,6 @@ import ( "strings" "time" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/cosmos/common" - "github.com/Azure/azure-sdk-for-go/services/preview/cosmos-db/mgmt/2020-04-01-preview/documentdb" "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" @@ -22,6 +19,8 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/cosmos/common" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" @@ -117,6 +116,14 @@ func resourceArmCosmosDbAccount() *schema.Resource { Default: false, }, + "key_vault_key_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: diffSuppressIgnoreKeyVaultKeyVersion, + ValidateFunc: azure.ValidateKeyVaultChildIdVersionOptional, + }, + "consistency_policy": { Type: schema.TypeList, Required: true, @@ -407,6 +414,15 @@ func resourceArmCosmosDbAccountCreate(d *schema.ResourceData, meta interface{}) Tags: tags.Expand(t), } + if keyVaultKeyIDRaw, ok := d.GetOk("key_vault_key_id"); ok { + keyVaultKey, err := azure.ParseKeyVaultChildIDVersionOptional(keyVaultKeyIDRaw.(string)) + if err != nil { + return fmt.Errorf("could not parse Key Vault Key ID: %+v", err) + } + keyVaultKeyURI := fmt.Sprintf("%skeys/%s", keyVaultKey.KeyVaultBaseUrl, keyVaultKey.Name) + account.DatabaseAccountCreateUpdateProperties.KeyVaultKeyURI = utils.String(keyVaultKeyURI) + } + // additional validation on MaxStalenessPrefix as it varies depending on if the DB is multi region or not consistencyPolicy := account.DatabaseAccountCreateUpdateProperties.ConsistencyPolicy if len(geoLocations) > 1 && consistencyPolicy != nil && consistencyPolicy.DefaultConsistencyLevel == documentdb.BoundedStaleness { @@ -596,6 +612,10 @@ func resourceArmCosmosDbAccountRead(d *schema.ResourceData, meta interface{}) er d.Set("enable_automatic_failover", resp.EnableAutomaticFailover) } + if v := resp.KeyVaultKeyURI; v != nil { + d.Set("key_vault_key_id", resp.KeyVaultKeyURI) + } + if v := resp.EnableMultipleWriteLocations; v != nil { d.Set("enable_multiple_write_locations", resp.EnableMultipleWriteLocations) } @@ -1020,3 +1040,16 @@ func resourceAzureRMCosmosDBAccountVirtualNetworkRuleHash(v interface{}) int { return hashcode.String(buf.String()) } + +func diffSuppressIgnoreKeyVaultKeyVersion(k, old, new string, d *schema.ResourceData) bool { + oldKey, err := azure.ParseKeyVaultChildIDVersionOptional(old) + if err != nil { + return false + } + newKey, err := azure.ParseKeyVaultChildIDVersionOptional(new) + if err != nil { + return false + } + + return (oldKey.KeyVaultBaseUrl == newKey.KeyVaultBaseUrl) && (oldKey.Name == newKey.Name) +} diff --git a/azurerm/internal/services/cosmos/cosmosdb_account_resource_test.go b/azurerm/internal/services/cosmos/cosmosdb_account_resource_test.go index 8a099b7445ca..1b465c1a9620 100644 --- a/azurerm/internal/services/cosmos/cosmosdb_account_resource_test.go +++ b/azurerm/internal/services/cosmos/cosmosdb_account_resource_test.go @@ -74,6 +74,29 @@ func TestAccAzureRMCosmosDBAccount_basic_parse_strong(t *testing.T) { testAccAzureRMCosmosDBAccount_basicWith(t, documentdb.MongoDB, documentdb.Strong) } +func TestAccAzureRMCosmosDBAccount_key_vault_uri(t *testing.T) { + testAccAzureRMCosmosDBAccount_key_vault_uri(t, documentdb.MongoDB, documentdb.Strong) +} + +func testAccAzureRMCosmosDBAccount_key_vault_uri(t *testing.T, kind documentdb.DatabaseAccountKind, consistency documentdb.DefaultConsistencyLevel) { + data := acceptance.BuildTestData(t, "azurerm_cosmosdb_account", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMCosmosDBAccountDestroy, + Steps: []resource.TestStep{ + { + Config: checkAccAzureRMCosmosDBAccount_key_vault_uri(data, kind, consistency), + Check: resource.ComposeAggregateTestCheckFunc( + checkAccAzureRMCosmosDBAccount_basic(data, consistency, 1), + ), + }, + data.ImportStep(), + }, + }) +} + func testAccAzureRMCosmosDBAccount_basicWith(t *testing.T, kind documentdb.DatabaseAccountKind, consistency documentdb.DefaultConsistencyLevel) { data := acceptance.BuildTestData(t, "azurerm_cosmosdb_account", "test") @@ -993,3 +1016,107 @@ func checkAccAzureRMCosmosDBAccount_basic(data acceptance.TestData, consistency resource.TestCheckResourceAttrSet(data.ResourceName, "secondary_readonly_key"), ) } + +func checkAccAzureRMCosmosDBAccount_key_vault_uri(data acceptance.TestData, kind documentdb.DatabaseAccountKind, consistency documentdb.DefaultConsistencyLevel) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-cosmos-%d" + location = "%s" +} + +data "azuread_service_principal" "cosmosdb" { + display_name = "Azure Cosmos DB" +} + +data "azurerm_client_config" "current" {} + +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 = "premium" + + purge_protection_enabled = true + soft_delete_enabled = true + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "list", + "create", + "delete", + "get", + "update", + ] + + secret_permissions = [ + "get", + "delete", + "set", + ] + } + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azuread_service_principal.cosmosdb.id + + key_permissions = [ + "list", + "create", + "delete", + "get", + "update", + "unwrapKey", + "wrapKey", + ] + + secret_permissions = [ + "get", + "delete", + "set", + ] + } +} + +resource "azurerm_key_vault_key" "test" { + name = "key-%s" + key_vault_id = azurerm_key_vault.test.id + key_type = "RSA" + key_size = 2048 + + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] +} + +resource "azurerm_cosmosdb_account" "test" { + name = "acctest-ca-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + offer_type = "Standard" + kind = "%s" + key_vault_key_id = azurerm_key_vault_key.test.id + + consistency_policy { + consistency_level = "%s" + } + + geo_location { + location = azurerm_resource_group.test.location + failover_priority = 0 + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomString, data.RandomInteger, string(kind), string(consistency)) +} diff --git a/examples/cosmos-db/customer-managed-key/README.md b/examples/cosmos-db/customer-managed-key/README.md new file mode 100644 index 000000000000..7a2a9ec19443 --- /dev/null +++ b/examples/cosmos-db/customer-managed-key/README.md @@ -0,0 +1,3 @@ +## Example: CosmosDB with CMK (Customer Managed Key) encryption + +This example provisions a CosmosDB Account in a single region that uses a generated key stored in a Key Vault for encryption. diff --git a/examples/cosmos-db/customer-managed-key/main.tf b/examples/cosmos-db/customer-managed-key/main.tf new file mode 100644 index 000000000000..4869572072a9 --- /dev/null +++ b/examples/cosmos-db/customer-managed-key/main.tf @@ -0,0 +1,83 @@ +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "example" { + name = "${var.prefix}-resources" + location = var.location +} + +data "azuread_service_principal" "cosmosdb" { + display_name = "Azure Cosmos DB" +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_key_vault" "example" { + name = "${var.prefix}kv" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + + purge_protection_enabled = true + soft_delete_enabled = true + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "list", + "create", + "delete", + "get", + "update", + ] + + } + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azuread_service_principal.cosmosdb.id + + key_permissions = [ + "get", + "unwrapKey", + "wrapKey", + ] + } +} + +resource "azurerm_key_vault_key" "example" { + name = "${var.prefix}key" + key_vault_id = azurerm_key_vault.example.id + key_type = "RSA" + key_size = 3072 + + key_opts = [ + "decrypt", + "encrypt", + "wrapKey", + "unwrapKey", + ] +} + +resource "azurerm_cosmosdb_account" "example" { + name = "${var.prefix}-cosmosdb" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + offer_type = "Standard" + kind = "MongoDB" + key_vault_key_id = azurerm_key_vault_key.example.id + + consistency_policy { + consistency_level = "Strong" + } + + geo_location { + prefix = "${var.prefix}-customid" + location = azurerm_resource_group.example.location + failover_priority = 0 + } +} diff --git a/examples/cosmos-db/customer-managed-key/outputs.tf b/examples/cosmos-db/customer-managed-key/outputs.tf new file mode 100644 index 000000000000..8e4782f1bc3c --- /dev/null +++ b/examples/cosmos-db/customer-managed-key/outputs.tf @@ -0,0 +1,23 @@ +output "cosmos-db-id" { + value = "${azurerm_cosmosdb_account.example.id}" +} + +output "cosmos-db-endpoint" { + value = "${azurerm_cosmosdb_account.example.endpoint}" +} + +output "cosmos-db-endpoints_read" { + value = "${azurerm_cosmosdb_account.example.read_endpoints}" +} + +output "cosmos-db-endpoints_write" { + value = "${azurerm_cosmosdb_account.example.write_endpoints}" +} + +output "cosmos-db-primary_key" { + value = "${azurerm_cosmosdb_account.example.primary_key}" +} + +output "cosmos-db-secondary_key" { + value = "${azurerm_cosmosdb_account.example.secondary_key}" +} diff --git a/examples/cosmos-db/customer-managed-key/variables.tf b/examples/cosmos-db/customer-managed-key/variables.tf new file mode 100644 index 000000000000..68babb00b91a --- /dev/null +++ b/examples/cosmos-db/customer-managed-key/variables.tf @@ -0,0 +1,7 @@ +variable "prefix" { + description = "The prefix which should be used for all resources in this example" +} + +variable "location" { + description = "The Azure Region in which all resources in this example should be created." +} diff --git a/website/docs/d/cosmosdb_account.html.markdown b/website/docs/d/cosmosdb_account.html.markdown index 066128756fba..298cea475637 100644 --- a/website/docs/d/cosmosdb_account.html.markdown +++ b/website/docs/d/cosmosdb_account.html.markdown @@ -45,6 +45,10 @@ The following attributes are exported: * `kind` - The Kind of the CosmosDB account. +* `key_vault_key_id` - The Key Vault key URI for CMK encryption. + +~> **NOTE:** The CosmosDB service always uses the latest version of the specified key. + * `ip_range_filter` - The current IP Filter for this CosmosDB account * `enable_free_tier` - If Free Tier pricing option is enabled for this CosmosDB Account. diff --git a/website/docs/r/cosmosdb_account.html.markdown b/website/docs/r/cosmosdb_account.html.markdown index 4f92294df4a4..6c25ec57619e 100644 --- a/website/docs/r/cosmosdb_account.html.markdown +++ b/website/docs/r/cosmosdb_account.html.markdown @@ -92,6 +92,10 @@ The following arguments are supported: * `is_virtual_network_filter_enabled` - (Optional) Enables virtual network filtering for this Cosmos DB account. +* `key_vault_key_id` - (Optional) A Key Vault Key ID for CMK encryption. Changing this forces a new resource to be created. + +~> **NOTE:** The CosmosDB service always uses the latest version of the specified key, so terraform ignores the version specified in the Key Vault Key ID. + * `virtual_network_rule` - (Optional) Specifies a `virtual_network_rules` resource, used to define which subnets are allowed to access this CosmosDB account. * `enable_multiple_write_locations` - (Optional) Enable multi-master support for this Cosmos DB account.