Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: test

on:
workflow_dispatch:

jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
environment: poc
concurrency: deploy-poc-${{ github.ref }}

steps:
- name: Checkout code
uses: actions/checkout@v5

- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
__pycache__
lung_cancer_screening/assets/compiled/*
!lung_cancer_screening/assets/compiled/.gitkeep
.DS_Store
10 changes: 10 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# SEE: https://github.com/gitleaks/gitleaks/blob/master/README.md#gitleaksignore

cd9c0efec38c5d63053dd865e5d4e207c0760d91:docs/guides/Perform_static_analysis.md:generic-api-key:37
infrastructure/terraform/resource_group_init/core.bicep:generic-api-key:10
infrastructure/terraform/resource_group_init/core.bicep:generic-api-key:11
infrastructure/terraform/resource_group_init/core.bicep:generic-api-key:12
infrastructure/terraform/resource_group_init/main.bicep:generic-api-key:29
infrastructure/terraform/resource_group_init/main.bicep:generic-api-key:30
infrastructure/terraform/resource_group_init/main.bicep:generic-api-key:31
infrastructure/terraform/resource_group_init/main.bicep:generic-api-key:32
infrastructure/terraform/resource_group_init/main.bicep:generic-api-key:33
infrastructure/terraform/resource_group_init/storage.bicep:generic-api-key:59
infrastructure/terraform/resource_group_init/keyVault.bicep:generic-api-key:10
4 changes: 4 additions & 0 deletions infrastructure/environments/poc/variables.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ENVIRONMENT=poc
AZURE_SUBSCRIPTION="Lung Cancer Screening - Dev"
TERRAFORM_MODULES_REF=main
ENABLE_SOFT_DELETE=false
47 changes: 47 additions & 0 deletions infrastructure/terraform/resource_group_init/core.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
targetScope='subscription'

@minLength(1)
param miPrincipalId string
@minLength(1)
param miName string

// See: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
var roleID = {
contributor: 'b24988ac-6180-42a0-ab88-20f7382dd24c'
kvSecretsUser: '4633458b-17de-408a-b874-0445c86b69e6'
rbacAdmin: 'f58310d9-a9f6-439a-9e8d-f62e7b41a168'
storageBlobDataContributor: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
storageQueueDataContributor: '974c5e8b-45b9-4653-ba55-5f855dd0fb88'
}

// Let the managed identity configure resources in the subscription
resource contributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(subscription().subscriptionId, miPrincipalId, 'contributor')
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.contributor)
principalId: miPrincipalId
description: '${miName} Contributor access to subscription'
}
}

// Let the managed identity read key vault secrets during terraform plan
resource kvSecretsUserAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(subscription().subscriptionId, miPrincipalId, 'kvSecretsUser')
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.kvSecretsUser)
principalId: miPrincipalId
description: '${miName} kvSecretsUser access to subscription'
}
}

// Let the managed identity assign the Key Vault Secrets User role to the container app managed identity
resource rbacAdminAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(subscription().subscriptionId, miPrincipalId, 'rbacAdmin')
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.rbacAdmin)
principalId: miPrincipalId
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}}))'
conditionVersion: '2.0'
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.'
}
}
14 changes: 14 additions & 0 deletions infrastructure/terraform/resource_group_init/dns.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
param resourceServiceType string

var dnsZoneName = {
storage: 'privatelink.blob.${environment().suffixes.storage}'
// Cannot read vault URL from environment() because of https://github.com/Azure/bicep/issues/9839
keyVault: 'privatelink.vaultcore.azure.net'
}

// Retrieve the private DNS zone for storage accounts
resource privateDNSZone 'Microsoft.Network/privateDnsZones@2024-06-01' existing = {
name: dnsZoneName[resourceServiceType]
}

output privateDNSZoneID string = privateDNSZone.id
42 changes: 42 additions & 0 deletions infrastructure/terraform/resource_group_init/keyVault.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

param enableSoftDelete bool
param keyVaultName string
param miPrincipalId string
param miName string
param region string

// See: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
var roleID = {
kvSecretsUser: '4633458b-17de-408a-b874-0445c86b69e6'
}

resource keyVault 'Microsoft.KeyVault/vaults@2024-11-01' = {
name: keyVaultName
location: region
properties: {
tenantId: subscription().tenantId
sku: {
name: 'standard'
family: 'A'
}
enableRbacAuthorization: true
enabledForDeployment: true
enabledForTemplateDeployment: true
enabledForDiskEncryption: true
enableSoftDelete: enableSoftDelete
publicNetworkAccess: 'disabled'
}
}

// Let the managed identity read key vault secrets during terraform plan
resource kvSecretsUserAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(subscription().subscriptionId, miPrincipalId, 'kvSecretsUser')
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.kvSecretsUser)
principalId: miPrincipalId
description: '${miName} kvSecretsUser access to resource group'
}
}

// Output the key vault ID so it can be used to create the private endpoint
output keyVaultID string = keyVault.id
194 changes: 194 additions & 0 deletions infrastructure/terraform/resource_group_init/main.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
targetScope='subscription'

param enableSoftDelete bool
param envConfig string
param region string
param storageAccountRGName string
param storageAccountName string
param appShortName string

var hubMap = {
dev: 'dev'
int: 'dev'
review: 'dev'
nft: 'dev'
preprod: 'prod'
prd: 'prod'
}
var privateEndpointRGName = 'rg-hub-${envConfig}-uks-hub-private-endpoints'
var privateDNSZoneRGName = 'rg-hub-${hubMap[envConfig]}-uks-private-dns-zones'
var managedIdentityRGName = 'rg-mi-${envConfig}-uks'
var infraResourceGroupName = 'rg-lungcs-${envConfig}-infra'
var keyVaultName = 'kv-lungcs-${envConfig}-inf'

var miADOtoAZname = 'mi-${appShortName}-${envConfig}-adotoaz-uks'
var miGHtoADOname = 'mi-${appShortName}-${envConfig}-ghtoado-uks'

// See: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
var roleID = {
CDNContributor: 'ec156ff8-a8d1-4d15-830c-5b80698ca432'
kvSecretsUser: '4633458b-17de-408a-b874-0445c86b69e6'
networkContributor: '4d97b98b-1d4f-4787-a291-c67834d212e7'
rbacAdmin: 'f58310d9-a9f6-439a-9e8d-f62e7b41a168'
reader: 'acdd72a7-3385-48ef-bd42-f606fba81ae7'
}

// Retrieve existing terraform state resource group
resource storageAccountRG 'Microsoft.Resources/resourceGroups@2024-11-01' existing = {
name: storageAccountRGName
}
// Retrieve existing private endpoint resource group
resource privateEndpointResourceGroup 'Microsoft.Resources/resourceGroups@2024-11-01' existing = {
name: privateEndpointRGName
}
// Retrieve existing private DNS zone resource group
resource privateDNSZoneRG 'Microsoft.Resources/resourceGroups@2024-11-01' existing = {
name: privateDNSZoneRGName
}
// Retrieve existing managed identity resource group
resource managedIdentityRG 'Microsoft.Resources/resourceGroups@2024-11-01' existing = {
name: managedIdentityRGName
}

// Create the managed identity assumed by Azure devops to connect to Azure
module managedIdentiyADOtoAZ 'managedIdentity.bicep' = {
scope: managedIdentityRG
params: {
name: miADOtoAZname
region: region
}
}

// Create the managed identity assumed by Github actions to trigger Azure devops pipelines
module managedIdentiyGHtoADO 'managedIdentity.bicep' = {
scope: managedIdentityRG
params: {
name: miGHtoADOname
fedCredProperties: {
audiences: [ 'api://AzureADTokenExchange' ]
issuer: 'https://token.actions.githubusercontent.com'
subject: 'repo:NHSDigital/lung_cancer_screening:environment:${envConfig}'
}
region: region
}
}

// Let the GHtoADO managed identity access a subscription
resource readerAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(subscription().subscriptionId, envConfig, 'reader')
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.reader)
principalId: managedIdentiyGHtoADO.outputs.miPrincipalID
description: '${miGHtoADOname} Reader access to subscription'
}
}

// Create the storage account, blob service and container
module terraformStateStorageAccount 'storage.bicep' = {
scope: storageAccountRG
params: {
storageLocation: region
storageName: storageAccountName
enableSoftDelete: enableSoftDelete
miPrincipalID: managedIdentiyADOtoAZ.outputs.miPrincipalID
miName: miADOtoAZname
}
}

// Retrieve storage private DNS zone
module storagePrivateDNSZone 'dns.bicep' = {
scope: privateDNSZoneRG
params: {
resourceServiceType: 'storage'
}
}

// Retrieve key vault private DNS zone
module keyVaultPrivateDNSZone 'dns.bicep' = {
scope: privateDNSZoneRG
params: {
resourceServiceType: 'keyVault'
}
}

// Create private endpoint and register DNS
module storageAccountPrivateEndpoint 'privateEndpoint.bicep' = {
scope: privateEndpointResourceGroup
params: {
hub: hubMap[envConfig]
region: region
name: storageAccountName
resourceServiceType: 'storage'
resourceID: terraformStateStorageAccount.outputs.storageAccountID
privateDNSZoneID: storagePrivateDNSZone.outputs.privateDNSZoneID
}
}

// Let the managed identity configure vnet peering and DNS records
resource networkContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(subscription().subscriptionId, envConfig, 'networkContributor')
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.networkContributor)
principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
description: '${miADOtoAZname} Network Contributor access to subscription'
}
}

// Create infra resource group
resource infraRG 'Microsoft.Resources/resourceGroups@2024-11-01' = {
name: infraResourceGroupName
location: region
}

// Private endpoint for infra key vault
module kvPrivateEndpoint 'privateEndpoint.bicep' = {
scope: resourceGroup(infraResourceGroupName)
params: {
hub: hubMap[envConfig]
region: region
name: keyVaultName
resourceServiceType: 'keyVault'
resourceID: keyVaultModule.outputs.keyVaultID
privateDNSZoneID: keyVaultPrivateDNSZone.outputs.privateDNSZoneID
}
}

// Use a module to deploy Key Vault into the infra RG
module keyVaultModule 'keyVault.bicep' = {
name: 'keyVaultDeployment'
scope: resourceGroup(infraResourceGroupName)
params: {
enableSoftDelete : enableSoftDelete
keyVaultName: keyVaultName
miName: miADOtoAZname
miPrincipalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
region: region
}
}

// Let the managed identity configure Front door and its resources
resource CDNContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(subscription().subscriptionId, envConfig, 'CDNContributor')
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.CDNContributor)
principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
description: '${miADOtoAZname} CDN Contributor access to subscription'
}
}

// Let the managed identity assign the Key Vault Secrets User role to the container app managed identity
resource rbacAdminAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(subscription().subscriptionId, envConfig, 'rbacAdmin')
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleID.rbacAdmin)
principalId: managedIdentiyADOtoAZ.outputs.miPrincipalID
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}}))'
conditionVersion: '2.0'
description: '${miADOtoAZname} Role Based Access Control Administrator access to subscription. Only allows assigning the Key Vault Secrets User role.'
}
}

output miPrincipalID string = managedIdentiyADOtoAZ.outputs.miPrincipalID
output miName string = miADOtoAZname
output keyVaultPrivateDNSZone string = keyVaultPrivateDNSZone.outputs.privateDNSZoneID
output storagePrivateDNSZone string = storagePrivateDNSZone.outputs.privateDNSZoneID
16 changes: 16 additions & 0 deletions infrastructure/terraform/resource_group_init/managedIdentity.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
param name string
param region string
param fedCredProperties object = {}

resource mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
location: region
name: name
}

resource managedIdentiyGHtoADOFedCred 'Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials@2024-11-30' = if (!empty(fedCredProperties)) {
parent: mi
name: 'github-actions'
properties: fedCredProperties
}

output miPrincipalID string = mi.properties.principalId
Loading
Loading