Skip to content

Commit 967668e

Browse files
feat: GitHub Actions to Azure connectivity
1 parent b3b9e89 commit 967668e

File tree

10 files changed

+481
-0
lines changed

10 files changed

+481
-0
lines changed

.github/workflows/test.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: test
2+
3+
on:
4+
workflow_dispatch:
5+
6+
jobs:
7+
deploy:
8+
name: Deploy
9+
runs-on: ubuntu-latest
10+
environment: poc
11+
concurrency: deploy-poc-${{ github.ref }}
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v5
16+
17+
- uses: azure/login@v2
18+
with:
19+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
20+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
21+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@
1616
__pycache__
1717
lung_cancer_screening/assets/compiled/*
1818
!lung_cancer_screening/assets/compiled/.gitkeep
19+
.DS_Store
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ENVIRONMENT=poc
2+
AZURE_SUBSCRIPTION="Lung Cancer Screening - Dev"
3+
TERRAFORM_MODULES_REF=main
4+
ENABLE_SOFT_DELETE=false
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
targetScope='subscription'
2+
3+
@minLength(1)
4+
param miPrincipalId string
5+
@minLength(1)
6+
param miName string
7+
8+
// See: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
9+
var roleID = {
10+
contributor: 'b24988ac-6180-42a0-ab88-20f7382dd24c'
11+
kvSecretsUser: '4633458b-17de-408a-b874-0445c86b69e6'
12+
rbacAdmin: 'f58310d9-a9f6-439a-9e8d-f62e7b41a168'
13+
storageBlobDataContributor: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
14+
storageQueueDataContributor: '974c5e8b-45b9-4653-ba55-5f855dd0fb88'
15+
}
16+
17+
// Let the managed identity configure resources in the subscription
18+
resource contributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
19+
name: guid(subscription().subscriptionId, miPrincipalId, 'contributor')
20+
properties: {
21+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.contributor)
22+
principalId: miPrincipalId
23+
description: '${miName} Contributor access to subscription'
24+
}
25+
}
26+
27+
// Let the managed identity read key vault secrets during terraform plan
28+
resource kvSecretsUserAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
29+
name: guid(subscription().subscriptionId, miPrincipalId, 'kvSecretsUser')
30+
properties: {
31+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.kvSecretsUser)
32+
principalId: miPrincipalId
33+
description: '${miName} kvSecretsUser access to subscription'
34+
}
35+
}
36+
37+
// Let the managed identity assign the Key Vault Secrets User role to the container app managed identity
38+
resource rbacAdminAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
39+
name: guid(subscription().subscriptionId, miPrincipalId, 'rbacAdmin')
40+
properties: {
41+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.rbacAdmin)
42+
principalId: miPrincipalId
43+
condition: '((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/write\'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.kvSecretsUser}, ${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}})) AND ((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/delete\'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.kvSecretsUser}, ${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}}))'
44+
conditionVersion: '2.0'
45+
description: '${miName} Role Based Access Control Administrator access to subscription. Can assign Key Vault Secrets User, Storage Blob Data Contributor, and Storage Queue Data Contributor roles.'
46+
}
47+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
param resourceServiceType string
2+
3+
var dnsZoneName = {
4+
storage: 'privatelink.blob.${environment().suffixes.storage}'
5+
// Cannot read vault URL from environment() because of https://github.com/Azure/bicep/issues/9839
6+
keyVault: 'privatelink.vaultcore.azure.net'
7+
}
8+
9+
// Retrieve the private DNS zone for storage accounts
10+
resource privateDNSZone 'Microsoft.Network/privateDnsZones@2024-06-01' existing = {
11+
name: dnsZoneName[resourceServiceType]
12+
}
13+
14+
output privateDNSZoneID string = privateDNSZone.id
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
param enableSoftDelete bool
3+
param keyVaultName string
4+
param miPrincipalId string
5+
param miName string
6+
param region string
7+
8+
// See: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
9+
var roleID = {
10+
kvSecretsUser: '4633458b-17de-408a-b874-0445c86b69e6'
11+
}
12+
13+
resource keyVault 'Microsoft.KeyVault/vaults@2024-11-01' = {
14+
name: keyVaultName
15+
location: region
16+
properties: {
17+
tenantId: subscription().tenantId
18+
sku: {
19+
name: 'standard'
20+
family: 'A'
21+
}
22+
enableRbacAuthorization: true
23+
enabledForDeployment: true
24+
enabledForTemplateDeployment: true
25+
enabledForDiskEncryption: true
26+
enableSoftDelete: enableSoftDelete
27+
publicNetworkAccess: 'disabled'
28+
}
29+
}
30+
31+
// Let the managed identity read key vault secrets during terraform plan
32+
resource kvSecretsUserAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
33+
name: guid(subscription().subscriptionId, miPrincipalId, 'kvSecretsUser')
34+
properties: {
35+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.kvSecretsUser)
36+
principalId: miPrincipalId
37+
description: '${miName} kvSecretsUser access to resource group'
38+
}
39+
}
40+
41+
// Output the key vault ID so it can be used to create the private endpoint
42+
output keyVaultID string = keyVault.id
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
targetScope='subscription'
2+
3+
param enableSoftDelete bool
4+
param envConfig string
5+
param region string
6+
param storageAccountRGName string
7+
param storageAccountName string
8+
param appShortName string
9+
10+
var hubMap = {
11+
dev: 'dev'
12+
int: 'dev'
13+
review: 'dev'
14+
nft: 'dev'
15+
preprod: 'prod'
16+
prd: 'prod'
17+
}
18+
var privateEndpointRGName = 'rg-hub-${envConfig}-uks-hub-private-endpoints'
19+
var privateDNSZoneRGName = 'rg-hub-${hubMap[envConfig]}-uks-private-dns-zones'
20+
var managedIdentityRGName = 'rg-mi-${envConfig}-uks'
21+
var infraResourceGroupName = 'rg-lungcs-${envConfig}-infra'
22+
var keyVaultName = 'kv-lungcs-${envConfig}-inf'
23+
24+
var miADOtoAZname = 'mi-${appShortName}-${envConfig}-adotoaz-uks'
25+
var miGHtoADOname = 'mi-${appShortName}-${envConfig}-ghtoado-uks'
26+
27+
// See: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
28+
var roleID = {
29+
CDNContributor: 'ec156ff8-a8d1-4d15-830c-5b80698ca432'
30+
kvSecretsUser: '4633458b-17de-408a-b874-0445c86b69e6'
31+
networkContributor: '4d97b98b-1d4f-4787-a291-c67834d212e7'
32+
rbacAdmin: 'f58310d9-a9f6-439a-9e8d-f62e7b41a168'
33+
reader: 'acdd72a7-3385-48ef-bd42-f606fba81ae7'
34+
}
35+
36+
// Retrieve existing terraform state resource group
37+
resource storageAccountRG 'Microsoft.Resources/resourceGroups@2024-11-01' existing = {
38+
name: storageAccountRGName
39+
}
40+
// Retrieve existing private endpoint resource group
41+
resource privateEndpointResourceGroup 'Microsoft.Resources/resourceGroups@2024-11-01' existing = {
42+
name: privateEndpointRGName
43+
}
44+
// Retrieve existing private DNS zone resource group
45+
resource privateDNSZoneRG 'Microsoft.Resources/resourceGroups@2024-11-01' existing = {
46+
name: privateDNSZoneRGName
47+
}
48+
// Retrieve existing managed identity resource group
49+
resource managedIdentityRG 'Microsoft.Resources/resourceGroups@2024-11-01' existing = {
50+
name: managedIdentityRGName
51+
}
52+
53+
// Create the managed identity assumed by Azure devops to connect to Azure
54+
module managedIdentiyADOtoAZ 'managedIdentity.bicep' = {
55+
scope: managedIdentityRG
56+
params: {
57+
name: miADOtoAZname
58+
region: region
59+
}
60+
}
61+
62+
// Create the managed identity assumed by Github actions to trigger Azure devops pipelines
63+
module managedIdentiyGHtoADO 'managedIdentity.bicep' = {
64+
scope: managedIdentityRG
65+
params: {
66+
name: miGHtoADOname
67+
fedCredProperties: {
68+
audiences: [ 'api://AzureADTokenExchange' ]
69+
issuer: 'https://token.actions.githubusercontent.com'
70+
subject: 'repo:NHSDigital/lung_cancer_screening:environment:${envConfig}'
71+
}
72+
region: region
73+
}
74+
}
75+
76+
// Let the GHtoADO managed identity access a subscription
77+
resource readerAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
78+
name: guid(subscription().subscriptionId, envConfig, 'reader')
79+
properties: {
80+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.reader)
81+
principalId: managedIdentiyGHtoADO.outputs.miPrincipalID
82+
description: '${miGHtoADOname} Reader access to subscription'
83+
}
84+
}
85+
86+
// Create the storage account, blob service and container
87+
module terraformStateStorageAccount 'storage.bicep' = {
88+
scope: storageAccountRG
89+
params: {
90+
storageLocation: region
91+
storageName: storageAccountName
92+
enableSoftDelete: enableSoftDelete
93+
miPrincipalID: managedIdentiyADOtoAZ.outputs.miPrincipalID
94+
miName: miADOtoAZname
95+
}
96+
}
97+
98+
// Retrieve storage private DNS zone
99+
module storagePrivateDNSZone 'dns.bicep' = {
100+
scope: privateDNSZoneRG
101+
params: {
102+
resourceServiceType: 'storage'
103+
}
104+
}
105+
106+
// Retrieve key vault private DNS zone
107+
module keyVaultPrivateDNSZone 'dns.bicep' = {
108+
scope: privateDNSZoneRG
109+
params: {
110+
resourceServiceType: 'keyVault'
111+
}
112+
}
113+
114+
// Create private endpoint and register DNS
115+
module storageAccountPrivateEndpoint 'privateEndpoint.bicep' = {
116+
scope: privateEndpointResourceGroup
117+
params: {
118+
hub: hubMap[envConfig]
119+
region: region
120+
name: storageAccountName
121+
resourceServiceType: 'storage'
122+
resourceID: terraformStateStorageAccount.outputs.storageAccountID
123+
privateDNSZoneID: storagePrivateDNSZone.outputs.privateDNSZoneID
124+
}
125+
}
126+
127+
// Let the managed identity configure vnet peering and DNS records
128+
resource networkContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
129+
name: guid(subscription().subscriptionId, envConfig, 'networkContributor')
130+
properties: {
131+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.networkContributor)
132+
principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
133+
description: '${miADOtoAZname} Network Contributor access to subscription'
134+
}
135+
}
136+
137+
// Create infra resource group
138+
resource infraRG 'Microsoft.Resources/resourceGroups@2024-11-01' = {
139+
name: infraResourceGroupName
140+
location: region
141+
}
142+
143+
// Private endpoint for infra key vault
144+
module kvPrivateEndpoint 'privateEndpoint.bicep' = {
145+
scope: resourceGroup(infraResourceGroupName)
146+
params: {
147+
hub: hubMap[envConfig]
148+
region: region
149+
name: keyVaultName
150+
resourceServiceType: 'keyVault'
151+
resourceID: keyVaultModule.outputs.keyVaultID
152+
privateDNSZoneID: keyVaultPrivateDNSZone.outputs.privateDNSZoneID
153+
}
154+
}
155+
156+
// Use a module to deploy Key Vault into the infra RG
157+
module keyVaultModule 'keyVault.bicep' = {
158+
name: 'keyVaultDeployment'
159+
scope: resourceGroup(infraResourceGroupName)
160+
params: {
161+
enableSoftDelete : enableSoftDelete
162+
keyVaultName: keyVaultName
163+
miName: miADOtoAZname
164+
miPrincipalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
165+
region: region
166+
}
167+
}
168+
169+
// Let the managed identity configure Front door and its resources
170+
resource CDNContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
171+
name: guid(subscription().subscriptionId, envConfig, 'CDNContributor')
172+
properties: {
173+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.CDNContributor)
174+
principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
175+
description: '${miADOtoAZname} CDN Contributor access to subscription'
176+
}
177+
}
178+
179+
// Let the managed identity assign the Key Vault Secrets User role to the container app managed identity
180+
resource rbacAdminAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
181+
name: guid(subscription().subscriptionId, envConfig, 'rbacAdmin')
182+
properties: {
183+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.rbacAdmin)
184+
principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
185+
condition: '((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/write\'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.kvSecretsUser}})) AND ((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/delete\'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.kvSecretsUser}}))'
186+
conditionVersion: '2.0'
187+
description: '${miADOtoAZname} Role Based Access Control Administrator access to subscription. Only allows assigning the Key Vault Secrets User role.'
188+
}
189+
}
190+
191+
output miPrincipalID string = managedIdentiyADOtoAZ.outputs.miPrincipalID
192+
output miName string = miADOtoAZname
193+
output keyVaultPrivateDNSZone string = keyVaultPrivateDNSZone.outputs.privateDNSZoneID
194+
output storagePrivateDNSZone string = storagePrivateDNSZone.outputs.privateDNSZoneID
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
param name string
2+
param region string
3+
param fedCredProperties object = {}
4+
5+
resource mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
6+
location: region
7+
name: name
8+
}
9+
10+
resource managedIdentiyGHtoADOFedCred 'Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials@2024-11-30' = if (!empty(fedCredProperties)) {
11+
parent: mi
12+
name: 'github-actions'
13+
properties: fedCredProperties
14+
}
15+
16+
output miPrincipalID string = mi.properties.principalId

0 commit comments

Comments
 (0)