Skip to content

Commit 8cf7f41

Browse files
feat: add support for deploying Azure Bastion and Jumpbox resources in private networking
1 parent 45f9efa commit 8cf7f41

5 files changed

Lines changed: 44 additions & 111 deletions

File tree

docs/CustomizingAzdParameters.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ By default this template will use the environment name as the prefix to prevent
2525
| `enableScalability` | boolean | `false` | Enable auto-scaling and higher SKUs (WAF-aligned). |
2626
| `enableRedundancy` | boolean | `false` | Enable zone redundancy and geo-replication (WAF-aligned). |
2727
| `enablePrivateNetworking` | boolean | `false` | Enable VNet integration and private endpoints (WAF-aligned). |
28+
| `deployBastionAndJumpbox` | boolean | `false` | Deploy Azure Bastion and jumpbox admin-path resources when private networking is enabled. |
2829
| `AZURE_ENV_VM_SIZE` | string | `""` | Overrides the jumpbox VM size (private networking only). Must support accelerated networking and Premium SSD. |
2930
| `AZURE_ENV_VM_ADMIN_USERNAME` | string | `""` | Sets the jumpbox VM admin username (private networking only). |
30-
| `AZURE_ENV_VM_ADMIN_PASSWORD` | string | `""` | Sets the jumpbox VM admin password. Required to deploy the jumpbox when private networking is enabled. |
31+
| `AZURE_ENV_VM_ADMIN_PASSWORD` | string | `""` | Sets the jumpbox VM admin password. Required when `deployBastionAndJumpbox=true`. |
3132
| `ACR_NAME` | string | `contentgencontainerreg` | Sets the existing Azure Container Registry name (without `.azurecr.io`). |
3233
| `IMAGE_TAG` | string | `latest` | Sets the container image tag (e.g., `latest`, `dev`, `hotfix`). |
3334

infra/main.bicep

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ param existingLogAnalyticsWorkspaceId string = ''
107107
@description('Optional. Resource ID of an existing Foundry project.')
108108
param azureExistingAIProjectResourceId string = ''
109109

110+
@description('Optional. Deploy Azure Bastion and Jumpbox resources for private network administration.')
111+
param deployBastionAndJumpbox bool = false
112+
110113
@description('Optional. Jumpbox VM size. Must support accelerated networking and Premium SSD.')
111114
param vmSize string = ''
112115

@@ -380,6 +383,7 @@ module virtualNetwork 'modules/virtualNetwork.bicep' = if (enablePrivateNetworki
380383
name: 'vnet-${solutionSuffix}'
381384
addressPrefixes: ['10.0.0.0/20'] // 4096 addresses (enough for 8 /23 subnets or 16 /24)
382385
location: location
386+
deployBastionAndJumpbox: enablePrivateNetworking && deployBastionAndJumpbox && !empty(vmAdminPassword)
383387
tags: tags
384388
logAnalyticsWorkspaceId: logAnalyticsWorkspaceResourceId
385389
resourceSuffix: solutionSuffix
@@ -401,8 +405,8 @@ var zoneSupportedJumpboxLocations = [
401405
'uksouth'
402406
'westus3'
403407
]
404-
var deployJumpbox = enablePrivateNetworking && !empty(vmAdminPassword)
405-
module bastionHost 'br/public:avm/res/network/bastion-host:0.8.2' = if (enablePrivateNetworking) {
408+
var deployAdminAccessResources = enablePrivateNetworking && deployBastionAndJumpbox && !empty(vmAdminPassword)
409+
module bastionHost 'br/public:avm/res/network/bastion-host:0.8.2' = if (deployAdminAccessResources) {
406410
name: take('avm.res.network.bastion-host.${bastionHostName}', 64)
407411
params: {
408412
name: bastionHostName
@@ -431,7 +435,7 @@ module bastionHost 'br/public:avm/res/network/bastion-host:0.8.2' = if (enablePr
431435

432436
// Jumpbox Virtual Machine
433437
var jumpboxVmName = take('vm-jumpbox-${solutionSuffix}', 15)
434-
module jumpboxVM 'br/public:avm/res/compute/virtual-machine:0.21.0' = if (deployJumpbox) {
438+
module jumpboxVM 'br/public:avm/res/compute/virtual-machine:0.21.0' = if (deployAdminAccessResources) {
435439
name: take('avm.res.compute.virtual-machine.${jumpboxVmName}', 64)
436440
params: {
437441
name: take(jumpboxVmName, 15)

infra/main.json

Lines changed: 24 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"_generator": {
77
"name": "bicep",
88
"version": "0.41.2.15936",
9-
"templateHash": "17721141165286158425"
9+
"templateHash": "11738775177613917473"
1010
},
1111
"name": "Intelligent Content Generation Accelerator",
1212
"description": "Solution Accelerator for multimodal marketing content generation using Microsoft Agent Framework.\n"
@@ -169,6 +169,13 @@
169169
"description": "Optional. Resource ID of an existing Foundry project."
170170
}
171171
},
172+
"deployBastionAndJumpbox": {
173+
"type": "bool",
174+
"defaultValue": false,
175+
"metadata": {
176+
"description": "Optional. Deploy Azure Bastion and Jumpbox resources for private network administration."
177+
}
178+
},
172179
"vmSize": {
173180
"type": "string",
174181
"defaultValue": "",
@@ -351,7 +358,7 @@
351358
"uksouth",
352359
"westus3"
353360
],
354-
"deployJumpbox": "[and(parameters('enablePrivateNetworking'), not(empty(parameters('vmAdminPassword'))))]",
361+
"deployAdminAccessResources": "[and(and(parameters('enablePrivateNetworking'), parameters('deployBastionAndJumpbox')), not(empty(parameters('vmAdminPassword'))))]",
355362
"jumpboxVmName": "[take(format('vm-jumpbox-{0}', variables('solutionSuffix')), 15)]",
356363
"privateDnsZones": [
357364
"privatelink.cognitiveservices.azure.com",
@@ -4851,6 +4858,9 @@
48514858
"location": {
48524859
"value": "[parameters('location')]"
48534860
},
4861+
"deployBastionAndJumpbox": {
4862+
"value": "[and(and(parameters('enablePrivateNetworking'), parameters('deployBastionAndJumpbox')), not(empty(parameters('vmAdminPassword'))))]"
4863+
},
48544864
"tags": {
48554865
"value": "[parameters('tags')]"
48564866
},
@@ -4869,7 +4879,7 @@
48694879
"_generator": {
48704880
"name": "bicep",
48714881
"version": "0.41.2.15936",
4872-
"templateHash": "1152564857842534701"
4882+
"templateHash": "11359193981707837191"
48734883
}
48744884
},
48754885
"parameters": {
@@ -4895,6 +4905,13 @@
48954905
"description": "Required. An Array of 1 or more IP Address Prefixes for the Virtual Network."
48964906
}
48974907
},
4908+
"deployBastionAndJumpbox": {
4909+
"type": "bool",
4910+
"defaultValue": false,
4911+
"metadata": {
4912+
"description": "Optional. Deploy Azure Bastion and Jumpbox subnets for VM-based administration."
4913+
}
4914+
},
48984915
"tags": {
48994916
"type": "object",
49004917
"defaultValue": {},
@@ -5026,102 +5043,7 @@
50265043
}
50275044
}
50285045
],
5029-
"bastionSubnets": [
5030-
{
5031-
"name": "AzureBastionSubnet",
5032-
"addressPrefixes": [
5033-
"10.0.10.0/26"
5034-
],
5035-
"networkSecurityGroup": {
5036-
"name": "nsg-bastion",
5037-
"securityRules": [
5038-
{
5039-
"name": "AllowGatewayManager",
5040-
"properties": {
5041-
"access": "Allow",
5042-
"direction": "Inbound",
5043-
"priority": 2702,
5044-
"protocol": "*",
5045-
"sourcePortRange": "*",
5046-
"destinationPortRange": "443",
5047-
"sourceAddressPrefix": "GatewayManager",
5048-
"destinationAddressPrefix": "*"
5049-
}
5050-
},
5051-
{
5052-
"name": "AllowHttpsInBound",
5053-
"properties": {
5054-
"access": "Allow",
5055-
"direction": "Inbound",
5056-
"priority": 2703,
5057-
"protocol": "*",
5058-
"sourcePortRange": "*",
5059-
"destinationPortRange": "443",
5060-
"sourceAddressPrefix": "Internet",
5061-
"destinationAddressPrefix": "*"
5062-
}
5063-
},
5064-
{
5065-
"name": "AllowSshRdpOutbound",
5066-
"properties": {
5067-
"access": "Allow",
5068-
"direction": "Outbound",
5069-
"priority": 100,
5070-
"protocol": "*",
5071-
"sourcePortRange": "*",
5072-
"destinationPortRanges": [
5073-
"22",
5074-
"3389"
5075-
],
5076-
"sourceAddressPrefix": "*",
5077-
"destinationAddressPrefix": "VirtualNetwork"
5078-
}
5079-
},
5080-
{
5081-
"name": "AllowAzureCloudOutbound",
5082-
"properties": {
5083-
"access": "Allow",
5084-
"direction": "Outbound",
5085-
"priority": 110,
5086-
"protocol": "Tcp",
5087-
"sourcePortRange": "*",
5088-
"destinationPortRange": "443",
5089-
"sourceAddressPrefix": "*",
5090-
"destinationAddressPrefix": "AzureCloud"
5091-
}
5092-
}
5093-
]
5094-
}
5095-
},
5096-
{
5097-
"name": "jumpbox",
5098-
"addressPrefixes": [
5099-
"10.0.12.0/23"
5100-
],
5101-
"networkSecurityGroup": {
5102-
"name": "nsg-jumpbox",
5103-
"securityRules": [
5104-
{
5105-
"name": "AllowRdpFromBastion",
5106-
"properties": {
5107-
"access": "Allow",
5108-
"direction": "Inbound",
5109-
"priority": 100,
5110-
"protocol": "Tcp",
5111-
"sourcePortRange": "*",
5112-
"destinationPortRange": "3389",
5113-
"sourceAddressPrefixes": [
5114-
"10.0.10.0/26"
5115-
],
5116-
"destinationAddressPrefixes": [
5117-
"10.0.12.0/23"
5118-
]
5119-
}
5120-
}
5121-
]
5122-
}
5123-
}
5124-
],
5046+
"bastionSubnets": "[if(parameters('deployBastionAndJumpbox'), createArray(createObject('name', 'AzureBastionSubnet', 'addressPrefixes', createArray('10.0.10.0/26'), 'networkSecurityGroup', createObject('name', 'nsg-bastion', 'securityRules', createArray(createObject('name', 'AllowGatewayManager', 'properties', createObject('access', 'Allow', 'direction', 'Inbound', 'priority', 2702, 'protocol', '*', 'sourcePortRange', '*', 'destinationPortRange', '443', 'sourceAddressPrefix', 'GatewayManager', 'destinationAddressPrefix', '*')), createObject('name', 'AllowHttpsInBound', 'properties', createObject('access', 'Allow', 'direction', 'Inbound', 'priority', 2703, 'protocol', '*', 'sourcePortRange', '*', 'destinationPortRange', '443', 'sourceAddressPrefix', 'Internet', 'destinationAddressPrefix', '*')), createObject('name', 'AllowSshRdpOutbound', 'properties', createObject('access', 'Allow', 'direction', 'Outbound', 'priority', 100, 'protocol', '*', 'sourcePortRange', '*', 'destinationPortRanges', createArray('22', '3389'), 'sourceAddressPrefix', '*', 'destinationAddressPrefix', 'VirtualNetwork')), createObject('name', 'AllowAzureCloudOutbound', 'properties', createObject('access', 'Allow', 'direction', 'Outbound', 'priority', 110, 'protocol', 'Tcp', 'sourcePortRange', '*', 'destinationPortRange', '443', 'sourceAddressPrefix', '*', 'destinationAddressPrefix', 'AzureCloud'))))), createObject('name', 'jumpbox', 'addressPrefixes', createArray('10.0.12.0/23'), 'networkSecurityGroup', createObject('name', 'nsg-jumpbox', 'securityRules', createArray(createObject('name', 'AllowRdpFromBastion', 'properties', createObject('access', 'Allow', 'direction', 'Inbound', 'priority', 100, 'protocol', 'Tcp', 'sourcePortRange', '*', 'destinationPortRange', '3389', 'sourceAddressPrefixes', createArray('10.0.10.0/26'), 'destinationAddressPrefixes', createArray('10.0.12.0/23'))))))), createArray())]",
51255047
"vnetSubnets": "[concat(variables('coreSubnets'), variables('bastionSubnets'))]"
51265048
},
51275049
"resources": [
@@ -7504,7 +7426,7 @@
75047426
]
75057427
},
75067428
"bastionHost": {
7507-
"condition": "[parameters('enablePrivateNetworking')]",
7429+
"condition": "[variables('deployAdminAccessResources')]",
75087430
"type": "Microsoft.Resources/deployments",
75097431
"apiVersion": "2025-04-01",
75107432
"name": "[take(format('avm.res.network.bastion-host.{0}', variables('bastionHostName')), 64)]",
@@ -9245,7 +9167,7 @@
92459167
]
92469168
},
92479169
"jumpboxVM": {
9248-
"condition": "[variables('deployJumpbox')]",
9170+
"condition": "[variables('deployAdminAccessResources')]",
92499171
"type": "Microsoft.Resources/deployments",
92509172
"apiVersion": "2025-04-01",
92519173
"name": "[take(format('avm.res.compute.virtual-machine.{0}', variables('jumpboxVmName')), 64)]",
@@ -24951,8 +24873,8 @@
2495124873
},
2495224874
"dependsOn": [
2495324875
"aiFoundryAiServices",
24954-
"[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]",
2495524876
"[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]",
24877+
"[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]",
2495624878
"virtualNetwork"
2495724879
]
2495824880
},

infra/main.waf.parameters.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@
5656
"enableScalability": {
5757
"value": true
5858
},
59+
"deployBastionAndJumpbox": {
60+
"value": true
61+
},
5962
"vmAdminUsername": {
6063
"value": "${AZURE_ENV_VM_ADMIN_USERNAME}"
6164
},

infra/modules/virtualNetwork.bicep

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
// Networking - NSGs, VNET and Subnets for Content Generation Solution
33
/****************************************************************************************************************************/
44
@description('Name of the virtual network.')
5-
param name string
5+
param name string
66

77
@description('Azure region to deploy resources.')
88
param location string = resourceGroup().location
99

1010
@description('Required. An Array of 1 or more IP Address Prefixes for the Virtual Network.')
1111
param addressPrefixes array = ['10.0.0.0/20']
1212

13+
@description('Optional. Deploy Azure Bastion and Jumpbox subnets for VM-based administration.')
14+
param deployBastionAndJumpbox bool = false
15+
1316
@description('An array of subnets to be created within the virtual network.')
1417
// Core subnets: web (App Service), peps (Private Endpoints), aci (Container Instance)
1518
// Optional: AzureBastionSubnet and jumpbox (only when deployBastionAndJumpbox is true)
@@ -98,7 +101,7 @@ var coreSubnets = [
98101
}
99102
]
100103

101-
// Bastion and Jumpbox subnets (always deployed with private networking)
104+
// Bastion and Jumpbox subnets (only deployed when deployBastionAndJumpbox is true)
102105
// VM Size Notes:
103106
// 1 B-series VMs (like Standard_B2ms) do not support accelerated networking.
104107
// 2 Pick a VM size that supports accelerated networking + Premium SSD (the usual jump-box candidates):
@@ -107,7 +110,7 @@ var coreSubnets = [
107110
// Standard_D2s_v4 (2 vCPU, 8 GiB RAM, Premium SSD) // Previous gen, also broadly available.
108111
// Standard_DS2_v2 (2 vCPU, 7 GiB RAM, Premium SSD) // Legacy SKU, being retired from some regions - avoid for new deployments.
109112
// 3 A-series (Av2) is NOT suitable: no Premium SSD support, no accelerated networking.
110-
var bastionSubnets = [
113+
var bastionSubnets = deployBastionAndJumpbox ? [
111114
{
112115
name: 'AzureBastionSubnet'
113116
addressPrefixes: ['10.0.10.0/26']
@@ -191,7 +194,7 @@ var bastionSubnets = [
191194
]
192195
}
193196
}
194-
]
197+
] : []
195198

196199
var vnetSubnets = concat(coreSubnets, bastionSubnets)
197200

@@ -270,6 +273,6 @@ output webSubnetResourceId string = contains(map(vnetSubnets, subnet => subnet.n
270273
output pepsSubnetResourceId string = contains(map(vnetSubnets, subnet => subnet.name), 'peps') ? virtualNetwork.outputs.subnetResourceIds[indexOf(map(vnetSubnets, subnet => subnet.name), 'peps')] : ''
271274
output aciSubnetResourceId string = contains(map(vnetSubnets, subnet => subnet.name), 'aci') ? virtualNetwork.outputs.subnetResourceIds[indexOf(map(vnetSubnets, subnet => subnet.name), 'aci')] : ''
272275

273-
// Bastion/jumpbox subnet outputs (always present with private networking)
276+
// Bastion/jumpbox subnet outputs (present only when deployBastionAndJumpbox is true)
274277
output bastionSubnetResourceId string = contains(map(vnetSubnets, subnet => subnet.name), 'AzureBastionSubnet') ? virtualNetwork.outputs.subnetResourceIds[indexOf(map(vnetSubnets, subnet => subnet.name), 'AzureBastionSubnet')] : ''
275278
output jumpboxSubnetResourceId string = contains(map(vnetSubnets, subnet => subnet.name), 'jumpbox') ? virtualNetwork.outputs.subnetResourceIds[indexOf(map(vnetSubnets, subnet => subnet.name), 'jumpbox')] : ''

0 commit comments

Comments
 (0)