diff --git a/modules/net-app/net-app-account/.test/nfs41/dependencies.bicep b/modules/net-app/net-app-account/.test/nfs41/dependencies.bicep index 624322e555..a3f4dbf21c 100644 --- a/modules/net-app/net-app-account/.test/nfs41/dependencies.bicep +++ b/modules/net-app/net-app-account/.test/nfs41/dependencies.bicep @@ -7,6 +7,9 @@ param virtualNetworkName string @description('Required. The name of the Managed Identity to create.') param managedIdentityName string +@description('Required. The name of the Key Vault to create.') +param keyVaultName string + var addressPrefix = '10.0.0.0/16' resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = { @@ -42,6 +45,42 @@ resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018- location: location } +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: keyVaultName + location: location + properties: { + sku: { + family: 'A' + name: 'standard' + } + tenantId: tenant().tenantId + enablePurgeProtection: true // Required by batch account + softDeleteRetentionInDays: 7 + enabledForTemplateDeployment: true + enabledForDiskEncryption: true + enabledForDeployment: true + enableRbacAuthorization: true + accessPolicies: [] + } + + resource key 'keys@2022-07-01' = { + name: 'keyEncryptionKey' + properties: { + kty: 'RSA' + } + } +} + +resource keyPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('msi-${keyVault::key.id}-${location}-${managedIdentity.id}-Key-Key-Vault-Crypto-User-RoleAssignment') + scope: keyVault::key + properties: { + principalId: managedIdentity.properties.principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424') // Key Vault Crypto User + principalType: 'ServicePrincipal' + } +} + @description('The resource ID of the created Virtual Network Subnet.') output subnetResourceId string = virtualNetwork.properties.subnets[0].id @@ -50,3 +89,9 @@ output managedIdentityPrincipalId string = managedIdentity.properties.principalI @description('The resource ID of the created Managed Identity.') output managedIdentityResourceId string = managedIdentity.id + +@description('The resource ID of the created Key Vault.') +output keyVaultResourceId string = keyVault.id + +@description('The name of the created Key Vault encryption key.') +output keyVaultKeyName string = keyVault::key.name diff --git a/modules/net-app/net-app-account/.test/nfs41/main.test.bicep b/modules/net-app/net-app-account/.test/nfs41/main.test.bicep index 2ef7beb8c2..c9204d32a5 100644 --- a/modules/net-app/net-app-account/.test/nfs41/main.test.bicep +++ b/modules/net-app/net-app-account/.test/nfs41/main.test.bicep @@ -12,7 +12,10 @@ param resourceGroupName string = 'ms.netapp.netappaccounts-${serviceShort}-rg' param location string = deployment().location @description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') -param serviceShort string = 'nanaanfs41' +param serviceShort string = 'naanfs41' + +@description('Generated. Used as a basis for unique resource names.') +param baseTime string = utcNow('u') @description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') param enableDefaultTelemetry bool = true @@ -37,6 +40,8 @@ module nestedDependencies 'dependencies.bicep' = { params: { virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + // Adding base time to make the name unique as purge protection must be enabled (but may not be longer than 24 characters total) + keyVaultName: 'dep${namePrefix}kv${serviceShort}${substring(uniqueString(baseTime), 0, 3)}' } } @@ -148,5 +153,8 @@ module testDeployment '../../main.bicep' = { userAssignedIdentities: { '${nestedDependencies.outputs.managedIdentityResourceId}': {} } + cMKKeyName: nestedDependencies.outputs.keyVaultKeyName + cMKKeyVaultResourceId: nestedDependencies.outputs.keyVaultResourceId + cMKUserAssignedIdentityResourceId: nestedDependencies.outputs.managedIdentityResourceId } } diff --git a/modules/net-app/net-app-account/README.md b/modules/net-app/net-app-account/README.md index 28fb3f81fb..3e5b744c06 100644 --- a/modules/net-app/net-app-account/README.md +++ b/modules/net-app/net-app-account/README.md @@ -28,11 +28,19 @@ This module deploys an Azure NetApp File. | :-- | :-- | :-- | | `name` | string | The name of the NetApp account. | +**Conditional parameters** + +| Parameter Name | Type | Default Value | Description | +| :-- | :-- | :-- | :-- | +| `cMKKeyVaultResourceId` | string | `''` | The resource ID of a key vault to reference a customer managed key for encryption from. Required if 'cMKKeyName' is not empty. | +| `cMKUserAssignedIdentityResourceId` | string | `''` | User assigned identity to use when fetching the customer managed key. Required if 'cMKKeyName' is not empty. | + **Optional parameters** | Parameter Name | Type | Default Value | Allowed Values | Description | | :-- | :-- | :-- | :-- | :-- | | `capacityPools` | array | `[]` | | Capacity pools to create. | +| `cMKKeyName` | string | `''` | | The name of the customer managed key to use for encryption. | | `dnsServers` | string | `''` | | Required if domainName is specified. Comma separated list of DNS server IP addresses (IPv4 only) required for the Active Directory (AD) domain join and SMB authentication operations to succeed. | | `domainJoinOU` | string | `''` | | Used only if domainName is specified. LDAP Path for the Organization Unit (OU) where SMB Server machine accounts will be created (i.e. 'OU=SecondLevel,OU=FirstLevel'). | | `domainJoinPassword` | securestring | `''` | | Required if domainName is specified. Password of the user specified in domainJoinUser parameter. | @@ -478,14 +486,14 @@ module netAppAccount './net-app/net-app-account/main.bicep' = { ```bicep module netAppAccount './net-app/net-app-account/main.bicep' = { - name: '${uniqueString(deployment().name, location)}-test-nanaanfs41' + name: '${uniqueString(deployment().name, location)}-test-naanfs41' params: { // Required parameters - name: 'nanaanfs41001' + name: 'naanfs41001' // Non-required parameters capacityPools: [ { - name: 'nanaanfs41-cp-001' + name: 'naanfs41-cp-001' roleAssignments: [ { principalIds: [ @@ -509,7 +517,7 @@ module netAppAccount './net-app/net-app-account/main.bicep' = { unixReadWrite: true } ] - name: 'nanaanfs41-vol-001' + name: 'naanfs41-vol-001' protocolTypes: [ 'NFSv4.1' ] @@ -536,7 +544,7 @@ module netAppAccount './net-app/net-app-account/main.bicep' = { unixReadWrite: true } ] - name: 'nanaanfs41-vol-002' + name: 'naanfs41-vol-002' protocolTypes: [ 'NFSv4.1' ] @@ -546,7 +554,7 @@ module netAppAccount './net-app/net-app-account/main.bicep' = { ] } { - name: 'nanaanfs41-cp-002' + name: 'naanfs41-cp-002' roleAssignments: [ { principalIds: [ @@ -561,6 +569,9 @@ module netAppAccount './net-app/net-app-account/main.bicep' = { volumes: [] } ] + cMKKeyName: '' + cMKKeyVaultResourceId: '' + cMKUserAssignedIdentityResourceId: '' enableDefaultTelemetry: '' roleAssignments: [ { @@ -600,13 +611,13 @@ module netAppAccount './net-app/net-app-account/main.bicep' = { "parameters": { // Required parameters "name": { - "value": "nanaanfs41001" + "value": "naanfs41001" }, // Non-required parameters "capacityPools": { "value": [ { - "name": "nanaanfs41-cp-001", + "name": "naanfs41-cp-001", "roleAssignments": [ { "principalIds": [ @@ -630,7 +641,7 @@ module netAppAccount './net-app/net-app-account/main.bicep' = { "unixReadWrite": true } ], - "name": "nanaanfs41-vol-001", + "name": "naanfs41-vol-001", "protocolTypes": [ "NFSv4.1" ], @@ -657,7 +668,7 @@ module netAppAccount './net-app/net-app-account/main.bicep' = { "unixReadWrite": true } ], - "name": "nanaanfs41-vol-002", + "name": "naanfs41-vol-002", "protocolTypes": [ "NFSv4.1" ], @@ -667,7 +678,7 @@ module netAppAccount './net-app/net-app-account/main.bicep' = { ] }, { - "name": "nanaanfs41-cp-002", + "name": "naanfs41-cp-002", "roleAssignments": [ { "principalIds": [ @@ -683,6 +694,15 @@ module netAppAccount './net-app/net-app-account/main.bicep' = { } ] }, + "cMKKeyName": { + "value": "" + }, + "cMKKeyVaultResourceId": { + "value": "" + }, + "cMKUserAssignedIdentityResourceId": { + "value": "" + }, "enableDefaultTelemetry": { "value": "" }, diff --git a/modules/net-app/net-app-account/main.bicep b/modules/net-app/net-app-account/main.bicep index 12fc192758..ee8bedcd73 100644 --- a/modules/net-app/net-app-account/main.bicep +++ b/modules/net-app/net-app-account/main.bicep @@ -47,6 +47,15 @@ param lock string = '' @description('Optional. Tags for all resources.') param tags object = {} +@description('Conditional. The resource ID of a key vault to reference a customer managed key for encryption from. Required if \'cMKKeyName\' is not empty.') +param cMKKeyVaultResourceId string = '' + +@description('Optional. The name of the customer managed key to use for encryption.') +param cMKKeyName string = '' + +@description('Conditional. User assigned identity to use when fetching the customer managed key. Required if \'cMKKeyName\' is not empty.') +param cMKUserAssignedIdentityResourceId string = '' + @description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') param enableDefaultTelemetry bool = true @@ -70,6 +79,11 @@ var identity = identityType != 'None' ? { userAssignedIdentities: !empty(userAssignedIdentities) ? userAssignedIdentities : null } : null +resource cMKKeyVault 'Microsoft.KeyVault/vaults@2021-10-01' existing = if (!empty(cMKKeyVaultResourceId)) { + name: last(split(cMKKeyVaultResourceId, '/'))! + scope: resourceGroup(split(cMKKeyVaultResourceId, '/')[2], split(cMKKeyVaultResourceId, '/')[4]) +} + resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name)}' properties: { @@ -89,6 +103,19 @@ resource netAppAccount 'Microsoft.NetApp/netAppAccounts@2022-11-01' = { location: location properties: { activeDirectories: !empty(domainName) ? activeDirectoryConnectionProperties : null + encryption: !empty(cMKKeyName) ? { + identity: !empty(cMKUserAssignedIdentityResourceId) ? { + userAssignedIdentity: cMKUserAssignedIdentityResourceId + } : null + keySource: 'Microsoft.KeyVault' + keyVaultProperties: { + keyName: cMKKeyName + keyVaultResourceId: cMKKeyVault.id + keyVaultUri: cMKKeyVault.properties.vaultUri + } + } : { + keySource: 'Microsoft.NetApp' + } } } diff --git a/modules/net-app/net-app-account/main.json b/modules/net-app/net-app-account/main.json index 1ab6f4ee7a..4300aee600 100644 --- a/modules/net-app/net-app-account/main.json +++ b/modules/net-app/net-app-account/main.json @@ -107,6 +107,27 @@ "description": "Optional. Tags for all resources." } }, + "cMKKeyVaultResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. The resource ID of a key vault to reference a customer managed key for encryption from. Required if 'cMKKeyName' is not empty." + } + }, + "cMKKeyName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The name of the customer managed key to use for encryption." + } + }, + "cMKUserAssignedIdentityResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. User assigned identity to use when fetching the customer managed key. Required if 'cMKKeyName' is not empty." + } + }, "enableDefaultTelemetry": { "type": "bool", "defaultValue": true, @@ -153,7 +174,8 @@ "identity": "[variables('identity')]", "location": "[parameters('location')]", "properties": { - "activeDirectories": "[if(not(empty(parameters('domainName'))), variables('activeDirectoryConnectionProperties'), null())]" + "activeDirectories": "[if(not(empty(parameters('domainName'))), variables('activeDirectoryConnectionProperties'), null())]", + "encryption": "[if(not(empty(parameters('cMKKeyName'))), createObject('identity', if(not(empty(parameters('cMKUserAssignedIdentityResourceId'))), createObject('userAssignedIdentity', parameters('cMKUserAssignedIdentityResourceId')), null()), 'keySource', 'Microsoft.KeyVault', 'keyVaultProperties', createObject('keyName', parameters('cMKKeyName'), 'keyVaultResourceId', extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('cMKKeyVaultResourceId'), '/')[2], split(parameters('cMKKeyVaultResourceId'), '/')[4]), 'Microsoft.KeyVault/vaults', last(split(parameters('cMKKeyVaultResourceId'), '/'))), 'keyVaultUri', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('cMKKeyVaultResourceId'), '/')[2], split(parameters('cMKKeyVaultResourceId'), '/')[4]), 'Microsoft.KeyVault/vaults', last(split(parameters('cMKKeyVaultResourceId'), '/'))), '2021-10-01').vaultUri)), createObject('keySource', 'Microsoft.NetApp'))]" } }, { @@ -1072,4 +1094,4 @@ "value": "[reference(resourceId('Microsoft.NetApp/netAppAccounts', parameters('name')), '2022-11-01', 'full').location]" } } -} \ No newline at end of file +} diff --git a/utilities/pipelines/staticValidation/module.tests.ps1 b/utilities/pipelines/staticValidation/module.tests.ps1 index fcb740727c..ad1dae4f12 100644 --- a/utilities/pipelines/staticValidation/module.tests.ps1 +++ b/utilities/pipelines/staticValidation/module.tests.ps1 @@ -1643,7 +1643,7 @@ Describe 'API version tests' -Tag 'ApiCheck' { $approvedApiVersions = @() if ($AllowPreviewVersionsInAPITests) { - # We allow the latest 5 including previews (in case somebody wants to use preview), or the latest 3 non-preview + # We allow the latest 5 including previews (in case somebody wants to use preview), or the latest 5 non-preview $approvedApiVersions += $resourceTypeApiVersions | Select-Object -Last 5 $approvedApiVersions += $resourceTypeApiVersions | Where-Object { $_ -notlike '*-preview' } | Select-Object -Last 5 } else {