Skip to content

Commit

Permalink
azurerm_servicebus_namespace supports customer_managed_key (#15601)
Browse files Browse the repository at this point in the history
  • Loading branch information
ms-henglu committed Apr 6, 2022
1 parent fe83e0f commit ce99c26
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 0 deletions.
93 changes: 93 additions & 0 deletions internal/services/servicebus/servicebus_namespace_resource.go
@@ -1,6 +1,7 @@
package servicebus

import (
"context"
"fmt"
"log"
"strings"
Expand All @@ -15,6 +16,9 @@ import (
"github.com/hashicorp/terraform-provider-azurerm/helpers/tf"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/features"
keyVaultParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse"
keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate"
msiValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/servicebus/migration"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/servicebus/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/servicebus/validate"
Expand Down Expand Up @@ -90,6 +94,33 @@ func resourceServiceBusNamespace() *pluginsdk.Resource {
ValidateFunc: validation.IntInSlice([]int{0, 1, 2, 4, 8, 16}),
},

"customer_managed_key": {
Type: pluginsdk.TypeList,
Optional: true,
MaxItems: 1,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"key_vault_key_id": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: keyVaultValidate.NestedItemIdWithOptionalVersion,
},

"identity_id": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: msiValidate.UserAssignedIdentityID,
},

"infrastructure_encryption_enabled": {
Type: pluginsdk.TypeBool,
Optional: true,
ForceNew: true,
},
},
},
},

"default_primary_connection_string": {
Type: pluginsdk.TypeString,
Computed: true,
Expand Down Expand Up @@ -122,6 +153,14 @@ func resourceServiceBusNamespace() *pluginsdk.Resource {

"tags": tags.Schema(),
},

CustomizeDiff: pluginsdk.CustomizeDiffShim(func(ctx context.Context, diff *pluginsdk.ResourceDiff, v interface{}) error {
oldCustomerManagedKey, newCustomerManagedKey := diff.GetChange("customer_managed_key")
if len(oldCustomerManagedKey.([]interface{})) != 0 && len(newCustomerManagedKey.([]interface{})) == 0 {
diff.ForceNew("customer_managed_key")
}
return nil
}),
}
}

Expand Down Expand Up @@ -165,6 +204,7 @@ func resourceServiceBusNamespaceCreateUpdate(d *pluginsdk.ResourceData, meta int
},
SBNamespaceProperties: &servicebus.SBNamespaceProperties{
ZoneRedundant: utils.Bool(d.Get("zone_redundant").(bool)),
Encryption: expandServiceBusNamespaceEncryption(d.Get("customer_managed_key").([]interface{})),
},
Tags: tags.Expand(t),
}
Expand Down Expand Up @@ -238,6 +278,9 @@ func resourceServiceBusNamespaceRead(d *pluginsdk.ResourceData, meta interface{}

if properties := resp.SBNamespaceProperties; properties != nil {
d.Set("zone_redundant", properties.ZoneRedundant)
if customerManagedKey, err := flattenServiceBusNamespaceEncryption(properties.Encryption); err == nil {
d.Set("customer_managed_key", customerManagedKey)
}
}

keys, err := clientStable.ListKeys(ctx, id.ResourceGroup, id.Name, serviceBusNamespaceDefaultAuthorizationRule)
Expand Down Expand Up @@ -298,6 +341,28 @@ func expandServiceBusNamespaceIdentity(input []interface{}) (*servicebus.Identit
return &out, nil
}

func expandServiceBusNamespaceEncryption(input []interface{}) *servicebus.Encryption {
if len(input) == 0 || input[0] == nil {
return nil
}
v := input[0].(map[string]interface{})
keyId, _ := keyVaultParse.ParseOptionallyVersionedNestedItemID(v["key_vault_key_id"].(string))
return &servicebus.Encryption{
KeyVaultProperties: &[]servicebus.KeyVaultProperties{
{
KeyName: utils.String(keyId.Name),
KeyVersion: utils.String(keyId.Version),
KeyVaultURI: utils.String(keyId.KeyVaultBaseUrl),
Identity: &servicebus.UserAssignedIdentityProperties{
UserAssignedIdentity: utils.String(v["identity_id"].(string)),
},
},
},
KeySource: servicebus.KeySourceMicrosoftKeyVault,
RequireInfrastructureEncryption: utils.Bool(v["infrastructure_encryption_enabled"].(bool)),
}
}

func flattenServiceBusNamespaceIdentity(input *servicebus.Identity) (*[]interface{}, error) {
var transform *identity.SystemAndUserAssignedMap

Expand All @@ -322,3 +387,31 @@ func flattenServiceBusNamespaceIdentity(input *servicebus.Identity) (*[]interfac

return identity.FlattenSystemAndUserAssignedMap(transform)
}

func flattenServiceBusNamespaceEncryption(encryption *servicebus.Encryption) ([]interface{}, error) {
if encryption == nil {
return []interface{}{}, nil
}

var keyId string
var identityId string
if keyVaultProperties := encryption.KeyVaultProperties; keyVaultProperties != nil && len(*keyVaultProperties) != 0 {
props := (*keyVaultProperties)[0]
keyVaultKeyId, err := keyVaultParse.NewNestedItemID(*props.KeyVaultURI, "keys", *props.KeyName, *props.KeyVersion)
if err != nil {
return nil, fmt.Errorf("parsing `key_vault_key_id`: %+v", err)
}
keyId = keyVaultKeyId.ID()
if props.Identity != nil && props.Identity.UserAssignedIdentity != nil {
identityId = *props.Identity.UserAssignedIdentity
}
}

return []interface{}{
map[string]interface{}{
"infrastructure_encryption_enabled": encryption.RequireInfrastructureEncryption,
"key_vault_key_id": keyId,
"identity_id": identityId,
},
}, nil
}
101 changes: 101 additions & 0 deletions internal/services/servicebus/servicebus_namespace_resource_test.go
Expand Up @@ -171,6 +171,21 @@ func TestAccAzureRMServiceBusNamespace_identity(t *testing.T) {
})
}

func TestAccAzureRMServiceBusNamespace_customerManagedKey(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_servicebus_namespace", "test")
r := ServiceBusNamespaceResource{}

data.ResourceSequentialTest(t, r, []acceptance.TestStep{
{
Config: r.customerManagedKey(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func (t ServiceBusNamespaceResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := parse.NamespaceID(state.ID)
if err != nil {
Expand Down Expand Up @@ -415,3 +430,89 @@ resource "azurerm_servicebus_namespace" "test" {
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
}

func (ServiceBusNamespaceResource) customerManagedKey(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-%[2]d"
location = "%[1]s"
}
resource "azurerm_user_assigned_identity" "test" {
name = "acctestUAI-%[2]d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
}
resource "azurerm_key_vault" "test" {
name = "acctestkv%[3]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
access_policy {
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"
]
secret_permissions = [
"Get",
]
}
access_policy {
tenant_id = azurerm_user_assigned_identity.test.tenant_id
object_id = azurerm_user_assigned_identity.test.principal_id
key_permissions = [
"Get", "Create", "Delete", "List", "Restore", "Recover", "UnwrapKey", "WrapKey", "Purge", "Encrypt", "Decrypt", "Sign", "Verify"
]
secret_permissions = [
"Get",
]
}
}
resource "azurerm_key_vault_key" "test" {
name = "acctestkvkey%[3]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_servicebus_namespace" "test" {
name = "acctestservicebusnamespace-%[2]d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
sku = "Premium"
capacity = 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
identity_id = azurerm_user_assigned_identity.test.id
infrastructure_encryption_enabled = true
}
}
`, data.Locations.Primary, data.RandomInteger, data.RandomString)
}
15 changes: 15 additions & 0 deletions website/docs/r/servicebus_namespace.html.markdown
Expand Up @@ -52,6 +52,8 @@ The following arguments are supported:

* `capacity` - (Optional) Specifies the capacity. When `sku` is `Premium`, capacity can be `1`, `2`, `4`, `8` or `16`. When `sku` is `Basic` or `Standard`, capacity can be `0` only.

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

* `zone_redundant` - (Optional) Whether or not this resource is zone redundant. `sku` needs to be `Premium`. Defaults to `false`.

* `tags` - (Optional) A mapping of tags to assign to the resource.
Expand All @@ -67,6 +69,19 @@ An `identity` block supports the following:
~> **NOTE:** This is required when `type` is set to `UserAssigned` or `SystemAssigned, UserAssigned`.


---

-> **Note:** Once customer-managed key encryption has been enabled, it cannot be disabled.

A `customer_managed_key` block supports the following:


* `key_vault_key_id` - (Required) The ID of the Key Vault Key which should be used to Encrypt the data in this ServiceBus Namespace.

* `identity_id` - (Required) The ID of the User Assigned Identity that has access to the key.

* `infrastructure_encryption_enabled` - (Optional) Used to specify whether enable Infrastructure Encryption (Double Encryption).

## Attributes Reference

The following attributes are exported:
Expand Down

0 comments on commit ce99c26

Please sign in to comment.