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_eventhub_namespace_customer_managed_key - support for user_assigned_identity_id #23635

Merged
merged 31 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7e8dc72
working implementation, still need to finish UAC tests
bruceharrison1984 Oct 19, 2023
038f8f9
Support Users Assigned IDs with BYOK
jake-scott Aug 8, 2023
e793543
add docs
bruceharrison1984 Oct 19, 2023
1f75f99
add heading to docs
bruceharrison1984 Oct 19, 2023
64a2c5a
add note to property
bruceharrison1984 Oct 19, 2023
32dcc63
use existing identity schema
bruceharrison1984 Oct 20, 2023
a7d1fbe
fix typo
bruceharrison1984 Oct 20, 2023
4463ffc
move method
bruceharrison1984 Oct 20, 2023
aefa032
linting
bruceharrison1984 Oct 20, 2023
d6832ae
fix test to use identity block
bruceharrison1984 Oct 20, 2023
3df4e1a
update docs
bruceharrison1984 Oct 20, 2023
a8644d1
rename test
bruceharrison1984 Oct 20, 2023
741cab4
linting
bruceharrison1984 Oct 20, 2023
791a6cc
unnest for loop
bruceharrison1984 Oct 20, 2023
e43ffe8
remove redunant string check
bruceharrison1984 Oct 20, 2023
32a7bf3
rename method
bruceharrison1984 Oct 20, 2023
1b03cfc
cleanup if checks
bruceharrison1984 Oct 20, 2023
7186e6a
fix faulty if statement
bruceharrison1984 Oct 23, 2023
d0f6ce8
update docs
bruceharrison1984 Oct 23, 2023
9bfe4ff
populate identity property on read
bruceharrison1984 Oct 23, 2023
475d11f
fix error in docs
bruceharrison1984 Oct 23, 2023
c1b10be
code review cleanup, fix null ref error
bruceharrison1984 Oct 24, 2023
99d5d51
remove dead code
bruceharrison1984 Oct 24, 2023
d6022dd
add additional docs note
bruceharrison1984 Oct 24, 2023
b4c2f6e
Update internal/services/eventhub/eventhub_namespace_customer_managed…
katbyte Oct 24, 2023
72ca0af
Update internal/services/eventhub/eventhub_namespace_customer_managed…
katbyte Oct 24, 2023
1252b0a
refactor to use a string Id instead of an identity block
bruceharrison1984 Oct 26, 2023
259eb27
add comments
bruceharrison1984 Oct 26, 2023
d979e2a
move if check up
bruceharrison1984 Oct 26, 2023
5a89a56
move method body up
bruceharrison1984 Oct 26, 2023
87e9ae7
Update eventhub_namespace_customer_managed_key_resource.go
katbyte Oct 27, 2023
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 @@ -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"
Expand Down Expand Up @@ -84,6 +85,12 @@ func resourceEventHubNamespaceCustomerManagedKey() *pluginsdk.Resource {
Default: false,
ForceNew: true,
},

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

namespace := resp.Model

keySource := namespaces.KeySourceMicrosoftPointKeyVault
namespace.Properties.Encryption = &namespaces.Encryption{
KeySource: &keySource,
Expand All @@ -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++ {
katbyte marked this conversation as resolved.
Show resolved Hide resolved
(*keyVaultProps)[i].Identity = &namespaces.UserAssignedIdentityProperties{
UserAssignedIdentity: &userAssignedIdentity,
}
}
}

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

Expand Down Expand Up @@ -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)
katbyte marked this conversation as resolved.
Show resolved Hide resolved
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
}

Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
Loading