-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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" | ||
|
@@ -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() | ||
|
@@ -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), | ||
|
@@ -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) | ||
|
@@ -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) | ||
|
||
return tags.FlattenAndSet(d, resp.Tags) | ||
} | ||
|
||
|
@@ -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": { | ||
|
@@ -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, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this needs to be using Common Schema: https://github.com/hashicorp/go-azure-helpers/blob/main/resourcemanager/commonschema/identity_user.go There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. have updated to |
||
|
||
"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(), | ||
} | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.