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

r\hpc_cache: Add support for encryption key #16972

Merged
merged 5 commits into from
Jun 8, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
223 changes: 222 additions & 1 deletion internal/services/hpccache/hpc_cache_resource.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
package hpccache

import (
"context"
"fmt"
"log"
"regexp"
"strconv"
"time"

"github.com/Azure/azure-sdk-for-go/services/storagecache/mgmt/2021-09-01/storagecache"
"github.com/hashicorp/go-azure-helpers/resourcemanager/identity"
"github.com/hashicorp/terraform-provider-azurerm/helpers/azure"
"github.com/hashicorp/terraform-provider-azurerm/helpers/tf"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/hpccache/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/client"
keyVaultParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse"
keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate"
msiparse "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/parse"
msivalidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/validate"
resourcesClient "github.com/hashicorp/terraform-provider-azurerm/internal/services/resource/client"
"github.com/hashicorp/terraform-provider-azurerm/internal/tags"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
Expand All @@ -38,11 +46,20 @@ func resourceHPCCache() *pluginsdk.Resource {
}),

Schema: resourceHPCCacheSchema(),

CustomizeDiff: pluginsdk.CustomDiffInSequence(
pluginsdk.ForceNewIfChange("key_vault_key_id", func(ctx context.Context, old, new, meta interface{}) bool {
// `key_vault_key_id` cannot be added or removed after created
return (old != "" && new == "") || (old == "" && new != "")
}),
),
}
}

func resourceHPCCacheCreateOrUpdate(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).HPCCache.CachesClient
keyVaultsClient := meta.(*clients.Client).KeyVault
resourcesClient := meta.(*clients.Client).Resource
subscriptionId := meta.(*clients.Client).Account.SubscriptionId
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
defer cancel()
Expand Down Expand Up @@ -108,6 +125,11 @@ func resourceHPCCacheCreateOrUpdate(d *pluginsdk.ResourceData, meta interface{})

directorySetting := expandStorageCacheDirectorySettings(d)

identity, err := expandStorageCacheIdentity(d.Get("identity").([]interface{}))
if err != nil {
return fmt.Errorf("expanding `identity`: %+v", err)
}

cache := &storagecache.Cache{
Name: utils.String(name),
Location: utils.String(location),
Expand All @@ -123,7 +145,41 @@ func resourceHPCCacheCreateOrUpdate(d *pluginsdk.ResourceData, meta interface{})
Sku: &storagecache.CacheSku{
Name: utils.String(skuName),
},
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
Identity: identity,
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
}

if v, ok := d.GetOk("key_vault_key_id"); ok {
autoKeyRotationEnabled := d.Get("auto_key_rotation_enabled").(bool)
if !d.IsNewResource() && d.HasChange("key_vault_key_id") && autoKeyRotationEnabled {
// It is by design that `auto_key_rotation_enabled` changes to `false` internally in service when `key_vault_key_id` is changed
return fmt.Errorf("`auto_key_rotation_enabled` must be set to `false` when updating `key_vault_key_id`")
}

keyVaultKeyId := v.(string)
keyVaultDetails, err := storageCacheRetrieveKeyVault(ctx, keyVaultsClient, resourcesClient, keyVaultKeyId)
if err != nil {
return fmt.Errorf("validating Key Vault Key %q for HPC Cache: %+v", keyVaultKeyId, err)
}
if azure.NormalizeLocation(keyVaultDetails.location) != azure.NormalizeLocation(location) {
return fmt.Errorf("validating Key Vault %q (Resource Group %q) for HPC Cache: Key Vault must be in the same region as HPC Cache!", keyVaultDetails.keyVaultName, keyVaultDetails.resourceGroupName)
}
if !keyVaultDetails.softDeleteEnabled {
return fmt.Errorf("validating Key Vault %q (Resource Group %q) for HPC Cache: Soft Delete must be enabled but it isn't!", keyVaultDetails.keyVaultName, keyVaultDetails.resourceGroupName)
}
if !keyVaultDetails.purgeProtectionEnabled {
return fmt.Errorf("validating Key Vault %q (Resource Group %q) for HPC Cache: Purge Protection must be enabled but it isn't!", keyVaultDetails.keyVaultName, keyVaultDetails.resourceGroupName)
}

cache.CacheProperties.EncryptionSettings = &storagecache.CacheEncryptionSettings{
KeyEncryptionKey: &storagecache.KeyVaultKeyReference{
KeyURL: utils.String(keyVaultKeyId),
SourceVault: &storagecache.KeyVaultKeyReferenceSourceVault{
ID: utils.String(keyVaultDetails.keyVaultId),
},
},
RotationToLatestKeyVersionEnabled: utils.Bool(autoKeyRotationEnabled),
}
}

future, err := client.CreateOrUpdate(ctx, resourceGroup, name, cache)
Expand Down Expand Up @@ -251,6 +307,28 @@ func resourceHPCCacheRead(d *pluginsdk.ResourceData, meta interface{}) error {
d.Set("sku_name", sku.Name)
}

identity, err := flattenStorageCacheIdentity(resp.Identity)
if err != nil {
return err
}
if err := d.Set("identity", identity); err != nil {
return fmt.Errorf("setting `identity`: %+v", err)
}

keyVaultKeyId := ""
autoKeyRotationEnabled := false
if props := resp.EncryptionSettings; props != nil {
if props.KeyEncryptionKey != nil && props.KeyEncryptionKey.KeyURL != nil {
keyVaultKeyId = *props.KeyEncryptionKey.KeyURL
}

if props.RotationToLatestKeyVersionEnabled != nil {
autoKeyRotationEnabled = *props.RotationToLatestKeyVersionEnabled
}
}
d.Set("key_vault_key_id", keyVaultKeyId)
d.Set("auto_key_rotation_enabled", autoKeyRotationEnabled)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given the name of the property

Suggested change
autoKeyRotationEnabled = *props.RotationToLatestKeyVersionEnabled
}
}
d.Set("key_vault_key_id", keyVaultKeyId)
d.Set("auto_key_rotation_enabled", autoKeyRotationEnabled)
autoKeyRotationEnabled = *props.RotationToLatestKeyVersionEnabled
}
}
d.Set("key_vault_key_id", keyVaultKeyId)
d.Set("automatically_rotate_key_to_latest_enabled", autoKeyRotationEnabled)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@katbyte I've renamed the property name.


return tags.FlattenAndSet(d, resp.Tags)
}

Expand Down Expand Up @@ -624,6 +702,109 @@ func expandStorageCacheDirectoryLdapBind(input []interface{}) *storagecache.Cach
}
}

func expandStorageCacheIdentity(input []interface{}) (*storagecache.CacheIdentity, error) {
config, err := identity.ExpandUserAssignedMap(input)
if err != nil {
return nil, err
}

identity := storagecache.CacheIdentity{
Type: storagecache.CacheIdentityType(config.Type),
}

if len(config.IdentityIds) != 0 {
identityIds := make(map[string]*storagecache.CacheIdentityUserAssignedIdentitiesValue, len(config.IdentityIds))
for id := range config.IdentityIds {
identityIds[id] = &storagecache.CacheIdentityUserAssignedIdentitiesValue{}
}
identity.UserAssignedIdentities = identityIds
}

return &identity, nil
}

func flattenStorageCacheIdentity(input *storagecache.CacheIdentity) (*[]interface{}, error) {
var config *identity.UserAssignedMap

if input != nil {
identityIds := map[string]identity.UserAssignedIdentityDetails{}
for id := range input.UserAssignedIdentities {
parsedId, err := msiparse.UserAssignedIdentityIDInsensitively(id)
if err != nil {
return nil, err
}
identityIds[parsedId.ID()] = identity.UserAssignedIdentityDetails{}
}

config = &identity.UserAssignedMap{
Type: identity.Type(string(input.Type)),
IdentityIds: identityIds,
}
}

return identity.FlattenUserAssignedMap(config)
}

type storageCacheKeyVault struct {
keyVaultId string
resourceGroupName string
keyVaultName string
location string
purgeProtectionEnabled bool
softDeleteEnabled bool
}

func storageCacheRetrieveKeyVault(ctx context.Context, keyVaultsClient *client.Client, resourcesClient *resourcesClient.Client, id string) (*storageCacheKeyVault, error) {
keyVaultKeyId, err := keyVaultParse.ParseNestedItemID(id)
if err != nil {
return nil, err
}
keyVaultID, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, resourcesClient, keyVaultKeyId.KeyVaultBaseUrl)
if err != nil {
return nil, fmt.Errorf("retrieving the Resource ID the Key Vault at URL %q: %s", keyVaultKeyId.KeyVaultBaseUrl, err)
}
if keyVaultID == nil {
return nil, fmt.Errorf("Unable to determine the Resource ID for the Key Vault at URL %q", keyVaultKeyId.KeyVaultBaseUrl)
}

parsedKeyVaultID, err := keyVaultParse.VaultID(*keyVaultID)
if err != nil {
return nil, err
}

resp, err := keyVaultsClient.VaultsClient.Get(ctx, parsedKeyVaultID.ResourceGroup, parsedKeyVaultID.Name)
if err != nil {
return nil, fmt.Errorf("retrieving %s: %+v", *parsedKeyVaultID, err)
}

purgeProtectionEnabled := false
softDeleteEnabled := false

if props := resp.Properties; props != nil {
if props.EnableSoftDelete != nil {
softDeleteEnabled = *props.EnableSoftDelete
}

if props.EnablePurgeProtection != nil {
purgeProtectionEnabled = *props.EnablePurgeProtection
}
}

location := ""
if resp.Location != nil {
location = *resp.Location
}

return &storageCacheKeyVault{
keyVaultId: *keyVaultID,
resourceGroupName: parsedKeyVaultID.ResourceGroup,
keyVaultName: parsedKeyVaultID.Name,
location: location,
purgeProtectionEnabled: purgeProtectionEnabled,
softDeleteEnabled: softDeleteEnabled,
}, nil
}

func resourceHPCCacheSchema() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"name": {
Expand Down Expand Up @@ -923,6 +1104,46 @@ func resourceHPCCacheSchema() map[string]*pluginsdk.Schema {
Elem: &pluginsdk.Schema{Type: pluginsdk.TypeString},
},

"identity": {
Type: pluginsdk.TypeList,
Optional: true,
ForceNew: true,
MaxItems: 1,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"type": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
// System-assigned Managed Identity requires manual operation on Portal
string(storagecache.CacheIdentityTypeUserAssigned),
}, false),
},
"identity_ids": {
Type: pluginsdk.TypeSet,
Optional: true,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
ValidateFunc: msivalidate.UserAssignedIdentityID,
},
},
},
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have updated to commonschema.UserAssignedIdentityOptionalForceNew()


"key_vault_key_id": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: keyVaultValidate.NestedItemId,
RequiredWith: []string{"identity"},
},

"auto_key_rotation_enabled": {
Type: pluginsdk.TypeBool,
Optional: true,
RequiredWith: []string{"key_vault_key_id"},
},

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