Skip to content

Commit db3a289

Browse files
DTOSS-12586: Set up preprod environment infrastructure and deploy Arc to dev and preprod
1 parent 1f79e7b commit db3a289

15 files changed

Lines changed: 310 additions & 310 deletions

File tree

.azuredevops/pipelines/deploy-app.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ stages:
3636
- task: AzureCLI@2
3737
displayName: Deploy gateway app to ${{ parameters.environment }}
3838
inputs:
39-
azureSubscription: manbgw-${{ parameters.environment }}
39+
azureSubscription: mbsgw-${{ parameters.environment }}
4040
scriptType: bash
4141
scriptLocation: inlineScript
4242
addSpnToEnvironment: true

.azuredevops/pipelines/deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ stages:
4343
- task: AzureCLI@2
4444
displayName: Run Terraform
4545
inputs:
46-
azureSubscription: manbgw-${{ parameters.environment }}
46+
azureSubscription: mbsgw-${{ parameters.environment }}
4747
scriptType: bash
4848
scriptLocation: inlineScript
4949
addSpnToEnvironment: true

.github/workflows/cicd-2-main-branch.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ jobs:
104104
id-token: write
105105
uses: ./.github/workflows/stage-4-deploy.yaml
106106
with:
107-
environments: '["review"]'
107+
environments: '["review", "dev", "preprod"]'
108108
commit_sha: ${{ github.sha }}
109109
secrets: inherit
110110

@@ -120,7 +120,7 @@ jobs:
120120
id-token: write
121121
uses: ./.github/workflows/stage-4-deploy-app.yaml
122122
with:
123-
environments: '["dev", "preprod", "prod"]'
123+
environments: '["dev", "preprod"]'
124124
release_tag: ${{ needs.release-stage.outputs.new_release_version }}
125125
commit_sha: ${{ github.sha }}
126126
secrets: inherit

docs/deployment/16-Deploy-Script-Analysis.md

Lines changed: 0 additions & 280 deletions
This file was deleted.
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# Create an environment
2+
3+
This is the initial manual process to create a new Azure environment (e.g. `dev`, `preprod`, `prod`).
4+
5+
All commands assume you are authenticated to Azure via `az login` and are running from the root of this repository unless stated otherwise.
6+
7+
## Prerequisites
8+
9+
- Owner role on both the hub and spoke Azure subscriptions
10+
- Access to Azure DevOps project `manage-breast-screening-gateway` at `https://dev.azure.com/nhse-dtos`
11+
- Access to the GitHub repository `NHSDigital/manage-breast-screening-gateway`
12+
- Access to an Azure Virtual Desktop (AVD) session connected to the internal network
13+
14+
## Azure resource providers
15+
16+
The following resource providers must be registered on the **spoke subscription** before running Terraform. Missing registrations will cause obscure errors during `terraform apply`.
17+
18+
Run these once per subscription:
19+
20+
```bash
21+
az provider register --namespace Microsoft.HybridCompute
22+
az provider register --namespace Microsoft.GuestConfiguration
23+
az provider register --namespace Microsoft.HybridConnectivity
24+
az provider register --namespace Microsoft.Relay
25+
az provider register --namespace Microsoft.PolicyInsights
26+
az provider register --namespace Microsoft.Insights
27+
az provider register --namespace Microsoft.OperationalInsights
28+
az provider register --namespace Microsoft.ManagedIdentity
29+
```
30+
31+
Verify registration status (wait until all show `Registered`):
32+
33+
```bash
34+
az provider show --namespace Microsoft.HybridCompute --query registrationState -o tsv
35+
az provider show --namespace Microsoft.HybridConnectivity --query registrationState -o tsv
36+
az provider show --namespace Microsoft.GuestConfiguration --query registrationState -o tsv
37+
```
38+
39+
## Entra ID
40+
41+
- Create Entra ID group in the `Digital screening` Administrative Unit:
42+
- `screening_mbsgw_[environment]`
43+
44+
- Ask CCOE to assign the following roles to `mi-mbsgw-[environment]-adotoaz-uks`:
45+
- [Form for PIM](https://nhsdigitallive.service-now.com/nhs_digital?id=sc_cat_item&sys_id=28f3ab4f1bf3ca1078ac4337b04bcb78&sysparm_category=114fced51bdae1502eee65b9bd4bcbdc)
46+
- Role Names: `Group.Read.All` and `Directory Readers`
47+
- Application Name: `mi-mbsgw-[environment]-adotoaz-uks`
48+
- Managed Identity: `mi-mbsgw-[environment]-adotoaz-uks`
49+
- Description: Required to resolve the Arc onboarding service principal by display name during Terraform runs
50+
51+
- Confirm the Arc onboarding service principal exists:
52+
- Name: `spn-azure-arc-onboarding-screening-[environment]`
53+
- If it does not exist, raise a request with the platform team to create it
54+
55+
## Code
56+
57+
- Create the configuration files in `infrastructure/environments/[environment]/`:
58+
- `variables.sh` — Azure subscription names, hub subscription, ADO pool, soft delete flag
59+
- `variables.tfvars` — Terraform variable overrides (can be empty if all defaults apply)
60+
- Add an `[environment]:` target in `scripts/terraform/terraform.mk` following the existing pattern
61+
- Add `[environment]` to the `environments` list in the `deploy-stage` step of `.github/workflows/cicd-2-main-branch.yaml`
62+
63+
## Bicep (resource group initialisation)
64+
65+
> [!IMPORTANT]
66+
> This step creates the Azure infrastructure that Terraform depends on — managed identities, Terraform state storage, Key Vault, and resource groups. It must be completed before running Terraform.
67+
> **Required**: Owner role on both the hub and spoke subscriptions.
68+
69+
From an AVD session:
70+
71+
1. Login with Microsoft Graph scope:
72+
73+
```bash
74+
az login --scope https://graph.microsoft.com//.default -t HSCIC365.onmicrosoft.com
75+
```
76+
77+
2. Run the Bicep deployment:
78+
79+
```bash
80+
make [environment] resource-group-init
81+
```
82+
83+
This deploys two Bicep templates:
84+
85+
**Hub subscription** (`main.bicep`) creates:
86+
87+
- Managed identity `mi-mbsgw-[environment]-adotoaz-uks` — used by ADO to deploy Azure resources via Terraform
88+
- Managed identity `mi-mbsgw-[environment]-ghtoado-uks` — used by GitHub Actions to trigger ADO pipelines (OIDC federated)
89+
- Terraform state storage account `sambsgw[environment]tfstate` with private endpoint
90+
- Infra Key Vault `kv-mbsgw-[environment]-inf` with private endpoint
91+
- Reader role assigned to `mi-mbsgw-[environment]-ghtoado-uks` at hub subscription scope
92+
93+
**Spoke subscription** (`core.bicep`) creates:
94+
95+
- Resource group `rg-mbsgw-[environment]-uks-arc-enabled-servers` — where Arc machines register
96+
- Contributor, RBAC Administrator (delegated), and Resource Policy Contributor roles for `mi-mbsgw-[environment]-adotoaz-uks`
97+
- Contributor and Key Vault Secrets Officer roles for the `screening_mbsgw_[environment]` Entra ID group
98+
99+
Note the outputs printed at the end of the deployment — you will need the managed identity client IDs in subsequent steps.
100+
101+
## Azure DevOps
102+
103+
### ADO group
104+
105+
- Navigate to **Project Settings → Permissions → New group**
106+
- Name: `Run pipeline - [environment]`
107+
- Members: `mi-mbsgw-[environment]-ghtoado-uks`
108+
- There may be more than one identity with a similar name — check the client ID printed below the name matches the one from the Bicep output
109+
- Permissions (at project level):
110+
- View project-level information
111+
112+
### ADO pipelines
113+
114+
Create two pipelines, applying the same security settings to each:
115+
116+
**Infrastructure pipeline** — deploys Arc infrastructure via Terraform:
117+
118+
- Navigate to **Pipelines → New pipeline**
119+
- Source: Azure Repos Git → select this repository
120+
- Existing Azure Pipelines YAML file: `.azuredevops/pipelines/deploy.yml`
121+
- Name: `Deploy to Azure - [environment]`
122+
- Do not run yet
123+
124+
**App pipeline** — deploys the gateway app to Arc machines:
125+
126+
- Navigate to **Pipelines → New pipeline**
127+
- Source: Azure Repos Git → select this repository
128+
- Existing Azure Pipelines YAML file: `.azuredevops/pipelines/deploy-app.yml`
129+
- Name: `Deploy Gateway App - [environment]`
130+
- Do not run yet
131+
132+
For each pipeline, manage security:
133+
134+
- Navigate to the pipeline → **⋮ → Manage security**
135+
- Add group: `Run pipeline - [environment]`
136+
- Grant permissions:
137+
- Edit queue build configuration
138+
- Queue builds
139+
- View build pipeline
140+
- View builds
141+
142+
### ADO service connection
143+
144+
> [!NOTE]
145+
> If the Managed Identity dropdown is empty (common with guest accounts), use the **create manually** link and enter the values directly.
146+
147+
- Navigate to **Project Settings → Service connections → New service connection**
148+
- Connection type: `Azure Resource Manager`
149+
- Authentication method: `Workload Identity Federation (manual)`
150+
- Issuer: `https://token.actions.githubusercontent.com`
151+
- Subject identifier: `repo:NHSDigital/manage-breast-screening-gateway:environment:[environment]`
152+
- Enter the following (from the Bicep output or by running the commands below):
153+
154+
```bash
155+
# Client ID and tenant ID of mi-mbsgw-[environment]-adotoaz-uks
156+
az identity show --name mi-mbsgw-[environment]-adotoaz-uks \
157+
--resource-group rg-mi-[environment]-uks \
158+
--subscription "[hub-subscription-name]" \
159+
--query "{clientId: clientId, tenantId: tenantId}" -o json
160+
161+
# Spoke subscription ID
162+
az account show --subscription "[spoke-subscription-name]" --query id -o tsv
163+
```
164+
165+
- Scope level: `Subscription`
166+
- Subscription ID: `[spoke-subscription-id]`
167+
- Subscription name: `[spoke-subscription-name]`
168+
- Resource group for service connection: leave blank
169+
- Service connection name: `mbsgw-[environment]`
170+
- Do **NOT** tick: Grant access permission to all pipelines
171+
172+
Manage service connection security:
173+
174+
- Navigate to the service connection → **⋮ → Security**
175+
- Add both `Deploy to Azure - [environment]` and `Deploy Gateway App - [environment]` pipelines with permission to use the connection
176+
177+
### ADO environment
178+
179+
- Navigate to **Pipelines → Environments → New environment**
180+
- Name: `[environment]`
181+
- Resource: None
182+
- Set exclusive lock (all environments except `review`)
183+
- Add pipeline permission for both `Deploy to Azure - [environment]` and `Deploy Gateway App - [environment]`
184+
185+
## GitHub
186+
187+
- Create a GitHub environment named `[environment]`:
188+
- Navigate to **Settings → Environments → New environment**
189+
190+
- Add protection rules (all environments except `review`):
191+
- Deselect `Allow administrators to bypass configured protection rules`
192+
- In `Deployment branches and tags` choose `Selected branches and tags`
193+
- Click `Add deployment branch or tag rule` and enter `main`
194+
195+
- Add the following environment secrets (values from `mi-mbsgw-[environment]-ghtoado-uks`):
196+
197+
```bash
198+
# AZURE_CLIENT_ID — client ID of mi-mbsgw-[environment]-ghtoado-uks
199+
az identity show --name mi-mbsgw-[environment]-ghtoado-uks \
200+
--resource-group rg-mi-[environment]-uks \
201+
--subscription "[hub-subscription-name]" \
202+
--query clientId -o tsv
203+
204+
# AZURE_TENANT_ID
205+
az account show --query tenantId -o tsv
206+
207+
# AZURE_SUBSCRIPTION_ID — hub subscription ID (where the managed identity has Reader)
208+
az account show --subscription "[hub-subscription-name]" --query id -o tsv
209+
```
210+
211+
| Secret | Value |
212+
| ---------------------- | ------------------------------------------------- |
213+
| `AZURE_CLIENT_ID` | Client ID of `mi-mbsgw-[environment]-ghtoado-uks` |
214+
| `AZURE_TENANT_ID` | Azure tenant ID |
215+
| `AZURE_SUBSCRIPTION_ID` | Hub subscription ID |
216+
217+
## First run
218+
219+
1. Merge a pull request to `main` to trigger the CI/CD pipeline, or trigger the ADO pipeline manually from Azure DevOps
220+
2. On the first run you will be prompted to authorise in ADO:
221+
- Service connection access by the pipeline
222+
- Agent pool access by the environment
223+
3. Check the pipeline completes successfully — Terraform will create:
224+
- Arc-enabled server RBAC assignments
225+
- Azure Relay hybrid connections (empty on first deploy — populated as Arc machines onboard)
226+
- Log Analytics workspace and Data Collection Rule
227+
- Azure Monitor policy assignments and remediation tasks
228+
229+
## Arc machine discovery
230+
231+
Terraform automatically discovers Arc machines registered in the Arc-enabled servers resource group and creates a relay Hybrid Connection for each one. On first deploy the resource group is empty, so no connections are created. After each Arc machine is onboarded at a hospital site, run `terraform apply` to pick it up:
232+
233+
```bash
234+
make [environment] terraform-apply
235+
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
enable_gateway_test_vm = true
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
vnet_address_space = "10.131.0.0/16"
21
enable_gateway_test_vm = true

infrastructure/modules/arc-infra/arc.tf

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
# Resource group for Arc-enabled servers (where machines register on onboarding)
2-
resource "azurerm_resource_group" "arc_enabled_servers" {
1+
# Resource group for Arc-enabled servers — created by resource-group-init (Bicep),
2+
# not managed by Terraform, so it exists before this module runs.
3+
data "azurerm_resource_group" "arc_enabled_servers" {
34
count = var.enable_arc_servers ? 1 : 0
45

5-
name = "${var.resource_group_name}-arc-enabled-servers"
6-
location = var.region
6+
name = "${var.resource_group_name}-arc-enabled-servers"
77
}
88

99
# Look up the Arc onboarding service principal by its standard name
@@ -18,7 +18,7 @@ module "arc_onboarding_role" {
1818

1919
source = "../dtos-devops-templates/infrastructure/modules/rbac-assignment"
2020

21-
scope = azurerm_resource_group.arc_enabled_servers[0].id
21+
scope = data.azurerm_resource_group.arc_enabled_servers[0].id
2222
role_definition_name = "Azure Connected Machine Onboarding"
2323
principal_id = data.azuread_service_principal.arc_onboarding[0].object_id
2424
}

0 commit comments

Comments
 (0)