diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4127d4f9..d98d09ac 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -70,7 +70,7 @@ In case of any conflicting instructions, the following hierarchy shall apply. If - `shared/`: Shared resources, such as Bicep modules, Python libraries, and other reusable components. - `tests/`: Contains unit tests for Python code and Bicep modules. This folder should contain all tests for all code in the repository. - ## Language-specific Instructions +## Language-specific Instructions - Python: see `.github/copilot-instructions.python.md` - Bicep: see `.github/copilot-instructions.bicep.md` diff --git a/infrastructure/afd-apim-pe/main.bicep b/infrastructure/afd-apim-pe/main.bicep index d9ca9bce..edb1ffc0 100644 --- a/infrastructure/afd-apim-pe/main.bicep +++ b/infrastructure/afd-apim-pe/main.bicep @@ -72,17 +72,40 @@ module appInsightsModule '../../shared/bicep/modules/monitor/v1/appinsights.bice var appInsightsId = appInsightsModule.outputs.id var appInsightsInstrumentationKey = appInsightsModule.outputs.instrumentationKey -// 3. Virtual Network and Subnets +// 3. Storage Account for NSG Flow Logs +module storageFlowLogsModule '../../shared/bicep/modules/vnet/v1/storage-flowlogs.bicep' = { + name: 'storageFlowLogsModule' + params: { + location: location + resourceSuffix: resourceSuffix + } +} + +// 4. Network Security Groups -// We are using a standard NSG for our subnets here. Production workloads should use a relevant, custom NSG for each subnet. -// We also do not presently use a custom route table for the subnets, which is a best practice for production workloads. +// NSG for API Management with Private Link from Front Door +module nsgApimModule '../../shared/bicep/modules/vnet/v1/nsg-apim-pe.bicep' = { + name: 'nsgApimModule' + params: { + location: location + nsgName: 'nsg-apim' + apimSubnetPrefix: apimSubnetPrefix + allowFrontDoorBackend: true + } +} -// https://learn.microsoft.com/azure/templates/microsoft.network/networksecuritygroups -resource nsg 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { - name: 'nsg-default' - location: location +// NSG for Container Apps - only allow traffic from APIM +module nsgAcaModule '../../shared/bicep/modules/vnet/v1/nsg-aca.bicep' = if (useACA) { + name: 'nsgAcaModule' + params: { + location: location + nsgName: 'nsg-aca' + acaSubnetPrefix: acaSubnetPrefix + apimSubnetPrefix: apimSubnetPrefix + } } +// 5. Virtual Network and Subnets module vnetModule '../../shared/bicep/modules/vnet/v1/vnet.bicep' = { name: 'vnetModule' params: { @@ -95,7 +118,7 @@ module vnetModule '../../shared/bicep/modules/vnet/v1/vnet.bicep' = { properties: { addressPrefix: apimSubnetPrefix networkSecurityGroup: { - id: nsg.id + id: nsgApimModule.outputs.nsgId } delegations: [ { @@ -113,7 +136,7 @@ module vnetModule '../../shared/bicep/modules/vnet/v1/vnet.bicep' = { properties: { addressPrefix: acaSubnetPrefix networkSecurityGroup: { - id: nsg.id + id: useACA ? nsgAcaModule!.outputs.nsgId : nsgApimModule.outputs.nsgId } delegations: [ { @@ -150,7 +173,37 @@ resource acaSubnetResource 'Microsoft.Network/virtualNetworks/subnets@2024-05-01 var apimSubnetResourceId = apimSubnetResource.id var acaSubnetResourceId = acaSubnetResource.id -// 4. Azure Container App Environment (ACAE) +// 6. NSG Flow Logs and Traffic Analytics + +// NSG Flow Logs for APIM +module nsgFlowLogsApimModule '../../shared/bicep/modules/vnet/v1/nsg-flow-logs.bicep' = { + name: 'nsgFlowLogsApimModule' + params: { + location: location + flowLogName: 'fl-nsg-apim-${resourceSuffix}' + nsgResourceId: nsgApimModule.outputs.nsgId + storageAccountResourceId: storageFlowLogsModule.outputs.storageAccountId + logAnalyticsWorkspaceResourceId: lawId + retentionDays: 7 + enableTrafficAnalytics: true + } +} + +// NSG Flow Logs for ACA +module nsgFlowLogsAcaModule '../../shared/bicep/modules/vnet/v1/nsg-flow-logs.bicep' = if (useACA) { + name: 'nsgFlowLogsAcaModule' + params: { + location: location + flowLogName: 'fl-nsg-aca-${resourceSuffix}' + nsgResourceId: nsgAcaModule!.outputs.nsgId + storageAccountResourceId: storageFlowLogsModule.outputs.storageAccountId + logAnalyticsWorkspaceResourceId: lawId + retentionDays: 7 + enableTrafficAnalytics: true + } +} + +// 7. Azure Container App Environment (ACAE) module acaEnvModule '../../shared/bicep/modules/aca/v1/environment.bicep' = if (useACA) { name: 'acaEnvModule' params: { @@ -161,7 +214,7 @@ module acaEnvModule '../../shared/bicep/modules/aca/v1/environment.bicep' = if ( } } -// 5. Azure Container Apps (ACA) for Mock Web API +// 8. Azure Container Apps (ACA) for Mock Web API module acaModule1 '../../shared/bicep/modules/aca/v1/containerapp.bicep' = if (useACA) { name: 'acaModule-1' params: { @@ -180,7 +233,7 @@ module acaModule2 '../../shared/bicep/modules/aca/v1/containerapp.bicep' = if (u } } -// 6. API Management +// 9. API Management module apimModule '../../shared/bicep/modules/apim/v1/apim.bicep' = { name: 'apimModule' params: { @@ -193,7 +246,7 @@ module apimModule '../../shared/bicep/modules/apim/v1/apim.bicep' = { } } -// 7. APIM Policy Fragments +// 10. APIM Policy Fragments module policyFragmentModule '../../shared/bicep/modules/apim/v1/policy-fragment.bicep' = [for pf in policyFragments: { name: 'pf-${pf.name}' params:{ @@ -207,7 +260,7 @@ module policyFragmentModule '../../shared/bicep/modules/apim/v1/policy-fragment. ] }] -// 8. APIM Backends for ACA +// 11. APIM Backends for ACA module backendModule1 '../../shared/bicep/modules/apim/v1/backend.bicep' = if (useACA) { name: 'aca-backend-1' params: { @@ -256,7 +309,7 @@ module backendPoolModule '../../shared/bicep/modules/apim/v1/backend-pool.bicep' ] } -// 9. APIM APIs +// 12. APIM APIs module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in apis: if(length(apis) > 0) { name: 'api-${api.name}' params: { @@ -275,7 +328,7 @@ module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in a ] }] -// 10. APIM Private DNS Zone, VNet Link, and (optional) DNS Zone Group +// 13. APIM Private DNS Zone, VNet Link, and (optional) DNS Zone Group module apimDnsPrivateLinkModule '../../shared/bicep/modules/dns/v1/dns-private-link.bicep' = { name: 'apimDnsPrivateLinkModule' params: { @@ -288,7 +341,7 @@ module apimDnsPrivateLinkModule '../../shared/bicep/modules/dns/v1/dns-private-l } } -// 11. ACA Private DNS Zone (regional, e.g., eastus2.azurecontainerapps.io), VNet Link, and wildcard A record via shared module +// 14. ACA Private DNS Zone (regional, e.g., eastus2.azurecontainerapps.io), VNet Link, and wildcard A record via shared module module acaDnsPrivateZoneModule '../../shared/bicep/modules/dns/v1/aca-dns-private-zone.bicep' = if (useACA && !empty(acaSubnetResourceId)) { name: 'acaDnsPrivateZoneModule' params: { @@ -298,7 +351,7 @@ module acaDnsPrivateZoneModule '../../shared/bicep/modules/dns/v1/aca-dns-privat } } -// 12. Front Door +// 15. Front Door module afdModule '../../shared/bicep/modules/afd/v1/afd.bicep' = { name: 'afdModule' params: { diff --git a/infrastructure/appgw-apim-pe/main.bicep b/infrastructure/appgw-apim-pe/main.bicep index ab705a8d..af55d5d0 100644 --- a/infrastructure/appgw-apim-pe/main.bicep +++ b/infrastructure/appgw-apim-pe/main.bicep @@ -1,3 +1,10 @@ +// ------------------ +// IMPORTS +// ------------------ + +import {nsgsr_denyAllInbound} from '../../shared/bicep/modules/vnet/v1/nsg_rules.bicep' + + // ------------------ // PARAMETERS // ------------------ @@ -96,7 +103,16 @@ module appInsightsModule '../../shared/bicep/modules/monitor/v1/appinsights.bice var appInsightsId = appInsightsModule.outputs.id var appInsightsInstrumentationKey = appInsightsModule.outputs.instrumentationKey -// 3. Virtual Network and Subnets +// 3. Storage Account for NSG Flow Logs +module storageFlowLogsModule '../../shared/bicep/modules/vnet/v1/storage-flowlogs.bicep' = { + name: 'storageFlowLogsModule' + params: { + location: location + resourceSuffix: resourceSuffix + } +} + +// 4. Virtual Network and Subnets resource nsgDefault 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { name: 'nsg-default' location: location @@ -112,7 +128,7 @@ resource nsgAppGw 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { name: 'AllowGatewayManagerInbound' properties: { description: 'Allow Azure infrastructure communication' - protocol: 'TCP' + protocol: 'Tcp' sourcePortRange: '*' destinationPortRange: '65200-65535' sourceAddressPrefix: 'GatewayManager' @@ -125,8 +141,8 @@ resource nsgAppGw 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { { name: 'AllowHTTPSInbound' properties: { - description: 'Allow HTTPS traffic' - protocol: 'TCP' + description: 'Allow HTTPS traffic from internet' + protocol: 'Tcp' sourcePortRange: '*' destinationPortRange: '443' sourceAddressPrefix: '*' @@ -139,7 +155,7 @@ resource nsgAppGw 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { { name: 'AllowAzureLoadBalancerInbound' properties: { - description: 'Allow Azure Load Balancer' + description: 'Allow Azure Load Balancer health probes' protocol: '*' sourcePortRange: '*' destinationPortRange: '*' @@ -150,10 +166,34 @@ resource nsgAppGw 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { direction: 'Inbound' } } + nsgsr_denyAllInbound ] } } +// NSG for APIM with Private Link from Application Gateway +module nsgApimModule '../../shared/bicep/modules/vnet/v1/nsg-apim-pe.bicep' = { + name: 'nsgApimModule' + params: { + location: location + nsgName: 'nsg-apim' + apimSubnetPrefix: apimSubnetPrefix + allowAppGateway: true + appgwSubnetPrefix: appgwSubnetPrefix + } +} + +// NSG for Container Apps - only allow traffic from APIM +module nsgAcaModule '../../shared/bicep/modules/vnet/v1/nsg-aca.bicep' = if (useACA) { + name: 'nsgAcaModule' + params: { + location: location + nsgName: 'nsg-aca' + acaSubnetPrefix: acaSubnetPrefix + apimSubnetPrefix: apimSubnetPrefix + } +} + module vnetModule '../../shared/bicep/modules/vnet/v1/vnet.bicep' = { name: 'vnetModule' params: { @@ -166,7 +206,7 @@ module vnetModule '../../shared/bicep/modules/vnet/v1/vnet.bicep' = { properties: { addressPrefix: apimSubnetPrefix networkSecurityGroup: { - id: nsgDefault.id + id: nsgApimModule.outputs.nsgId } delegations: [ { @@ -184,7 +224,7 @@ module vnetModule '../../shared/bicep/modules/vnet/v1/vnet.bicep' = { properties: { addressPrefix: acaSubnetPrefix networkSecurityGroup: { - id: nsgDefault.id + id: useACA ? nsgAcaModule!.outputs.nsgId : nsgDefault.id } delegations: [ { @@ -226,7 +266,51 @@ var acaSubnetResourceId = '${vnetModule.outputs.vnetId}/subnets/${acaSubnetNam var appgwSubnetResourceId = '${vnetModule.outputs.vnetId}/subnets/${appgwSubnetName}' var peSubnetResourceId = '${vnetModule.outputs.vnetId}/subnets/${privateEndpointSubnetName}' -// 4. User Assigned Managed Identity +// 5. NSG Flow Logs and Traffic Analytics + +// NSG Flow Logs for Application Gateway +module nsgFlowLogsAppGwModule '../../shared/bicep/modules/vnet/v1/nsg-flow-logs.bicep' = { + name: 'nsgFlowLogsAppGwModule' + params: { + location: location + flowLogName: 'fl-nsg-appgw-${resourceSuffix}' + nsgResourceId: nsgAppGw.id + storageAccountResourceId: storageFlowLogsModule.outputs.storageAccountId + logAnalyticsWorkspaceResourceId: lawId + retentionDays: 7 + enableTrafficAnalytics: true + } +} + +// NSG Flow Logs for APIM +module nsgFlowLogsApimModule '../../shared/bicep/modules/vnet/v1/nsg-flow-logs.bicep' = { + name: 'nsgFlowLogsApimModule' + params: { + location: location + flowLogName: 'fl-nsg-apim-${resourceSuffix}' + nsgResourceId: nsgApimModule.outputs.nsgId + storageAccountResourceId: storageFlowLogsModule.outputs.storageAccountId + logAnalyticsWorkspaceResourceId: lawId + retentionDays: 7 + enableTrafficAnalytics: true + } +} + +// NSG Flow Logs for ACA +module nsgFlowLogsAcaModule '../../shared/bicep/modules/vnet/v1/nsg-flow-logs.bicep' = if (useACA) { + name: 'nsgFlowLogsAcaModule' + params: { + location: location + flowLogName: 'fl-nsg-aca-${resourceSuffix}' + nsgResourceId: nsgAcaModule!.outputs.nsgId + storageAccountResourceId: storageFlowLogsModule.outputs.storageAccountId + logAnalyticsWorkspaceResourceId: lawId + retentionDays: 7 + enableTrafficAnalytics: true + } +} + +// 6. User Assigned Managed Identity // https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/managed-identity/user-assigned-identity module uamiModule 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.2' = { name: 'uamiModule' @@ -236,7 +320,7 @@ module uamiModule 'br/public:avm/res/managed-identity/user-assigned-identity:0.4 } } -// 5. Key Vault +// 7. Key Vault // https://learn.microsoft.com/azure/templates/microsoft.keyvault/vaults // This assignment is helpful for testing to allow you to examine and administer the Key Vault. Adjust accordingly for real workloads! var keyVaultAdminRoleAssignment = setCurrentUserAsKeyVaultAdmin && !empty(currentUserId) ? [ @@ -269,7 +353,7 @@ module keyVaultModule 'br/public:avm/res/key-vault/vault:0.13.3' = { } } -// 6. Public IP for Application Gateway +// 8. Public IP for Application Gateway // https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/public-ip-address module appgwPipModule 'br/public:avm/res/network/public-ip-address:0.9.1' = { name: 'appgwPipModule' @@ -282,7 +366,7 @@ module appgwPipModule 'br/public:avm/res/network/public-ip-address:0.9.1' = { } } -// 7. WAF Policy for Application Gateway +// 9. WAF Policy for Application Gateway // https://learn.microsoft.com/azure/templates/microsoft.network/applicationgatewaywebapplicationfirewallpolicies resource wafPolicy 'Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies@2025-01-01' = { name: 'waf-${resourceSuffix}' @@ -308,7 +392,7 @@ resource wafPolicy 'Microsoft.Network/ApplicationGatewayWebApplicationFirewallPo } } -// 8. Azure Container App Environment (ACAE) +// 10. Azure Container App Environment (ACAE) module acaEnvModule '../../shared/bicep/modules/aca/v1/environment.bicep' = if (useACA) { name: 'acaEnvModule' params: { @@ -319,7 +403,7 @@ module acaEnvModule '../../shared/bicep/modules/aca/v1/environment.bicep' = if ( } } -// 9. Azure Container Apps (ACA) for Mock Web API +// 11. Azure Container Apps (ACA) for Mock Web API module acaModule1 '../../shared/bicep/modules/aca/v1/containerapp.bicep' = if (useACA) { name: 'acaModule-1' params: { @@ -338,7 +422,7 @@ module acaModule2 '../../shared/bicep/modules/aca/v1/containerapp.bicep' = if (u } } -// 10. API Management +// 12. API Management module apimModule '../../shared/bicep/modules/apim/v1/apim.bicep' = { name: 'apimModule' params: { @@ -352,7 +436,7 @@ module apimModule '../../shared/bicep/modules/apim/v1/apim.bicep' = { } } -// 11. APIM Policy Fragments +// 13. APIM Policy Fragments module policyFragmentModule '../../shared/bicep/modules/apim/v1/policy-fragment.bicep' = [for pf in policyFragments: { name: 'pf-${pf.name}' params:{ @@ -366,7 +450,7 @@ module policyFragmentModule '../../shared/bicep/modules/apim/v1/policy-fragment. ] }] -// 12. APIM Backends for ACA +// 14. APIM Backends for ACA module backendModule1 '../../shared/bicep/modules/apim/v1/backend.bicep' = if (useACA) { name: 'aca-backend-1' params: { @@ -415,7 +499,7 @@ module backendPoolModule '../../shared/bicep/modules/apim/v1/backend-pool.bicep' ] } -// 13. APIM APIs +// 15. APIM APIs module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in apis: if(length(apis) > 0) { name: 'api-${api.name}' params: { @@ -434,7 +518,7 @@ module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in a ] }] -// 14. Private Endpoint for APIM +// 16. Private Endpoint for APIM // https://learn.microsoft.com/azure/templates/microsoft.network/privateendpoints resource apimPrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = { name: 'pe-apim-${resourceSuffix}' @@ -457,7 +541,7 @@ resource apimPrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = { } } -// 15. Private DNS Zone Group for APIM Private Endpoint +// 17. Private DNS Zone Group for APIM Private Endpoint // https://learn.microsoft.com/azure/templates/microsoft.network/privateendpoints/privatednszoneegroups resource apimPrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = { name: 'apim-dns-zone-group' @@ -474,7 +558,7 @@ resource apimPrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZ } } -// 16. APIM Private DNS Zone, VNet Link +// 18. APIM Private DNS Zone, VNet Link module apimDnsPrivateLinkModule '../../shared/bicep/modules/dns/v1/dns-private-link.bicep' = { name: 'apimDnsPrivateLinkModule' params: { @@ -487,7 +571,7 @@ module apimDnsPrivateLinkModule '../../shared/bicep/modules/dns/v1/dns-private-l } } -// 17. ACA Private DNS Zone +// 19. ACA Private DNS Zone module acaDnsPrivateZoneModule '../../shared/bicep/modules/dns/v1/aca-dns-private-zone.bicep' = if (useACA) { name: 'acaDnsPrivateZoneModule' params: { @@ -497,7 +581,7 @@ module acaDnsPrivateZoneModule '../../shared/bicep/modules/dns/v1/aca-dns-privat } } -// 18. Application Gateway +// 20. Application Gateway // https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/application-gateway module appgwModule 'br/public:avm/res/network/application-gateway:0.7.2' = { name: 'appgwModule' diff --git a/infrastructure/appgw-apim/main.bicep b/infrastructure/appgw-apim/main.bicep index 55c1184c..e8c40459 100644 --- a/infrastructure/appgw-apim/main.bicep +++ b/infrastructure/appgw-apim/main.bicep @@ -106,7 +106,16 @@ module appInsightsModule '../../shared/bicep/modules/monitor/v1/appinsights.bice var appInsightsId = appInsightsModule.outputs.id var appInsightsInstrumentationKey = appInsightsModule.outputs.instrumentationKey -// 3. Virtual Network and Subnets +// 3. Storage Account for NSG Flow Logs +module storageFlowLogsModule '../../shared/bicep/modules/vnet/v1/storage-flowlogs.bicep' = { + name: 'storageFlowLogsModule' + params: { + location: location + resourceSuffix: resourceSuffix + } +} + +// 4. Virtual Network and Subnets // https://learn.microsoft.com/azure/templates/microsoft.network/networksecuritygroups resource nsgDefault 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { name: 'nsg-default' @@ -124,7 +133,7 @@ resource nsgAppGw 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { name: 'AllowGatewayManagerInbound' properties: { description: 'Allow Azure infrastructure communication' - protocol: 'TCP' + protocol: 'Tcp' sourcePortRange: '*' destinationPortRange: '65200-65535' sourceAddressPrefix: 'GatewayManager' @@ -137,8 +146,8 @@ resource nsgAppGw 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { { name: 'AllowHTTPSInbound' properties: { - description: 'Allow HTTPS traffic' - protocol: 'TCP' + description: 'Allow HTTPS traffic from internet' + protocol: 'Tcp' sourcePortRange: '*' destinationPortRange: '443' sourceAddressPrefix: '*' @@ -151,7 +160,7 @@ resource nsgAppGw 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { { name: 'AllowAzureLoadBalancerInbound' properties: { - description: 'Allow Azure Load Balancer' + description: 'Allow Azure Load Balancer health probes' protocol: '*' sourcePortRange: '*' destinationPortRange: '*' @@ -162,6 +171,7 @@ resource nsgAppGw 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { direction: 'Inbound' } } + nsgsr_denyAllInbound ] } } @@ -176,10 +186,10 @@ resource nsgApimV1 'Microsoft.Network/networkSecurityGroups@2025-01-01' = if (is securityRules: [ // INBOUND Security Rules { - name: 'AllowApimInbound' + name: 'AllowApimManagement' properties: { - description: 'Allow Management endpoint for Azure portal and Powershell traffic' - protocol: 'TCP' + description: 'Allow Management endpoint for Azure portal and PowerShell traffic' + protocol: 'Tcp' sourcePortRange: '*' destinationPortRange: '3443' sourceAddressPrefix: 'ApiManagement' @@ -192,8 +202,8 @@ resource nsgApimV1 'Microsoft.Network/networkSecurityGroups@2025-01-01' = if (is { name: 'AllowAzureLoadBalancerInbound' properties: { - description: 'Allow Azure Load Balancer' - protocol: 'TCP' + description: 'Allow Azure Load Balancer health probes' + protocol: 'Tcp' sourcePortRange: '*' destinationPortRange: '6390' sourceAddressPrefix: 'AzureLoadBalancer' @@ -207,8 +217,8 @@ resource nsgApimV1 'Microsoft.Network/networkSecurityGroups@2025-01-01' = if (is { name: 'AllowAppGatewayToApim' properties: { - description: 'Allows inbound App Gateway traffic to APIM' - protocol: 'TCP' + description: 'Allow inbound HTTPS traffic from Application Gateway to APIM' + protocol: 'Tcp' sourcePortRange: '*' destinationPortRange: '443' sourceAddressPrefix: appgwSubnetPrefix @@ -223,8 +233,8 @@ resource nsgApimV1 'Microsoft.Network/networkSecurityGroups@2025-01-01' = if (is { name: 'AllowApimToStorage' properties: { - description: 'Allow APIM to reach Azure Storage endpoints for core service functionality (i.e. pull binaries to provision units, etc.)' - protocol: 'TCP' + description: 'Allow APIM to reach Azure Storage for core service functionality' + protocol: 'Tcp' sourcePortRange: '*' destinationPortRange: '443' sourceAddressPrefix: 'VirtualNetwork' @@ -237,12 +247,12 @@ resource nsgApimV1 'Microsoft.Network/networkSecurityGroups@2025-01-01' = if (is { name: 'AllowApimToSql' properties: { - description: 'Allow APIM to reach Azure SQL endpoints for core service functionality' - protocol: 'TCP' + description: 'Allow APIM to reach Azure SQL for core service functionality' + protocol: 'Tcp' sourcePortRange: '*' destinationPortRange: '1433' sourceAddressPrefix: 'VirtualNetwork' - destinationAddressPrefix: 'SQL' + destinationAddressPrefix: 'Sql' access: 'Allow' priority: 110 direction: 'Outbound' @@ -251,8 +261,8 @@ resource nsgApimV1 'Microsoft.Network/networkSecurityGroups@2025-01-01' = if (is { name: 'AllowApimToKeyVault' properties: { - description: 'Allow APIM to reach Azure Key Vault endpoints for core service functionality' - protocol: 'TCP' + description: 'Allow APIM to reach Azure Key Vault for core service functionality' + protocol: 'Tcp' sourcePortRange: '*' destinationPortRange: '443' sourceAddressPrefix: 'VirtualNetwork' @@ -265,8 +275,8 @@ resource nsgApimV1 'Microsoft.Network/networkSecurityGroups@2025-01-01' = if (is { name: 'AllowApimToMonitor' properties: { - description: 'Allow APIM to reach Azure Monitor to publish diagnostics logs, metrics, resource health, and application insights' - protocol: 'TCP' + description: 'Allow APIM to reach Azure Monitor for diagnostics logs, metrics, and Application Insights' + protocol: 'Tcp' sourcePortRange: '*' destinationPortRanges: [ '1886' @@ -283,6 +293,17 @@ resource nsgApimV1 'Microsoft.Network/networkSecurityGroups@2025-01-01' = if (is } } +// NSG for Container Apps - only allow traffic from APIM +module nsgAcaModule '../../shared/bicep/modules/vnet/v1/nsg-aca.bicep' = if (useACA) { + name: 'nsgAcaModule' + params: { + location: location + nsgName: 'nsg-aca' + acaSubnetPrefix: acaSubnetPrefix + apimSubnetPrefix: apimSubnetPrefix + } +} + module vnetModule '../../shared/bicep/modules/vnet/v1/vnet.bicep' = { name: 'vnetModule' params: { @@ -314,7 +335,7 @@ module vnetModule '../../shared/bicep/modules/vnet/v1/vnet.bicep' = { properties: { addressPrefix: acaSubnetPrefix networkSecurityGroup: { - id: nsgDefault.id + id: useACA ? nsgAcaModule!.outputs.nsgId : nsgDefault.id } delegations: [ { @@ -344,7 +365,51 @@ var apimSubnetResourceId = '${vnetModule.outputs.vnetId}/subnets/${apimSubnetNa var acaSubnetResourceId = '${vnetModule.outputs.vnetId}/subnets/${acaSubnetName}' var appgwSubnetResourceId = '${vnetModule.outputs.vnetId}/subnets/${appgwSubnetName}' -// 4. User Assigned Managed Identity +// 5. NSG Flow Logs and Traffic Analytics + +// NSG Flow Logs for Application Gateway +module nsgFlowLogsAppGwModule '../../shared/bicep/modules/vnet/v1/nsg-flow-logs.bicep' = { + name: 'nsgFlowLogsAppGwModule' + params: { + location: location + flowLogName: 'fl-nsg-appgw-${resourceSuffix}' + nsgResourceId: nsgAppGw.id + storageAccountResourceId: storageFlowLogsModule.outputs.storageAccountId + logAnalyticsWorkspaceResourceId: lawId + retentionDays: 7 + enableTrafficAnalytics: true + } +} + +// NSG Flow Logs for APIM +module nsgFlowLogsApimModule '../../shared/bicep/modules/vnet/v1/nsg-flow-logs.bicep' = if (is_apim_sku_v1(apimSku)) { + name: 'nsgFlowLogsApimModule' + params: { + location: location + flowLogName: 'fl-nsg-apim-${resourceSuffix}' + nsgResourceId: nsgApimV1.id + storageAccountResourceId: storageFlowLogsModule.outputs.storageAccountId + logAnalyticsWorkspaceResourceId: lawId + retentionDays: 7 + enableTrafficAnalytics: true + } +} + +// NSG Flow Logs for ACA +module nsgFlowLogsAcaModule '../../shared/bicep/modules/vnet/v1/nsg-flow-logs.bicep' = if (useACA) { + name: 'nsgFlowLogsAcaModule' + params: { + location: location + flowLogName: 'fl-nsg-aca-${resourceSuffix}' + nsgResourceId: nsgAcaModule!.outputs.nsgId + storageAccountResourceId: storageFlowLogsModule.outputs.storageAccountId + logAnalyticsWorkspaceResourceId: lawId + retentionDays: 7 + enableTrafficAnalytics: true + } +} + +// 6. User Assigned Managed Identity // https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/managed-identity/user-assigned-identity module uamiModule 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.2' = { name: 'uamiModule' @@ -354,7 +419,7 @@ module uamiModule 'br/public:avm/res/managed-identity/user-assigned-identity:0.4 } } -// 5. Key Vault +// 7. Key Vault // https://learn.microsoft.com/azure/templates/microsoft.keyvault/vaults // This assignment is helpful for testing to allow you to examine and administer the Key Vault. Adjust accordingly for real workloads! var keyVaultAdminRoleAssignment = setCurrentUserAsKeyVaultAdmin && !empty(currentUserId) ? [ @@ -387,7 +452,7 @@ module keyVaultModule 'br/public:avm/res/key-vault/vault:0.13.3' = { } } -// 6. Public IP for Application Gateway +// 8. Public IP for Application Gateway // https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/public-ip-address module appgwPipModule 'br/public:avm/res/network/public-ip-address:0.9.1' = { name: 'appgwPipModule' @@ -400,7 +465,7 @@ module appgwPipModule 'br/public:avm/res/network/public-ip-address:0.9.1' = { } } -// 7. WAF Policy for Application Gateway +// 9. WAF Policy for Application Gateway // https://learn.microsoft.com/azure/templates/microsoft.network/applicationgatewaywebapplicationfirewallpolicies resource wafPolicy 'Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies@2025-01-01' = { name: 'waf-${resourceSuffix}' @@ -426,7 +491,7 @@ resource wafPolicy 'Microsoft.Network/ApplicationGatewayWebApplicationFirewallPo } } -// 8. Azure Container App Environment (ACAE) +// 10. Azure Container App Environment (ACAE) module acaEnvModule '../../shared/bicep/modules/aca/v1/environment.bicep' = if (useACA) { name: 'acaEnvModule' params: { @@ -437,7 +502,7 @@ module acaEnvModule '../../shared/bicep/modules/aca/v1/environment.bicep' = if ( } } -// 9. Azure Container Apps (ACA) for Mock Web API +// 11. Azure Container Apps (ACA) for Mock Web API module acaModule1 '../../shared/bicep/modules/aca/v1/containerapp.bicep' = if (useACA) { name: 'acaModule-1' params: { @@ -455,7 +520,7 @@ module acaModule2 '../../shared/bicep/modules/aca/v1/containerapp.bicep' = if (u } } -// 10. API Management (VNet Internal) +// 12. API Management (VNet Internal) module apimModule '../../shared/bicep/modules/apim/v1/apim.bicep' = { name: 'apimModule' params: { @@ -470,7 +535,7 @@ module apimModule '../../shared/bicep/modules/apim/v1/apim.bicep' = { } } -// 11. APIM Policy Fragments +// 13. APIM Policy Fragments module policyFragmentModule '../../shared/bicep/modules/apim/v1/policy-fragment.bicep' = [for pf in policyFragments: { name: 'pf-${pf.name}' params:{ @@ -484,7 +549,7 @@ module policyFragmentModule '../../shared/bicep/modules/apim/v1/policy-fragment. ] }] -// 12. APIM Backends for ACA +// 14. APIM Backends for ACA module backendModule1 '../../shared/bicep/modules/apim/v1/backend.bicep' = if (useACA) { name: 'aca-backend-1' params: { @@ -533,8 +598,8 @@ module backendPoolModule '../../shared/bicep/modules/apim/v1/backend-pool.bicep' ] } -// 13. APIM APIs -module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in apis: if(length(apis) > 0) { +// 15. APIM APIs +module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in apis: { name: 'api-${api.name}' params: { apimName: apimName @@ -544,15 +609,15 @@ module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in a } dependsOn: useACA ? [ apimModule - backendModule1 - backendModule2 - backendPoolModule + backendModule1! + backendModule2! + backendPoolModule! ] : [ apimModule ] }] -// 14. Application Gateway +// 16. Application Gateway // https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/application-gateway module appgwModule 'br/public:avm/res/network/application-gateway:0.7.2' = { name: 'appgwModule' diff --git a/shared/bicep/modules/vnet/v1/nsg-aca.bicep b/shared/bicep/modules/vnet/v1/nsg-aca.bicep new file mode 100644 index 00000000..c8836d43 --- /dev/null +++ b/shared/bicep/modules/vnet/v1/nsg-aca.bicep @@ -0,0 +1,108 @@ +/** + * @module nsg-aca-v1 + * @description Network Security Group for Azure Container Apps allowing traffic only from API Management + */ + +// ------------------------------ +// PARAMETERS +// ------------------------------ + +@description('Location for the NSG') +param location string = resourceGroup().location + +@description('Name of the NSG') +param nsgName string = 'nsg-aca' + +@description('ACA subnet prefix for destination filtering') +param acaSubnetPrefix string + +@description('APIM subnet prefix for source filtering') +param apimSubnetPrefix string + +// Import the deny all inbound rule +import {nsgsr_denyAllInbound} from './nsg_rules.bicep' + +// ------------------------------ +// RESOURCES +// ------------------------------ + +// https://learn.microsoft.com/azure/templates/microsoft.network/networksecuritygroups +resource nsgAca 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { + name: nsgName + location: location + properties: { + securityRules: [ + // INBOUND Security Rules + { + name: 'AllowApimToAca' + properties: { + description: 'Allow inbound HTTPS traffic from APIM to Container Apps' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: apimSubnetPrefix + destinationAddressPrefix: acaSubnetPrefix + access: 'Allow' + priority: 100 + direction: 'Inbound' + } + } + { + name: 'AllowAzureLoadBalancerInbound' + properties: { + description: 'Allow Azure Load Balancer health probes for Container Apps' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationAddressPrefix: acaSubnetPrefix + access: 'Allow' + priority: 110 + direction: 'Inbound' + } + } + { + name: 'AllowAcaControlPlane' + properties: { + description: 'Allow Container Apps control plane communication' + protocol: '*' + sourcePortRange: '*' + destinationPortRanges: [ + '443' + '4789' + '5671' + '5672' + ] + sourceAddressPrefix: 'MicrosoftContainerRegistry' + destinationAddressPrefix: acaSubnetPrefix + access: 'Allow' + priority: 120 + direction: 'Inbound' + } + } + nsgsr_denyAllInbound + // OUTBOUND Security Rules + { + name: 'AllowAcaToInternet' + properties: { + description: 'Allow Container Apps to reach internet for container image pulls and other dependencies' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: acaSubnetPrefix + destinationAddressPrefix: 'Internet' + access: 'Allow' + priority: 100 + direction: 'Outbound' + } + } + ] + } +} + +// ------------------------------ +// OUTPUTS +// ------------------------------ + +output nsgId string = nsgAca.id +output nsgName string = nsgAca.name diff --git a/shared/bicep/modules/vnet/v1/nsg-apim-pe.bicep b/shared/bicep/modules/vnet/v1/nsg-apim-pe.bicep new file mode 100644 index 00000000..a810eb29 --- /dev/null +++ b/shared/bicep/modules/vnet/v1/nsg-apim-pe.bicep @@ -0,0 +1,176 @@ +/** + * @module nsg-apim-pe-v1 + * @description Network Security Group for Azure API Management with Private Link from Front Door or Application Gateway + */ + +// ------------------------------ +// PARAMETERS +// ------------------------------ + +@description('Location for the NSG') +param location string = resourceGroup().location + +@description('Name of the NSG') +param nsgName string = 'nsg-apim' + +@description('APIM subnet prefix for destination filtering') +param apimSubnetPrefix string + +@description('Whether to allow traffic from Azure Front Door Backend service tag') +param allowFrontDoorBackend bool = false + +@description('Whether to allow traffic from Application Gateway subnet') +param allowAppGateway bool = false + +@description('Application Gateway subnet prefix for source filtering (required if allowAppGateway is true)') +param appgwSubnetPrefix string = '' + +// Import the deny all inbound rule +import {nsgsr_denyAllInbound} from './nsg_rules.bicep' + +// ------------------------------ +// RESOURCES +// ------------------------------ + +// https://learn.microsoft.com/azure/templates/microsoft.network/networksecuritygroups +resource nsgApim 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { + name: nsgName + location: location + properties: { + securityRules: concat( + [ + // INBOUND Security Rules + { + name: 'AllowApimManagement' + properties: { + description: 'Allow Management endpoint for Azure portal and PowerShell traffic' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '3443' + sourceAddressPrefix: 'ApiManagement' + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 100 + direction: 'Inbound' + } + } + { + name: 'AllowAzureLoadBalancerInbound' + properties: { + description: 'Allow Azure Load Balancer health probes' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '6390' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationAddressPrefix: apimSubnetPrefix + access: 'Allow' + priority: 110 + direction: 'Inbound' + } + } + ], + allowFrontDoorBackend ? [ + { + name: 'AllowFrontDoorBackendToApim' + properties: { + description: 'Allow inbound HTTPS traffic from Azure Front Door Backend to APIM via Private Link' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'AzureFrontDoor.Backend' + destinationAddressPrefix: apimSubnetPrefix + access: 'Allow' + priority: 120 + direction: 'Inbound' + } + } + ] : [], + allowAppGateway && !empty(appgwSubnetPrefix) ? [ + { + name: 'AllowAppGatewayToApim' + properties: { + description: 'Allow inbound HTTPS traffic from Application Gateway to APIM via Private Link' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: appgwSubnetPrefix + destinationAddressPrefix: apimSubnetPrefix + access: 'Allow' + priority: 130 + direction: 'Inbound' + } + } + ] : [], + [ + nsgsr_denyAllInbound + // OUTBOUND Security Rules + { + name: 'AllowApimToStorage' + properties: { + description: 'Allow APIM to reach Azure Storage for core service functionality' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'Storage' + access: 'Allow' + priority: 100 + direction: 'Outbound' + } + } + { + name: 'AllowApimToSql' + properties: { + description: 'Allow APIM to reach Azure SQL for core service functionality' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '1433' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'Sql' + access: 'Allow' + priority: 110 + direction: 'Outbound' + } + } + { + name: 'AllowApimToKeyVault' + properties: { + description: 'Allow APIM to reach Azure Key Vault for core service functionality' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'AzureKeyVault' + access: 'Allow' + priority: 120 + direction: 'Outbound' + } + } + { + name: 'AllowApimToMonitor' + properties: { + description: 'Allow APIM to reach Azure Monitor for diagnostics logs, metrics, and Application Insights' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRanges: [ + '1886' + '443' + ] + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'AzureMonitor' + access: 'Allow' + priority: 130 + direction: 'Outbound' + } + } + ] + ) + } +} + +// ------------------------------ +// OUTPUTS +// ------------------------------ + +output nsgId string = nsgApim.id +output nsgName string = nsgApim.name diff --git a/shared/bicep/modules/vnet/v1/nsg-apim-vnet.bicep b/shared/bicep/modules/vnet/v1/nsg-apim-vnet.bicep new file mode 100644 index 00000000..357553ec --- /dev/null +++ b/shared/bicep/modules/vnet/v1/nsg-apim-vnet.bicep @@ -0,0 +1,154 @@ +/** + * @module nsg-apim-vnet-v1 + * @description Network Security Group for Azure API Management in VNet mode with traffic from Application Gateway + */ + +// ------------------------------ +// PARAMETERS +// ------------------------------ + +@description('Location for the NSG') +param location string = resourceGroup().location + +@description('Name of the NSG') +param nsgName string = 'nsg-apim' + +@description('APIM subnet prefix for destination filtering') +param apimSubnetPrefix string + +@description('Application Gateway subnet prefix for source filtering') +param appgwSubnetPrefix string + +@description('Log Analytics Workspace ID for NSG flow logs') +param logAnalyticsWorkspaceId string = '' + +@description('Storage Account ID for NSG flow logs') +param storageAccountId string = '' + +// Import the deny all inbound rule +import {nsgsr_denyAllInbound} from './nsg_rules.bicep' + +// ------------------------------ +// RESOURCES +// ------------------------------ + +// https://learn.microsoft.com/azure/templates/microsoft.network/networksecuritygroups +resource nsgApim 'Microsoft.Network/networkSecurityGroups@2025-01-01' = { + name: nsgName + location: location + properties: { + securityRules: [ + // INBOUND Security Rules + { + name: 'AllowApimManagement' + properties: { + description: 'Allow Management endpoint for Azure portal and PowerShell traffic' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '3443' + sourceAddressPrefix: 'ApiManagement' + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 100 + direction: 'Inbound' + } + } + { + name: 'AllowAzureLoadBalancerInbound' + properties: { + description: 'Allow Azure Load Balancer health probes' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '6390' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationAddressPrefix: apimSubnetPrefix + access: 'Allow' + priority: 110 + direction: 'Inbound' + } + } + { + name: 'AllowAppGatewayToApim' + properties: { + description: 'Allow inbound HTTPS traffic from Application Gateway to APIM' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: appgwSubnetPrefix + destinationAddressPrefix: apimSubnetPrefix + access: 'Allow' + priority: 120 + direction: 'Inbound' + } + } + nsgsr_denyAllInbound + // OUTBOUND Security Rules + { + name: 'AllowApimToStorage' + properties: { + description: 'Allow APIM to reach Azure Storage for core service functionality' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'Storage' + access: 'Allow' + priority: 100 + direction: 'Outbound' + } + } + { + name: 'AllowApimToSql' + properties: { + description: 'Allow APIM to reach Azure SQL for core service functionality' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '1433' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'Sql' + access: 'Allow' + priority: 110 + direction: 'Outbound' + } + } + { + name: 'AllowApimToKeyVault' + properties: { + description: 'Allow APIM to reach Azure Key Vault for core service functionality' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'AzureKeyVault' + access: 'Allow' + priority: 120 + direction: 'Outbound' + } + } + { + name: 'AllowApimToMonitor' + properties: { + description: 'Allow APIM to reach Azure Monitor for diagnostics logs, metrics, and Application Insights' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRanges: [ + '1886' + '443' + ] + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'AzureMonitor' + access: 'Allow' + priority: 130 + direction: 'Outbound' + } + } + ] + } +} + +// ------------------------------ +// OUTPUTS +// ------------------------------ + +output nsgId string = nsgApim.id +output nsgName string = nsgApim.name diff --git a/shared/bicep/modules/vnet/v1/nsg-flow-logs.bicep b/shared/bicep/modules/vnet/v1/nsg-flow-logs.bicep new file mode 100644 index 00000000..c2a8a65d --- /dev/null +++ b/shared/bicep/modules/vnet/v1/nsg-flow-logs.bicep @@ -0,0 +1,82 @@ +/** + * @module nsg-flow-logs-v1 + * @description Enable NSG Flow Logs and Traffic Analytics for Network Security Groups + */ + +// ------------------------------ +// PARAMETERS +// ------------------------------ + +@description('Location for resources') +param location string = resourceGroup().location + +@description('Name of the NSG Flow Log') +param flowLogName string + +@description('NSG Resource ID to enable flow logs on') +param nsgResourceId string + +@description('Storage Account Resource ID for flow log storage') +param storageAccountResourceId string + +@description('Log Analytics Workspace Resource ID for Traffic Analytics') +param logAnalyticsWorkspaceResourceId string + +@description('Flow log retention in days (0 = indefinite)') +@minValue(0) +@maxValue(365) +param retentionDays int = 7 + +@description('Flow log version (1 or 2)') +@allowed([1, 2]) +param flowLogVersion int = 2 + +@description('Enable Traffic Analytics') +param enableTrafficAnalytics bool = true + +@description('Traffic Analytics interval in minutes') +@allowed([10, 60]) +param trafficAnalyticsInterval int = 60 + +// ------------------------------ +// RESOURCES +// ------------------------------ + +// Network Watcher - using existing instance in the region +resource networkWatcher 'Microsoft.Network/networkWatchers@2025-01-01' existing = { + name: 'NetworkWatcher_${location}' +} + +// https://learn.microsoft.com/azure/templates/microsoft.network/networkwatchers/flowlogs +resource flowLog 'Microsoft.Network/networkWatchers/flowLogs@2025-01-01' = { + name: flowLogName + parent: networkWatcher + location: location + properties: { + targetResourceId: nsgResourceId + storageId: storageAccountResourceId + enabled: true + retentionPolicy: { + days: retentionDays + enabled: retentionDays > 0 + } + format: { + type: 'JSON' + version: flowLogVersion + } + flowAnalyticsConfiguration: enableTrafficAnalytics ? { + networkWatcherFlowAnalyticsConfiguration: { + enabled: true + workspaceResourceId: logAnalyticsWorkspaceResourceId + trafficAnalyticsInterval: trafficAnalyticsInterval + } + } : null + } +} + +// ------------------------------ +// OUTPUTS +// ------------------------------ + +output flowLogId string = flowLog.id +output flowLogName string = flowLog.name diff --git a/shared/bicep/modules/vnet/v1/storage-flowlogs.bicep b/shared/bicep/modules/vnet/v1/storage-flowlogs.bicep new file mode 100644 index 00000000..2ff9bad7 --- /dev/null +++ b/shared/bicep/modules/vnet/v1/storage-flowlogs.bicep @@ -0,0 +1,69 @@ +/** + * @module storage-account-flowlogs-v1 + * @description Storage Account for NSG Flow Logs + */ + +// ------------------------------ +// PARAMETERS +// ------------------------------ + +@description('Location for the storage account') +param location string = resourceGroup().location + +@description('The unique suffix to append') +param resourceSuffix string = uniqueString(subscription().id, resourceGroup().id) + +@description('Storage account name (must be globally unique, 3-24 chars, lowercase alphanumeric)') +@minLength(3) +@maxLength(24) +param storageAccountName string = 'stflowlogs${take(resourceSuffix, 13)}' + +@description('Storage account SKU') +@allowed([ + 'Standard_LRS' + 'Standard_GRS' + 'Standard_RAGRS' + 'Standard_ZRS' +]) +param skuName string = 'Standard_LRS' + +// ------------------------------ +// RESOURCES +// ------------------------------ + +// https://learn.microsoft.com/azure/templates/microsoft.storage/storageaccounts +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = { + name: storageAccountName + location: location + sku: { + name: skuName + } + kind: 'StorageV2' + properties: { + minimumTlsVersion: 'TLS1_2' + allowBlobPublicAccess: false + supportsHttpsTrafficOnly: true + encryption: { + services: { + blob: { + enabled: true + } + file: { + enabled: true + } + } + keySource: 'Microsoft.Storage' + } + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Allow' + } + } +} + +// ------------------------------ +// OUTPUTS +// ------------------------------ + +output storageAccountId string = storageAccount.id +output storageAccountName string = storageAccount.name