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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
include scripts/shared.mk
include scripts/terraform/terraform.mk

.DEFAULT_GOAL := help

Expand Down
8 changes: 8 additions & 0 deletions infrastructure/environments/dev/variables.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ENV_CONFIG=dev
ENVIRONMENT=dev
AZURE_SUBSCRIPTION="Breast Screening - Manage Breast Screening - Dev"
HUB_SUBSCRIPTION="Digital Screening DToS - DevOps"
HUB=dev
TERRAFORM_MODULES_REF=main
ENABLE_SOFT_DELETE=false
ADO_MANAGEMENT_POOL=private-pool-dev-uks
8 changes: 8 additions & 0 deletions infrastructure/environments/preprod/variables.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ENV_CONFIG=preprod
ENVIRONMENT=preprod
AZURE_SUBSCRIPTION="Breast Screening - Manage Breast Screening - Preprod"
HUB_SUBSCRIPTION="Digital Screening DToS - Core Services Prod Hub"
HUB=prod
TERRAFORM_MODULES_REF=main
ENABLE_SOFT_DELETE=false
ADO_MANAGEMENT_POOL=private-pool-prod-uks
8 changes: 8 additions & 0 deletions infrastructure/environments/prod/variables.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ENV_CONFIG=prod
ENVIRONMENT=prod
AZURE_SUBSCRIPTION="Breast Screening - Manage Breast Screening - Prod"
HUB_SUBSCRIPTION="Digital Screening DToS - Core Services Prod Hub"
HUB=prod
TERRAFORM_MODULES_REF=main
ENABLE_SOFT_DELETE=false
ADO_MANAGEMENT_POOL=private-pool-prod-uks
8 changes: 8 additions & 0 deletions infrastructure/environments/review/variables.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ENV_CONFIG=review
ENVIRONMENT=review
AZURE_SUBSCRIPTION="Breast Screening - Manage Breast Screening - Dev"
HUB_SUBSCRIPTION="Digital Screening DToS - DevOps"
HUB=dev
TERRAFORM_MODULES_REF=main
ENABLE_SOFT_DELETE=false
ADO_MANAGEMENT_POOL=private-pool-dev-uks
96 changes: 96 additions & 0 deletions infrastructure/terraform/resource_group_init/core.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
targetScope='subscription'

@minLength(1)
param miPrincipalId string
@minLength(1)
param miName string
@minLength(1)
param userGroupPrincipalID string
@minLength(1)
param userGroupName 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' // gitleaks:allow
kvSecretsOfficer: 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7' // gitleaks:allow
rbacAdmin: 'f58310d9-a9f6-439a-9e8d-f62e7b41a168'
storageBlobDataContributor: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
storageQueueDataContributor: '974c5e8b-45b9-4653-ba55-5f855dd0fb88'
AzureConnectedMachineOnboarding: 'b64e21ea-ac4e-4cdf-9dc9-5b892992bee7'
}

// Define role assignments for managed identity
var miRoleAssignments = [
{
roleName: 'contributor'
roleId: roleID.contributor
description: 'Contributor access to subscription'
}
{
roleName: 'kvSecretsUser'
roleId: roleID.kvSecretsUser
description: 'kvSecretsUser access to subscription'
}
{
roleName: 'rbacAdmin'
roleId: roleID.rbacAdmin
description: 'RBAC Administrator. Restricted to only assign/remove: Storage Blob Data Contributor, Storage Queue Data Contributor, and Azure Connected Machine Onboarding.'
// Delegated RBAC: This condition restricts the RBAC Administrator to only manage specific roles.
// This is a security best practice that prevents the identity from granting itself or others sensitive roles like 'Owner' or 'User Access Administrator'.
condition: '((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/write\'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}, ${roleID.AzureConnectedMachineOnboarding}})) AND ((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/delete\'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}, ${roleID.AzureConnectedMachineOnboarding}}))'
conditionVersion: '2.0'
}
]

// Define role assignments for Entra ID group
var groupRoleAssignments = [
{
roleName: 'contributor'
roleId: roleID.contributor
description: 'Contributor access to subscription'
}
{
roleName: 'kvSecretsOfficer'
roleId: roleID.kvSecretsOfficer
description: 'kvSecretsOfficer access to subscription'
}
{
roleName: 'rbacAdmin'
roleId: roleID.rbacAdmin
description: 'RBAC Administrator. Restricted to only assign/remove: Storage Blob Data Contributor, Storage Queue Data Contributor, and Azure Connected Machine Onboarding.'
// Delegated RBAC: This condition restricts the RBAC Administrator to only manage specific roles.
// This is a security best practice that prevents the identity from granting itself or others sensitive roles like 'Owner' or 'User Access Administrator'.
condition: '((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/write\'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}, ${roleID.AzureConnectedMachineOnboarding}})) AND ((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/delete\'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}, ${roleID.AzureConnectedMachineOnboarding}}))'
conditionVersion: '2.0'
}
]

// This creates one resource for each item in the miRoleAssignments array
resource miRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for role in miRoleAssignments: {
// guid() ensures unique names for each assignment
name: guid(subscription().subscriptionId, miPrincipalId, role.roleName)
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', role.roleId)
principalId: miPrincipalId
principalType: 'ServicePrincipal'
description: '${miName} ${role.description}'
// Conditionally include the 'condition' property only if it exists in the role object
condition: role.?condition
conditionVersion: role.?conditionVersion
}
}]


// This creates one resource for each item in the groupRoleAssignments array
resource groupRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for role in groupRoleAssignments: {
name: guid(subscription().subscriptionId, userGroupPrincipalID, role.roleName)
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', role.roleId)
principalId: userGroupPrincipalID
principalType: 'Group'
description: '${userGroupName} ${role.description}'
condition: role.?condition
conditionVersion: role.?conditionVersion
}
}]
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
75 changes: 75 additions & 0 deletions infrastructure/terraform/resource_group_init/keyVault.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@

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

// See: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
var roleID = {
kvSecretsUser: '4633458b-17de-408a-b874-0445c86b69e6' // gitleaks:allow
kvSecretsOfficer: 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7' // gitleaks:allow
}

// Define role assignments for managed identity (read-only)
var miRoleAssignments = [
{
roleName: 'kvSecretsUser'
roleId: roleID.kvSecretsUser
description: 'kvSecretsUser access to resource group'
}
]

// Define role assignments for Entra ID group (full management)
var groupRoleAssignments = [
{
roleName: 'kvSecretsOfficer'
roleId: roleID.kvSecretsOfficer
description: 'kvSecretsOfficer access to resource group'
}
]

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'
}
}

// Managed identity RBAC assignments using loop
resource miRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for role in miRoleAssignments: {
name: guid(subscription().subscriptionId, miPrincipalId, role.roleName)
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', role.roleId)
principalId: miPrincipalId
principalType: 'ServicePrincipal'
description: '${miName} ${role.description}'
}
}]

// Entra ID Group RBAC assignments using loop
resource groupRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for role in groupRoleAssignments: {
name: guid(subscription().subscriptionId, userGroupPrincipalID, role.roleName)
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', role.roleId)
principalId: userGroupPrincipalID
principalType: 'Group'
description: '${userGroupName} ${role.description}'
}
}]

// Output the key vault ID so it can be used to create the private endpoint
output keyVaultID string = keyVault.id
Loading
Loading