diff --git a/avm/res/cdn/profile/CHANGELOG.md b/avm/res/cdn/profile/CHANGELOG.md index 1200360d07a..ed7e619a2ab 100644 --- a/avm/res/cdn/profile/CHANGELOG.md +++ b/avm/res/cdn/profile/CHANGELOG.md @@ -2,10 +2,13 @@ The latest version of the changelog can be found [here](https://github.com/Azure/bicep-registry-modules/blob/main/avm/res/cdn/profile/CHANGELOG.md). -## 0.14.1 +## 0.15.0 ### Changes +- Added TLS13 as allowed minimumTlsVersion parameter for the customDomain resource +- Updated Microsoft.Cdn/profiles API version to 2025-06-01 +- Removed "Standard_Microsoft" SKU from tests since this SKU can no longer be newly deployed as of Aug 15th, 2025. However, we keep the SKU as allowed parameter for users who already have it deployed and want to manage it with AVM. Ref: https://azure.microsoft.com/en-us/updates/?id=498522 - Updated LockType to 'avm-common-types version' `0.6.0`, enabling custom notes for locks. ### Breaking Changes diff --git a/avm/res/cdn/profile/README.md b/avm/res/cdn/profile/README.md index 3022ce9b71c..8a7094e7dd8 100644 --- a/avm/res/cdn/profile/README.md +++ b/avm/res/cdn/profile/README.md @@ -17,10 +17,10 @@ This module deploys a CDN Profile. | :-- | :-- | :-- | | `Microsoft.Authorization/locks` | 2020-05-01 | | | `Microsoft.Authorization/roleAssignments` | 2022-04-01 | | -| `Microsoft.Cdn/profiles` | 2025-04-15 | | +| `Microsoft.Cdn/profiles` | 2025-06-01 | | | `Microsoft.Cdn/profiles/afdEndpoints` | 2025-04-15 | | | `Microsoft.Cdn/profiles/afdEndpoints/routes` | 2025-04-15 | | -| `Microsoft.Cdn/profiles/customDomains` | 2025-04-15 | | +| `Microsoft.Cdn/profiles/customDomains` | 2025-06-01 | | | `Microsoft.Cdn/profiles/endpoints` | 2025-04-15 | | | `Microsoft.Cdn/profiles/endpoints/origins` | 2025-04-15 | | | `Microsoft.Cdn/profiles/originGroups` | 2025-04-15 | | @@ -42,8 +42,8 @@ The following section provides usage examples for the module, which were used to - [As Azure Front Door Premium](#example-1-as-azure-front-door-premium) - [As Azure Front Door](#example-2-as-azure-front-door) - [Using only defaults](#example-3-using-only-defaults) -- [Using large parameter set](#example-4-using-large-parameter-set) -- [WAF-aligned](#example-5-waf-aligned) +- [Using maximum parameter set](#example-4-using-maximum-parameter-set) +- [WAF-aligned Premium AFD](#example-5-waf-aligned-premium-afd) ### Example 1: _As Azure Front Door Premium_ @@ -59,12 +59,12 @@ module profile 'br/public:avm/res/cdn/profile:' = { name: 'profileDeployment' params: { // Required parameters - name: 'dep-test-cdnpafdp' + name: 'dep-test-afd-cdnpafdp' sku: 'Premium_AzureFrontDoor' // Non-required parameters afdEndpoints: [ { - name: 'dep-test-cdnpafdp-afd-endpoint' + name: 'dep-test-afd-cdnpafdp-afd-endpoint' routes: [ { customDomainNames: [ @@ -165,7 +165,7 @@ module profile 'br/public:avm/res/cdn/profile:' = { "parameters": { // Required parameters "name": { - "value": "dep-test-cdnpafdp" + "value": "dep-test-afd-cdnpafdp" }, "sku": { "value": "Premium_AzureFrontDoor" @@ -174,7 +174,7 @@ module profile 'br/public:avm/res/cdn/profile:' = { "afdEndpoints": { "value": [ { - "name": "dep-test-cdnpafdp-afd-endpoint", + "name": "dep-test-afd-cdnpafdp-afd-endpoint", "routes": [ { "customDomainNames": [ @@ -285,12 +285,12 @@ module profile 'br/public:avm/res/cdn/profile:' = { using 'br/public:avm/res/cdn/profile:' // Required parameters -param name = 'dep-test-cdnpafdp' +param name = 'dep-test-afd-cdnpafdp' param sku = 'Premium_AzureFrontDoor' // Non-required parameters param afdEndpoints = [ { - name: 'dep-test-cdnpafdp-afd-endpoint' + name: 'dep-test-afd-cdnpafdp-afd-endpoint' routes: [ { customDomainNames: [ @@ -747,9 +747,9 @@ module profile 'br/public:avm/res/cdn/profile:' = { params: { // Required parameters name: 'dep-test-cdnpmin' - sku: 'Standard_Microsoft' + sku: 'Standard_AzureFrontDoor' // Non-required parameters - location: '' + location: 'global' } } ``` @@ -771,11 +771,11 @@ module profile 'br/public:avm/res/cdn/profile:' = { "value": "dep-test-cdnpmin" }, "sku": { - "value": "Standard_Microsoft" + "value": "Standard_AzureFrontDoor" }, // Non-required parameters "location": { - "value": "" + "value": "global" } } } @@ -793,17 +793,17 @@ using 'br/public:avm/res/cdn/profile:' // Required parameters param name = 'dep-test-cdnpmin' -param sku = 'Standard_Microsoft' +param sku = 'Standard_AzureFrontDoor' // Non-required parameters -param location = '' +param location = 'global' ```

-### Example 4: _Using large parameter set_ +### Example 4: _Using maximum parameter set_ -This instance deploys the module with most of its features enabled. +This instance deploys the module with all available features and parameters for Premium_AzureFrontDoor SKU.

@@ -816,8 +816,81 @@ module profile 'br/public:avm/res/cdn/profile:' = { params: { // Required parameters name: 'dep-test-cdnpmax' - sku: 'Standard_Microsoft' + sku: 'Premium_AzureFrontDoor' // Non-required parameters + afdEndpoints: [ + { + autoGeneratedDomainNameLabelScope: 'TenantReuse' + enabledState: 'Enabled' + name: 'dep-test-cdnpmax-afd-endpoint-1' + routes: [ + { + cacheConfiguration: { + compressionSettings: { + contentTypesToCompress: [ + 'application/json' + 'text/css' + 'text/html' + ] + isCompressionEnabled: true + } + queryParameters: 'version,locale' + queryStringCachingBehavior: 'IncludeSpecifiedQueryStrings' + } + customDomainNames: [ + 'dep-test1-cdnpmax-custom-domain' + ] + enabledState: 'Enabled' + forwardingProtocol: 'MatchRequest' + httpsRedirect: 'Enabled' + linkToDefaultDomain: 'Enabled' + name: 'dep-test-cdnpmax-afd-route-1' + originGroupName: 'dep-test-cdnpmax-origin-group-1' + patternsToMatch: [ + '/api/*' + '/health' + ] + ruleSets: [ + { + name: 'deptestcdnpmaxruleset1' + } + ] + supportedProtocols: [ + 'Http' + 'Https' + ] + } + ] + } + ] + customDomains: [ + { + certificateType: 'ManagedCertificate' + hostName: 'dep-test1-cdnpmax-custom-domain.azurewebsites.net' + minimumTlsVersion: 'TLS12' + name: 'dep-test1-cdnpmax-custom-domain' + } + { + certificateType: 'ManagedCertificate' + cipherSuiteSetType: 'TLS12_2022' + hostName: 'dep-test2-cdnpmax-custom-domain.azurewebsites.net' + minimumTlsVersion: 'TLS12' + name: 'dep-test2-cdnpmax-custom-domain' + } + { + certificateType: 'ManagedCertificate' + cipherSuiteSetType: 'Customized' + customizedCipherSuiteSet: { + cipherSuiteSetForTls13: [ + 'TLS_AES_128_GCM_SHA256' + 'TLS_AES_256_GCM_SHA384' + ] + } + hostName: 'dep-test3-cdnpmax-custom-domain.azurewebsites.net' + minimumTlsVersion: 'TLS13' + name: 'dep-test3-cdnpmax-custom-domain' + } + ] diagnosticSettings: [ { eventHubAuthorizationRuleResourceId: '' @@ -839,61 +912,109 @@ module profile 'br/public:avm/res/cdn/profile:' = { workspaceResourceId: '' } ] - endpointProperties: { - contentTypesToCompress: [ - 'application/javascript' - 'application/json' - 'application/x-javascript' - 'application/xml' - 'text/css' - 'text/html' - 'text/javascript' - 'text/plain' + location: 'global' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + notes: 'This resource cannot be deleted for security reasons.' + } + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + '' ] - geoFilters: [] - isCompressionEnabled: true - isHttpAllowed: true - isHttpsAllowed: true - originGroups: [] - originHostHeader: '' - origins: [ - { - name: 'dep-cdn-endpoint01' - properties: { - enabled: true + } + originGroups: [ + { + healthProbeSettings: { + probeIntervalInSeconds: 120 + probePath: '/health' + probeProtocol: 'Https' + probeRequestType: 'GET' + } + loadBalancingSettings: { + additionalLatencyInMilliseconds: 50 + sampleSize: 4 + successfulSamplesRequired: 3 + } + name: 'dep-test-cdnpmax-origin-group-1' + origins: [ + { + enabledState: 'Enabled' + enforceCertificateNameCheck: true hostName: '' httpPort: 80 httpsPort: 443 + name: 'dep-test-cdnpmax-origin-1' + priority: 1 + weight: 1000 } - } - ] - queryStringCachingBehavior: 'IgnoreQueryString' - } - location: '' - lock: { - kind: 'CanNotDelete' - name: 'myCustomLockName' - } - originResponseTimeoutSeconds: 60 - roleAssignments: [ + ] + sessionAffinityState: 'Enabled' + trafficRestorationTimeToHealedOrNewEndpointsInMinutes: 15 + } { - name: '50362c78-6910-43c3-8639-9cae123943bb' - principalId: '' - principalType: 'ServicePrincipal' - roleDefinitionIdOrName: 'Owner' + loadBalancingSettings: { + additionalLatencyInMilliseconds: 100 + sampleSize: 6 + successfulSamplesRequired: 4 + } + name: 'dep-test-cdnpmax-origin-group-2' + origins: [ + { + enabledState: 'Enabled' + enforceCertificateNameCheck: true + hostName: '' + httpPort: 80 + httpsPort: 443 + name: 'dep-test-cdnpmax-origin-2' + priority: 1 + weight: 1000 + } + ] + sessionAffinityState: 'Disabled' + trafficRestorationTimeToHealedOrNewEndpointsInMinutes: 10 } + ] + originResponseTimeoutSeconds: 240 + roleAssignments: [ { - name: '' principalId: '' principalType: 'ServicePrincipal' - roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + roleDefinitionIdOrName: 'CDN Profile Contributor' } + ] + ruleSets: [ { - principalId: '' - principalType: 'ServicePrincipal' - roleDefinitionIdOrName: '' + name: 'deptestcdnpmaxruleset1' + rules: [ + { + actions: [ + { + name: 'UrlRedirect' + parameters: { + customHostname: 'api.example.com' + customPath: '/v2/api/' + destinationProtocol: 'Https' + redirectType: 'PermanentRedirect' + typeName: 'DeliveryRuleUrlRedirectActionParameters' + } + } + ] + conditions: [] + matchProcessingBehavior: 'Continue' + name: 'deptestcdnpmaxrule1' + order: 1 + } + ] } ] + tags: { + Application: 'CDN' + CostCenter: '12345' + Environment: 'Test' + Owner: 'TestTeam' + } } } ``` @@ -915,9 +1036,86 @@ module profile 'br/public:avm/res/cdn/profile:' = { "value": "dep-test-cdnpmax" }, "sku": { - "value": "Standard_Microsoft" + "value": "Premium_AzureFrontDoor" }, // Non-required parameters + "afdEndpoints": { + "value": [ + { + "autoGeneratedDomainNameLabelScope": "TenantReuse", + "enabledState": "Enabled", + "name": "dep-test-cdnpmax-afd-endpoint-1", + "routes": [ + { + "cacheConfiguration": { + "compressionSettings": { + "contentTypesToCompress": [ + "application/json", + "text/css", + "text/html" + ], + "isCompressionEnabled": true + }, + "queryParameters": "version,locale", + "queryStringCachingBehavior": "IncludeSpecifiedQueryStrings" + }, + "customDomainNames": [ + "dep-test1-cdnpmax-custom-domain" + ], + "enabledState": "Enabled", + "forwardingProtocol": "MatchRequest", + "httpsRedirect": "Enabled", + "linkToDefaultDomain": "Enabled", + "name": "dep-test-cdnpmax-afd-route-1", + "originGroupName": "dep-test-cdnpmax-origin-group-1", + "patternsToMatch": [ + "/api/*", + "/health" + ], + "ruleSets": [ + { + "name": "deptestcdnpmaxruleset1" + } + ], + "supportedProtocols": [ + "Http", + "Https" + ] + } + ] + } + ] + }, + "customDomains": { + "value": [ + { + "certificateType": "ManagedCertificate", + "hostName": "dep-test1-cdnpmax-custom-domain.azurewebsites.net", + "minimumTlsVersion": "TLS12", + "name": "dep-test1-cdnpmax-custom-domain" + }, + { + "certificateType": "ManagedCertificate", + "cipherSuiteSetType": "TLS12_2022", + "hostName": "dep-test2-cdnpmax-custom-domain.azurewebsites.net", + "minimumTlsVersion": "TLS12", + "name": "dep-test2-cdnpmax-custom-domain" + }, + { + "certificateType": "ManagedCertificate", + "cipherSuiteSetType": "Customized", + "customizedCipherSuiteSet": { + "cipherSuiteSetForTls13": [ + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384" + ] + }, + "hostName": "dep-test3-cdnpmax-custom-domain.azurewebsites.net", + "minimumTlsVersion": "TLS13", + "name": "dep-test3-cdnpmax-custom-domain" + } + ] + }, "diagnosticSettings": { "value": [ { @@ -941,70 +1139,124 @@ module profile 'br/public:avm/res/cdn/profile:' = { } ] }, - "endpointProperties": { - "value": { - "contentTypesToCompress": [ - "application/javascript", - "application/json", - "application/x-javascript", - "application/xml", - "text/css", - "text/html", - "text/javascript", - "text/plain" - ], - "geoFilters": [], - "isCompressionEnabled": true, - "isHttpAllowed": true, - "isHttpsAllowed": true, - "originGroups": [], - "originHostHeader": "", - "origins": [ - { - "name": "dep-cdn-endpoint01", - "properties": { - "enabled": true, - "hostName": "", - "httpPort": 80, - "httpsPort": 443 - } - } - ], - "queryStringCachingBehavior": "IgnoreQueryString" - } - }, "location": { - "value": "" + "value": "global" }, "lock": { "value": { "kind": "CanNotDelete", - "name": "myCustomLockName" + "name": "myCustomLockName", + "notes": "This resource cannot be deleted for security reasons." } }, - "originResponseTimeoutSeconds": { - "value": 60 + "managedIdentities": { + "value": { + "systemAssigned": true, + "userAssignedResourceIds": [ + "" + ] + } }, - "roleAssignments": { + "originGroups": { "value": [ { - "name": "50362c78-6910-43c3-8639-9cae123943bb", - "principalId": "", - "principalType": "ServicePrincipal", - "roleDefinitionIdOrName": "Owner" + "healthProbeSettings": { + "probeIntervalInSeconds": 120, + "probePath": "/health", + "probeProtocol": "Https", + "probeRequestType": "GET" + }, + "loadBalancingSettings": { + "additionalLatencyInMilliseconds": 50, + "sampleSize": 4, + "successfulSamplesRequired": 3 + }, + "name": "dep-test-cdnpmax-origin-group-1", + "origins": [ + { + "enabledState": "Enabled", + "enforceCertificateNameCheck": true, + "hostName": "", + "httpPort": 80, + "httpsPort": 443, + "name": "dep-test-cdnpmax-origin-1", + "priority": 1, + "weight": 1000 + } + ], + "sessionAffinityState": "Enabled", + "trafficRestorationTimeToHealedOrNewEndpointsInMinutes": 15 }, { - "name": "", - "principalId": "", - "principalType": "ServicePrincipal", - "roleDefinitionIdOrName": "b24988ac-6180-42a0-ab88-20f7382dd24c" - }, + "loadBalancingSettings": { + "additionalLatencyInMilliseconds": 100, + "sampleSize": 6, + "successfulSamplesRequired": 4 + }, + "name": "dep-test-cdnpmax-origin-group-2", + "origins": [ + { + "enabledState": "Enabled", + "enforceCertificateNameCheck": true, + "hostName": "", + "httpPort": 80, + "httpsPort": 443, + "name": "dep-test-cdnpmax-origin-2", + "priority": 1, + "weight": 1000 + } + ], + "sessionAffinityState": "Disabled", + "trafficRestorationTimeToHealedOrNewEndpointsInMinutes": 10 + } + ] + }, + "originResponseTimeoutSeconds": { + "value": 240 + }, + "roleAssignments": { + "value": [ { "principalId": "", "principalType": "ServicePrincipal", - "roleDefinitionIdOrName": "" + "roleDefinitionIdOrName": "CDN Profile Contributor" + } + ] + }, + "ruleSets": { + "value": [ + { + "name": "deptestcdnpmaxruleset1", + "rules": [ + { + "actions": [ + { + "name": "UrlRedirect", + "parameters": { + "customHostname": "api.example.com", + "customPath": "/v2/api/", + "destinationProtocol": "Https", + "redirectType": "PermanentRedirect", + "typeName": "DeliveryRuleUrlRedirectActionParameters" + } + } + ], + "conditions": [], + "matchProcessingBehavior": "Continue", + "name": "deptestcdnpmaxrule1", + "order": 1 + } + ] } ] + }, + "tags": { + "value": { + "Application": "CDN", + "CostCenter": "12345", + "Environment": "Test", + "Owner": "TestTeam" + } } } } @@ -1022,92 +1274,213 @@ using 'br/public:avm/res/cdn/profile:' // Required parameters param name = 'dep-test-cdnpmax' -param sku = 'Standard_Microsoft' +param sku = 'Premium_AzureFrontDoor' // Non-required parameters -param diagnosticSettings = [ +param afdEndpoints = [ { - eventHubAuthorizationRuleResourceId: '' - eventHubName: '' - logCategoriesAndGroups: [ - { - categoryGroup: 'allLogs' - enabled: true - } - ] - metricCategories: [ + autoGeneratedDomainNameLabelScope: 'TenantReuse' + enabledState: 'Enabled' + name: 'dep-test-cdnpmax-afd-endpoint-1' + routes: [ { - category: 'AllMetrics' - enabled: true + cacheConfiguration: { + compressionSettings: { + contentTypesToCompress: [ + 'application/json' + 'text/css' + 'text/html' + ] + isCompressionEnabled: true + } + queryParameters: 'version,locale' + queryStringCachingBehavior: 'IncludeSpecifiedQueryStrings' + } + customDomainNames: [ + 'dep-test1-cdnpmax-custom-domain' + ] + enabledState: 'Enabled' + forwardingProtocol: 'MatchRequest' + httpsRedirect: 'Enabled' + linkToDefaultDomain: 'Enabled' + name: 'dep-test-cdnpmax-afd-route-1' + originGroupName: 'dep-test-cdnpmax-origin-group-1' + patternsToMatch: [ + '/api/*' + '/health' + ] + ruleSets: [ + { + name: 'deptestcdnpmaxruleset1' + } + ] + supportedProtocols: [ + 'Http' + 'Https' + ] } ] - name: 'customSetting' - storageAccountResourceId: '' - workspaceResourceId: '' } ] -param endpointProperties = { - contentTypesToCompress: [ - 'application/javascript' - 'application/json' - 'application/x-javascript' - 'application/xml' - 'text/css' - 'text/html' - 'text/javascript' - 'text/plain' - ] - geoFilters: [] - isCompressionEnabled: true - isHttpAllowed: true - isHttpsAllowed: true - originGroups: [] - originHostHeader: '' - origins: [ - { - name: 'dep-cdn-endpoint01' - properties: { - enabled: true - hostName: '' - httpPort: 80 - httpsPort: 443 - } +param customDomains = [ + { + certificateType: 'ManagedCertificate' + hostName: 'dep-test1-cdnpmax-custom-domain.azurewebsites.net' + minimumTlsVersion: 'TLS12' + name: 'dep-test1-cdnpmax-custom-domain' + } + { + certificateType: 'ManagedCertificate' + cipherSuiteSetType: 'TLS12_2022' + hostName: 'dep-test2-cdnpmax-custom-domain.azurewebsites.net' + minimumTlsVersion: 'TLS12' + name: 'dep-test2-cdnpmax-custom-domain' + } + { + certificateType: 'ManagedCertificate' + cipherSuiteSetType: 'Customized' + customizedCipherSuiteSet: { + cipherSuiteSetForTls13: [ + 'TLS_AES_128_GCM_SHA256' + 'TLS_AES_256_GCM_SHA384' + ] } - ] - queryStringCachingBehavior: 'IgnoreQueryString' -} -param location = '' + hostName: 'dep-test3-cdnpmax-custom-domain.azurewebsites.net' + minimumTlsVersion: 'TLS13' + name: 'dep-test3-cdnpmax-custom-domain' + } +] +param diagnosticSettings = [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + enabled: true + } + ] + metricCategories: [ + { + category: 'AllMetrics' + enabled: true + } + ] + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } +] +param location = 'global' param lock = { kind: 'CanNotDelete' name: 'myCustomLockName' + notes: 'This resource cannot be deleted for security reasons.' } -param originResponseTimeoutSeconds = 60 -param roleAssignments = [ +param managedIdentities = { + systemAssigned: true + userAssignedResourceIds: [ + '' + ] +} +param originGroups = [ { - name: '50362c78-6910-43c3-8639-9cae123943bb' - principalId: '' - principalType: 'ServicePrincipal' - roleDefinitionIdOrName: 'Owner' + healthProbeSettings: { + probeIntervalInSeconds: 120 + probePath: '/health' + probeProtocol: 'Https' + probeRequestType: 'GET' + } + loadBalancingSettings: { + additionalLatencyInMilliseconds: 50 + sampleSize: 4 + successfulSamplesRequired: 3 + } + name: 'dep-test-cdnpmax-origin-group-1' + origins: [ + { + enabledState: 'Enabled' + enforceCertificateNameCheck: true + hostName: '' + httpPort: 80 + httpsPort: 443 + name: 'dep-test-cdnpmax-origin-1' + priority: 1 + weight: 1000 + } + ] + sessionAffinityState: 'Enabled' + trafficRestorationTimeToHealedOrNewEndpointsInMinutes: 15 } { - name: '' - principalId: '' - principalType: 'ServicePrincipal' - roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + loadBalancingSettings: { + additionalLatencyInMilliseconds: 100 + sampleSize: 6 + successfulSamplesRequired: 4 + } + name: 'dep-test-cdnpmax-origin-group-2' + origins: [ + { + enabledState: 'Enabled' + enforceCertificateNameCheck: true + hostName: '' + httpPort: 80 + httpsPort: 443 + name: 'dep-test-cdnpmax-origin-2' + priority: 1 + weight: 1000 + } + ] + sessionAffinityState: 'Disabled' + trafficRestorationTimeToHealedOrNewEndpointsInMinutes: 10 } +] +param originResponseTimeoutSeconds = 240 +param roleAssignments = [ { principalId: '' principalType: 'ServicePrincipal' - roleDefinitionIdOrName: '' + roleDefinitionIdOrName: 'CDN Profile Contributor' + } +] +param ruleSets = [ + { + name: 'deptestcdnpmaxruleset1' + rules: [ + { + actions: [ + { + name: 'UrlRedirect' + parameters: { + customHostname: 'api.example.com' + customPath: '/v2/api/' + destinationProtocol: 'Https' + redirectType: 'PermanentRedirect' + typeName: 'DeliveryRuleUrlRedirectActionParameters' + } + } + ] + conditions: [] + matchProcessingBehavior: 'Continue' + name: 'deptestcdnpmaxrule1' + order: 1 + } + ] } ] +param tags = { + Application: 'CDN' + CostCenter: '12345' + Environment: 'Test' + Owner: 'TestTeam' +} ```

-### Example 5: _WAF-aligned_ +### Example 5: _WAF-aligned Premium AFD_ -This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. +This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework using Premium_AzureFrontDoor SKU.

@@ -1119,41 +1492,291 @@ module profile 'br/public:avm/res/cdn/profile:' = { name: 'profileDeployment' params: { // Required parameters - name: 'dep-test-cdnpwaf' - sku: 'Standard_Microsoft' + name: 'dep-waf-cdnpwaf' + sku: 'Premium_AzureFrontDoor' // Non-required parameters - endpointProperties: { - contentTypesToCompress: [ - 'application/javascript' - 'application/json' - 'application/x-javascript' - 'application/xml' - 'text/css' - 'text/html' - 'text/javascript' - 'text/plain' - ] - geoFilters: [] - isCompressionEnabled: true - isHttpAllowed: true - isHttpsAllowed: true - originGroups: [] - originHostHeader: '' - origins: [ - { - name: 'dep-cdn-endpoint01' - properties: { + afdEndpoints: [ + { + autoGeneratedDomainNameLabelScope: 'TenantReuse' + enabledState: 'Enabled' + name: 'dep-waf-primary-endpoint' + routes: [ + { + cacheConfiguration: { + compressionSettings: { + contentTypesToCompress: [ + 'application/json' + 'application/xml' + 'text/xml' + ] + isCompressionEnabled: true + } + queryParameters: 'timestamp,nonce' + queryStringCachingBehavior: 'IgnoreSpecifiedQueryStrings' + } + customDomainNames: [ + 'dep-waf-api-cdnpwaf-domain' + ] + enabledState: 'Enabled' + forwardingProtocol: 'HttpsOnly' + httpsRedirect: 'Enabled' + linkToDefaultDomain: 'Disabled' + name: 'dep-waf-api-route' + originGroupName: 'dep-waf-api-origin-group' + patternsToMatch: [ + '/api/*' + '/v1/*' + '/v2/*' + ] + ruleSets: [ + { + name: 'depwafsecurityrulescdnpwaf' + } + ] + supportedProtocols: [ + 'Https' + ] + } + ] + } + ] + customDomains: [ + { + certificateType: 'ManagedCertificate' + cipherSuiteSetType: 'TLS12_2023' + hostName: 'dep-waf-primary-cdnpwaf.example.com' + minimumTlsVersion: 'TLS12' + name: 'dep-waf-primary-cdnpwaf-domain' + } + { + certificateType: 'ManagedCertificate' + cipherSuiteSetType: 'Customized' + customizedCipherSuiteSet: { + cipherSuiteSetForTls13: [ + 'TLS_AES_128_GCM_SHA256' + 'TLS_AES_256_GCM_SHA384' + ] + } + hostName: 'api-dep-waf-cdnpwaf.example.com' + minimumTlsVersion: 'TLS13' + name: 'dep-waf-api-cdnpwaf-domain' + } + ] + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' enabled: true + } + ] + metricCategories: [ + { + category: 'AllMetrics' + enabled: true + } + ] + name: 'waf-comprehensive-diagnostics' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + location: 'global' + lock: { + kind: 'CanNotDelete' + name: 'waf-protection-lock' + notes: 'WAF: Protected against accidental deletion for business continuity' + } + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + '' + ] + } + originGroups: [ + { + healthProbeSettings: { + probeIntervalInSeconds: 15 + probePath: '/api/health' + probeProtocol: 'Https' + probeRequestType: 'GET' + } + loadBalancingSettings: { + additionalLatencyInMilliseconds: 25 + sampleSize: 6 + successfulSamplesRequired: 4 + } + name: 'dep-waf-api-origin-group' + origins: [ + { + enabledState: 'Enabled' + enforceCertificateNameCheck: true hostName: '' httpPort: 80 httpsPort: 443 + name: 'dep-waf-api-origin' + priority: 1 + weight: 1000 } - } - ] - queryStringCachingBehavior: 'IgnoreQueryString' - } - location: '' + ] + sessionAffinityState: 'Disabled' + trafficRestorationTimeToHealedOrNewEndpointsInMinutes: 2 + } + ] originResponseTimeoutSeconds: 60 + roleAssignments: [ + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'CDN Profile Reader' + } + ] + ruleSets: [ + { + name: 'depwafsecurityrulescdnpwaf' + rules: [ + { + actions: [ + { + name: 'UrlRedirect' + parameters: { + destinationProtocol: 'Https' + redirectType: 'PermanentRedirect' + typeName: 'DeliveryRuleUrlRedirectActionParameters' + } + } + ] + conditions: [ + { + name: 'RequestScheme' + parameters: { + matchValues: [ + 'HTTP' + ] + negateCondition: false + operator: 'Equal' + typeName: 'DeliveryRuleRequestSchemeConditionParameters' + } + } + ] + matchProcessingBehavior: 'Stop' + name: 'HTTPSRedirectRule' + order: 1 + } + { + actions: [ + { + name: 'ModifyResponseHeader' + parameters: { + headerAction: 'Overwrite' + headerName: 'Strict-Transport-Security' + typeName: 'DeliveryRuleHeaderActionParameters' + value: 'max-age=31536000; includeSubDomains; preload' + } + } + { + name: 'ModifyResponseHeader' + parameters: { + headerAction: 'Overwrite' + headerName: 'X-Content-Type-Options' + typeName: 'DeliveryRuleHeaderActionParameters' + value: 'nosniff' + } + } + { + name: 'ModifyResponseHeader' + parameters: { + headerAction: 'Overwrite' + headerName: 'X-Frame-Options' + typeName: 'DeliveryRuleHeaderActionParameters' + value: 'DENY' + } + } + { + name: 'ModifyResponseHeader' + parameters: { + headerAction: 'Overwrite' + headerName: 'X-XSS-Protection' + typeName: 'DeliveryRuleHeaderActionParameters' + value: '1; mode=block' + } + } + { + name: 'ModifyResponseHeader' + parameters: { + headerAction: 'Overwrite' + headerName: 'Referrer-Policy' + typeName: 'DeliveryRuleHeaderActionParameters' + value: 'strict-origin-when-cross-origin' + } + } + ] + conditions: [ + { + name: 'RequestScheme' + parameters: { + matchValues: [ + 'HTTPS' + ] + negateCondition: false + operator: 'Equal' + typeName: 'DeliveryRuleRequestSchemeConditionParameters' + } + } + ] + matchProcessingBehavior: 'Continue' + name: 'SecurityHeadersRule' + order: 2 + } + { + actions: [ + { + name: 'ModifyResponseHeader' + parameters: { + headerAction: 'Overwrite' + headerName: 'X-RateLimit-Limit' + typeName: 'DeliveryRuleHeaderActionParameters' + value: '1000' + } + } + ] + conditions: [ + { + name: 'RequestUri' + parameters: { + matchValues: [ + '/api/' + ] + negateCondition: false + operator: 'BeginsWith' + transforms: [ + 'Lowercase' + ] + typeName: 'DeliveryRuleRequestUriConditionParameters' + } + } + ] + matchProcessingBehavior: 'Continue' + name: 'APIRateLimitRule' + order: 3 + } + ] + } + ] + tags: { + Application: 'CDN-WAF-Aligned' + BackupRequired: 'Yes' + BusinessUnit: 'Digital-Services' + CostCenter: 'IT-Infrastructure' + Criticality: 'High' + DataClassification: 'Internal' + Environment: 'Production' + MonitoringRequired: 'Yes' + Owner: 'Platform-Team' + 'WAF-Pillar': 'All' + } } } ``` @@ -1172,49 +1795,315 @@ module profile 'br/public:avm/res/cdn/profile:' = { "parameters": { // Required parameters "name": { - "value": "dep-test-cdnpwaf" + "value": "dep-waf-cdnpwaf" }, "sku": { - "value": "Standard_Microsoft" + "value": "Premium_AzureFrontDoor" }, // Non-required parameters - "endpointProperties": { - "value": { - "contentTypesToCompress": [ - "application/javascript", - "application/json", - "application/x-javascript", - "application/xml", - "text/css", - "text/html", - "text/javascript", - "text/plain" - ], - "geoFilters": [], - "isCompressionEnabled": true, - "isHttpAllowed": true, - "isHttpsAllowed": true, - "originGroups": [], - "originHostHeader": "", - "origins": [ - { - "name": "dep-cdn-endpoint01", - "properties": { - "enabled": true, - "hostName": "", - "httpPort": 80, - "httpsPort": 443 - } - } - ], - "queryStringCachingBehavior": "IgnoreQueryString" - } + "afdEndpoints": { + "value": [ + { + "autoGeneratedDomainNameLabelScope": "TenantReuse", + "enabledState": "Enabled", + "name": "dep-waf-primary-endpoint", + "routes": [ + { + "cacheConfiguration": { + "compressionSettings": { + "contentTypesToCompress": [ + "application/json", + "application/xml", + "text/xml" + ], + "isCompressionEnabled": true + }, + "queryParameters": "timestamp,nonce", + "queryStringCachingBehavior": "IgnoreSpecifiedQueryStrings" + }, + "customDomainNames": [ + "dep-waf-api-cdnpwaf-domain" + ], + "enabledState": "Enabled", + "forwardingProtocol": "HttpsOnly", + "httpsRedirect": "Enabled", + "linkToDefaultDomain": "Disabled", + "name": "dep-waf-api-route", + "originGroupName": "dep-waf-api-origin-group", + "patternsToMatch": [ + "/api/*", + "/v1/*", + "/v2/*" + ], + "ruleSets": [ + { + "name": "depwafsecurityrulescdnpwaf" + } + ], + "supportedProtocols": [ + "Https" + ] + } + ] + } + ] + }, + "customDomains": { + "value": [ + { + "certificateType": "ManagedCertificate", + "cipherSuiteSetType": "TLS12_2023", + "hostName": "dep-waf-primary-cdnpwaf.example.com", + "minimumTlsVersion": "TLS12", + "name": "dep-waf-primary-cdnpwaf-domain" + }, + { + "certificateType": "ManagedCertificate", + "cipherSuiteSetType": "Customized", + "customizedCipherSuiteSet": { + "cipherSuiteSetForTls13": [ + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384" + ] + }, + "hostName": "api-dep-waf-cdnpwaf.example.com", + "minimumTlsVersion": "TLS13", + "name": "dep-waf-api-cdnpwaf-domain" + } + ] + }, + "diagnosticSettings": { + "value": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "logCategoriesAndGroups": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ], + "metricCategories": [ + { + "category": "AllMetrics", + "enabled": true + } + ], + "name": "waf-comprehensive-diagnostics", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ] }, "location": { - "value": "" + "value": "global" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "waf-protection-lock", + "notes": "WAF: Protected against accidental deletion for business continuity" + } + }, + "managedIdentities": { + "value": { + "systemAssigned": true, + "userAssignedResourceIds": [ + "" + ] + } + }, + "originGroups": { + "value": [ + { + "healthProbeSettings": { + "probeIntervalInSeconds": 15, + "probePath": "/api/health", + "probeProtocol": "Https", + "probeRequestType": "GET" + }, + "loadBalancingSettings": { + "additionalLatencyInMilliseconds": 25, + "sampleSize": 6, + "successfulSamplesRequired": 4 + }, + "name": "dep-waf-api-origin-group", + "origins": [ + { + "enabledState": "Enabled", + "enforceCertificateNameCheck": true, + "hostName": "", + "httpPort": 80, + "httpsPort": 443, + "name": "dep-waf-api-origin", + "priority": 1, + "weight": 1000 + } + ], + "sessionAffinityState": "Disabled", + "trafficRestorationTimeToHealedOrNewEndpointsInMinutes": 2 + } + ] }, "originResponseTimeoutSeconds": { "value": 60 + }, + "roleAssignments": { + "value": [ + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "CDN Profile Reader" + } + ] + }, + "ruleSets": { + "value": [ + { + "name": "depwafsecurityrulescdnpwaf", + "rules": [ + { + "actions": [ + { + "name": "UrlRedirect", + "parameters": { + "destinationProtocol": "Https", + "redirectType": "PermanentRedirect", + "typeName": "DeliveryRuleUrlRedirectActionParameters" + } + } + ], + "conditions": [ + { + "name": "RequestScheme", + "parameters": { + "matchValues": [ + "HTTP" + ], + "negateCondition": false, + "operator": "Equal", + "typeName": "DeliveryRuleRequestSchemeConditionParameters" + } + } + ], + "matchProcessingBehavior": "Stop", + "name": "HTTPSRedirectRule", + "order": 1 + }, + { + "actions": [ + { + "name": "ModifyResponseHeader", + "parameters": { + "headerAction": "Overwrite", + "headerName": "Strict-Transport-Security", + "typeName": "DeliveryRuleHeaderActionParameters", + "value": "max-age=31536000; includeSubDomains; preload" + } + }, + { + "name": "ModifyResponseHeader", + "parameters": { + "headerAction": "Overwrite", + "headerName": "X-Content-Type-Options", + "typeName": "DeliveryRuleHeaderActionParameters", + "value": "nosniff" + } + }, + { + "name": "ModifyResponseHeader", + "parameters": { + "headerAction": "Overwrite", + "headerName": "X-Frame-Options", + "typeName": "DeliveryRuleHeaderActionParameters", + "value": "DENY" + } + }, + { + "name": "ModifyResponseHeader", + "parameters": { + "headerAction": "Overwrite", + "headerName": "X-XSS-Protection", + "typeName": "DeliveryRuleHeaderActionParameters", + "value": "1; mode=block" + } + }, + { + "name": "ModifyResponseHeader", + "parameters": { + "headerAction": "Overwrite", + "headerName": "Referrer-Policy", + "typeName": "DeliveryRuleHeaderActionParameters", + "value": "strict-origin-when-cross-origin" + } + } + ], + "conditions": [ + { + "name": "RequestScheme", + "parameters": { + "matchValues": [ + "HTTPS" + ], + "negateCondition": false, + "operator": "Equal", + "typeName": "DeliveryRuleRequestSchemeConditionParameters" + } + } + ], + "matchProcessingBehavior": "Continue", + "name": "SecurityHeadersRule", + "order": 2 + }, + { + "actions": [ + { + "name": "ModifyResponseHeader", + "parameters": { + "headerAction": "Overwrite", + "headerName": "X-RateLimit-Limit", + "typeName": "DeliveryRuleHeaderActionParameters", + "value": "1000" + } + } + ], + "conditions": [ + { + "name": "RequestUri", + "parameters": { + "matchValues": [ + "/api/" + ], + "negateCondition": false, + "operator": "BeginsWith", + "transforms": [ + "Lowercase" + ], + "typeName": "DeliveryRuleRequestUriConditionParameters" + } + } + ], + "matchProcessingBehavior": "Continue", + "name": "APIRateLimitRule", + "order": 3 + } + ] + } + ] + }, + "tags": { + "value": { + "Application": "CDN-WAF-Aligned", + "BackupRequired": "Yes", + "BusinessUnit": "Digital-Services", + "CostCenter": "IT-Infrastructure", + "Criticality": "High", + "DataClassification": "Internal", + "Environment": "Production", + "MonitoringRequired": "Yes", + "Owner": "Platform-Team", + "WAF-Pillar": "All" + } } } } @@ -1231,41 +2120,291 @@ module profile 'br/public:avm/res/cdn/profile:' = { using 'br/public:avm/res/cdn/profile:' // Required parameters -param name = 'dep-test-cdnpwaf' -param sku = 'Standard_Microsoft' +param name = 'dep-waf-cdnpwaf' +param sku = 'Premium_AzureFrontDoor' // Non-required parameters -param endpointProperties = { - contentTypesToCompress: [ - 'application/javascript' - 'application/json' - 'application/x-javascript' - 'application/xml' - 'text/css' - 'text/html' - 'text/javascript' - 'text/plain' - ] - geoFilters: [] - isCompressionEnabled: true - isHttpAllowed: true - isHttpsAllowed: true - originGroups: [] - originHostHeader: '' - origins: [ - { - name: 'dep-cdn-endpoint01' - properties: { +param afdEndpoints = [ + { + autoGeneratedDomainNameLabelScope: 'TenantReuse' + enabledState: 'Enabled' + name: 'dep-waf-primary-endpoint' + routes: [ + { + cacheConfiguration: { + compressionSettings: { + contentTypesToCompress: [ + 'application/json' + 'application/xml' + 'text/xml' + ] + isCompressionEnabled: true + } + queryParameters: 'timestamp,nonce' + queryStringCachingBehavior: 'IgnoreSpecifiedQueryStrings' + } + customDomainNames: [ + 'dep-waf-api-cdnpwaf-domain' + ] + enabledState: 'Enabled' + forwardingProtocol: 'HttpsOnly' + httpsRedirect: 'Enabled' + linkToDefaultDomain: 'Disabled' + name: 'dep-waf-api-route' + originGroupName: 'dep-waf-api-origin-group' + patternsToMatch: [ + '/api/*' + '/v1/*' + '/v2/*' + ] + ruleSets: [ + { + name: 'depwafsecurityrulescdnpwaf' + } + ] + supportedProtocols: [ + 'Https' + ] + } + ] + } +] +param customDomains = [ + { + certificateType: 'ManagedCertificate' + cipherSuiteSetType: 'TLS12_2023' + hostName: 'dep-waf-primary-cdnpwaf.example.com' + minimumTlsVersion: 'TLS12' + name: 'dep-waf-primary-cdnpwaf-domain' + } + { + certificateType: 'ManagedCertificate' + cipherSuiteSetType: 'Customized' + customizedCipherSuiteSet: { + cipherSuiteSetForTls13: [ + 'TLS_AES_128_GCM_SHA256' + 'TLS_AES_256_GCM_SHA384' + ] + } + hostName: 'api-dep-waf-cdnpwaf.example.com' + minimumTlsVersion: 'TLS13' + name: 'dep-waf-api-cdnpwaf-domain' + } +] +param diagnosticSettings = [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + enabled: true + } + ] + metricCategories: [ + { + category: 'AllMetrics' enabled: true + } + ] + name: 'waf-comprehensive-diagnostics' + storageAccountResourceId: '' + workspaceResourceId: '' + } +] +param location = 'global' +param lock = { + kind: 'CanNotDelete' + name: 'waf-protection-lock' + notes: 'WAF: Protected against accidental deletion for business continuity' +} +param managedIdentities = { + systemAssigned: true + userAssignedResourceIds: [ + '' + ] +} +param originGroups = [ + { + healthProbeSettings: { + probeIntervalInSeconds: 15 + probePath: '/api/health' + probeProtocol: 'Https' + probeRequestType: 'GET' + } + loadBalancingSettings: { + additionalLatencyInMilliseconds: 25 + sampleSize: 6 + successfulSamplesRequired: 4 + } + name: 'dep-waf-api-origin-group' + origins: [ + { + enabledState: 'Enabled' + enforceCertificateNameCheck: true hostName: '' httpPort: 80 httpsPort: 443 + name: 'dep-waf-api-origin' + priority: 1 + weight: 1000 } - } - ] - queryStringCachingBehavior: 'IgnoreQueryString' -} -param location = '' + ] + sessionAffinityState: 'Disabled' + trafficRestorationTimeToHealedOrNewEndpointsInMinutes: 2 + } +] param originResponseTimeoutSeconds = 60 +param roleAssignments = [ + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'CDN Profile Reader' + } +] +param ruleSets = [ + { + name: 'depwafsecurityrulescdnpwaf' + rules: [ + { + actions: [ + { + name: 'UrlRedirect' + parameters: { + destinationProtocol: 'Https' + redirectType: 'PermanentRedirect' + typeName: 'DeliveryRuleUrlRedirectActionParameters' + } + } + ] + conditions: [ + { + name: 'RequestScheme' + parameters: { + matchValues: [ + 'HTTP' + ] + negateCondition: false + operator: 'Equal' + typeName: 'DeliveryRuleRequestSchemeConditionParameters' + } + } + ] + matchProcessingBehavior: 'Stop' + name: 'HTTPSRedirectRule' + order: 1 + } + { + actions: [ + { + name: 'ModifyResponseHeader' + parameters: { + headerAction: 'Overwrite' + headerName: 'Strict-Transport-Security' + typeName: 'DeliveryRuleHeaderActionParameters' + value: 'max-age=31536000; includeSubDomains; preload' + } + } + { + name: 'ModifyResponseHeader' + parameters: { + headerAction: 'Overwrite' + headerName: 'X-Content-Type-Options' + typeName: 'DeliveryRuleHeaderActionParameters' + value: 'nosniff' + } + } + { + name: 'ModifyResponseHeader' + parameters: { + headerAction: 'Overwrite' + headerName: 'X-Frame-Options' + typeName: 'DeliveryRuleHeaderActionParameters' + value: 'DENY' + } + } + { + name: 'ModifyResponseHeader' + parameters: { + headerAction: 'Overwrite' + headerName: 'X-XSS-Protection' + typeName: 'DeliveryRuleHeaderActionParameters' + value: '1; mode=block' + } + } + { + name: 'ModifyResponseHeader' + parameters: { + headerAction: 'Overwrite' + headerName: 'Referrer-Policy' + typeName: 'DeliveryRuleHeaderActionParameters' + value: 'strict-origin-when-cross-origin' + } + } + ] + conditions: [ + { + name: 'RequestScheme' + parameters: { + matchValues: [ + 'HTTPS' + ] + negateCondition: false + operator: 'Equal' + typeName: 'DeliveryRuleRequestSchemeConditionParameters' + } + } + ] + matchProcessingBehavior: 'Continue' + name: 'SecurityHeadersRule' + order: 2 + } + { + actions: [ + { + name: 'ModifyResponseHeader' + parameters: { + headerAction: 'Overwrite' + headerName: 'X-RateLimit-Limit' + typeName: 'DeliveryRuleHeaderActionParameters' + value: '1000' + } + } + ] + conditions: [ + { + name: 'RequestUri' + parameters: { + matchValues: [ + '/api/' + ] + negateCondition: false + operator: 'BeginsWith' + transforms: [ + 'Lowercase' + ] + typeName: 'DeliveryRuleRequestUriConditionParameters' + } + } + ] + matchProcessingBehavior: 'Continue' + name: 'APIRateLimitRule' + order: 3 + } + ] + } +] +param tags = { + Application: 'CDN-WAF-Aligned' + BackupRequired: 'Yes' + BusinessUnit: 'Digital-Services' + CostCenter: 'IT-Infrastructure' + Criticality: 'High' + DataClassification: 'Internal' + Environment: 'Production' + MonitoringRequired: 'Yes' + Owner: 'Platform-Team' + 'WAF-Pillar': 'All' +} ```
@@ -1955,6 +3094,7 @@ The minimum TLS version. [ 'TLS10' 'TLS12' + 'TLS13' ] ``` diff --git a/avm/res/cdn/profile/afd-endpoint/main.json b/avm/res/cdn/profile/afd-endpoint/main.json index 4059cd82266..abc4c8d36f5 100644 --- a/avm/res/cdn/profile/afd-endpoint/main.json +++ b/avm/res/cdn/profile/afd-endpoint/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "10968148801425056098" + "version": "0.37.4.10188", + "templateHash": "1610449730249909746" }, "name": "CDN Profiles AFD Endpoints", "description": "This module deploys a CDN Profile AFD Endpoint." @@ -382,8 +382,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "17117420688790383138" + "version": "0.37.4.10188", + "templateHash": "15191299450065701668" }, "name": "CDN Profiles AFD Endpoint Route", "description": "This module deploys a CDN Profile AFD Endpoint route." diff --git a/avm/res/cdn/profile/afd-endpoint/route/main.json b/avm/res/cdn/profile/afd-endpoint/route/main.json index e35a78fedfe..e120fdbb707 100644 --- a/avm/res/cdn/profile/afd-endpoint/route/main.json +++ b/avm/res/cdn/profile/afd-endpoint/route/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "17117420688790383138" + "version": "0.37.4.10188", + "templateHash": "15191299450065701668" }, "name": "CDN Profiles AFD Endpoint Route", "description": "This module deploys a CDN Profile AFD Endpoint route." diff --git a/avm/res/cdn/profile/custom-domain/README.md b/avm/res/cdn/profile/custom-domain/README.md index b5cffda1402..33cbdab38ee 100644 --- a/avm/res/cdn/profile/custom-domain/README.md +++ b/avm/res/cdn/profile/custom-domain/README.md @@ -12,7 +12,7 @@ This module deploys a CDN Profile Custom Domains. | Resource Type | API Version | References | | :-- | :-- | :-- | -| `Microsoft.Cdn/profiles/customDomains` | 2025-04-15 |
  • [AzAdvertizer](https://www.azadvertizer.net/azresourcetypes/microsoft.cdn_profiles_customdomains.html)
  • [Template reference](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Cdn/2025-04-15/profiles/customDomains)
| +| `Microsoft.Cdn/profiles/customDomains` | 2025-06-01 |
  • [AzAdvertizer](https://www.azadvertizer.net/azresourcetypes/microsoft.cdn_profiles_customdomains.html)
  • [Template reference](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Cdn/2025-06-01/profiles/customDomains)
| ## Parameters @@ -114,6 +114,7 @@ The minimum TLS version required for the custom domain. Default value: TLS12. [ 'TLS10' 'TLS12' + 'TLS13' ] ``` diff --git a/avm/res/cdn/profile/custom-domain/main.bicep b/avm/res/cdn/profile/custom-domain/main.bicep index 51ec1fe616c..edad3023cbf 100644 --- a/avm/res/cdn/profile/custom-domain/main.bicep +++ b/avm/res/cdn/profile/custom-domain/main.bicep @@ -30,6 +30,7 @@ param certificateType string @allowed([ 'TLS10' 'TLS12' + 'TLS13' ]) @description('Optional. The minimum TLS version required for the custom domain. Default value: TLS12.') param minimumTlsVersion string = 'TLS12' @@ -51,7 +52,7 @@ resource profile 'Microsoft.Cdn/profiles@2025-04-15' existing = { } } -resource customDomain 'Microsoft.Cdn/profiles/customDomains@2025-04-15' = { +resource customDomain 'Microsoft.Cdn/profiles/customDomains@2025-06-01' = { name: name parent: profile properties: { @@ -125,7 +126,7 @@ type customDomainType = { secretName: string? @description('Optional. The minimum TLS version.') - minimumTlsVersion: 'TLS10' | 'TLS12' | null + minimumTlsVersion: 'TLS10' | 'TLS12' | 'TLS13' | null @description('Optional. Extended properties.') extendedProperties: object? diff --git a/avm/res/cdn/profile/custom-domain/main.json b/avm/res/cdn/profile/custom-domain/main.json index 8061c2448dc..8fa19e5cead 100644 --- a/avm/res/cdn/profile/custom-domain/main.json +++ b/avm/res/cdn/profile/custom-domain/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "4152856745534187528" + "version": "0.37.4.10188", + "templateHash": "1486369118418758330" }, "name": "CDN Profiles Custom Domains", "description": "This module deploys a CDN Profile Custom Domains." @@ -63,7 +63,8 @@ "type": "string", "allowedValues": [ "TLS10", - "TLS12" + "TLS12", + "TLS13" ], "nullable": true, "metadata": { @@ -184,7 +185,8 @@ "defaultValue": "TLS12", "allowedValues": [ "TLS10", - "TLS12" + "TLS12", + "TLS13" ], "metadata": { "description": "Optional. The minimum TLS version required for the custom domain. Default value: TLS12." @@ -228,7 +230,7 @@ }, "customDomain": { "type": "Microsoft.Cdn/profiles/customDomains", - "apiVersion": "2025-04-15", + "apiVersion": "2025-06-01", "name": "[format('{0}/{1}', parameters('profileName'), parameters('name'))]", "properties": { "azureDnsZone": "[if(not(empty(parameters('azureDnsZoneResourceId'))), createObject('id', parameters('azureDnsZoneResourceId')), null())]", diff --git a/avm/res/cdn/profile/endpoint/main.json b/avm/res/cdn/profile/endpoint/main.json index 64dd4928667..f6f46c08109 100644 --- a/avm/res/cdn/profile/endpoint/main.json +++ b/avm/res/cdn/profile/endpoint/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "3222821096361861437" + "version": "0.37.4.10188", + "templateHash": "5226882540835560084" }, "name": "CDN Profiles Endpoints", "description": "This module deploys a CDN Profile Endpoint." @@ -121,8 +121,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "13368120440571907708" + "version": "0.37.4.10188", + "templateHash": "10119907844998577144" }, "name": "CDN Profiles Endpoints Origins", "description": "This module deploys a CDN Profile Endpoint Origin." diff --git a/avm/res/cdn/profile/endpoint/origin/main.json b/avm/res/cdn/profile/endpoint/origin/main.json index cc5ffa34b1b..dafb9ad55a5 100644 --- a/avm/res/cdn/profile/endpoint/origin/main.json +++ b/avm/res/cdn/profile/endpoint/origin/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "13368120440571907708" + "version": "0.37.4.10188", + "templateHash": "10119907844998577144" }, "name": "CDN Profiles Endpoints Origins", "description": "This module deploys a CDN Profile Endpoint Origin." diff --git a/avm/res/cdn/profile/main.bicep b/avm/res/cdn/profile/main.bicep index 22f8670d7c9..80f6263ea62 100644 --- a/avm/res/cdn/profile/main.bicep +++ b/avm/res/cdn/profile/main.bicep @@ -145,7 +145,7 @@ resource avmTelemetry 'Microsoft.Resources/deployments@2025-04-01' = if (enableT } } -resource profile 'Microsoft.Cdn/profiles@2025-04-15' = { +resource profile 'Microsoft.Cdn/profiles@2025-06-01' = { name: name location: location identity: identity diff --git a/avm/res/cdn/profile/main.json b/avm/res/cdn/profile/main.json index 3660b7b3d87..5344701b939 100644 --- a/avm/res/cdn/profile/main.json +++ b/avm/res/cdn/profile/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "5810889614348598823" + "templateHash": "9277089994275321905" }, "name": "CDN Profiles", "description": "This module deploys a CDN Profile." @@ -434,7 +434,8 @@ "type": "string", "allowedValues": [ "TLS10", - "TLS12" + "TLS12", + "TLS13" ], "nullable": true, "metadata": { @@ -1183,7 +1184,7 @@ }, "profile": { "type": "Microsoft.Cdn/profiles", - "apiVersion": "2025-04-15", + "apiVersion": "2025-06-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "identity": "[variables('identity')]", @@ -1820,7 +1821,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "1129107995084048204" + "templateHash": "1486369118418758330" }, "name": "CDN Profiles Custom Domains", "description": "This module deploys a CDN Profile Custom Domains." @@ -1877,7 +1878,8 @@ "type": "string", "allowedValues": [ "TLS10", - "TLS12" + "TLS12", + "TLS13" ], "nullable": true, "metadata": { @@ -1998,7 +2000,8 @@ "defaultValue": "TLS12", "allowedValues": [ "TLS10", - "TLS12" + "TLS12", + "TLS13" ], "metadata": { "description": "Optional. The minimum TLS version required for the custom domain. Default value: TLS12." @@ -2042,7 +2045,7 @@ }, "customDomain": { "type": "Microsoft.Cdn/profiles/customDomains", - "apiVersion": "2025-04-15", + "apiVersion": "2025-06-01", "name": "[format('{0}/{1}', parameters('profileName'), parameters('name'))]", "properties": { "azureDnsZone": "[if(not(empty(parameters('azureDnsZoneResourceId'))), createObject('id', parameters('azureDnsZoneResourceId')), null())]", @@ -4202,7 +4205,7 @@ "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('profile', '2025-04-15', 'full').location]" + "value": "[reference('profile', '2025-06-01', 'full').location]" }, "endpointName": { "type": "string", @@ -4231,7 +4234,7 @@ "metadata": { "description": "The principal ID of the system assigned identity." }, - "value": "[tryGet(tryGet(reference('profile', '2025-04-15', 'full'), 'identity'), 'principalId')]" + "value": "[tryGet(tryGet(reference('profile', '2025-06-01', 'full'), 'identity'), 'principalId')]" }, "dnsValidation": { "type": "array", diff --git a/avm/res/cdn/profile/origin-group/main.json b/avm/res/cdn/profile/origin-group/main.json index f4383e9e9bd..16f6ab81ac2 100644 --- a/avm/res/cdn/profile/origin-group/main.json +++ b/avm/res/cdn/profile/origin-group/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "17376902853351027070" + "version": "0.37.4.10188", + "templateHash": "14962728161506551015" }, "name": "CDN Profiles Origin Group", "description": "This module deploys a CDN Profile Origin Group." @@ -352,8 +352,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "3654686564131775119" + "version": "0.37.4.10188", + "templateHash": "8524246809029737366" }, "name": "CDN Profiles Origin", "description": "This module deploys a CDN Profile Origin." diff --git a/avm/res/cdn/profile/origin-group/origin/main.json b/avm/res/cdn/profile/origin-group/origin/main.json index ad8882e1cc0..5263acfdbce 100644 --- a/avm/res/cdn/profile/origin-group/origin/main.json +++ b/avm/res/cdn/profile/origin-group/origin/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "3654686564131775119" + "version": "0.37.4.10188", + "templateHash": "8524246809029737366" }, "name": "CDN Profiles Origin", "description": "This module deploys a CDN Profile Origin." diff --git a/avm/res/cdn/profile/rule-set/main.json b/avm/res/cdn/profile/rule-set/main.json index 8f2d49de289..611d8ab0922 100644 --- a/avm/res/cdn/profile/rule-set/main.json +++ b/avm/res/cdn/profile/rule-set/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "9308092005992913249" + "version": "0.37.4.10188", + "templateHash": "2563031334159492138" }, "name": "CDN Profiles Rule Sets", "description": "This module deploys a CDN Profile rule set." @@ -165,8 +165,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "13557087255389320076" + "version": "0.37.4.10188", + "templateHash": "14802510232650562515" }, "name": "CDN Profiles Rules", "description": "This module deploys a CDN Profile rule." diff --git a/avm/res/cdn/profile/rule-set/rule/main.json b/avm/res/cdn/profile/rule-set/rule/main.json index 4476fdd9b2b..a2ff2de21d1 100644 --- a/avm/res/cdn/profile/rule-set/rule/main.json +++ b/avm/res/cdn/profile/rule-set/rule/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "13557087255389320076" + "version": "0.37.4.10188", + "templateHash": "14802510232650562515" }, "name": "CDN Profiles Rules", "description": "This module deploys a CDN Profile rule." diff --git a/avm/res/cdn/profile/secret/main.json b/avm/res/cdn/profile/secret/main.json index 41cfe5045a9..fd82adbece8 100644 --- a/avm/res/cdn/profile/secret/main.json +++ b/avm/res/cdn/profile/secret/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "10255459755240506859" + "version": "0.37.4.10188", + "templateHash": "932660675787735782" }, "name": "CDN Profiles Secret", "description": "This module deploys a CDN Profile Secret." diff --git a/avm/res/cdn/profile/security-policy/main.json b/avm/res/cdn/profile/security-policy/main.json index 1797a680e5e..be99f0dfcdd 100644 --- a/avm/res/cdn/profile/security-policy/main.json +++ b/avm/res/cdn/profile/security-policy/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "564000263660798116" + "version": "0.37.4.10188", + "templateHash": "15025860363241476047" }, "name": "CDN Profiles Security Policy", "description": "This module deploys a CDN Profile Security Policy." diff --git a/avm/res/cdn/profile/tests/e2e/afd.premium/main.test.bicep b/avm/res/cdn/profile/tests/e2e/afd.premium/main.test.bicep index b6089adcc05..f67743e2e3b 100644 --- a/avm/res/cdn/profile/tests/e2e/afd.premium/main.test.bicep +++ b/avm/res/cdn/profile/tests/e2e/afd.premium/main.test.bicep @@ -50,7 +50,7 @@ module testDeployment '../../../main.bicep' = [ scope: resourceGroup name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' params: { - name: 'dep-${namePrefix}-test-${serviceShort}' + name: 'dep-${namePrefix}-test-afd-${serviceShort}' location: 'global' originResponseTimeoutSeconds: 60 sku: 'Premium_AzureFrontDoor' @@ -102,7 +102,7 @@ module testDeployment '../../../main.bicep' = [ ] afdEndpoints: [ { - name: 'dep-${namePrefix}-test-${serviceShort}-afd-endpoint' + name: 'dep-${namePrefix}-test-afd-${serviceShort}-afd-endpoint' routes: [ { name: 'dep-${namePrefix}-test-${serviceShort}-afd-route' @@ -127,9 +127,9 @@ module testDeployment '../../../main.bicep' = [ id: resourceId( subscription().subscriptionId, resourceGroup.name, - 'Microsoft.Cdn/profiles/afdEndpoints', - 'dep-${namePrefix}-test-${serviceShort}', - 'dep-${namePrefix}-test-${serviceShort}-afd-endpoint' + 'Microsoft.Cdn/profiles/customDomains', + 'dep-${namePrefix}-test-afd-${serviceShort}', + 'dep-${namePrefix}-test-${serviceShort}-custom-domain' ) } ] diff --git a/avm/res/cdn/profile/tests/e2e/defaults/main.test.bicep b/avm/res/cdn/profile/tests/e2e/defaults/main.test.bicep index 68c415ae4d7..88d186d2e96 100644 --- a/avm/res/cdn/profile/tests/e2e/defaults/main.test.bicep +++ b/avm/res/cdn/profile/tests/e2e/defaults/main.test.bicep @@ -38,8 +38,8 @@ module testDeployment '../../../main.bicep' = [ name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' params: { name: 'dep-${namePrefix}-test-${serviceShort}' - location: resourceLocation - sku: 'Standard_Microsoft' + location: 'global' + sku: 'Standard_AzureFrontDoor' } } ] diff --git a/avm/res/cdn/profile/tests/e2e/max/dependencies.bicep b/avm/res/cdn/profile/tests/e2e/max/dependencies.bicep index 72f40561cee..9b8c2c7183b 100644 --- a/avm/res/cdn/profile/tests/e2e/max/dependencies.bicep +++ b/avm/res/cdn/profile/tests/e2e/max/dependencies.bicep @@ -35,4 +35,7 @@ output storageAccountResourceId string = storageAccount.id output storageAccountName string = storageAccount.name @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 diff --git a/avm/res/cdn/profile/tests/e2e/max/main.test.bicep b/avm/res/cdn/profile/tests/e2e/max/main.test.bicep index dcb647e5f30..e5c92799c4f 100644 --- a/avm/res/cdn/profile/tests/e2e/max/main.test.bicep +++ b/avm/res/cdn/profile/tests/e2e/max/main.test.bicep @@ -1,7 +1,7 @@ targetScope = 'subscription' -metadata name = 'Using large parameter set' -metadata description = 'This instance deploys the module with most of its features enabled.' +metadata name = 'Using maximum parameter set' +metadata description = 'This instance deploys the module with all available features and parameters for Premium_AzureFrontDoor SKU.' // ========== // // Parameters // @@ -9,7 +9,7 @@ metadata description = 'This instance deploys the module with most of its featur @description('Optional. The name of the resource group to deploy for testing purposes.') @maxLength(90) -param resourceGroupName string = 'dep-${namePrefix}-cdn.profiles-${serviceShort}-rg' +param resourceGroupName string = 'dep-${namePrefix}-cdn.profiles.max-${serviceShort}-rg' @description('Optional. The location to deploy resources to.') param resourceLocation string = deployment().location @@ -66,43 +66,203 @@ module testDeployment '../../../main.bicep' = [ name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' params: { name: 'dep-${namePrefix}-test-${serviceShort}' - location: resourceLocation + location: 'global' + sku: 'Premium_AzureFrontDoor' + originResponseTimeoutSeconds: 240 + + // Managed Identity + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] + } + + // Lock configuration lock: { kind: 'CanNotDelete' name: 'myCustomLockName' + notes: 'This resource cannot be deleted for security reasons.' } - originResponseTimeoutSeconds: 60 - sku: 'Standard_Microsoft' - endpointProperties: { - originHostHeader: '${nestedDependencies.outputs.storageAccountName}.blob.${environment().suffixes.storage}' - contentTypesToCompress: [ - 'text/plain' - 'text/html' - 'text/css' - 'text/javascript' - 'application/x-javascript' - 'application/javascript' - 'application/json' - 'application/xml' - ] - isCompressionEnabled: true - isHttpAllowed: true - isHttpsAllowed: true - queryStringCachingBehavior: 'IgnoreQueryString' - origins: [ - { - name: 'dep-${namePrefix}-cdn-endpoint01' - properties: { + + // Tags + tags: { + Environment: 'Test' + Application: 'CDN' + CostCenter: '12345' + Owner: 'TestTeam' + } + + // Custom Domains with simplified configurations (FIXED) + customDomains: [ + { + name: 'dep-${namePrefix}-test1-${serviceShort}-custom-domain' + hostName: 'dep-${namePrefix}-test1-${serviceShort}-custom-domain.azurewebsites.net' + certificateType: 'ManagedCertificate' + minimumTlsVersion: 'TLS12' + } + { + name: 'dep-${namePrefix}-test2-${serviceShort}-custom-domain' + hostName: 'dep-${namePrefix}-test2-${serviceShort}-custom-domain.azurewebsites.net' + certificateType: 'ManagedCertificate' + minimumTlsVersion: 'TLS12' + cipherSuiteSetType: 'TLS12_2022' + } + { + name: 'dep-${namePrefix}-test3-${serviceShort}-custom-domain' + hostName: 'dep-${namePrefix}-test3-${serviceShort}-custom-domain.azurewebsites.net' + certificateType: 'ManagedCertificate' + minimumTlsVersion: 'TLS13' + cipherSuiteSetType: 'Customized' + customizedCipherSuiteSet: { + // cipherSuiteSetForTls12: [ + // 'DHE_RSA_AES128_GCM_SHA256' + // 'DHE_RSA_AES256_GCM_SHA384' + // 'ECDHE_RSA_AES128_GCM_SHA256' + // 'ECDHE_RSA_AES256_GCM_SHA384' + // ] + cipherSuiteSetForTls13: [ + 'TLS_AES_128_GCM_SHA256' + 'TLS_AES_256_GCM_SHA384' + ] + } + } + ] + + // Origin Groups with realistic hostnames (FIXED) + originGroups: [ + { + name: 'dep-${namePrefix}-test-${serviceShort}-origin-group-1' + loadBalancingSettings: { + additionalLatencyInMilliseconds: 50 + sampleSize: 4 + successfulSamplesRequired: 3 + } + healthProbeSettings: { + probePath: '/health' + probeProtocol: 'Https' + probeRequestType: 'GET' + probeIntervalInSeconds: 120 + } + sessionAffinityState: 'Enabled' + trafficRestorationTimeToHealedOrNewEndpointsInMinutes: 15 + origins: [ + { + name: 'dep-${namePrefix}-test-${serviceShort}-origin-1' hostName: '${nestedDependencies.outputs.storageAccountName}.blob.${environment().suffixes.storage}' httpPort: 80 httpsPort: 443 - enabled: true + priority: 1 + weight: 1000 + enabledState: 'Enabled' + enforceCertificateNameCheck: true } + ] + } + { + name: 'dep-${namePrefix}-test-${serviceShort}-origin-group-2' + loadBalancingSettings: { + additionalLatencyInMilliseconds: 100 + sampleSize: 6 + successfulSamplesRequired: 4 } - ] - originGroups: [] - geoFilters: [] - } + sessionAffinityState: 'Disabled' + trafficRestorationTimeToHealedOrNewEndpointsInMinutes: 10 + origins: [ + { + name: 'dep-${namePrefix}-test-${serviceShort}-origin-2' + hostName: '${nestedDependencies.outputs.storageAccountName}.blob.${environment().suffixes.storage}' + httpPort: 80 + httpsPort: 443 + priority: 1 + weight: 1000 + enabledState: 'Enabled' + enforceCertificateNameCheck: true + } + ] + } + ] + + // Rule Sets with comprehensive rules + ruleSets: [ + { + name: 'dep${namePrefix}test${serviceShort}ruleset1' + rules: [ + { + name: 'dep${namePrefix}test${serviceShort}rule1' + order: 1 + matchProcessingBehavior: 'Continue' + conditions: [ + // { + // name: 'RequestMethod' + // parameters: { + // typeName: 'DeliveryRuleRequestMethodConditionParameters' + // operator: 'Equal' + // negateCondition: false + // matchValues: ['GET', 'POST'] + // transforms: [] + // } + // } + ] + actions: [ + { + name: 'UrlRedirect' + parameters: { + typeName: 'DeliveryRuleUrlRedirectActionParameters' + redirectType: 'PermanentRedirect' + destinationProtocol: 'Https' + customPath: '/v2/api/' + customHostname: 'api.example.com' + } + } + ] + } + ] + } + ] + + // AFD Endpoints with simplified routing (FIXED) + afdEndpoints: [ + { + name: 'dep-${namePrefix}-test-${serviceShort}-afd-endpoint-1' + autoGeneratedDomainNameLabelScope: 'TenantReuse' + enabledState: 'Enabled' + routes: [ + { + name: 'dep-${namePrefix}-test-${serviceShort}-afd-route-1' + originGroupName: 'dep-${namePrefix}-test-${serviceShort}-origin-group-1' + customDomainNames: [ + 'dep-${namePrefix}-test1-${serviceShort}-custom-domain' + ] + enabledState: 'Enabled' + forwardingProtocol: 'MatchRequest' + httpsRedirect: 'Enabled' + linkToDefaultDomain: 'Enabled' + patternsToMatch: ['/api/*', '/health'] + supportedProtocols: ['Http', 'Https'] + cacheConfiguration: { + queryStringCachingBehavior: 'IncludeSpecifiedQueryStrings' + queryParameters: 'version,locale' + compressionSettings: { + contentTypesToCompress: [ + 'application/json' + 'text/css' + 'text/html' + ] + isCompressionEnabled: true + } + } + ruleSets: [ + { + name: 'dep${namePrefix}test${serviceShort}ruleset1' + } + ] + } + ] + } + ] + + // Diagnostics settings diagnosticSettings: [ { name: 'customSetting' @@ -124,24 +284,11 @@ module testDeployment '../../../main.bicep' = [ workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId } ] + + // Role assignments roleAssignments: [ { - name: '50362c78-6910-43c3-8639-9cae123943bb' - roleDefinitionIdOrName: 'Owner' - principalId: nestedDependencies.outputs.managedIdentityPrincipalId - principalType: 'ServicePrincipal' - } - { - name: guid('Custom seed ${namePrefix}${serviceShort}') - roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' - principalId: nestedDependencies.outputs.managedIdentityPrincipalId - principalType: 'ServicePrincipal' - } - { - roleDefinitionIdOrName: subscriptionResourceId( - 'Microsoft.Authorization/roleDefinitions', - 'acdd72a7-3385-48ef-bd42-f606fba81ae7' - ) + roleDefinitionIdOrName: 'CDN Profile Contributor' principalId: nestedDependencies.outputs.managedIdentityPrincipalId principalType: 'ServicePrincipal' } @@ -149,3 +296,25 @@ module testDeployment '../../../main.bicep' = [ } } ] + +// =========== // +// Outputs // +// =========== // + +@description('The name of the CDN profile.') +output profileName string = testDeployment[0].outputs.name + +@description('The resource ID of the CDN profile.') +output profileResourceId string = testDeployment[0].outputs.resourceId + +@description('The DNS validation records for custom domains.') +output dnsValidationRecords array = testDeployment[0].outputs.dnsValidation + +@description('The AFD endpoint host names.') +output afdEndpointNames array = testDeployment[0].outputs.frontDoorEndpointHostNames + +@description('The resource group name.') +output resourceGroupName string = resourceGroup.name + +@description('The system-assigned managed identity principal ID.') +output systemAssignedMIPrincipalId string = testDeployment[0].outputs.?systemAssignedMIPrincipalId diff --git a/avm/res/cdn/profile/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/cdn/profile/tests/e2e/waf-aligned/dependencies.bicep index 99f4f8887b7..822673cf323 100644 --- a/avm/res/cdn/profile/tests/e2e/waf-aligned/dependencies.bicep +++ b/avm/res/cdn/profile/tests/e2e/waf-aligned/dependencies.bicep @@ -1,27 +1,92 @@ @description('Optional. The location to deploy resources to.') param location string = resourceGroup().location -@description('Required. The name of the Storage Account to create.') +@description('Required. The name of the primary Storage Account to create.') param storageAccountName string +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +// WAF: Reliability - Primary storage account with geo-redundancy resource storageAccount 'Microsoft.Storage/storageAccounts@2024-01-01' = { name: storageAccountName location: location sku: { - name: 'Standard_LRS' + name: 'Standard_GRS' // WAF: Reliability - Geo-redundant storage + } + kind: 'StorageV2' + properties: { + allowBlobPublicAccess: true // Required for CDN access + minimumTlsVersion: 'TLS1_2' // WAF: Security - Minimum TLS version + supportsHttpsTrafficOnly: true // WAF: Security - HTTPS only + accessTier: 'Hot' // WAF: Performance - Hot tier for frequently accessed data + networkAcls: { + defaultAction: 'Allow' // WAF: Allow CDN access + bypass: 'AzureServices' + } + encryption: { + services: { + blob: { + enabled: true // WAF: Security - Encryption at rest + } + file: { + enabled: true + } + } + keySource: 'Microsoft.Storage' + } + } +} + +// WAF: Reliability - Secondary storage account for failover +resource secondaryStorageAccount 'Microsoft.Storage/storageAccounts@2024-01-01' = { + name: '${take(storageAccountName, 20)}sec' + location: location + sku: { + name: 'Standard_LRS' // WAF: Cost Optimization - LRS for secondary } kind: 'StorageV2' properties: { - allowBlobPublicAccess: false + allowBlobPublicAccess: true + minimumTlsVersion: 'TLS1_2' + supportsHttpsTrafficOnly: true + accessTier: 'Cool' // WAF: Cost Optimization - Cool tier for backup networkAcls: { - defaultAction: 'Deny' + defaultAction: 'Allow' bypass: 'AzureServices' } + encryption: { + services: { + blob: { + enabled: true + } + file: { + enabled: true + } + } + keySource: 'Microsoft.Storage' + } } } -@description('The resource ID of the created Storage Account.') +// WAF: Security - Managed identity for secure access +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = { + name: managedIdentityName + location: location +} + +// Outputs +@description('The resource ID of the primary Storage Account.') output storageAccountResourceId string = storageAccount.id -@description('The name of the created Storage Account.') +@description('The name of the primary Storage Account.') output storageAccountName string = storageAccount.name + +@description('The name of the secondary Storage Account.') +output secondaryStorageAccountName string = secondaryStorageAccount.name + +@description('The resource ID of the Managed Identity.') +output managedIdentityResourceId string = managedIdentity.id + +@description('The principal ID of the Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId diff --git a/avm/res/cdn/profile/tests/e2e/waf-aligned/main.test.bicep b/avm/res/cdn/profile/tests/e2e/waf-aligned/main.test.bicep index 3ac6419d8d8..c5023dd8f07 100644 --- a/avm/res/cdn/profile/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/cdn/profile/tests/e2e/waf-aligned/main.test.bicep @@ -1,7 +1,7 @@ targetScope = 'subscription' -metadata name = 'WAF-aligned' -metadata description = 'This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework.' +metadata name = 'WAF-aligned Premium AFD' +metadata description = 'This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework using Premium_AzureFrontDoor SKU.' // ========== // // Parameters // @@ -35,7 +35,23 @@ module nestedDependencies 'dependencies.bicep' = { scope: resourceGroup name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' params: { - storageAccountName: 'dep${namePrefix}cdnstore${serviceShort}' + storageAccountName: 'dep${namePrefix}wafsa${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-waf-msi-${serviceShort}' + // keyVaultName: 'dep-${namePrefix}-kv-${uniqueString(resourceGroupName)}' + location: resourceLocation + } +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-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: resourceLocation } } @@ -50,43 +66,357 @@ module testDeployment '../../../main.bicep' = [ scope: resourceGroup name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' params: { - name: 'dep-${namePrefix}-test-${serviceShort}' - location: resourceLocation - originResponseTimeoutSeconds: 60 - sku: 'Standard_Microsoft' - endpointProperties: { - originHostHeader: '${nestedDependencies.outputs.storageAccountName}.blob.${environment().suffixes.storage}' - contentTypesToCompress: [ - 'text/plain' - 'text/html' - 'text/css' - 'text/javascript' - 'application/x-javascript' - 'application/javascript' - 'application/json' - 'application/xml' - ] - isCompressionEnabled: true - isHttpAllowed: true - isHttpsAllowed: true - queryStringCachingBehavior: 'IgnoreQueryString' - origins: [ - { - name: 'dep-${namePrefix}-cdn-endpoint01' - properties: { + name: 'dep-${namePrefix}-waf-${serviceShort}' + location: 'global' + sku: 'Premium_AzureFrontDoor' // WAF: Premium SKU for advanced security features + originResponseTimeoutSeconds: 60 // WAF: Reasonable timeout for reliability + + // WAF: Security - Resource locking to prevent accidental deletion + lock: { + kind: 'CanNotDelete' + name: 'waf-protection-lock' + notes: 'WAF: Protected against accidental deletion for business continuity' + } + + // WAF: Cost Optimization & Operational Excellence - Comprehensive tagging + tags: { + Environment: 'Production' + Application: 'CDN-WAF-Aligned' + CostCenter: 'IT-Infrastructure' + Owner: 'Platform-Team' + BusinessUnit: 'Digital-Services' + Criticality: 'High' + DataClassification: 'Internal' + BackupRequired: 'Yes' + MonitoringRequired: 'Yes' + 'WAF-Pillar': 'All' + // 'Last-Review': utcNow('yyyy-MM-dd') + } + + // WAF: Security - Custom domains with strong TLS configuration + customDomains: [ + { + name: 'dep-${namePrefix}-waf-primary-${serviceShort}-domain' + hostName: 'dep-${namePrefix}-waf-primary-${serviceShort}.example.com' + certificateType: 'ManagedCertificate' + minimumTlsVersion: 'TLS12' // WAF: Security - Use latest TLS version + cipherSuiteSetType: 'TLS12_2023' // WAF: Security - Modern cipher suites + } + { + name: 'dep-${namePrefix}-waf-api-${serviceShort}-domain' + hostName: 'api-dep-${namePrefix}-waf-${serviceShort}.example.com' + certificateType: 'ManagedCertificate' + minimumTlsVersion: 'TLS13' // WAF: Security - Use latest TLS version + cipherSuiteSetType: 'Customized' // WAF: Security - Custom cipher suite for enhanced security + customizedCipherSuiteSet: { + // cipherSuiteSetForTls12: [ + // 'ECDHE_RSA_AES256_GCM_SHA384' // Strong encryption + // 'ECDHE_RSA_AES128_GCM_SHA256' // Performance balance + // ] + cipherSuiteSetForTls13: [ + 'TLS_AES_256_GCM_SHA384' // Strong TLS 1.3 cipher + 'TLS_AES_128_GCM_SHA256' // Performance balance + ] + } + } + ] + + // WAF: Reliability & Performance - Multi-origin setup with health probes + originGroups: [ + { + name: 'dep-${namePrefix}-waf-api-origin-group' + loadBalancingSettings: { + additionalLatencyInMilliseconds: 25 // WAF: Performance - Lower latency for APIs + sampleSize: 6 // WAF: Reliability - More samples for critical APIs + successfulSamplesRequired: 4 // WAF: Reliability - Higher threshold for APIs + } + healthProbeSettings: { + probePath: '/api/health' // WAF: Reliability - API-specific health check + probeProtocol: 'Https' // WAF: Security - Encrypted health checks + probeRequestType: 'GET' + probeIntervalInSeconds: 15 // WAF: Reliability - More frequent for APIs + } + sessionAffinityState: 'Disabled' + trafficRestorationTimeToHealedOrNewEndpointsInMinutes: 2 // WAF: Reliability - Faster recovery for APIs + origins: [ + { + name: 'dep-${namePrefix}-waf-api-origin' hostName: '${nestedDependencies.outputs.storageAccountName}.blob.${environment().suffixes.storage}' httpPort: 80 httpsPort: 443 + priority: 1 + weight: 1000 + enabledState: 'Enabled' + enforceCertificateNameCheck: true // WAF: Security - Certificate validation + } + ] + } + ] + + // WAF: Security & Performance - Comprehensive routing rules + ruleSets: [ + { + name: 'dep${namePrefix}wafsecurityrules${serviceShort}' + rules: [ + { + name: 'HTTPSRedirectRule' + order: 1 + matchProcessingBehavior: 'Stop' // WAF: Security - Force HTTPS immediately + conditions: [ + { + name: 'RequestScheme' + parameters: { + typeName: 'DeliveryRuleRequestSchemeConditionParameters' + operator: 'Equal' + negateCondition: false + matchValues: ['HTTP'] + } + } + ] + actions: [ + { + name: 'UrlRedirect' + parameters: { + typeName: 'DeliveryRuleUrlRedirectActionParameters' + redirectType: 'PermanentRedirect' // WAF: Security - Permanent HTTPS enforcement + destinationProtocol: 'Https' + } + } + ] + } + { + name: 'SecurityHeadersRule' + order: 2 + matchProcessingBehavior: 'Continue' + conditions: [ + { + name: 'RequestScheme' + parameters: { + typeName: 'DeliveryRuleRequestSchemeConditionParameters' + operator: 'Equal' + negateCondition: false + matchValues: ['HTTPS'] + } + } + ] + actions: [ + { + name: 'ModifyResponseHeader' + parameters: { + typeName: 'DeliveryRuleHeaderActionParameters' + headerAction: 'Overwrite' + headerName: 'Strict-Transport-Security' + value: 'max-age=31536000; includeSubDomains; preload' // WAF: Security - HSTS + } + } + { + name: 'ModifyResponseHeader' + parameters: { + typeName: 'DeliveryRuleHeaderActionParameters' + headerAction: 'Overwrite' + headerName: 'X-Content-Type-Options' + value: 'nosniff' // WAF: Security - MIME type sniffing protection + } + } + { + name: 'ModifyResponseHeader' + parameters: { + typeName: 'DeliveryRuleHeaderActionParameters' + headerAction: 'Overwrite' + headerName: 'X-Frame-Options' + value: 'DENY' // WAF: Security - Clickjacking protection + } + } + { + name: 'ModifyResponseHeader' + parameters: { + typeName: 'DeliveryRuleHeaderActionParameters' + headerAction: 'Overwrite' + headerName: 'X-XSS-Protection' + value: '1; mode=block' // WAF: Security - XSS protection + } + } + { + name: 'ModifyResponseHeader' + parameters: { + typeName: 'DeliveryRuleHeaderActionParameters' + headerAction: 'Overwrite' + headerName: 'Referrer-Policy' + value: 'strict-origin-when-cross-origin' // WAF: Security - Referrer policy + } + } + ] + } + { + name: 'APIRateLimitRule' + order: 3 + matchProcessingBehavior: 'Continue' + conditions: [ + { + name: 'RequestUri' + parameters: { + typeName: 'DeliveryRuleRequestUriConditionParameters' + operator: 'BeginsWith' + negateCondition: false + matchValues: ['/api/'] + transforms: ['Lowercase'] + } + } + ] + actions: [ + { + name: 'ModifyResponseHeader' + parameters: { + typeName: 'DeliveryRuleHeaderActionParameters' + headerAction: 'Overwrite' + headerName: 'X-RateLimit-Limit' + value: '1000' // WAF: Reliability - Rate limiting for APIs + } + } + ] + } + ] + } + ] + + // WAF: Performance & Reliability - Optimized AFD endpoints + afdEndpoints: [ + { + name: 'dep-${namePrefix}-waf-primary-endpoint' + autoGeneratedDomainNameLabelScope: 'TenantReuse' // WAF: Cost Optimization - Reuse domains + enabledState: 'Enabled' + routes: [ + { + name: 'dep-${namePrefix}-waf-api-route' + originGroupName: 'dep-${namePrefix}-waf-api-origin-group' + customDomainNames: [ + 'dep-${namePrefix}-waf-api-${serviceShort}-domain' + ] + enabledState: 'Enabled' + forwardingProtocol: 'HttpsOnly' // WAF: Security - HTTPS only for APIs + httpsRedirect: 'Enabled' + linkToDefaultDomain: 'Disabled' // WAF: Security - Custom domain only for APIs + patternsToMatch: ['/api/*', '/v1/*', '/v2/*'] + supportedProtocols: ['Https'] // WAF: Security - HTTPS only + cacheConfiguration: { + queryStringCachingBehavior: 'IgnoreSpecifiedQueryStrings' // WAF: Performance - API-specific caching + queryParameters: 'timestamp,nonce' // WAF: Performance - Ignore security parameters + compressionSettings: { + contentTypesToCompress: [ + 'application/json' + 'application/xml' + 'text/xml' + ] + isCompressionEnabled: true // WAF: Performance - API response compression + } + } + ruleSets: [ + { + name: 'dep${namePrefix}wafsecurityrules${serviceShort}' + } + ] + } + ] + } + ] + + // WAF: Operational Excellence - Comprehensive diagnostics and monitoring + diagnosticSettings: [ + { + name: 'waf-comprehensive-diagnostics' + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' enabled: true } - } + ] + metricCategories: [ + { + category: 'AllMetrics' + enabled: true + } + ] + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + + // WAF: Security - Role-based access control + roleAssignments: [ + { + roleDefinitionIdOrName: 'CDN Profile Reader' // WAF: Security - Principle of least privilege + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + + // WAF: Reliability - Enable managed identities for secure access + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId ] - originGroups: [] - geoFilters: [] } } - dependsOn: [ - nestedDependencies - ] } ] + +// =========== // +// Outputs // +// =========== // + +@description('The name of the WAF-aligned CDN profile.') +output profileName string = testDeployment[0].outputs.name + +@description('The resource ID of the WAF-aligned CDN profile.') +output profileResourceId string = testDeployment[0].outputs.resourceId + +@description('The DNS validation records for custom domains.') +output dnsValidationRecords array = testDeployment[0].outputs.dnsValidation + +@description('The AFD endpoint host names.') +output afdEndpointNames array = testDeployment[0].outputs.frontDoorEndpointHostNames + +@description('The resource group name.') +output resourceGroupName string = resourceGroup.name + +@description('WAF compliance summary.') +output wafComplianceSummary object = { + reliability: { + multiOriginSetup: true + healthProbes: true + loadBalancing: true + failoverConfiguration: true + quickRecovery: true + } + security: { + httpsOnly: true + tls13Support: true + securityHeaders: true + certificateValidation: true + managedIdentities: true + rbacImplemented: true + resourceLocks: true + } + costOptimization: { + comprehensiveTagging: true + efficientCaching: true + domainReuse: true + rightsizedSku: true + } + operationalExcellence: { + comprehensiveMonitoring: true + structuredLogging: true + automatedDiagnostics: true + documentedConfiguration: true + } + performance: { + compressionEnabled: true + optimizedCaching: true + lowLatencyThresholds: true + efficientRouting: true + staticContentOptimization: true + } +} diff --git a/avm/res/cdn/profile/version.json b/avm/res/cdn/profile/version.json index ed8416987fa..804998a2ad3 100644 --- a/avm/res/cdn/profile/version.json +++ b/avm/res/cdn/profile/version.json @@ -1,4 +1,4 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.14" + "version": "0.15" }