diff --git a/.github/workflows/avm.res.network.vpn-server-configuration.yml b/.github/workflows/avm.res.network.vpn-server-configuration.yml new file mode 100644 index 0000000000..785cee942d --- /dev/null +++ b/.github/workflows/avm.res.network.vpn-server-configuration.yml @@ -0,0 +1,88 @@ +name: "avm.res.network.vpn-server-configuration" + +on: + workflow_dispatch: + inputs: + staticValidation: + type: boolean + description: "Execute static validation" + required: false + default: true + deploymentValidation: + type: boolean + description: "Execute deployment validation" + required: false + default: true + removeDeployment: + type: boolean + description: "Remove deployed module" + required: false + default: true + customLocation: + type: string + description: "Default location overwrite (e.g., eastus)" + required: false + push: + branches: + - main + paths: + - ".github/actions/templates/avm-**" + - ".github/workflows/avm.template.module.yml" + - ".github/workflows/avm.res.network.vpn-server-configuration.yml" + - "avm/res/network/vpn-server-configuration/**" + - "avm/utilities/pipelines/**" + - "!avm/utilities/pipelines/platform/**" + - "!*/**/README.md" + +env: + modulePath: "avm/res/network/vpn-server-configuration" + workflowPath: ".github/workflows/avm.res.network.vpn-server-configuration.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-latest + name: "Initialize pipeline" + steps: + - name: "Checkout" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: "Set input parameters to output variables" + id: get-workflow-param + uses: ./.github/actions/templates/avm-getWorkflowInput + with: + workflowPath: "${{ env.workflowPath}}" + - name: "Get module test file paths" + id: get-module-test-file-paths + uses: ./.github/actions/templates/avm-getModuleTestFiles + with: + modulePath: "${{ env.modulePath }}" + outputs: + workflowInput: ${{ steps.get-workflow-param.outputs.workflowInput }} + moduleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.moduleTestFilePaths }} + psRuleModuleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.psRuleModuleTestFilePaths }} + modulePath: "${{ env.modulePath }}" + + ############################## + # Call reusable workflow # + ############################## + call-workflow-passing-data: + name: "Run" + permissions: + id-token: write # For OIDC + contents: write # For release tags + needs: + - job_initialize_pipeline + uses: ./.github/workflows/avm.template.module.yml + with: + workflowInput: "${{ needs.job_initialize_pipeline.outputs.workflowInput }}" + moduleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.moduleTestFilePaths }}" + psRuleModuleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.psRuleModuleTestFilePaths }}" + modulePath: "${{ needs.job_initialize_pipeline.outputs.modulePath}}" + secrets: inherit diff --git a/avm/res/network/vpn-server-configuration/main.bicep b/avm/res/network/vpn-server-configuration/main.bicep new file mode 100644 index 0000000000..f01ec4b2af --- /dev/null +++ b/avm/res/network/vpn-server-configuration/main.bicep @@ -0,0 +1,197 @@ +@description('Required: The name of the user VPN configuration.') +param name string + +@description('Optional. Location where all resources will be created.') +param location string = resourceGroup().location + +@description('Optional. The audience for the AAD/Entrance authentication.') +param aadAudience string? + +@description('Optional. The issuer for the AAD/Entrance authentication.') +param aadIssuer string? + +@description('Optional. The audience for the AAD/Entrance authentication.') +param aadTenant string? + +@description('') +param p2sConfigurationPolicyGroups array = [] + +@description('') +param vpnServerConfigurationName string + +@description('') +param radiusClientRootCertificates array = [] + +@description('') +param radiusServerAddress string? + +@description('') +param radiusServerRootCertificates array = [] + +@description('') +param radiusServers array = [] + +@description('') +@secure() +param radiusServerSecret string? + +@description('') +@allowed([ + 'AAD' + 'Certificate' + 'Radius' +]) +param vpnAuthenticationTypes array = [] + +@description('') +param vpnClientIpsecPolicies array = [] + +@description('') +param vpnClientRevokedCertificates array = [] + +@description('') +param vpnClientRootCertificates array = [] + +@description('') +@allowed([ + 'IkeV2' + 'OpenVPN' +]) +param vpnProtocols array = [] + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. The lock settings of the service.') +param lock lockType + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +#disable-next-line no-deployments-resources +resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) { + name: take( + '46d3xbcp.res.network-vpnserverconfiguration.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}', + 64 + ) + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + outputs: { + telemetry: { + type: 'String' + value: 'For more information, see https://aka.ms/avm/TelemetryInfo' + } + } + } + } +} + +resource vpnServerConfig 'Microsoft.Network/vpnServerConfigurations@2024-01-01' = { + name: name + location: location + tags: tags + properties: { + aadAuthenticationParameters: { + aadAudience: aadAudience + aadIssuer: aadIssuer + aadTenant: aadTenant + } + configurationPolicyGroups: [ + for group in p2sConfigurationPolicyGroups: { + name: group.userVPNPolicyGroupName + properties: { + isDefault: group.isDefault + policyMembers: group.policyMembers + priority: group.priority + } + } + ] + name: vpnServerConfigurationName + radiusClientRootCertificates: [ + for clientRootroot in radiusClientRootCertificates: { + name: clientRootroot.name + thumbprint: clientRootroot.thumbprint + } + ] + radiusServerAddress: radiusServerAddress + radiusServerRootCertificates: [ + for serverRoot in radiusServerRootCertificates: { + name: serverRoot.name + publicCertData: serverRoot.publicCertData + } + ] + radiusServers: [ + for server in radiusServers: { + radiusServerAddress: server.radiusServerAddress + radiusServerScore: server.radiusServerScore + radiusServerSecret: server.radiusServerSecret + } + ] + radiusServerSecret: radiusServerSecret + vpnAuthenticationTypes: vpnAuthenticationTypes + vpnClientIpsecPolicies: [ + for policy in vpnClientIpsecPolicies: { + dhGroup: policy.dhGroup + ikeEncryption: policy.ikeEncryption + ikeIntegrity: policy.ikeIntegrity + ipsecEncryption: policy.ipsecEncryption + ipsecIntegrity: policy.ipsecIntegrity + pfsGroup: policy.pfsGroup + saDataSizeKilobytes: policy.saDataSizeKilobytes + saLifeTimeSeconds: policy.saLifeTimeSeconds + } + ] + vpnClientRevokedCertificates: [ + for cert in vpnClientRevokedCertificates: { + name: cert.name + thumbprint:cert.thumbprint + } + ] + vpnClientRootCertificates: [ + for cert in vpnClientRootCertificates: { + name: cert.name + publicCertData: cert.thumbprint + } + ] + vpnProtocols: vpnProtocols + } +} + +resource vpnGateway_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' + ? 'Cannot delete resource or child resources.' + : 'Cannot delete or modify the resource or child resources.' + } + scope: vpnServerConfig +} + +@description('The name of the user VPN configuration.') +output name string = vpnServerConfig.name + +@description('The resource ID of the user VPN configuration.') +output resourceId string = vpnServerConfig.id + +@description('The name of the resource group the user VPN configuration was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The location the resource was deployed into.') +output location string = vpnServerConfig.location + +// =============== // +// Definitions // +// =============== // + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? diff --git a/avm/res/network/vpn-server-configuration/tests/e2e/defaults/dependencies.bicep b/avm/res/network/vpn-server-configuration/tests/e2e/defaults/dependencies.bicep new file mode 100644 index 0000000000..bb151ad9d8 --- /dev/null +++ b/avm/res/network/vpn-server-configuration/tests/e2e/defaults/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Required. The name of the virtual WAN to create.') +param virtualWANName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +resource virtualWan 'Microsoft.Network/virtualWans@2023-04-01' = { + name: virtualWANName + location: location +} + +@description('The resource ID of the created Virtual WAN.') +output virtualWWANResourceId string = virtualWan.id diff --git a/avm/res/network/vpn-server-configuration/tests/e2e/defaults/main.test.bicep b/avm/res/network/vpn-server-configuration/tests/e2e/defaults/main.test.bicep new file mode 100644 index 0000000000..a918b3e03c --- /dev/null +++ b/avm/res/network/vpn-server-configuration/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,58 @@ +targetScope = 'subscription' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +// e.g., for a module 'network/private-endpoint' you could use 'dep-dev-network.privateendpoints-${serviceShort}-rg' +param resourceGroupName string = 'dep-${namePrefix}---${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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.') +// e.g., for a module 'network/private-endpoint' you could use 'npe' as a prefix and then 'waf' as a suffix for the waf-aligned test +param serviceShort string = 'vscdef' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + virtualWANName: 'dep-${namePrefix}-vw-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + // You parameters go here + name: '${namePrefix}${serviceShort}001' + vpnServerConfigurationName: '${namePrefix}${serviceShort}-vpnServerConfig' + location: resourceLocation + } + } +] diff --git a/avm/res/network/vpn-server-configuration/tests/e2e/max/dependencies.bicep b/avm/res/network/vpn-server-configuration/tests/e2e/max/dependencies.bicep new file mode 100644 index 0000000000..bb151ad9d8 --- /dev/null +++ b/avm/res/network/vpn-server-configuration/tests/e2e/max/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Required. The name of the virtual WAN to create.') +param virtualWANName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +resource virtualWan 'Microsoft.Network/virtualWans@2023-04-01' = { + name: virtualWANName + location: location +} + +@description('The resource ID of the created Virtual WAN.') +output virtualWWANResourceId string = virtualWan.id diff --git a/avm/res/network/vpn-server-configuration/tests/e2e/max/main.test.bicep b/avm/res/network/vpn-server-configuration/tests/e2e/max/main.test.bicep new file mode 100644 index 0000000000..de1847d24b --- /dev/null +++ b/avm/res/network/vpn-server-configuration/tests/e2e/max/main.test.bicep @@ -0,0 +1,58 @@ +targetScope = 'subscription' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +// e.g., for a module 'network/private-endpoint' you could use 'dep-dev-network.privateendpoints-${serviceShort}-rg' +param resourceGroupName string = 'dep-${namePrefix}---${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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.') +// e.g., for a module 'network/private-endpoint' you could use 'npe' as a prefix and then 'waf' as a suffix for the waf-aligned test +param serviceShort string = 'vscmax' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + virtualWANName: 'dep-${namePrefix}-vw-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + // You parameters go here + name: '${namePrefix}${serviceShort}001' + vpnServerConfigurationName: '${namePrefix}${serviceShort}-vpnServerConfig' + location: resourceLocation + } + } +] diff --git a/avm/res/network/vpn-server-configuration/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/network/vpn-server-configuration/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 0000000000..bb151ad9d8 --- /dev/null +++ b/avm/res/network/vpn-server-configuration/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Required. The name of the virtual WAN to create.') +param virtualWANName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +resource virtualWan 'Microsoft.Network/virtualWans@2023-04-01' = { + name: virtualWANName + location: location +} + +@description('The resource ID of the created Virtual WAN.') +output virtualWWANResourceId string = virtualWan.id diff --git a/avm/res/network/vpn-server-configuration/tests/e2e/waf-aligned/main.test.bicep b/avm/res/network/vpn-server-configuration/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..0546a52e3b --- /dev/null +++ b/avm/res/network/vpn-server-configuration/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,49 @@ +targetScope = 'subscription' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +// e.g., for a module 'network/private-endpoint' you could use 'dep-dev-network.privateendpoints-${serviceShort}-rg' +param resourceGroupName string = 'dep-${namePrefix}---${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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.') +// e.g., for a module 'network/private-endpoint' you could use 'npe' as a prefix and then 'waf' as a suffix for the waf-aligned test +param serviceShort string = 'vscwaf' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + // You parameters go here + name: '${namePrefix}${serviceShort}001' + location: resourceLocation + vpnServerConfigurationName: '${namePrefix}${serviceShort}-vpnServerConfig' + } + } +] diff --git a/avm/res/network/vpn-server-configuration/version.json b/avm/res/network/vpn-server-configuration/version.json new file mode 100644 index 0000000000..8def869ede --- /dev/null +++ b/avm/res/network/vpn-server-configuration/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +}