Skip to content

Commit acaa1ad

Browse files
committed
Ppha 417: Create infra as code for hub resources
This is an update to the Bicep code to deploy the foundation in hub subscription for Terraform to take over the rest of the deployment. This will update: The Managed DevOps Pool (with ADO Org integration) necessary to run the ADO Terraform deployment pipelines inside the private network. The Compute Gallary to store the AVD images
1 parent 1874502 commit acaa1ad

File tree

13 files changed

+318
-28
lines changed

13 files changed

+318
-28
lines changed

infrastructure/bootstrap/environments/live/hub.bicepparam

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ param vnetAddressPrefixes = [
55
'10.21.0.0/16'
66
]
77
param devopsSubnetAddressPrefix = '10.21.1.0/24'
8-
param devopsInfrastructureId = ''
8+
//param devopsInfrastructureId = ''

infrastructure/bootstrap/environments/nonlive/hub.bicepparam

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ param vnetAddressPrefixes = [
55
'10.11.0.0/16'
66
]
77
param devopsSubnetAddressPrefix = '10.11.1.0/24'
8-
param devopsInfrastructureId = ''
8+
param privateEndpointSubnetAddressPrefix = '10.11.2.0/24'
9+
param enableSoftDelete = true
10+
// param devopsInfrastructureId = ''
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
AZURE_SUBSCRIPTION="Digital Screening DToS - Sandbox"
1+
AZURE_SUBSCRIPTION="Lung Cancer Risk Check - Non-live hub"
22
BOOTSTRAP=hub
33
HUB_TYPE=nonlive

infrastructure/bootstrap/hub.bicep

Lines changed: 211 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,50 @@
1717

1818
targetScope = 'subscription'
1919

20+
// param devopsInfrastructureId string
2021
param devopsSubnetAddressPrefix string
22+
param privateEndpointSubnetAddressPrefix string
2123
// param enableSoftDelete bool
2224
param hubType string // live / nonlive
2325
param region string = 'uksouth'
2426
param regionShortName string = 'uks'
2527
param vnetAddressPrefixes array
28+
param enableSoftDelete bool
2629

27-
// var keyVaultName = 'kv-lungcs-${envConfig}-inf'
2830

31+
// removed when generalised
32+
var appShortName = 'lungcs'
33+
34+
var devCenterSuffix = substring(uniqueString(subscription().id), 0, 3)
35+
var devCenterName = 'devc-hub-${hubType}-${regionShortName}-${devCenterSuffix}'
2936
var devopsSubnetName = 'sn-hub-${hubType}-${regionShortName}-devops'
30-
var devCenterName = 'devc-hub-${hubType}-${regionShortName}'
3137
var devCenterProjectName = 'prj-hub-${hubType}-${regionShortName}'
3238
var poolName = 'private-pool-hub-${hubType}-${regionShortName}'
3339
var resourceGroupName = 'rg-hub-${hubType}-${regionShortName}-bootstrap'
3440
var virtualNetworkName = 'vnet-hub-${hubType}-${regionShortName}'
41+
var managedIdentityRGName = 'rg-mi-${hubType}-${regionShortName}'
42+
var miHub = 'mi-hub-${hubType}-${regionShortName}'
43+
var privateDNSZoneRGName = 'rg-hub-${hubType}-${regionShortName}-private-dns-zones'
44+
var keyVaultName = 'kv-${appShortName}-${hubType}-inf'
45+
var privateEndpointSubnetName = 'sn-hub-${hubType}-${regionShortName}-private-endpoint'
46+
var storageAccountName = 'sa${appShortName}${regionShortName}state'
47+
var computeGalleryName = '${appShortName}_hub_compute_gallery'
48+
49+
var miADOtoAZname = 'mi-${appShortName}-${hubType}-adotoaz-${regionShortName}'
50+
var miGHtoADOname = 'mi-${appShortName}-${hubType}-ghtoado-${regionShortName}'
51+
52+
53+
// See: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
54+
var roleID = {
55+
CDNContributor: 'ec156ff8-a8d1-4d15-830c-5b80698ca432'
56+
kvSecretsUser: '4633458b-17de-408a-b874-0445c86b69e6'
57+
networkContributor: '4d97b98b-1d4f-4787-a291-c67834d212e7'
58+
rbacAdmin: 'f58310d9-a9f6-439a-9e8d-f62e7b41a168'
59+
reader: 'acdd72a7-3385-48ef-bd42-f606fba81ae7'
60+
contributor: 'b24988ac-6180-42a0-ab88-20f7382dd24c'
61+
storageBlobDataContributor: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
62+
}
3563

36-
// var miADOtoAZname = 'mi-${appShortName}-${envConfig}-adotoaz-uks'
37-
// var miGHtoADOname = 'mi-${appShortName}-${envConfig}-ghtoado-uks'
3864

3965
resource bootstrapRG 'Microsoft.Resources/resourceGroups@2025-04-01' = {
4066
name: resourceGroupName
@@ -54,7 +80,8 @@ module virtualNetwork 'modules/virtualNetwork.bicep' = {
5480
module managedDevopsPool 'modules/managedDevopsPool.bicep' = {
5581
scope: bootstrapRG
5682
params: {
57-
adoOrg: 'nhse-pps-1'
83+
//adoOrg: 'nhse-pps-1'
84+
adoOrg: 'nhse-dtos'
5885
agentProfileMaxAgentLifetime: '00.04:00:00'
5986
devCenterName: devCenterName
6087
devCenterProjectName: devCenterProjectName
@@ -64,3 +91,182 @@ module managedDevopsPool 'modules/managedDevopsPool.bicep' = {
6491
virtualNetworkName: virtualNetwork.outputs.name
6592
}
6693
}
94+
95+
@description('Retrieve existing managed identity resource group')
96+
resource managedIdentityRG 'Microsoft.Resources/resourceGroups@2024-11-01' = {
97+
name: managedIdentityRGName
98+
location: region
99+
}
100+
101+
@description('Retrieve existing private DNS zone resource group')
102+
resource privateDNSZoneRG 'Microsoft.Resources/resourceGroups@2024-11-01' = {
103+
name: privateDNSZoneRGName
104+
location: region
105+
}
106+
107+
@description('Create the managed identity assumed by Azure devops to connect to Azure')
108+
module managedIdentiyHub 'modules/managedIdentity.bicep' = {
109+
scope: managedIdentityRG
110+
params: {
111+
name: miHub
112+
region: region
113+
}
114+
}
115+
116+
@description('Storage Deployment')
117+
module terraformStateStorageAccount 'modules/storage.bicep' = {
118+
scope: bootstrapRG
119+
params: {
120+
storageLocation: region
121+
storageName: storageAccountName
122+
enableSoftDelete: true
123+
miPrincipalID: managedIdentiyHub.outputs.miPrincipalID
124+
miName: miHub
125+
}
126+
}
127+
128+
@description('Create private endpoint and register DNS')
129+
module storageAccountPrivateEndpoint 'modules/privateEndpoint.bicep' = {
130+
scope: bootstrapRG
131+
params: {
132+
hub: hubType
133+
region: region
134+
name: storageAccountName
135+
vnetName: virtualNetwork.outputs.name
136+
virtualNetworkName: virtualNetwork.outputs.name
137+
privateEndpointSubnetName: privateEndpointSubnetName
138+
privateEndpointSubnetAddressPrefix: privateEndpointSubnetAddressPrefix
139+
RGName: bootstrapRG.name
140+
resourceServiceType: 'storage'
141+
resourceID: terraformStateStorageAccount.outputs.storageAccountID
142+
privateDNSZoneID: storagePrivateDNSZone.outputs.privateDNSZoneID
143+
}
144+
}
145+
146+
@description('Retrieve storage private DNS zone')
147+
module storagePrivateDNSZone 'modules/dns.bicep' = {
148+
scope: privateDNSZoneRG
149+
params: {
150+
resourceServiceType: 'storage'
151+
vnetId: virtualNetwork.outputs.id
152+
location: region
153+
}
154+
}
155+
156+
@description('Create the managed identity assumed by Azure devops to connect to Azure')
157+
module managedIdentiyADOtoAZ 'modules/managedIdentity.bicep' = {
158+
scope: managedIdentityRG
159+
params: {
160+
name: miADOtoAZname
161+
region: region
162+
}
163+
}
164+
165+
@description('Let the managed identity configure vnet peering and DNS records')
166+
resource networkContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
167+
name: guid(subscription().subscriptionId, hubType, 'networkContributor')
168+
properties: {
169+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.networkContributor)
170+
principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
171+
description: '${miADOtoAZname} Network Contributor access to subscription'
172+
}
173+
}
174+
175+
@description('Let the managed identity assign RBAC roles (required for Azure Virtual Desktop)')
176+
resource userAccessAdministratorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
177+
name: guid(subscription().subscriptionId, hubType, 'UserAccessAdministrator')
178+
properties: {
179+
roleDefinitionId: subscriptionResourceId(
180+
'Microsoft.Authorization/roleDefinitions',
181+
roleID.rbacAdmin
182+
)
183+
principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
184+
description: '${miADOtoAZname} User Access Administrator access to subscription'
185+
}
186+
}
187+
188+
@description('Let the managed identity configure Front door and its resources')
189+
resource CDNContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
190+
name: guid(subscription().subscriptionId, hubType, 'CDNContributor')
191+
properties: {
192+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.CDNContributor)
193+
principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
194+
description: '${miADOtoAZname} CDN Contributor access to subscription'
195+
}
196+
}
197+
198+
@description('Let the managed identity deploy terraform on the subscription')
199+
resource TerraformContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
200+
name: guid(subscription().subscriptionId, hubType, 'TerraformContributor')
201+
properties: {
202+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.contributor)
203+
principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
204+
description: '${miADOtoAZname} Terraform Contributor access to subscription'
205+
}
206+
}
207+
208+
@description('Let the managed identity strore blobs in storage account')
209+
resource StorageAccountBlobContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
210+
name: guid(subscription().subscriptionId, hubType, 'StorageAccountBlobContributorAssignment')
211+
properties: {
212+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.storageBlobDataContributor)
213+
principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
214+
description: '${miADOtoAZname} Storage Account Blob Contributor access to subscription'
215+
}
216+
}
217+
218+
@description('Create the managed identity assumed by Github actions to trigger Azure devops pipelines')
219+
module managedIdentiyGHtoADO 'modules/managedIdentity.bicep' = {
220+
scope: managedIdentityRG
221+
params: {
222+
name: miGHtoADOname
223+
fedCredProperties: {
224+
audiences: [ 'api://AzureADTokenExchange' ]
225+
issuer: 'https://token.actions.githubusercontent.com'
226+
subject: 'repo:NHSDigital/dtos-manage-breast-screening:environment:${hubType}'
227+
}
228+
region: region
229+
}
230+
}
231+
232+
@description('Let the GHtoADO managed identity access a subscription')
233+
resource readerAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
234+
name: guid(subscription().subscriptionId, hubType, 'reader')
235+
properties: {
236+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.reader)
237+
principalId: managedIdentiyGHtoADO.outputs.miPrincipalID
238+
description: '${miGHtoADOname} Reader access to subscription'
239+
}
240+
}
241+
242+
@description('Deploy the Key Vault for storing secrets')
243+
module keyVaultModule 'modules/keyVault.bicep' = {
244+
name: 'keyVaultDeployment'
245+
scope: resourceGroup(resourceGroupName)
246+
params: {
247+
enableSoftDelete : enableSoftDelete
248+
keyVaultName: keyVaultName
249+
miName: miADOtoAZname
250+
miPrincipalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
251+
region: region
252+
}
253+
}
254+
255+
@description('Retrieve key vault private DNS zone')
256+
module keyVaultPrivateDNSZone 'modules/dns.bicep' = {
257+
scope: privateDNSZoneRG
258+
params: {
259+
resourceServiceType: 'keyVault'
260+
vnetId: virtualNetwork.outputs.id
261+
location: region
262+
}
263+
}
264+
265+
266+
module computeGallery 'modules/computeGallery.bicep' = {
267+
scope: resourceGroup(resourceGroupName)
268+
params: {
269+
galleryName: computeGalleryName
270+
location: region
271+
}
272+
}

infrastructure/bootstrap/main.bicep

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ module managedIdentiyADOtoAZ 'modules/managedIdentity.bicep' = {
5959
}
6060
}
6161

62-
// Create the managed identity assumed by Github actions to trigger Azure devops pipelines
62+
// Create the managed identity assumed by GitHub actions to trigger Azure devops pipelines
6363
module managedIdentiyGHtoADO 'modules/managedIdentity.bicep' = {
6464
scope: managedIdentityRG
6565
params: {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
targetScope = 'resourceGroup'
2+
3+
@description('Name of the Azure Compute Gallery')
4+
param galleryName string
5+
6+
@description('Location for the gallery')
7+
param location string
8+
9+
resource computeGallery 'Microsoft.Compute/galleries@2023-07-03' = {
10+
name: galleryName
11+
location: location
12+
properties: {
13+
description: ''
14+
softDeletePolicy: {
15+
isSoftDeleteEnabled: false
16+
}
17+
}
18+
}
Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
targetScope = 'resourceGroup'
2+
13
param resourceServiceType string
4+
param vnetId string
5+
6+
// location inside the resource — it just needs to exist so ARM can stamp it onto the nested deployment.
7+
param location string = 'uksouth'
28

39
var dnsZoneName = {
410
storage: 'privatelink.blob.${environment().suffixes.storage}'
@@ -7,8 +13,21 @@ var dnsZoneName = {
713
}
814

915
// Retrieve the private DNS zone for storage accounts
10-
resource privateDNSZone 'Microsoft.Network/privateDnsZones@2024-06-01' existing = {
16+
resource privateDNSZone 'Microsoft.Network/privateDnsZones@2024-06-01' = {
1117
name: dnsZoneName[resourceServiceType]
18+
location: 'global'
19+
}
20+
21+
resource vnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = {
22+
name: '${last(split(vnetId, '/'))}-link'
23+
parent: privateDNSZone
24+
location: 'global'
25+
properties: {
26+
virtualNetwork: {
27+
id: vnetId
28+
}
29+
registrationEnabled: false
30+
}
1231
}
1332

1433
output privateDNSZoneID string = privateDNSZone.id

infrastructure/bootstrap/modules/managedDevopsPool.bicep

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ param devopsSubnetAddressPrefix string
1313
param virtualNetworkName string
1414

1515
param fabricProfileSkuName string = 'Standard_D2ads_v5'
16-
param poolSize int = 2
16+
17+
param poolSize int = 1
1718
param location string = 'uksouth'
1819

1920

infrastructure/bootstrap/modules/privateEndpoint.bicep

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ param privateDNSZoneID string
44
param name string
55
param resourceID string
66
param resourceServiceType string
7-
8-
var RGName = 'rg-hub-${hub}-uks-hub-networking'
9-
var vnetName = 'VNET-${toUpper(hub)}-UKS-HUB'
10-
var subnetName = 'SN-${toUpper(hub)}-UKS-HUB-pep'
7+
param RGName string
8+
param vnetName string
9+
param privateEndpointSubnetName string
10+
param virtualNetworkName string
11+
param privateEndpointSubnetAddressPrefix string
1112

1213
var groupID = {
1314
storage: 'blob'
@@ -27,18 +28,28 @@ resource vnet 'Microsoft.Network/virtualNetworks@2024-01-01' existing = {
2728
}
2829

2930
// Retrieve the existing Subnet within the vnet
30-
resource subnet 'Microsoft.Network/virtualNetworks/subnets@2024-01-01' existing = {
31-
parent: vnet
32-
name: subnetName
31+
// resource subnet 'Microsoft.Network/virtualNetworks/subnets@2024-01-01' = {
32+
// parent: vnet
33+
// name: subnetName
34+
// region: region
35+
// }
36+
37+
resource privateEndpointSubnet 'Microsoft.Network/virtualNetworks/subnets@2025-01-01' = {
38+
name: '${virtualNetworkName}/${privateEndpointSubnetName}'
39+
properties: {
40+
addressPrefix: privateEndpointSubnetAddressPrefix
41+
privateEndpointNetworkPolicies: 'Disabled'
42+
}
3343
}
3444

45+
3546
// Create the private endpoint for the storage account
3647
resource privateEndpoint 'Microsoft.Network/privateEndpoints@2024-01-01' = {
3748
name: '${name}-pep'
3849
location: region
3950
properties: {
4051
subnet: {
41-
id: subnet.id
52+
id: privateEndpointSubnet.id
4253
}
4354
privateLinkServiceConnections: [
4455
{

infrastructure/bootstrap/modules/virtualNetwork.bicep

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2025-01-01' = {
1414
}
1515

1616
output name string = virtualNetwork.name
17+
output id string = virtualNetwork.id

0 commit comments

Comments
 (0)