Skip to content

Commit

Permalink
Allow setting User Assigned Identity for Event Hub Encryption (#23635)
Browse files Browse the repository at this point in the history
Co-authored-by: Jake Scott <jake@jakethesnake.dev>
Co-authored-by: kt <kt@katbyte.me>
3 people authored Oct 27, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent bcbfa8f commit 0f9acc6
Showing 3 changed files with 274 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import (
"time"

"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids"
"github.com/hashicorp/go-azure-sdk/resource-manager/eventhub/2022-01-01-preview/namespaces"
"github.com/hashicorp/terraform-provider-azurerm/helpers/tf"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
@@ -84,6 +85,12 @@ func resourceEventHubNamespaceCustomerManagedKey() *pluginsdk.Resource {
Default: false,
ForceNew: true,
},

"user_assigned_identity_id": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: commonids.ValidateUserAssignedIdentityID,
},
},
}
}
@@ -116,7 +123,6 @@ func resourceEventHubNamespaceCustomerManagedKeyCreateUpdate(d *pluginsdk.Resour
}

namespace := resp.Model

keySource := namespaces.KeySourceMicrosoftPointKeyVault
namespace.Properties.Encryption = &namespaces.Encryption{
KeySource: &keySource,
@@ -126,6 +132,34 @@ func resourceEventHubNamespaceCustomerManagedKeyCreateUpdate(d *pluginsdk.Resour
if err != nil {
return err
}

userAssignedIdentity := d.Get("user_assigned_identity_id").(string)
if userAssignedIdentity != "" && keyVaultProps != nil {

// this provides a more helpful error message than the API response
if namespace.Identity == nil {
return fmt.Errorf("user assigned identity '%s' must also be assigned to the parent event hub - currently no user assigned identities are assigned to the parent event hub", userAssignedIdentity)
}

isIdentityAssignedToParent := false
for item := range namespace.Identity.IdentityIds {
if item == userAssignedIdentity {
isIdentityAssignedToParent = true
}
}

// this provides a more helpful error message than the API response
if !isIdentityAssignedToParent {
return fmt.Errorf("user assigned identity '%s' must also be assigned to the parent event hub", userAssignedIdentity)
}

for i := 0; i < len(*keyVaultProps); i++ {
(*keyVaultProps)[i].Identity = &namespaces.UserAssignedIdentityProperties{
UserAssignedIdentity: &userAssignedIdentity,
}
}
}

namespace.Properties.Encryption.KeyVaultProperties = keyVaultProps
namespace.Properties.Encryption.RequireInfrastructureEncryption = utils.Bool(d.Get("infrastructure_encryption_enabled").(bool))

@@ -174,14 +208,30 @@ func resourceEventHubNamespaceCustomerManagedKeyRead(d *pluginsdk.ResourceData,

d.Set("key_vault_key_ids", keyVaultKeyIds)
d.Set("infrastructure_encryption_enabled", props.Encryption.RequireInfrastructureEncryption)

if kvprops := props.Encryption.KeyVaultProperties; kvprops != nil {
// we can only have a single user managed id for N number of keys, azure portal only allows setting a single one and then applies it to each key
for _, item := range *kvprops {
if item.Identity != nil && item.Identity.UserAssignedIdentity != nil {
userAssignedId, err := commonids.ParseUserAssignedIdentityIDInsensitively(*item.Identity.UserAssignedIdentity)
if err != nil {
return fmt.Errorf("parsing `user_assigned_identity_id`: %+v", err)
}
if err := d.Set("user_assigned_identity_id", userAssignedId.ID()); err != nil {
return fmt.Errorf("setting `user_assigned_identity_id`: %+v", err)
}

break
}
}
}
}

return nil
}

func resourceEventHubNamespaceCustomerManagedKeyDelete(d *pluginsdk.ResourceData, meta interface{}) error {
log.Printf(`[INFO] Customer Managed Keys cannot be removed from EventHub Namespaces once added. To remove the Customer Managed Key delete and recreate the parent EventHub Namespace")
`)
log.Printf(`[INFO] Customer Managed Keys cannot be removed from EventHub Namespaces once added. To remove the Customer Managed Key delete and recreate the parent EventHub Namespace`)
return nil
}

@@ -208,8 +258,8 @@ func expandEventHubNamespaceKeyVaultKeyIds(input []interface{}) (*[]namespaces.K
return &results, nil
}

func flattenEventHubNamespaceKeyVaultKeyIds(input *namespaces.Encryption) ([]interface{}, error) {
results := make([]interface{}, 0)
func flattenEventHubNamespaceKeyVaultKeyIds(input *namespaces.Encryption) ([]string, error) {
results := make([]string, 0)
if input == nil || input.KeyVaultProperties == nil {
return results, nil
}
Original file line number Diff line number Diff line change
@@ -33,6 +33,21 @@ func TestAccEventHubNamespaceCustomerManagedKey_basic(t *testing.T) {
})
}

func TestAccEventHubNamespaceCustomerManagedKey_withUserAssignedIdentity(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_eventhub_namespace_customer_managed_key", "test")
r := EventHubNamespaceCustomerManagedKeyResource{}

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

func TestAccEventHubNamespaceCustomerManagedKey_requiresImport(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_eventhub_namespace_customer_managed_key", "test")
r := EventHubNamespaceCustomerManagedKeyResource{}
@@ -129,6 +144,18 @@ resource "azurerm_eventhub_namespace_customer_managed_key" "test" {
`, r.template(data))
}

func (r EventHubNamespaceCustomerManagedKeyResource) withUserAssignedIdentity(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
resource "azurerm_eventhub_namespace_customer_managed_key" "test" {
eventhub_namespace_id = azurerm_eventhub_namespace.test.id
key_vault_key_ids = [azurerm_key_vault_key.test.id]
user_assigned_identity_id = azurerm_user_assigned_identity.test.id
}
`, r.templateWithUserAssignedIdentity(data))
}

func (r EventHubNamespaceCustomerManagedKeyResource) update(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
@@ -262,3 +289,95 @@ resource "azurerm_key_vault_key" "test" {
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomString, data.RandomString)
}

func (r EventHubNamespaceCustomerManagedKeyResource) templateWithUserAssignedIdentity(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
}
}
}
resource "azurerm_resource_group" "test" {
name = "acctestRG-namespacecmk-%d"
location = "%s"
}
resource "azurerm_eventhub_cluster" "test" {
name = "acctest-cluster-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
sku_name = "Dedicated_1"
}
resource "azurerm_user_assigned_identity" "test" {
location = azurerm_resource_group.test.location
name = "acctest-identity-%s"
resource_group_name = azurerm_resource_group.test.name
}
resource "azurerm_eventhub_namespace" "test" {
name = "acctest-namespace-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
sku = "Standard"
dedicated_cluster_id = azurerm_eventhub_cluster.test.id
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.test.id]
}
}
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 = "standard"
purge_protection_enabled = true
}
resource "azurerm_key_vault_access_policy" "test" {
key_vault_id = azurerm_key_vault.test.id
tenant_id = azurerm_user_assigned_identity.test.tenant_id
object_id = azurerm_user_assigned_identity.test.principal_id
key_permissions = ["Get", "UnwrapKey", "WrapKey", "GetRotationPolicy"]
}
resource "azurerm_key_vault_access_policy" "test2" {
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 = [
"Create",
"Delete",
"Get",
"List",
"Purge",
"Recover",
"GetRotationPolicy"
]
}
resource "azurerm_key_vault_key" "test" {
name = "acctestkvkey%s"
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.test,
azurerm_key_vault_access_policy.test2,
]
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomString, data.RandomString)
}
101 changes: 100 additions & 1 deletion website/docs/r/eventhub_namespace_customer_managed_key.html.markdown
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ Manages a Customer Managed Key for a EventHub Namespace.

!> **Note:** In 2.x versions of the Azure Provider during deletion this resource will **delete and recreate the parent EventHub Namespace which may involve data loss** as it's not possible to remove the Customer Managed Key from the EventHub Namespace once it's been added. Version 3.0 of the Azure Provider will change this so that the Delete operation is a noop, requiring the parent EventHub Namespace is deleted/recreated to remove the Customer Managed Key.

## Example Usage
## Example Usage with System Assigned Identity

```hcl
resource "azurerm_resource_group" "example" {
@@ -93,6 +93,99 @@ resource "azurerm_eventhub_namespace_customer_managed_key" "example" {
}
```

## Example Usage with User Assigned Identity

```hcl
resource "azurerm_resource_group" "example" {
name = "example-resources"
location = "West Europe"
}
resource "azurerm_eventhub_cluster" "example" {
name = "example-cluster"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
sku_name = "Dedicated_1"
}
resource "azurerm_user_assigned_identity" "example" {
location = azurerm_resource_group.example.location
name = "example"
resource_group_name = azurerm_resource_group.example.name
}
resource "azurerm_eventhub_namespace" "example" {
name = "example-namespace"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
sku = "Standard"
dedicated_cluster_id = azurerm_eventhub_cluster.example.id
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.example.id]
}
}
data "azurerm_client_config" "current" {}
resource "azurerm_key_vault" "example" {
name = "examplekv"
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 = "standard"
purge_protection_enabled = true
}
resource "azurerm_key_vault_access_policy" "example" {
key_vault_id = azurerm_key_vault.example.id
tenant_id = azurerm_user_assigned_identity.test.tenant_id
object_id = azurerm_user_assigned_identity.test.principal_id
key_permissions = ["Get", "UnwrapKey", "WrapKey"]
}
resource "azurerm_key_vault_access_policy" "example2" {
key_vault_id = azurerm_key_vault.example.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
key_permissions = [
"Create",
"Delete",
"Get",
"List",
"Purge",
"Recover",
"GetRotationPolicy",
]
}
resource "azurerm_key_vault_key" "example" {
name = "examplekvkey"
key_vault_id = azurerm_key_vault.example.id
key_type = "RSA"
key_size = 2048
key_opts = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"]
depends_on = [
azurerm_key_vault_access_policy.example,
azurerm_key_vault_access_policy.example2,
]
}
resource "azurerm_eventhub_namespace_customer_managed_key" "example" {
eventhub_namespace_id = azurerm_eventhub_namespace.example.id
key_vault_key_ids = [azurerm_key_vault_key.example.id]
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.example.id]
}
}
```

## Arguments Reference

The following arguments are supported:
@@ -103,6 +196,12 @@ The following arguments are supported:

* `infrastructure_encryption_enabled` - (Optional) Whether to enable Infrastructure Encryption (Double Encryption). Changing this forces a new resource to be created.

* `user_assigned_identity_id` - (Optional) The ID of a User Managed Identity that will be used to access Key Vaults that contain the encryption keys.

~> **Note:** If using `user_assigned_identity_id`, ensure the User Assigned Identity is also assigned to the parent Event Hub.

~> **Note:** If using `user_assigned_identity_id`, make sure to assign the identity the appropriate permissions to access the Key Vault key. Failure to grant `Get, UnwrapKey, and WrapKey` will cause this resource to fail to apply.

## Attributes Reference

In addition to the Arguments listed above - the following Attributes are exported:

0 comments on commit 0f9acc6

Please sign in to comment.