From 640939d11600db3182834752944ed8cb03a2f4ce Mon Sep 17 00:00:00 2001 From: AlexanderSehr Date: Mon, 18 Sep 2023 16:01:31 +0100 Subject: [PATCH] Updated module test folders --- .../tests/e2e/encr-sys/main.test.bicep | 4 +- .../account/tests/e2e/encr/main.test.bicep | 4 +- .../e2e/{common => max}/dependencies.bicep | 0 .../tests/e2e/{common => max}/main.test.bicep | 8 +- .../account/tests/e2e/min/main.test.bicep | 3 +- .../account/tests/e2e/speech/main.test.bicep | 4 +- .../tests/e2e/waf-aligned/dependencies.bicep | 68 +++++ .../tests/e2e/waf-aligned/main.test.bicep | 150 +++++++++++ .../e2e/{common => max}/dependencies.bicep | 0 .../tests/e2e/{common => max}/main.test.bicep | 8 +- .../vault/tests/e2e/min/main.test.bicep | 3 +- .../vault/tests/e2e/pe/main.test.bicep | 8 +- .../tests/e2e/waf-aligned/dependencies.bicep | 65 +++++ .../tests/e2e/waf-aligned/main.test.bicep | 237 ++++++++++++++++++ .../e2e/{common => max}/dependencies.bicep | 0 .../tests/e2e/{common => max}/main.test.bicep | 6 +- .../tests/e2e/min/main.test.bicep | 4 +- .../tests/e2e/waf-aligned/dependencies.bicep | 95 +++++++ .../tests/e2e/waf-aligned/main.test.bicep | 93 +++++++ .../hello commit history.txt | 0 .../.scripts/Copy-VhdToStorageAccount.ps1 | 123 +++++++++ .../scripts/.scripts/Get-PairedRegion.ps1 | 32 +++ .../scripts/.scripts/New-SSHKey.ps1 | 40 +++ .../scripts/.scripts/Set-BlobContent.ps1 | 46 ++++ .../.scripts/Set-CertificateInKeyVault.ps1 | 67 +++++ .../.scripts/Set-PfxCertificateInKeyVault.ps1 | 62 +++++ .../scripts/.scripts/Start-ImageTemplate.ps1 | 79 ++++++ .../.templates/diagnostic.dependencies.bicep | 79 ++++++ .../scripts/Copy-VhdToStorageAccount.ps1 | 123 +++++++++ .../scripts/Get-PairedRegion.ps1 | 32 +++ .../scripts/New-SSHKey.ps1 | 40 +++ .../scripts/Set-BlobContent.ps1 | 46 ++++ .../scripts/Set-CertificateInKeyVault.ps1 | 67 +++++ .../scripts/Set-PfxCertificateInKeyVault.ps1 | 62 +++++ .../scripts/Start-ImageTemplate.ps1 | 79 ++++++ .../templates/diagnostic.dependencies.bicep | 79 ++++++ 36 files changed, 1799 insertions(+), 17 deletions(-) rename avm/res/cognitive-services/account/tests/e2e/{common => max}/dependencies.bicep (100%) rename avm/res/cognitive-services/account/tests/e2e/{common => max}/main.test.bicep (94%) create mode 100644 avm/res/cognitive-services/account/tests/e2e/waf-aligned/dependencies.bicep create mode 100644 avm/res/cognitive-services/account/tests/e2e/waf-aligned/main.test.bicep rename avm/res/key-vault/vault/tests/e2e/{common => max}/dependencies.bicep (100%) rename avm/res/key-vault/vault/tests/e2e/{common => max}/main.test.bicep (96%) create mode 100644 avm/res/key-vault/vault/tests/e2e/waf-aligned/dependencies.bicep create mode 100644 avm/res/key-vault/vault/tests/e2e/waf-aligned/main.test.bicep rename avm/res/network/private-endpoint/tests/e2e/{common => max}/dependencies.bicep (100%) rename avm/res/network/private-endpoint/tests/e2e/{common => max}/main.test.bicep (95%) create mode 100644 avm/res/network/private-endpoint/tests/e2e/waf-aligned/dependencies.bicep create mode 100644 avm/res/network/private-endpoint/tests/e2e/waf-aligned/main.test.bicep delete mode 100644 avm/utilities/e2e-template-assets/hello commit history.txt create mode 100644 avm/utilities/e2e-template-assets/scripts/.scripts/Copy-VhdToStorageAccount.ps1 create mode 100644 avm/utilities/e2e-template-assets/scripts/.scripts/Get-PairedRegion.ps1 create mode 100644 avm/utilities/e2e-template-assets/scripts/.scripts/New-SSHKey.ps1 create mode 100644 avm/utilities/e2e-template-assets/scripts/.scripts/Set-BlobContent.ps1 create mode 100644 avm/utilities/e2e-template-assets/scripts/.scripts/Set-CertificateInKeyVault.ps1 create mode 100644 avm/utilities/e2e-template-assets/scripts/.scripts/Set-PfxCertificateInKeyVault.ps1 create mode 100644 avm/utilities/e2e-template-assets/scripts/.scripts/Start-ImageTemplate.ps1 create mode 100644 avm/utilities/e2e-template-assets/scripts/.templates/diagnostic.dependencies.bicep create mode 100644 avm/utilities/e2e-template-assets/scripts/Copy-VhdToStorageAccount.ps1 create mode 100644 avm/utilities/e2e-template-assets/scripts/Get-PairedRegion.ps1 create mode 100644 avm/utilities/e2e-template-assets/scripts/New-SSHKey.ps1 create mode 100644 avm/utilities/e2e-template-assets/scripts/Set-BlobContent.ps1 create mode 100644 avm/utilities/e2e-template-assets/scripts/Set-CertificateInKeyVault.ps1 create mode 100644 avm/utilities/e2e-template-assets/scripts/Set-PfxCertificateInKeyVault.ps1 create mode 100644 avm/utilities/e2e-template-assets/scripts/Start-ImageTemplate.ps1 create mode 100644 avm/utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep diff --git a/avm/res/cognitive-services/account/tests/e2e/encr-sys/main.test.bicep b/avm/res/cognitive-services/account/tests/e2e/encr-sys/main.test.bicep index d217d793f3..2097d81cc7 100644 --- a/avm/res/cognitive-services/account/tests/e2e/encr-sys/main.test.bicep +++ b/avm/res/cognitive-services/account/tests/e2e/encr-sys/main.test.bicep @@ -41,6 +41,7 @@ module nestedDependencies 'dependencies.bicep' = { cognitiveServiceName: '${namePrefix}${serviceShort}001' // 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)}' + location: location } } @@ -48,13 +49,14 @@ module nestedDependencies 'dependencies.bicep' = { // Test Execution // // ============== // -module testDeployment '../../main.bicep' = { +module testDeployment '../../../main.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' params: { enableDefaultTelemetry: enableDefaultTelemetry name: nestedDependencies.outputs.cognitiveServiceName kind: 'SpeechServices' + location: location customerManagedKey: { keyName: nestedDependencies.outputs.keyVaultKeyName keyVaultResourceId: nestedDependencies.outputs.keyVaultResourceId diff --git a/avm/res/cognitive-services/account/tests/e2e/encr/main.test.bicep b/avm/res/cognitive-services/account/tests/e2e/encr/main.test.bicep index 86ef62b9f4..93baa21eb8 100644 --- a/avm/res/cognitive-services/account/tests/e2e/encr/main.test.bicep +++ b/avm/res/cognitive-services/account/tests/e2e/encr/main.test.bicep @@ -41,6 +41,7 @@ module nestedDependencies 'dependencies.bicep' = { 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)}' + location: location } } @@ -48,13 +49,14 @@ module nestedDependencies 'dependencies.bicep' = { // Test Execution // // ============== // -module testDeployment '../../main.bicep' = { +module testDeployment '../../../main.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' params: { enableDefaultTelemetry: enableDefaultTelemetry name: '${namePrefix}${serviceShort}001' kind: 'SpeechServices' + location: location customerManagedKey: { keyName: nestedDependencies.outputs.keyVaultKeyName keyVaultResourceId: nestedDependencies.outputs.keyVaultResourceId diff --git a/avm/res/cognitive-services/account/tests/e2e/common/dependencies.bicep b/avm/res/cognitive-services/account/tests/e2e/max/dependencies.bicep similarity index 100% rename from avm/res/cognitive-services/account/tests/e2e/common/dependencies.bicep rename to avm/res/cognitive-services/account/tests/e2e/max/dependencies.bicep diff --git a/avm/res/cognitive-services/account/tests/e2e/common/main.test.bicep b/avm/res/cognitive-services/account/tests/e2e/max/main.test.bicep similarity index 94% rename from avm/res/cognitive-services/account/tests/e2e/common/main.test.bicep rename to avm/res/cognitive-services/account/tests/e2e/max/main.test.bicep index 5bd858ac7f..30ecab17e1 100644 --- a/avm/res/cognitive-services/account/tests/e2e/common/main.test.bicep +++ b/avm/res/cognitive-services/account/tests/e2e/max/main.test.bicep @@ -12,7 +12,7 @@ param resourceGroupName string = 'ms.cognitiveservices.accounts-${serviceShort}- 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 = 'csacom' +param serviceShort string = 'csamax' @description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') param enableDefaultTelemetry bool = true @@ -37,12 +37,13 @@ module nestedDependencies 'dependencies.bicep' = { params: { virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + location: location } } // Diagnostics // =========== -module diagnosticDependencies '../../../../.shared/.templates/diagnostic.dependencies.bicep' = { +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-diagnosticDependencies' params: { @@ -58,7 +59,7 @@ module diagnosticDependencies '../../../../.shared/.templates/diagnostic.depende // Test Execution // // ============== // -module testDeployment '../../main.bicep' = { +module testDeployment '../../../main.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' params: { @@ -66,6 +67,7 @@ module testDeployment '../../main.bicep' = { name: '${namePrefix}${serviceShort}001' kind: 'Face' customSubDomainName: '${namePrefix}xdomain' + location: location diagnosticSettings: [ { // logAnalyticsDestinationType: diff --git a/avm/res/cognitive-services/account/tests/e2e/min/main.test.bicep b/avm/res/cognitive-services/account/tests/e2e/min/main.test.bicep index 727b9a5a92..1da157a4f8 100644 --- a/avm/res/cognitive-services/account/tests/e2e/min/main.test.bicep +++ b/avm/res/cognitive-services/account/tests/e2e/min/main.test.bicep @@ -35,12 +35,13 @@ resource resourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = { // Test Execution // // ============== // -module testDeployment '../../main.bicep' = { +module testDeployment '../../../main.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' params: { enableDefaultTelemetry: enableDefaultTelemetry name: '${namePrefix}${serviceShort}001' kind: 'SpeechServices' + location: location } } diff --git a/avm/res/cognitive-services/account/tests/e2e/speech/main.test.bicep b/avm/res/cognitive-services/account/tests/e2e/speech/main.test.bicep index f67065d5b9..6e9fc12716 100644 --- a/avm/res/cognitive-services/account/tests/e2e/speech/main.test.bicep +++ b/avm/res/cognitive-services/account/tests/e2e/speech/main.test.bicep @@ -37,19 +37,21 @@ module nestedDependencies 'dependencies.bicep' = { params: { virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + location: location } } // ============== // // Test Execution // // ============== // -module testDeployment '../../main.bicep' = { +module testDeployment '../../../main.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' params: { enableDefaultTelemetry: enableDefaultTelemetry name: '${namePrefix}${serviceShort}001' kind: 'SpeechServices' + location: location customSubDomainName: '${namePrefix}speechdomain' privateEndpoints: [ { diff --git a/avm/res/cognitive-services/account/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/cognitive-services/account/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 0000000000..129b6f6579 --- /dev/null +++ b/avm/res/cognitive-services/account/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,68 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +var addressPrefix = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'defaultSubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + serviceEndpoints: [ + { + service: 'Microsoft.CognitiveServices' + } + ] + } + } + ] + } +} + +resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.cognitiveservices.azure.com' + location: 'global' + + resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = { + name: '${virtualNetwork.name}-vnetlink' + location: 'global' + properties: { + virtualNetwork: { + id: virtualNetwork.id + } + registrationEnabled: false + } + } +} + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +@description('The resource ID of the created Virtual Network Subnet.') +output subnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The resource ID of the created Managed Identity.') +output managedIdentityResourceId string = managedIdentity.id + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId + +@description('The resource ID of the created Private DNS zone.') +output privateDNSZoneResourceId string = privateDNSZone.id diff --git a/avm/res/cognitive-services/account/tests/e2e/waf-aligned/main.test.bicep b/avm/res/cognitive-services/account/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..308c3f00fe --- /dev/null +++ b/avm/res/cognitive-services/account/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,150 @@ +targetScope = 'subscription' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'ms.cognitiveservices.accounts-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +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 = 'csawaf' + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '[[namePrefix]]' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + location: location + } +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-diagnosticDependencies' + params: { + storageAccountName: 'dep${namePrefix}diasa${serviceShort}01' + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' + eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + enableDefaultTelemetry: enableDefaultTelemetry + name: '${namePrefix}${serviceShort}001' + kind: 'Face' + location: location + customSubDomainName: '${namePrefix}xdomain' + diagnosticSettings: [ + { + // logAnalyticsDestinationType: + // marketplacePartnerResourceId: + name: 'customSetting' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + logCategoriesAndGroups: [ + { + category: 'RequestResponse' + } + { + category: 'Audit' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + { + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + lock: 'CanNotDelete' + networkAcls: { + defaultAction: 'Deny' + ipRules: [ + { + value: '40.74.28.0/23' + } + ] + virtualNetworkRules: [ + { + id: nestedDependencies.outputs.subnetResourceId + ignoreMissingVnetServiceEndpoint: false + } + ] + } + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + sku: 'S0' + managedIdentities: { + systemAssigned: true + userAssignedResourcesIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] + } + privateEndpoints: [ + { + privateDnsZoneResourceIds: [ + nestedDependencies.outputs.privateDNSZoneResourceId + ] + service: 'account' + subnetResourceId: nestedDependencies.outputs.subnetResourceId + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +} diff --git a/avm/res/key-vault/vault/tests/e2e/common/dependencies.bicep b/avm/res/key-vault/vault/tests/e2e/max/dependencies.bicep similarity index 100% rename from avm/res/key-vault/vault/tests/e2e/common/dependencies.bicep rename to avm/res/key-vault/vault/tests/e2e/max/dependencies.bicep diff --git a/avm/res/key-vault/vault/tests/e2e/common/main.test.bicep b/avm/res/key-vault/vault/tests/e2e/max/main.test.bicep similarity index 96% rename from avm/res/key-vault/vault/tests/e2e/common/main.test.bicep rename to avm/res/key-vault/vault/tests/e2e/max/main.test.bicep index 5a737e3b20..82c9f58e11 100644 --- a/avm/res/key-vault/vault/tests/e2e/common/main.test.bicep +++ b/avm/res/key-vault/vault/tests/e2e/max/main.test.bicep @@ -12,7 +12,7 @@ param resourceGroupName string = 'ms.keyvault.vaults-${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 = 'kvvcom' +param serviceShort string = 'kvvmax' @description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') param enableDefaultTelemetry bool = true @@ -37,12 +37,13 @@ module nestedDependencies 'dependencies.bicep' = { params: { virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + location: location } } // Diagnostics // =========== -module diagnosticDependencies '../../../../.shared/.templates/diagnostic.dependencies.bicep' = { +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-diagnosticDependencies' params: { @@ -58,12 +59,13 @@ module diagnosticDependencies '../../../../.shared/.templates/diagnostic.depende // Test Execution // // ============== // -module testDeployment '../../main.bicep' = { +module testDeployment '../../../main.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' params: { enableDefaultTelemetry: enableDefaultTelemetry name: '${namePrefix}${serviceShort}002' + location: location accessPolicies: [ { objectId: nestedDependencies.outputs.managedIdentityPrincipalId diff --git a/avm/res/key-vault/vault/tests/e2e/min/main.test.bicep b/avm/res/key-vault/vault/tests/e2e/min/main.test.bicep index 0ecea959ed..9aee14b980 100644 --- a/avm/res/key-vault/vault/tests/e2e/min/main.test.bicep +++ b/avm/res/key-vault/vault/tests/e2e/min/main.test.bicep @@ -35,12 +35,13 @@ resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { // Test Execution // // ============== // -module testDeployment '../../main.bicep' = { +module testDeployment '../../../main.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' params: { enableDefaultTelemetry: enableDefaultTelemetry name: '${namePrefix}${serviceShort}002' + location: location // Only for testing purposes enablePurgeProtection: false } diff --git a/avm/res/key-vault/vault/tests/e2e/pe/main.test.bicep b/avm/res/key-vault/vault/tests/e2e/pe/main.test.bicep index d64d7cf5d2..0e1e2bfc20 100644 --- a/avm/res/key-vault/vault/tests/e2e/pe/main.test.bicep +++ b/avm/res/key-vault/vault/tests/e2e/pe/main.test.bicep @@ -36,6 +36,7 @@ module nestedDependencies 'dependencies.bicep' = { name: '${uniqueString(deployment().name, location)}-nestedDependencies' params: { virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + location: location } } @@ -43,14 +44,13 @@ module nestedDependencies 'dependencies.bicep' = { // Test Execution // // ============== // -module testDeployment '../../main.bicep' = { +module testDeployment '../../../main.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' params: { enableDefaultTelemetry: enableDefaultTelemetry name: '${namePrefix}${serviceShort}001' - // Only for testing purposes - enablePurgeProtection: false + location: location privateEndpoints: [ { privateDnsZoneResourceIds: [ @@ -70,5 +70,7 @@ module testDeployment '../../main.bicep' = { Environment: 'Non-Prod' Role: 'DeploymentValidation' } + // Only for testing purposes + enablePurgeProtection: false } } diff --git a/avm/res/key-vault/vault/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/key-vault/vault/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 0000000000..f433490224 --- /dev/null +++ b/avm/res/key-vault/vault/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,65 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +var addressPrefix = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'defaultSubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + serviceEndpoints: [ + { + service: 'Microsoft.KeyVault' + } + ] + } + } + ] + } +} + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.vaultcore.azure.net' + location: 'global' + + resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = { + name: '${virtualNetwork.name}-vnetlink' + location: 'global' + properties: { + virtualNetwork: { + id: virtualNetwork.id + } + registrationEnabled: false + } + } +} + +@description('The resource ID of the created Virtual Network Subnet.') +output subnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId + +@description('The resource ID of the created Private DNS Zone.') +output privateDNSResourceId string = privateDNSZone.id diff --git a/avm/res/key-vault/vault/tests/e2e/waf-aligned/main.test.bicep b/avm/res/key-vault/vault/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..45b2d61d7e --- /dev/null +++ b/avm/res/key-vault/vault/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,237 @@ +targetScope = 'subscription' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'ms.keyvault.vaults-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +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 = 'kvvwaf' + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '[[namePrefix]]' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + location: location + } +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-diagnosticDependencies' + params: { + storageAccountName: 'dep${namePrefix}diasa${serviceShort}03' + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}01' + eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}01' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + enableDefaultTelemetry: enableDefaultTelemetry + name: '${namePrefix}${serviceShort}002' + location: location + accessPolicies: [ + { + objectId: nestedDependencies.outputs.managedIdentityPrincipalId + permissions: { + keys: [ + 'get' + 'list' + 'update' + ] + secrets: [ + 'all' + ] + } + tenantId: tenant().tenantId + } + { + objectId: nestedDependencies.outputs.managedIdentityPrincipalId + permissions: { + certificates: [ + 'backup' + 'create' + 'delete' + ] + secrets: [ + 'all' + ] + } + } + ] + diagnosticSettings: [ + { + // logAnalyticsDestinationType: + // marketplacePartnerResourceId: + name: 'customSetting' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + logCategoriesAndGroups: [ + { + category: 'AzurePolicyEvaluationDetails' + } + { + category: 'AuditEvent' + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + { + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + // Only for testing purposes + enablePurgeProtection: false + enableRbacAuthorization: false + keys: [ + { + attributesExp: 1725109032 + attributesNbf: 10000 + name: 'keyName' + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + rotationPolicy: { + attributes: { + expiryTime: 'P2Y' + } + lifetimeActions: [ + { + trigger: { + timeBeforeExpiry: 'P2M' + } + action: { + type: 'Rotate' + } + } + { + trigger: { + timeBeforeExpiry: 'P30D' + } + action: { + type: 'Notify' + } + } + ] + } + } + ] + lock: 'CanNotDelete' + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + ipRules: [ + { + value: '40.74.28.0/23' + } + ] + virtualNetworkRules: [ + { + id: nestedDependencies.outputs.subnetResourceId + ignoreMissingVnetServiceEndpoint: false + } + ] + } + privateEndpoints: [ + { + privateDnsZoneResourceIds: [ + nestedDependencies.outputs.privateDNSResourceId + ] + service: 'vault' + subnetResourceId: nestedDependencies.outputs.subnetResourceId + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + } + ] + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + secrets: { + secureList: [ + { + attributesExp: 1702648632 + attributesNbf: 10000 + contentType: 'Something' + name: 'secretName' + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + value: 'secretValue' + } + ] + } + softDeleteRetentionInDays: 7 + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +} diff --git a/avm/res/network/private-endpoint/tests/e2e/common/dependencies.bicep b/avm/res/network/private-endpoint/tests/e2e/max/dependencies.bicep similarity index 100% rename from avm/res/network/private-endpoint/tests/e2e/common/dependencies.bicep rename to avm/res/network/private-endpoint/tests/e2e/max/dependencies.bicep diff --git a/avm/res/network/private-endpoint/tests/e2e/common/main.test.bicep b/avm/res/network/private-endpoint/tests/e2e/max/main.test.bicep similarity index 95% rename from avm/res/network/private-endpoint/tests/e2e/common/main.test.bicep rename to avm/res/network/private-endpoint/tests/e2e/max/main.test.bicep index ae3591b55b..7178513116 100644 --- a/avm/res/network/private-endpoint/tests/e2e/common/main.test.bicep +++ b/avm/res/network/private-endpoint/tests/e2e/max/main.test.bicep @@ -12,7 +12,7 @@ param resourceGroupName string = 'ms.network.privateendpoints-${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 = 'npecom' +param serviceShort string = 'npemax' @description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') param enableDefaultTelemetry bool = true @@ -39,6 +39,7 @@ module nestedDependencies 'dependencies.bicep' = { keyVaultName: 'dep-${namePrefix}-kv-${serviceShort}' managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' applicationSecurityGroupName: 'dep-${namePrefix}-asg-${serviceShort}' + location: location } } @@ -46,12 +47,13 @@ module nestedDependencies 'dependencies.bicep' = { // Test Execution // // ============== // -module testDeployment '../../main.bicep' = { +module testDeployment '../../../main.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' params: { enableDefaultTelemetry: enableDefaultTelemetry name: '${namePrefix}${serviceShort}001' + location: location groupIds: [ 'vault' ] diff --git a/avm/res/network/private-endpoint/tests/e2e/min/main.test.bicep b/avm/res/network/private-endpoint/tests/e2e/min/main.test.bicep index f858091d54..67ee07779d 100644 --- a/avm/res/network/private-endpoint/tests/e2e/min/main.test.bicep +++ b/avm/res/network/private-endpoint/tests/e2e/min/main.test.bicep @@ -37,6 +37,7 @@ module nestedDependencies 'dependencies.bicep' = { params: { virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' keyVaultName: 'dep-${namePrefix}-kv-${serviceShort}' + location: location } } @@ -44,12 +45,13 @@ module nestedDependencies 'dependencies.bicep' = { // Test Execution // // ============== // -module testDeployment '../../main.bicep' = { +module testDeployment '../../../main.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' params: { enableDefaultTelemetry: enableDefaultTelemetry name: '${namePrefix}${serviceShort}001' + location: location groupIds: [ 'vault' ] diff --git a/avm/res/network/private-endpoint/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/network/private-endpoint/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 0000000000..a4bc9dabca --- /dev/null +++ b/avm/res/network/private-endpoint/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,95 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Key Vault to create.') +param keyVaultName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the Application Security Group to create.') +param applicationSecurityGroupName string + +var addressPrefix = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'defaultSubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + } + } + ] + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: keyVaultName + location: location + properties: { + sku: { + family: 'A' + name: 'standard' + } + tenantId: tenant().tenantId + enablePurgeProtection: null + enabledForTemplateDeployment: true + enabledForDiskEncryption: true + enabledForDeployment: true + enableRbacAuthorization: true + accessPolicies: [] + } +} + +resource applicationSecurityGroup 'Microsoft.Network/applicationSecurityGroups@2023-04-01' = { + name: applicationSecurityGroupName + location: location +} + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource privateDNSZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: 'privatelink.vaultcore.azure.net' + location: 'global' + + resource virtualNetworkLinks 'virtualNetworkLinks@2020-06-01' = { + name: '${virtualNetwork.name}-vnetlink' + location: 'global' + properties: { + virtualNetwork: { + id: virtualNetwork.id + } + registrationEnabled: false + } + } +} + +@description('The resource ID of the created Virtual Network Subnet.') +output subnetResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The resource ID of the created Key Vault.') +output keyVaultResourceId string = keyVault.id + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId + +@description('The resource ID of the created Private DNS Zone.') +output privateDNSZoneResourceId string = privateDNSZone.id + +@description('The resource ID of the created Application Security Group.') +output applicationSecurityGroupResourceId string = applicationSecurityGroup.id diff --git a/avm/res/network/private-endpoint/tests/e2e/waf-aligned/main.test.bicep b/avm/res/network/private-endpoint/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..02de88a3fb --- /dev/null +++ b/avm/res/network/private-endpoint/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,93 @@ +targetScope = 'subscription' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'ms.network.privateendpoints-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +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 = 'npewaf' + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '[[namePrefix]]' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + keyVaultName: 'dep-${namePrefix}-kv-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + applicationSecurityGroupName: 'dep-${namePrefix}-asg-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + enableDefaultTelemetry: enableDefaultTelemetry + name: '${namePrefix}${serviceShort}001' + location: location + groupIds: [ + 'vault' + ] + serviceResourceId: nestedDependencies.outputs.keyVaultResourceId + subnetResourceId: nestedDependencies.outputs.subnetResourceId + lock: 'CanNotDelete' + privateDnsZoneResourceIds: [ + nestedDependencies.outputs.privateDNSZoneResourceId + ] + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + ipConfigurations: [ + { + name: 'myIPconfig' + properties: { + groupId: 'vault' + memberName: 'default' + privateIPAddress: '10.0.0.10' + } + } + ] + customNetworkInterfaceName: '${namePrefix}${serviceShort}001nic' + applicationSecurityGroupResourceIds: [ + nestedDependencies.outputs.applicationSecurityGroupResourceId + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } +} diff --git a/avm/utilities/e2e-template-assets/hello commit history.txt b/avm/utilities/e2e-template-assets/hello commit history.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/avm/utilities/e2e-template-assets/scripts/.scripts/Copy-VhdToStorageAccount.ps1 b/avm/utilities/e2e-template-assets/scripts/.scripts/Copy-VhdToStorageAccount.ps1 new file mode 100644 index 0000000000..2a353dd74f --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/.scripts/Copy-VhdToStorageAccount.ps1 @@ -0,0 +1,123 @@ +<# +.SYNOPSIS +Copy a VHD baked from a given Image Template to a given destination storage account blob container + +.DESCRIPTION +Copy a VHD baked from a given Image Template to a given destination storage account blob container + +.PARAMETER ImageTemplateName +Mandatory. The name of the Image Template + +.PARAMETER ImageTemplateResourceGroup +Mandatory. The resource group name of the Image Template + +.PARAMETER DestinationStorageAccountName +Mandatory. The name of the destination storage account + +.PARAMETER DestinationContainerName +Optional. The name of the existing destination blob container + +.PARAMETER VhdName +Optional. Specify a different name for the destination VHD file + +.PARAMETER WaitForComplete +Optional. Run the command synchronously. Wait for the completion of the copy. + +.EXAMPLE +./Copy-VhdToStorageAccount -ImageTemplateName 'vhd-img-template-001-2022-07-29-15-54-01' -ImageTemplateResourceGroup 'validation-rg' -DestinationStorageAccountName 'vhdstorage001' + +Copy a VHD created by Image Template 'vhd-img-template-001-2022-07-29-15-54-01' in resource group 'validation-rg' to destination storage account 'vhdstorage001' in blob container named 'vhds'. Save the VHD file as 'vhd-img-template-001-2022-07-29-15-54-01.vhd'. + +.EXAMPLE +./Copy-VhdToStorageAccount -ImageTemplateName 'vhd-img-template-001-2022-07-29-15-54-01' -ImageTemplateResourceGroup 'validation-rg' -DestinationStorageAccountName 'vhdstorage001' -VhdName 'vhd-img-template-001' -WaitForComplete + +Copy a VHD baked by Image Template 'vhd-img-template-001-2022-07-29-15-54-01' in resource group 'validation-rg' to destination storage account 'vhdstorage001' in a blob container named 'vhds' and wait for the completion of the copy. Save the VHD file as 'vhd-img-template-001.vhd'. +#> + +[CmdletBinding(SupportsShouldProcess)] +param ( + [Parameter(Mandatory = $true)] + [string] $ImageTemplateName, + + [Parameter(Mandatory = $true)] + [string] $ImageTemplateResourceGroup, + + [Parameter(Mandatory = $true)] + [string] $DestinationStorageAccountName, + + [Parameter(Mandatory = $false)] + [string] $DestinationContainerName = 'vhds', + + [Parameter(Mandatory = $false)] + [string] $VhdName = $ImageTemplateName, + + [Parameter(Mandatory = $false)] + [switch] $WaitForComplete +) + +begin { + Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) + + # Install required modules + $currentVerbosePreference = $VerbosePreference + $VerbosePreference = 'SilentlyContinue' + $requiredModules = @( + 'Az.ImageBuilder', + 'Az.Storage' + ) + foreach ($moduleName in $requiredModules) { + if (-not ($installedModule = Get-Module $moduleName -ListAvailable)) { + Install-Module $moduleName -Repository 'PSGallery' -Force -Scope 'CurrentUser' + if ($installed = Get-Module -Name $moduleName -ListAvailable) { + Write-Verbose ('Installed module [{0}] with version [{1}]' -f $installed.Name, $installed.Version) -Verbose + } + } else { + Write-Verbose ('Module [{0}] already installed in version [{1}]' -f $installedModule[0].Name, $installedModule[0].Version) -Verbose + } + } + $VerbosePreference = $currentVerbosePreference +} + +process { + # Retrieving and initializing parameters before the blob copy + Write-Verbose 'Initializing source storage account parameters before the blob copy' -Verbose + Write-Verbose ('Retrieving source storage account from Image Template [{0}] in resource group [{1}]' -f $imageTemplateName, $imageTemplateResourceGroup) -Verbose + Get-InstalledModule + $imgtRunOutput = Get-AzImageBuilderTemplateRunOutput -ImageTemplateName $imageTemplateName -ResourceGroupName $imageTemplateResourceGroup | Where-Object ArtifactUri -NE $null + $sourceUri = $imgtRunOutput.ArtifactUri + $sourceStorageAccountName = $sourceUri.Split('//')[1].Split('.')[0] + $storageAccountList = Get-AzStorageAccount + $sourceStorageAccount = $storageAccountList | Where-Object StorageAccountName -EQ $sourceStorageAccountName + $sourceStorageAccountContext = $sourceStorageAccount.Context + $sourceStorageAccountRGName = $sourceStorageAccount.ResourceGroupName + Write-Verbose ('Retrieving artifact uri [{0}] stored in resource group [{1}]' -f $sourceUri, $sourceStorageAccountRGName) -Verbose + + Write-Verbose 'Initializing destination storage account parameters before the blob copy' -Verbose + $destinationStorageAccount = $storageAccountList | Where-Object StorageAccountName -EQ $destinationStorageAccountName + $destinationStorageAccountContext = $destinationStorageAccount.Context + $destinationBlobName = "$vhdName.vhd" + Write-Verbose ('Planning for destination blob name [{0}] in container [{1}] and storage account [{2}]' -f $destinationBlobName, $destinationContainerName, $destinationStorageAccountName) -Verbose + + # Copying the VHD to a destination blob container + $resourceActionInputObject = @{ + AbsoluteUri = $sourceUri + Context = $sourceStorageAccountContext + DestContext = $destinationStorageAccountContext + DestBlob = $destinationBlobName + DestContainer = $destinationContainerName + Force = $true + } + + if ($PSCmdlet.ShouldProcess('Storage blob copy of VHD [{0}]' -f $destinationBlobName, 'Start')) { + $destBlob = Start-AzStorageBlobCopy @resourceActionInputObject + Write-Verbose ('Copied/initialized copy of VHD from URI [{0}] to container [{1}] in storage account [{2}]' -f $sourceUri, $destinationContainerName, $destinationStorageAccountName) -Verbose + } + + if ($WaitForComplete) { + $destBlob | Get-AzStorageBlobCopyState -WaitForComplete + } +} + +end { + Write-Debug ('{0} exited' -f $MyInvocation.MyCommand) +} diff --git a/avm/utilities/e2e-template-assets/scripts/.scripts/Get-PairedRegion.ps1 b/avm/utilities/e2e-template-assets/scripts/.scripts/Get-PairedRegion.ps1 new file mode 100644 index 0000000000..839bd27995 --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/.scripts/Get-PairedRegion.ps1 @@ -0,0 +1,32 @@ +<# +.SYNOPSIS +Gets the paired region (location) for a particular Azure region. + +.DESCRIPTION +Gets the paired region (location) for a particular Azure region. + +.PARAMETER Location +Mandatory. The name of the Azure region (i.e. AustraliaEast, australiaeast, Australia East) + +.EXAMPLE +./Get-PairedRegion.ps1 -Location 'australiaeast' + +Output will be 'australiasoutheast'. +#> + +param( + [string] $Location +) + +# Sleep for role assignment propagation +Start-Sleep -Seconds 10 + +$PairedRegionName = Get-AzLocation | + Where-Object -FilterScript { $Location -in @($PSItem.Location, $PSItem.DisplayName) } | + Select-Object -ExpandProperty PairedRegion | + Select-Object -ExpandProperty Name + +# Write into Deployment Script output stream +$DeploymentScriptOutputs = @{ + pairedRegionName = $PairedRegionName +} diff --git a/avm/utilities/e2e-template-assets/scripts/.scripts/New-SSHKey.ps1 b/avm/utilities/e2e-template-assets/scripts/.scripts/New-SSHKey.ps1 new file mode 100644 index 0000000000..3e5c532388 --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/.scripts/New-SSHKey.ps1 @@ -0,0 +1,40 @@ +<# +.SYNOPSIS +Generate a new Public SSH Key or fetch it from an existing Public SSH Key resource. + +.DESCRIPTION +Generate a new Public SSH Key or fetch it from an existing Public SSH Key resource. + +.PARAMETER SSHKeyName +Mandatory. The name of the Public SSH Key Resource as it would be deployed in Azure + +.PARAMETER ResourceGroupName +Mandatory. The resource group name of the Public SSH Key Resource as it would be deployed in Azure + +.EXAMPLE +./New-SSHKey.ps1 -SSHKeyName 'myKeyResource' -ResourceGroupName 'ssh-rg' + +Generate a new Public SSH Key or fetch it from an existing Public SSH Key resource 'myKeyResource' in Resource Group 'ssh-rg' +#> +param( + [Parameter(Mandatory = $true)] + [string] $SSHKeyName, + + [Parameter(Mandatory = $true)] + [string] $ResourceGroupName +) + +if (-not ($sshKey = Get-AzSshKey -ResourceGroupName $ResourceGroupName | Where-Object { $_.Name -eq $SSHKeyName })) { + Write-Verbose "No SSH key [$SSHKeyName] found in Resource Group [$ResourceGroupName]. Generating new." -Verbose + $null = ssh-keygen -f generated -N (Get-Random -Maximum 99999) + $publicKey = Get-Content 'generated.pub' -Raw + # $privateKey = cat generated | Out-String +} else { + Write-Verbose "SSH key [$SSHKeyName] found in Resource Group [$ResourceGroupName]. Returning." -Verbose + $publicKey = $sshKey.publicKey +} +# Write into Deployment Script output stream +$DeploymentScriptOutputs = @{ + # Requires conversion as the script otherwise returns an object instead of the plain public key string + publicKey = $publicKey | Out-String +} diff --git a/avm/utilities/e2e-template-assets/scripts/.scripts/Set-BlobContent.ps1 b/avm/utilities/e2e-template-assets/scripts/.scripts/Set-BlobContent.ps1 new file mode 100644 index 0000000000..394bbd6b38 --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/.scripts/Set-BlobContent.ps1 @@ -0,0 +1,46 @@ +<# +.SYNOPSIS +Upload a test file to the given Storage Account Container. + +.DESCRIPTION +Upload a test file to the given Storage Account Container. + +.PARAMETER StorageAccountName +Mandatory. The name of the Storage Account to upload the file to + +.PARAMETER ResourceGroupName +Mandatory. The name of the Resource Group containing the Storage Account to upload the file to + +.PARAMETER ContainerName +Mandatory. The name of the Storage Account Container to upload the file to + +.PARAMETER FileName +Mandatory. The name of the file of the file to create in the container + +.EXAMPLE +./Set-BlobContent.ps1 -StorageAccountName 'mystorage' -ResourceGroupName 'storage-rg' -ContainerName 'mycontainer' -FileName 'testCSE.ps1' + +Generate a dummy file 'testCSE.ps1' to the Storage Account 'mystorage' Container 'mycontainer' in Resource Group 'storage-rg' +#> +param( + [Parameter(Mandatory = $true)] + [string] $StorageAccountName, + + [Parameter(Mandatory = $true)] + [string] $ResourceGroupName, + + [Parameter(Mandatory = $true)] + [string] $ContainerName, + + [Parameter(Mandatory = $true)] + [string] $FileName +) + +Write-Verbose "Create file [$FileName]" -Verbose +$file = New-Item -Value "Write-Host 'I am content'" -Path $FileName -Force + +Write-Verbose "Getting storage account [$StorageAccountName|$ResourceGroupName] context." -Verbose +$storageAccount = Get-AzStorageAccount -ResourceGroupName $ResourceGroupName -StorageAccountName $StorageAccountName -ErrorAction 'Stop' + +Write-Verbose 'Uploading file [$fileName]' -Verbose +Set-AzStorageBlobContent -File $file.FullName -Container $ContainerName -Context $storageAccount.Context -Force -ErrorAction 'Stop' | Out-Null diff --git a/avm/utilities/e2e-template-assets/scripts/.scripts/Set-CertificateInKeyVault.ps1 b/avm/utilities/e2e-template-assets/scripts/.scripts/Set-CertificateInKeyVault.ps1 new file mode 100644 index 0000000000..5f9bafaef5 --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/.scripts/Set-CertificateInKeyVault.ps1 @@ -0,0 +1,67 @@ +<# +.SYNOPSIS +Generate a new Key Vault Certificate or fetch its secret reference if already existing. + +.DESCRIPTION +Generate a new Key Vault Certificate or fetch its secret reference if already existing. + +.PARAMETER KeyVaultName +Mandatory. The name of the Key Vault to add a new certificate to, or fetch the secret reference it from + +.PARAMETER CertName +Mandatory. The name of the certificate to generate or fetch the secret reference from + +.PARAMETER CertSubjectName +Optional. The subject distinguished name is the name of the user of the certificate. The distinguished name for the certificate is a textual representation of the subject or issuer of the certificate. Default name is "CN=fabrikam.com" + +.EXAMPLE +./Set-CertificateInKeyVault.ps1 -KeyVaultName 'myVault' -CertName 'myCert' -CertSubjectName 'CN=fabrikam.com' + +Generate a new Key Vault Certificate with the default or provided subject name, or fetch its secret reference if already existing as 'myCert' in Key Vault 'myVault' +#> +param( + [Parameter(Mandatory = $true)] + [string] $KeyVaultName, + + [Parameter(Mandatory = $true)] + [string] $CertName, + + [Parameter(Mandatory = $false)] + [string] $CertSubjectName = 'CN=fabrikam.com' +) + +$certificate = Get-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $CertName -ErrorAction 'SilentlyContinue' + +if (-not $certificate) { + $policyInputObject = @{ + SecretContentType = 'application/x-pkcs12' + SubjectName = $CertSubjectName + IssuerName = 'Self' + ValidityInMonths = 12 + ReuseKeyOnRenewal = $true + } + $certPolicy = New-AzKeyVaultCertificatePolicy @policyInputObject + + $null = Add-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $CertName -CertificatePolicy $certPolicy + Write-Verbose ('Initiated creation of certificate [{0}] in key vault [{1}]' -f $CertName, $KeyVaultName) -Verbose + + while (-not (Get-AzKeyVaultCertificateOperation -VaultName $KeyVaultName -Name $CertName).Status -eq 'completed') { + Write-Verbose 'Waiting 10 seconds for certificate creation' -Verbose + Start-Sleep 10 + } + + Write-Verbose 'Certificate created' -Verbose +} + +$secretId = $certificate.SecretId +while ([String]::IsNullOrEmpty($secretId)) { + Write-Verbose 'Waiting 10 seconds until certificate can be fetched' -Verbose + Start-Sleep 10 + $certificate = Get-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $CertName -ErrorAction 'Stop' + $secretId = $certificate.SecretId +} + +# Write into Deployment Script output stream +$DeploymentScriptOutputs = @{ + secretUrl = $secretId +} diff --git a/avm/utilities/e2e-template-assets/scripts/.scripts/Set-PfxCertificateInKeyVault.ps1 b/avm/utilities/e2e-template-assets/scripts/.scripts/Set-PfxCertificateInKeyVault.ps1 new file mode 100644 index 0000000000..fd88a2243e --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/.scripts/Set-PfxCertificateInKeyVault.ps1 @@ -0,0 +1,62 @@ +<# +.SYNOPSIS +Generate a new PFX Certificate and store it alongside its password as Secrets in the given Key Vault. + +.DESCRIPTION +Generate a new PFX Certificate and store it alongside its password as Secrets in the given Key Vault. + +.PARAMETER KeyVaultName +Mandatory. The name of the Key Vault to store the Certificate & Password in + +.PARAMETER ResourceGroupName +Mandatory. The name of the Resource Group containing the Key Vault to store the Certificate & Password in + +.PARAMETER CertPWSecretName +Mandatory. The name of the Secret to store the Certificate's password in + +.PARAMETER CertSecretName +Mandatory. The name of the Secret to store the Secret in + +.EXAMPLE +./Set-PfxCertificateInKeyVault.ps1 -KeyVaultName 'myVault' -ResourceGroupName 'vault-rg' -CertPWSecretName 'pfxCertificatePassword' -CertSecretName 'pfxBase64Certificate' + +Generate a Certificate and store it as the Secret 'pfxCertificatePassword' in the Key Vault 'vault-rg' of Resource Group 'storage-rg' alongside its password as the Secret 'pfxCertificatePassword' +#> +param( + [Parameter(Mandatory = $true)] + [string] $KeyVaultName, + + [Parameter(Mandatory = $true)] + [string] $ResourceGroupName, + + [Parameter(Mandatory = $true)] + [string] $CertPWSecretName, + + [Parameter(Mandatory = $true)] + [string] $CertSecretName +) + +$password = ConvertTo-SecureString -String "$ResourceGroupName/$KeyVaultName/$CertSecretName" -AsPlainText -Force + +# Install open-ssl if not available +apt-get install openssl + +# Generate certificate +openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout './privateKey.key' -out './certificate.crt' -subj '/CN=*.contoso.onmicrosoft.com/O=contoso/C=US' + +# Sign certificate +openssl pkcs12 -export -out 'aadds.pfx' -inkey './privateKey.key' -in './certificate.crt' -passout pass:$password + +# Convert certificate to string +$rawCertByteStream = Get-Content './aadds.pfx' -AsByteStream +Write-Verbose 'Convert to secure string' -Verbose +$pfxCertificate = ConvertTo-SecureString -String ([System.Convert]::ToBase64String($rawCertByteStream)) -AsPlainText -Force + +# Set values +@( + @{ name = $CertPWSecretName; secretValue = $password } + @{ name = $CertSecretName; secretValue = $pfxCertificate } +) | ForEach-Object { + $null = Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name $_.name -SecretValue $_.secretValue + Write-Verbose ('Added secret [{0}] to key vault [{1}]' -f $_.name, $keyVaultName) -Verbose +} diff --git a/avm/utilities/e2e-template-assets/scripts/.scripts/Start-ImageTemplate.ps1 b/avm/utilities/e2e-template-assets/scripts/.scripts/Start-ImageTemplate.ps1 new file mode 100644 index 0000000000..798f799a75 --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/.scripts/Start-ImageTemplate.ps1 @@ -0,0 +1,79 @@ +<# +.SYNOPSIS +Create image artifacts from a given image template + +.DESCRIPTION +Create image artifacts from a given image template + +.PARAMETER ImageTemplateName +Mandatory. The name of the image template + +.PARAMETER ImageTemplateResourceGroup +Mandatory. The resource group name of the image template + +.PARAMETER NoWait +Optional. Run the command asynchronously + +.EXAMPLE +./Start-ImageTemplate -ImageTemplateName 'vhd-img-template-001-2022-07-29-15-54-01' -ImageTemplateResourceGroup 'validation-rg' + +Create image artifacts from image template 'vhd-img-template-001-2022-07-29-15-54-01' in resource group 'validation-rg' and wait for their completion + +.EXAMPLE +./Start-ImageTemplate -ImageTemplateName 'vhd-img-template-001-2022-07-29-15-54-01' -ImageTemplateResourceGroup 'validation-rg' -NoWait + +Start the creation of artifacts from image template 'vhd-img-template-001-2022-07-29-15-54-01' in resource group 'validation-rg' and do not wait for their completion +#> + +[CmdletBinding(SupportsShouldProcess)] +param ( + [Parameter(Mandatory = $true)] + [string] $ImageTemplateName, + + [Parameter(Mandatory = $true)] + [string] $ImageTemplateResourceGroup, + + [Parameter(Mandatory = $false)] + [switch] $NoWait +) + +begin { + Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) + + # Install required modules + $currentVerbosePreference = $VerbosePreference + $VerbosePreference = 'SilentlyContinue' + $requiredModules = @( + 'Az.ImageBuilder' + ) + foreach ($moduleName in $requiredModules) { + if (-not ($installedModule = Get-Module $moduleName -ListAvailable)) { + Install-Module $moduleName -Repository 'PSGallery' -Force -Scope 'CurrentUser' + if ($installed = Get-Module -Name $moduleName -ListAvailable) { + Write-Verbose ('Installed module [{0}] with version [{1}]' -f $installed.Name, $installed.Version) -Verbose + } + } else { + Write-Verbose ('Module [{0}] already installed in version [{1}]' -f $installedModule[0].Name, $installedModule[0].Version) -Verbose + } + } + $VerbosePreference = $currentVerbosePreference +} + +process { + # Create image artifacts from existing image template + $resourceActionInputObject = @{ + ImageTemplateName = $imageTemplateName + ResourceGroupName = $imageTemplateResourceGroup + } + if ($NoWait) { + $resourceActionInputObject['NoWait'] = $true + } + if ($PSCmdlet.ShouldProcess('Image template [{0}]' -f $imageTemplateName, 'Start')) { + $null = Start-AzImageBuilderTemplate @resourceActionInputObject + Write-Verbose ('Created/initialized creation of image artifacts from image template [{0}] in resource group [{1}]' -f $imageTemplateName, $imageTemplateResourceGroup) -Verbose + } +} + +end { + Write-Debug ('{0} exited' -f $MyInvocation.MyCommand) +} diff --git a/avm/utilities/e2e-template-assets/scripts/.templates/diagnostic.dependencies.bicep b/avm/utilities/e2e-template-assets/scripts/.templates/diagnostic.dependencies.bicep new file mode 100644 index 0000000000..d1fb78333b --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/.templates/diagnostic.dependencies.bicep @@ -0,0 +1,79 @@ +// ========== // +// Parameters // +// ========== // + +@description('Required. The name of the storage account to create.') +@maxLength(24) +param storageAccountName string + +@description('Required. The name of the log analytics workspace to create.') +param logAnalyticsWorkspaceName string + +@description('Required. The name of the event hub namespace to create.') +param eventHubNamespaceName string + +@description('Required. The name of the event hub to create inside the event hub namespace.') +param eventHubNamespaceEventHubName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +// ============ // +// Dependencies // +// ============ // + +resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = { + name: storageAccountName + location: location + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + properties: { + allowBlobPublicAccess: false + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: logAnalyticsWorkspaceName + location: location +} + +resource eventHubNamespace 'Microsoft.EventHub/namespaces@2021-11-01' = { + name: eventHubNamespaceName + location: location + + resource eventHub 'eventhubs@2021-11-01' = { + name: eventHubNamespaceEventHubName + } + + resource authorizationRule 'authorizationRules@2021-06-01-preview' = { + name: 'RootManageSharedAccessKey' + properties: { + rights: [ + 'Listen' + 'Manage' + 'Send' + ] + } + } +} + +// ======= // +// Outputs // +// ======= // + +@description('The resource ID of the created Storage Account.') +output storageAccountResourceId string = storageAccount.id + +@description('The resource ID of the created Log Analytics Workspace.') +output logAnalyticsWorkspaceResourceId string = logAnalyticsWorkspace.id + +@description('The resource ID of the created Event Hub Namespace.') +output eventHubNamespaceResourceId string = eventHubNamespace.id + +@description('The resource ID of the created Event Hub Namespace Authorization Rule.') +output eventHubAuthorizationRuleId string = eventHubNamespace::authorizationRule.id + +@description('The name of the created Event Hub Namespace Event Hub.') +output eventHubNamespaceEventHubName string = eventHubNamespace::eventHub.name diff --git a/avm/utilities/e2e-template-assets/scripts/Copy-VhdToStorageAccount.ps1 b/avm/utilities/e2e-template-assets/scripts/Copy-VhdToStorageAccount.ps1 new file mode 100644 index 0000000000..2a353dd74f --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/Copy-VhdToStorageAccount.ps1 @@ -0,0 +1,123 @@ +<# +.SYNOPSIS +Copy a VHD baked from a given Image Template to a given destination storage account blob container + +.DESCRIPTION +Copy a VHD baked from a given Image Template to a given destination storage account blob container + +.PARAMETER ImageTemplateName +Mandatory. The name of the Image Template + +.PARAMETER ImageTemplateResourceGroup +Mandatory. The resource group name of the Image Template + +.PARAMETER DestinationStorageAccountName +Mandatory. The name of the destination storage account + +.PARAMETER DestinationContainerName +Optional. The name of the existing destination blob container + +.PARAMETER VhdName +Optional. Specify a different name for the destination VHD file + +.PARAMETER WaitForComplete +Optional. Run the command synchronously. Wait for the completion of the copy. + +.EXAMPLE +./Copy-VhdToStorageAccount -ImageTemplateName 'vhd-img-template-001-2022-07-29-15-54-01' -ImageTemplateResourceGroup 'validation-rg' -DestinationStorageAccountName 'vhdstorage001' + +Copy a VHD created by Image Template 'vhd-img-template-001-2022-07-29-15-54-01' in resource group 'validation-rg' to destination storage account 'vhdstorage001' in blob container named 'vhds'. Save the VHD file as 'vhd-img-template-001-2022-07-29-15-54-01.vhd'. + +.EXAMPLE +./Copy-VhdToStorageAccount -ImageTemplateName 'vhd-img-template-001-2022-07-29-15-54-01' -ImageTemplateResourceGroup 'validation-rg' -DestinationStorageAccountName 'vhdstorage001' -VhdName 'vhd-img-template-001' -WaitForComplete + +Copy a VHD baked by Image Template 'vhd-img-template-001-2022-07-29-15-54-01' in resource group 'validation-rg' to destination storage account 'vhdstorage001' in a blob container named 'vhds' and wait for the completion of the copy. Save the VHD file as 'vhd-img-template-001.vhd'. +#> + +[CmdletBinding(SupportsShouldProcess)] +param ( + [Parameter(Mandatory = $true)] + [string] $ImageTemplateName, + + [Parameter(Mandatory = $true)] + [string] $ImageTemplateResourceGroup, + + [Parameter(Mandatory = $true)] + [string] $DestinationStorageAccountName, + + [Parameter(Mandatory = $false)] + [string] $DestinationContainerName = 'vhds', + + [Parameter(Mandatory = $false)] + [string] $VhdName = $ImageTemplateName, + + [Parameter(Mandatory = $false)] + [switch] $WaitForComplete +) + +begin { + Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) + + # Install required modules + $currentVerbosePreference = $VerbosePreference + $VerbosePreference = 'SilentlyContinue' + $requiredModules = @( + 'Az.ImageBuilder', + 'Az.Storage' + ) + foreach ($moduleName in $requiredModules) { + if (-not ($installedModule = Get-Module $moduleName -ListAvailable)) { + Install-Module $moduleName -Repository 'PSGallery' -Force -Scope 'CurrentUser' + if ($installed = Get-Module -Name $moduleName -ListAvailable) { + Write-Verbose ('Installed module [{0}] with version [{1}]' -f $installed.Name, $installed.Version) -Verbose + } + } else { + Write-Verbose ('Module [{0}] already installed in version [{1}]' -f $installedModule[0].Name, $installedModule[0].Version) -Verbose + } + } + $VerbosePreference = $currentVerbosePreference +} + +process { + # Retrieving and initializing parameters before the blob copy + Write-Verbose 'Initializing source storage account parameters before the blob copy' -Verbose + Write-Verbose ('Retrieving source storage account from Image Template [{0}] in resource group [{1}]' -f $imageTemplateName, $imageTemplateResourceGroup) -Verbose + Get-InstalledModule + $imgtRunOutput = Get-AzImageBuilderTemplateRunOutput -ImageTemplateName $imageTemplateName -ResourceGroupName $imageTemplateResourceGroup | Where-Object ArtifactUri -NE $null + $sourceUri = $imgtRunOutput.ArtifactUri + $sourceStorageAccountName = $sourceUri.Split('//')[1].Split('.')[0] + $storageAccountList = Get-AzStorageAccount + $sourceStorageAccount = $storageAccountList | Where-Object StorageAccountName -EQ $sourceStorageAccountName + $sourceStorageAccountContext = $sourceStorageAccount.Context + $sourceStorageAccountRGName = $sourceStorageAccount.ResourceGroupName + Write-Verbose ('Retrieving artifact uri [{0}] stored in resource group [{1}]' -f $sourceUri, $sourceStorageAccountRGName) -Verbose + + Write-Verbose 'Initializing destination storage account parameters before the blob copy' -Verbose + $destinationStorageAccount = $storageAccountList | Where-Object StorageAccountName -EQ $destinationStorageAccountName + $destinationStorageAccountContext = $destinationStorageAccount.Context + $destinationBlobName = "$vhdName.vhd" + Write-Verbose ('Planning for destination blob name [{0}] in container [{1}] and storage account [{2}]' -f $destinationBlobName, $destinationContainerName, $destinationStorageAccountName) -Verbose + + # Copying the VHD to a destination blob container + $resourceActionInputObject = @{ + AbsoluteUri = $sourceUri + Context = $sourceStorageAccountContext + DestContext = $destinationStorageAccountContext + DestBlob = $destinationBlobName + DestContainer = $destinationContainerName + Force = $true + } + + if ($PSCmdlet.ShouldProcess('Storage blob copy of VHD [{0}]' -f $destinationBlobName, 'Start')) { + $destBlob = Start-AzStorageBlobCopy @resourceActionInputObject + Write-Verbose ('Copied/initialized copy of VHD from URI [{0}] to container [{1}] in storage account [{2}]' -f $sourceUri, $destinationContainerName, $destinationStorageAccountName) -Verbose + } + + if ($WaitForComplete) { + $destBlob | Get-AzStorageBlobCopyState -WaitForComplete + } +} + +end { + Write-Debug ('{0} exited' -f $MyInvocation.MyCommand) +} diff --git a/avm/utilities/e2e-template-assets/scripts/Get-PairedRegion.ps1 b/avm/utilities/e2e-template-assets/scripts/Get-PairedRegion.ps1 new file mode 100644 index 0000000000..839bd27995 --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/Get-PairedRegion.ps1 @@ -0,0 +1,32 @@ +<# +.SYNOPSIS +Gets the paired region (location) for a particular Azure region. + +.DESCRIPTION +Gets the paired region (location) for a particular Azure region. + +.PARAMETER Location +Mandatory. The name of the Azure region (i.e. AustraliaEast, australiaeast, Australia East) + +.EXAMPLE +./Get-PairedRegion.ps1 -Location 'australiaeast' + +Output will be 'australiasoutheast'. +#> + +param( + [string] $Location +) + +# Sleep for role assignment propagation +Start-Sleep -Seconds 10 + +$PairedRegionName = Get-AzLocation | + Where-Object -FilterScript { $Location -in @($PSItem.Location, $PSItem.DisplayName) } | + Select-Object -ExpandProperty PairedRegion | + Select-Object -ExpandProperty Name + +# Write into Deployment Script output stream +$DeploymentScriptOutputs = @{ + pairedRegionName = $PairedRegionName +} diff --git a/avm/utilities/e2e-template-assets/scripts/New-SSHKey.ps1 b/avm/utilities/e2e-template-assets/scripts/New-SSHKey.ps1 new file mode 100644 index 0000000000..3e5c532388 --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/New-SSHKey.ps1 @@ -0,0 +1,40 @@ +<# +.SYNOPSIS +Generate a new Public SSH Key or fetch it from an existing Public SSH Key resource. + +.DESCRIPTION +Generate a new Public SSH Key or fetch it from an existing Public SSH Key resource. + +.PARAMETER SSHKeyName +Mandatory. The name of the Public SSH Key Resource as it would be deployed in Azure + +.PARAMETER ResourceGroupName +Mandatory. The resource group name of the Public SSH Key Resource as it would be deployed in Azure + +.EXAMPLE +./New-SSHKey.ps1 -SSHKeyName 'myKeyResource' -ResourceGroupName 'ssh-rg' + +Generate a new Public SSH Key or fetch it from an existing Public SSH Key resource 'myKeyResource' in Resource Group 'ssh-rg' +#> +param( + [Parameter(Mandatory = $true)] + [string] $SSHKeyName, + + [Parameter(Mandatory = $true)] + [string] $ResourceGroupName +) + +if (-not ($sshKey = Get-AzSshKey -ResourceGroupName $ResourceGroupName | Where-Object { $_.Name -eq $SSHKeyName })) { + Write-Verbose "No SSH key [$SSHKeyName] found in Resource Group [$ResourceGroupName]. Generating new." -Verbose + $null = ssh-keygen -f generated -N (Get-Random -Maximum 99999) + $publicKey = Get-Content 'generated.pub' -Raw + # $privateKey = cat generated | Out-String +} else { + Write-Verbose "SSH key [$SSHKeyName] found in Resource Group [$ResourceGroupName]. Returning." -Verbose + $publicKey = $sshKey.publicKey +} +# Write into Deployment Script output stream +$DeploymentScriptOutputs = @{ + # Requires conversion as the script otherwise returns an object instead of the plain public key string + publicKey = $publicKey | Out-String +} diff --git a/avm/utilities/e2e-template-assets/scripts/Set-BlobContent.ps1 b/avm/utilities/e2e-template-assets/scripts/Set-BlobContent.ps1 new file mode 100644 index 0000000000..394bbd6b38 --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/Set-BlobContent.ps1 @@ -0,0 +1,46 @@ +<# +.SYNOPSIS +Upload a test file to the given Storage Account Container. + +.DESCRIPTION +Upload a test file to the given Storage Account Container. + +.PARAMETER StorageAccountName +Mandatory. The name of the Storage Account to upload the file to + +.PARAMETER ResourceGroupName +Mandatory. The name of the Resource Group containing the Storage Account to upload the file to + +.PARAMETER ContainerName +Mandatory. The name of the Storage Account Container to upload the file to + +.PARAMETER FileName +Mandatory. The name of the file of the file to create in the container + +.EXAMPLE +./Set-BlobContent.ps1 -StorageAccountName 'mystorage' -ResourceGroupName 'storage-rg' -ContainerName 'mycontainer' -FileName 'testCSE.ps1' + +Generate a dummy file 'testCSE.ps1' to the Storage Account 'mystorage' Container 'mycontainer' in Resource Group 'storage-rg' +#> +param( + [Parameter(Mandatory = $true)] + [string] $StorageAccountName, + + [Parameter(Mandatory = $true)] + [string] $ResourceGroupName, + + [Parameter(Mandatory = $true)] + [string] $ContainerName, + + [Parameter(Mandatory = $true)] + [string] $FileName +) + +Write-Verbose "Create file [$FileName]" -Verbose +$file = New-Item -Value "Write-Host 'I am content'" -Path $FileName -Force + +Write-Verbose "Getting storage account [$StorageAccountName|$ResourceGroupName] context." -Verbose +$storageAccount = Get-AzStorageAccount -ResourceGroupName $ResourceGroupName -StorageAccountName $StorageAccountName -ErrorAction 'Stop' + +Write-Verbose 'Uploading file [$fileName]' -Verbose +Set-AzStorageBlobContent -File $file.FullName -Container $ContainerName -Context $storageAccount.Context -Force -ErrorAction 'Stop' | Out-Null diff --git a/avm/utilities/e2e-template-assets/scripts/Set-CertificateInKeyVault.ps1 b/avm/utilities/e2e-template-assets/scripts/Set-CertificateInKeyVault.ps1 new file mode 100644 index 0000000000..5f9bafaef5 --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/Set-CertificateInKeyVault.ps1 @@ -0,0 +1,67 @@ +<# +.SYNOPSIS +Generate a new Key Vault Certificate or fetch its secret reference if already existing. + +.DESCRIPTION +Generate a new Key Vault Certificate or fetch its secret reference if already existing. + +.PARAMETER KeyVaultName +Mandatory. The name of the Key Vault to add a new certificate to, or fetch the secret reference it from + +.PARAMETER CertName +Mandatory. The name of the certificate to generate or fetch the secret reference from + +.PARAMETER CertSubjectName +Optional. The subject distinguished name is the name of the user of the certificate. The distinguished name for the certificate is a textual representation of the subject or issuer of the certificate. Default name is "CN=fabrikam.com" + +.EXAMPLE +./Set-CertificateInKeyVault.ps1 -KeyVaultName 'myVault' -CertName 'myCert' -CertSubjectName 'CN=fabrikam.com' + +Generate a new Key Vault Certificate with the default or provided subject name, or fetch its secret reference if already existing as 'myCert' in Key Vault 'myVault' +#> +param( + [Parameter(Mandatory = $true)] + [string] $KeyVaultName, + + [Parameter(Mandatory = $true)] + [string] $CertName, + + [Parameter(Mandatory = $false)] + [string] $CertSubjectName = 'CN=fabrikam.com' +) + +$certificate = Get-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $CertName -ErrorAction 'SilentlyContinue' + +if (-not $certificate) { + $policyInputObject = @{ + SecretContentType = 'application/x-pkcs12' + SubjectName = $CertSubjectName + IssuerName = 'Self' + ValidityInMonths = 12 + ReuseKeyOnRenewal = $true + } + $certPolicy = New-AzKeyVaultCertificatePolicy @policyInputObject + + $null = Add-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $CertName -CertificatePolicy $certPolicy + Write-Verbose ('Initiated creation of certificate [{0}] in key vault [{1}]' -f $CertName, $KeyVaultName) -Verbose + + while (-not (Get-AzKeyVaultCertificateOperation -VaultName $KeyVaultName -Name $CertName).Status -eq 'completed') { + Write-Verbose 'Waiting 10 seconds for certificate creation' -Verbose + Start-Sleep 10 + } + + Write-Verbose 'Certificate created' -Verbose +} + +$secretId = $certificate.SecretId +while ([String]::IsNullOrEmpty($secretId)) { + Write-Verbose 'Waiting 10 seconds until certificate can be fetched' -Verbose + Start-Sleep 10 + $certificate = Get-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $CertName -ErrorAction 'Stop' + $secretId = $certificate.SecretId +} + +# Write into Deployment Script output stream +$DeploymentScriptOutputs = @{ + secretUrl = $secretId +} diff --git a/avm/utilities/e2e-template-assets/scripts/Set-PfxCertificateInKeyVault.ps1 b/avm/utilities/e2e-template-assets/scripts/Set-PfxCertificateInKeyVault.ps1 new file mode 100644 index 0000000000..fd88a2243e --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/Set-PfxCertificateInKeyVault.ps1 @@ -0,0 +1,62 @@ +<# +.SYNOPSIS +Generate a new PFX Certificate and store it alongside its password as Secrets in the given Key Vault. + +.DESCRIPTION +Generate a new PFX Certificate and store it alongside its password as Secrets in the given Key Vault. + +.PARAMETER KeyVaultName +Mandatory. The name of the Key Vault to store the Certificate & Password in + +.PARAMETER ResourceGroupName +Mandatory. The name of the Resource Group containing the Key Vault to store the Certificate & Password in + +.PARAMETER CertPWSecretName +Mandatory. The name of the Secret to store the Certificate's password in + +.PARAMETER CertSecretName +Mandatory. The name of the Secret to store the Secret in + +.EXAMPLE +./Set-PfxCertificateInKeyVault.ps1 -KeyVaultName 'myVault' -ResourceGroupName 'vault-rg' -CertPWSecretName 'pfxCertificatePassword' -CertSecretName 'pfxBase64Certificate' + +Generate a Certificate and store it as the Secret 'pfxCertificatePassword' in the Key Vault 'vault-rg' of Resource Group 'storage-rg' alongside its password as the Secret 'pfxCertificatePassword' +#> +param( + [Parameter(Mandatory = $true)] + [string] $KeyVaultName, + + [Parameter(Mandatory = $true)] + [string] $ResourceGroupName, + + [Parameter(Mandatory = $true)] + [string] $CertPWSecretName, + + [Parameter(Mandatory = $true)] + [string] $CertSecretName +) + +$password = ConvertTo-SecureString -String "$ResourceGroupName/$KeyVaultName/$CertSecretName" -AsPlainText -Force + +# Install open-ssl if not available +apt-get install openssl + +# Generate certificate +openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout './privateKey.key' -out './certificate.crt' -subj '/CN=*.contoso.onmicrosoft.com/O=contoso/C=US' + +# Sign certificate +openssl pkcs12 -export -out 'aadds.pfx' -inkey './privateKey.key' -in './certificate.crt' -passout pass:$password + +# Convert certificate to string +$rawCertByteStream = Get-Content './aadds.pfx' -AsByteStream +Write-Verbose 'Convert to secure string' -Verbose +$pfxCertificate = ConvertTo-SecureString -String ([System.Convert]::ToBase64String($rawCertByteStream)) -AsPlainText -Force + +# Set values +@( + @{ name = $CertPWSecretName; secretValue = $password } + @{ name = $CertSecretName; secretValue = $pfxCertificate } +) | ForEach-Object { + $null = Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name $_.name -SecretValue $_.secretValue + Write-Verbose ('Added secret [{0}] to key vault [{1}]' -f $_.name, $keyVaultName) -Verbose +} diff --git a/avm/utilities/e2e-template-assets/scripts/Start-ImageTemplate.ps1 b/avm/utilities/e2e-template-assets/scripts/Start-ImageTemplate.ps1 new file mode 100644 index 0000000000..798f799a75 --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/Start-ImageTemplate.ps1 @@ -0,0 +1,79 @@ +<# +.SYNOPSIS +Create image artifacts from a given image template + +.DESCRIPTION +Create image artifacts from a given image template + +.PARAMETER ImageTemplateName +Mandatory. The name of the image template + +.PARAMETER ImageTemplateResourceGroup +Mandatory. The resource group name of the image template + +.PARAMETER NoWait +Optional. Run the command asynchronously + +.EXAMPLE +./Start-ImageTemplate -ImageTemplateName 'vhd-img-template-001-2022-07-29-15-54-01' -ImageTemplateResourceGroup 'validation-rg' + +Create image artifacts from image template 'vhd-img-template-001-2022-07-29-15-54-01' in resource group 'validation-rg' and wait for their completion + +.EXAMPLE +./Start-ImageTemplate -ImageTemplateName 'vhd-img-template-001-2022-07-29-15-54-01' -ImageTemplateResourceGroup 'validation-rg' -NoWait + +Start the creation of artifacts from image template 'vhd-img-template-001-2022-07-29-15-54-01' in resource group 'validation-rg' and do not wait for their completion +#> + +[CmdletBinding(SupportsShouldProcess)] +param ( + [Parameter(Mandatory = $true)] + [string] $ImageTemplateName, + + [Parameter(Mandatory = $true)] + [string] $ImageTemplateResourceGroup, + + [Parameter(Mandatory = $false)] + [switch] $NoWait +) + +begin { + Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) + + # Install required modules + $currentVerbosePreference = $VerbosePreference + $VerbosePreference = 'SilentlyContinue' + $requiredModules = @( + 'Az.ImageBuilder' + ) + foreach ($moduleName in $requiredModules) { + if (-not ($installedModule = Get-Module $moduleName -ListAvailable)) { + Install-Module $moduleName -Repository 'PSGallery' -Force -Scope 'CurrentUser' + if ($installed = Get-Module -Name $moduleName -ListAvailable) { + Write-Verbose ('Installed module [{0}] with version [{1}]' -f $installed.Name, $installed.Version) -Verbose + } + } else { + Write-Verbose ('Module [{0}] already installed in version [{1}]' -f $installedModule[0].Name, $installedModule[0].Version) -Verbose + } + } + $VerbosePreference = $currentVerbosePreference +} + +process { + # Create image artifacts from existing image template + $resourceActionInputObject = @{ + ImageTemplateName = $imageTemplateName + ResourceGroupName = $imageTemplateResourceGroup + } + if ($NoWait) { + $resourceActionInputObject['NoWait'] = $true + } + if ($PSCmdlet.ShouldProcess('Image template [{0}]' -f $imageTemplateName, 'Start')) { + $null = Start-AzImageBuilderTemplate @resourceActionInputObject + Write-Verbose ('Created/initialized creation of image artifacts from image template [{0}] in resource group [{1}]' -f $imageTemplateName, $imageTemplateResourceGroup) -Verbose + } +} + +end { + Write-Debug ('{0} exited' -f $MyInvocation.MyCommand) +} diff --git a/avm/utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep b/avm/utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep new file mode 100644 index 0000000000..d1fb78333b --- /dev/null +++ b/avm/utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep @@ -0,0 +1,79 @@ +// ========== // +// Parameters // +// ========== // + +@description('Required. The name of the storage account to create.') +@maxLength(24) +param storageAccountName string + +@description('Required. The name of the log analytics workspace to create.') +param logAnalyticsWorkspaceName string + +@description('Required. The name of the event hub namespace to create.') +param eventHubNamespaceName string + +@description('Required. The name of the event hub to create inside the event hub namespace.') +param eventHubNamespaceEventHubName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +// ============ // +// Dependencies // +// ============ // + +resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = { + name: storageAccountName + location: location + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + properties: { + allowBlobPublicAccess: false + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: logAnalyticsWorkspaceName + location: location +} + +resource eventHubNamespace 'Microsoft.EventHub/namespaces@2021-11-01' = { + name: eventHubNamespaceName + location: location + + resource eventHub 'eventhubs@2021-11-01' = { + name: eventHubNamespaceEventHubName + } + + resource authorizationRule 'authorizationRules@2021-06-01-preview' = { + name: 'RootManageSharedAccessKey' + properties: { + rights: [ + 'Listen' + 'Manage' + 'Send' + ] + } + } +} + +// ======= // +// Outputs // +// ======= // + +@description('The resource ID of the created Storage Account.') +output storageAccountResourceId string = storageAccount.id + +@description('The resource ID of the created Log Analytics Workspace.') +output logAnalyticsWorkspaceResourceId string = logAnalyticsWorkspace.id + +@description('The resource ID of the created Event Hub Namespace.') +output eventHubNamespaceResourceId string = eventHubNamespace.id + +@description('The resource ID of the created Event Hub Namespace Authorization Rule.') +output eventHubAuthorizationRuleId string = eventHubNamespace::authorizationRule.id + +@description('The name of the created Event Hub Namespace Event Hub.') +output eventHubNamespaceEventHubName string = eventHubNamespace::eventHub.name