Skip to content

Commit e4a1e87

Browse files
Add deployment capability docs and register in nav
Move the deployment capability spec, index, and GitHub + Azure design from MSXOrg/Platform into Capabilities/deployment, converting cross-repo links to relative links and registering the pages in the site navigation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 65591bd commit e4a1e87

4 files changed

Lines changed: 490 additions & 0 deletions

File tree

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
---
2+
title: GitHub + Azure design
3+
description: How the deployment spec is delivered with GitHub as the deployment platform and Azure as the service provider, using Terraform to compute and apply the approved effect.
4+
---
5+
6+
# Deployment — GitHub + Azure design
7+
8+
This design delivers the [Deployment spec](../spec.md) for one combination:
9+
**GitHub** as the deployment platform and **Azure** (including Microsoft Entra
10+
ID) as the service provider, with **Terraform (open source)** as the change
11+
engine. The computed effect the spec speaks of is a **saved Terraform plan**;
12+
the record of state is a remote Terraform state per environment.
13+
14+
Another design covers another combination — Azure DevOps with Azure, GitHub with
15+
AWS, GitHub with GitHub — without changing the spec.
16+
17+
## Approach
18+
19+
A change is proposed as a pull request. On the pull request the process computes
20+
the effect for **every environment in the promotion path** and shows them all;
21+
approving and merging the pull request is the single approval of the code change
22+
together with all of those effects ([FR2](../spec.md#fr2), [FR3](../spec.md#fr3)).
23+
The merge then runs the promotion, deploying to each environment in order using
24+
the **exact plans that were approved** ([FR4](../spec.md#fr4)), running tests
25+
between environments ([FR7](../spec.md#fr7)) and recording every action
26+
([FR9](../spec.md#fr9)).
27+
28+
```mermaid
29+
flowchart TB
30+
subgraph Review [Pull request — one approval]
31+
C[Change as code] --> P[Plan per environment\ndev · test · preprod · prod]
32+
P --> R[All plans + tests shown in the PR]
33+
end
34+
R -->|team approves code + all effects, merges| P0
35+
subgraph Promote [Merge — promotion using approved plans]
36+
P0[Apply approved dev plan] --> T0[Automated tests] --> P1[Apply approved test plan]
37+
P1 --> T1[Automated tests] --> P2[Apply approved preprod plan]
38+
P2 --> T2[Automated tests] --> P3[Apply approved prod plan]
39+
end
40+
P3 --> Rec[Every action recorded]
41+
P0 -.effect no longer valid.-> Stop[Stop · recompute · re-approve]
42+
```
43+
44+
### Why this shape
45+
46+
- **One approval for the whole path.** Because the reviewer sees the effect on
47+
every environment, a single approval is sufficient; the design adds no
48+
per-environment reviewer gate, matching the spec's non-goal.
49+
- **Approved plans, not re-derived ones.** The saved plans reviewed on the pull
50+
request are the artifacts the promotion applies. This trades the convenience of
51+
re-planning at deploy time for the guarantee the spec requires — the deployed
52+
effect equals the approved effect.
53+
54+
### Alternatives considered
55+
56+
- **Re-plan at deploy.** Rejected: the deploy would compute its own effect, which
57+
can differ from the approved one — it fails [FR4](../spec.md#fr4).
58+
- **Approve only the source diff, plan at deploy.** Rejected: the reviewer never
59+
sees the effect, failing [FR2](../spec.md#fr2)[FR3](../spec.md#fr3).
60+
- **A required-reviewer gate on each environment.** Rejected as redundant: the
61+
effects for all environments are already approved together; extra gates add
62+
friction without adding a decision.
63+
64+
## Components
65+
66+
| Component | Role |
67+
| --- | --- |
68+
| **Reusable workflow** (`.github/workflows/terraform.yml`, `workflow_call`) | The provider-agnostic core: init, format, validate, plan, render, policy scan, test, apply. |
69+
| **Plan (pull-request) workflow** | For every environment in the path, computes `plan -out`, renders it into the pull request, scans it, and uploads it as the approved artifact. |
70+
| **Promotion (merge) workflow** | Walks the environments in order, applying each approved plan and running tests between them. |
71+
| **Azure identity step** | `azure/login` federated sign-in; sets `ARM_USE_OIDC=true` so the `azurerm` and `azuread` providers use the same token. |
72+
| **State backend** | Azure Storage per environment, with blob-lease locking and encryption at rest — the record of state. |
73+
| **Environments** | GitHub Environments per environment name — the per-environment **identity boundary** and deployment branch policy (not an approval gate). |
74+
| **Break-glass workflow** (`workflow_dispatch`) | The emergency path. |
75+
| **Drift job** (`schedule`) | Periodic `plan -detailed-exitcode` that opens an issue on drift. |
76+
77+
## The effect is a saved plan
78+
79+
Terraform's saved plan is what makes "approve the effect, then apply exactly it"
80+
concrete:
81+
82+
1. `terraform plan -out=tfplan.<env>` computes the effect against that
83+
environment's state and writes it to a file that pins provider versions and
84+
the state's **serial** and **lineage**.
85+
2. `terraform show` renders it for humans (pull-request comment) and as JSON for
86+
policy scanning; `sha256(tfplan.<env>)` is recorded.
87+
3. `terraform apply tfplan.<env>` carries out **exactly** that effect, and
88+
refuses with `Saved plan is stale` if the state advanced — the native backstop
89+
for [FR5](../spec.md#fr5).
90+
91+
## Review and approval
92+
93+
The pull-request workflow computes a plan for **each** environment in the
94+
promotion path and makes all of them visible in the pull request, alongside
95+
format, validation, policy, and test results. These are **required checks**, so a
96+
change cannot merge unless every environment's effect computed successfully. A
97+
branch ruleset on `main` requires review (CODEOWNERS for `**/*.tf`), dismisses
98+
stale approvals on new commits, and requires branches to be up to date — so the
99+
shown effects reflect current `main`. Approving and merging is the single
100+
approval the spec requires ([FR3](../spec.md#fr3)); the approved plan artifacts
101+
are retained for the promotion.
102+
103+
## Promotion using the approved plans
104+
105+
On merge, the promotion workflow retrieves the plan artifacts approved on the
106+
pull request (keyed to the merge commit) and, for each environment in order:
107+
108+
1. **Verifies** `sha256(tfplan.<env>)` against the value recorded at plan time,
109+
defeating artifact swap.
110+
2. **Applies** `terraform apply tfplan.<env>` — exactly the approved effect
111+
([FR4](../spec.md#fr4)). If the plan is stale, the apply fails closed and the
112+
promotion stops; a fresh plan is produced and re-approved
113+
([FR5](../spec.md#fr5)).
114+
3. **Tests** — runs the environment's automated tests; a failing required test
115+
stops the promotion before the next environment ([FR7](../spec.md#fr7)).
116+
117+
Deployments against the same state are **serialised** by a concurrency group
118+
keyed per state with `cancel-in-progress: false`, backed by the state lock, and
119+
an in-flight apply is never cancelled ([FR6](../spec.md#fr6)).
120+
121+
## Identity and access (OIDC)
122+
123+
- The workflow requests the GitHub OIDC token with `permissions: id-token: write`;
124+
`azure/login` federates on it using the non-secret `client-id`, `tenant-id`,
125+
and `subscription-id` — no client secret exists ([NFR1](../spec.md#nfr1)).
126+
- Azure federated credentials pin the subject claim
127+
`repo:MSXOrg/Platform:environment:<env>`, binding identity to the environment
128+
([NFR3](../spec.md#nfr3)).
129+
- Two identities per environment: **read-only** for computing the effect and
130+
**write** for applying it, the write identity reachable only from that
131+
environment ([NFR2](../spec.md#nfr2)).
132+
- Entra ID resources use the `azuread` provider on the same federated token —
133+
Entra rides the Azure identity context, no separate secret. Managing GitHub
134+
itself uses the `github` provider with a GitHub App installation token, never a
135+
personal access token.
136+
137+
## Break-glass path
138+
139+
The emergency path ([FR8](../spec.md#fr8)) is a `workflow_dispatch` "emergency
140+
deploy" with required inputs `reason`, `incident_id`, target environment, and a
141+
typed confirmation, restricted to authorised operators. It still runs
142+
`plan -out` then `apply tfplan`, so the effect is shown before it deploys; it
143+
records the inputs and logs as an immutable run record, and opens a follow-up
144+
issue to bring the change back through the standard pull-request path. There is
145+
no flag on the standard workflow that skips review.
146+
147+
## Records and drift
148+
149+
Every run's rendered plans, apply logs, and test results are retained as run
150+
artifacts and logs, and each apply is recorded in the environment's deployment
151+
history — the audit trail for [FR9](../spec.md#fr9)/[NFR4](../spec.md#nfr4). A
152+
scheduled `plan -detailed-exitcode` job detects drift and opens an issue
153+
([FR10](../spec.md#fr10)).
154+
155+
## Interface
156+
157+
```yaml
158+
# .github/workflows/terraform.yml (reusable — workflow_call)
159+
on:
160+
workflow_call:
161+
inputs:
162+
environment: { type: string, required: true } # dev | test | preprod | prod
163+
working-directory: { type: string, required: true }
164+
action: { type: string, default: plan } # plan | apply
165+
terraform-version: { type: string, default: "1.9.x" }
166+
```
167+
168+
The core is provider-agnostic; the Azure sign-in is the single service-provider
169+
step. A different service provider (AWS) or deployment platform (Azure DevOps)
170+
reuses this design's shape with its own identity step and is documented as its
171+
own design under [Deployment](../index.md).
172+
173+
## Security boundaries and threats
174+
175+
- **Credential exposure** — OIDC only; no static cloud secret exists, and the
176+
default token is read-only.
177+
- **Plan artifact confidentiality** — a saved plan can contain sensitive values
178+
in plaintext. Retention is short, artifacts are restricted, sensitive variables
179+
and outputs are marked, and pull-request comments carry scrubbed renders; on a
180+
public repository plan artifacts are never exposed unscrubbed.
181+
- **Artifact swap** — each apply verifies `sha256(tfplan.<env>)` before applying.
182+
- **Cross-environment escalation** — federated subjects are pinned per
183+
environment; the write identity is reachable only from its environment.
184+
- **Stale apply** — Terraform's serial/lineage guard, plus serialisation and
185+
branch-freshness rules.
186+
187+
## Testing and operability
188+
189+
- **Dry run** the full path on `dev`: pull-request plans for every environment,
190+
review, merge, promotion with tests.
191+
- **Exercise the stale path** by mutating state between approval and deploy and
192+
confirming the apply fails closed.
193+
- **Exercise break-glass** end to end and confirm the reconciliation follow-up is
194+
opened and attribution is recorded.
195+
196+
## Repository hardening
197+
198+
Third-party actions are pinned to commit SHAs; allowed actions are restricted;
199+
secret scanning with push protection is enabled as defence in depth even under an
200+
OIDC model.
201+
202+
## Decisions
203+
204+
One-way-door decisions are recorded here beside the spec, per
205+
[Decision before change](../../../Ways-of-Working/Principles/AI-First-Development.md#decision-before-change).
206+
207+
- **Approve every environment's effect on the pull request; apply those exact
208+
saved plans on merge.** Chosen over re-plan-at-deploy (does not apply the
209+
approved effect) and per-environment gates (redundant given the effects are
210+
already approved together). Revisit only if a saved plan can no longer be the
211+
review-to-deploy contract.
212+
213+
## References
214+
215+
- GitHub — security hardening with OpenID Connect: <https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect>
216+
- GitHub — configuring OIDC in Azure: <https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-azure>
217+
- GitHub — managing environments: <https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments>
218+
- GitHub — reusing workflows: <https://docs.github.com/en/actions/using-workflows/reusing-workflows>
219+
- GitHub — control concurrency: <https://docs.github.com/en/actions/using-jobs/using-concurrency>
220+
- HashiCorp — automate Terraform with GitHub Actions: <https://developer.hashicorp.com/terraform/tutorials/automation/github-actions>
221+
- HashiCorp — `terraform plan` / `terraform apply` (saved plan): <https://developer.hashicorp.com/terraform/cli/commands/plan> · <https://developer.hashicorp.com/terraform/cli/commands/apply>
222+
- Terraform AzureRM provider — OIDC: <https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_oidc>
223+
- Azure — `azure/login`: <https://github.com/Azure/login>
224+
- Microsoft — GitHub Actions + Terraform OIDC on Azure sample: <https://learn.microsoft.com/en-us/samples/azure-samples/github-terraform-oidc-ci-cd/github-terraform-oidc-ci-cd/>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
title: Deployment
3+
description: How a change to managed resources is approved together with its effect and deployed exactly as approved — one spec, one design per deployment platform and service provider.
4+
---
5+
6+
# Deployment
7+
8+
How a change to the resources Platform manages moves from a proposal to live: its
9+
**effect on the resources** is computed and shown, the team approves the code
10+
change together with that effect across every environment it will pass through,
11+
and the approved effect is exactly what is deployed — with every action recorded.
12+
13+
The **spec** is the durable contract and is deliberately free of any technology.
14+
Each **design** delivers that contract for one combination of **deployment
15+
platform** and **service provider** (for example GitHub with Azure, Azure DevOps
16+
with Azure, GitHub with AWS, or GitHub with GitHub). Adding a combination adds a
17+
design; it never changes the spec.
18+
19+
## Spec
20+
21+
| Page | Description |
22+
| --- | --- |
23+
| [Spec](spec.md) | The why and what — a change is approved together with its effect, and the approved effect is exactly what deploys. |
24+
25+
## Designs
26+
27+
| Design | Deployment platform | Service provider | Description |
28+
| --- | --- | --- | --- |
29+
| [GitHub + Azure](designs/github-azure.md) | GitHub | Azure | GitHub Actions and Terraform deploy Azure and Entra resources with passwordless identity, approving the code change together with its per-environment effect. |

0 commit comments

Comments
 (0)