🛸 Hitchhiker's Guide — Phase 08: CI/CD Pipelines & Secure Supply Chain

Read this if: you can make a .yml turn green but "why did the deploy skip," "how does keyless auth actually work," and "how do you roll back automatically" still sound like other people's problems. They're yours now. This is the compressed tour the WARMUP derives slowly. Skim it, then read the WARMUP.


0. The 30-second mental model

A pipeline is a DAG with a security boundary bolted on. Three things to own:

  1. The graph — stages depend on stages; the engine runs them in topological order; a stage runs iff its dependencies satisfied its condition and any required approval was granted. Failure propagates: a failed/skipped/blocked dep is not succeeded, so the default-condition dependents skip.
  2. The identity — the pipeline auths to Azure with no stored secret via OIDC workload-identity federation: it presents a short-lived OIDC token; Entra exchanges it only if a federated credential matches iss + sub + aud exactly. The subject pins the branch/environment — the wrong subject is denied. That denial is the security.
  3. The rollout — blue-green (instant swap, instant rollback) or canary (ramp %, gate on health, auto-rollback on the first failing step). The gate exists before the incident.

One sentence to tattoo: a stage runs iff every dependency satisfied its condition and its approval was granted — and the pipeline proves who it is with a subject-scoped OIDC token, not a stored secret.

1. The execution model, one breath

topo-order the DAG → for each stage:
    condition not satisfied by deps?  → SKIPPED
    needs approval, not granted?      → BLOCKED
    else run it → SUCCEEDED / FAILED   (failure propagates to dependents)

succeeded = all deps green (default) · always = run no matter what (notify/cleanup) · failed = run only if a dep FAILED (rollback). Skipped ≠ failed ≠ blocked — three statuses, three causes.

2. The OIDC exchange, one breath

run starts → CI mints OIDC JWT { iss, sub=repo:org/app:ref:refs/heads/main, aud } (lives ~minutes)
          → POST to Entra as client_assertion (jwt-bearer)
          → Entra verifies signature (JWKS) + exp, then MATCHES iss & sub & aud to a federated cred
          → match? mint Azure access token (scoped by RBAC) : DENY (AADSTS700213)

No secret stored, anywhere. The subject is the branch/environment knob. Deny by default.

3. The numbers & rules to tattoo on your arm

ThingNumber / rule
Default stage conditionsucceeded — needs all deps green
The three conditionssucceeded / always / failed
OIDC token lifetimeminutes (exp), minted per run
Entra exchange audienceapi://AzureADTokenExchange
Match requirementiss AND sub AND aud, exact, deny by default
Federated creds per identity~20 max
GitHub OIDC permissionpermissions: id-token: write (+ contents: read)
Wrong-branch errorAADSTS700213 (no matching federated credential)
Blue-green rollbackinstant (swap back; old slot stays warm)
Canary on failureroll back to 0% at the first failing step
Stored secret =the worst CI security smell — don't store it, federate

4. az / gh one-liners you'll actually run

# Create an Entra app + service principal to federate (no secret created!)
az ad app create --display-name "ci-deployer"
APP_ID=$(az ad app list --display-name ci-deployer --query "[0].appId" -o tsv)

# Add a FEDERATED credential pinned to main (this is the security boundary)
az ad app federated-credential create --id "$APP_ID" --parameters '{
  "name": "github-main",
  "issuer": "https://token.actions.githubusercontent.com",
  "subject": "repo:contoso/app:ref:refs/heads/main",
  "audiences": ["api://AzureADTokenExchange"]
}'

# Give the identity ONLY the role it needs, at the narrowest scope (P04)
az role assignment create --assignee "$APP_ID" --role Contributor \
  --scope /subscriptions/<sub>/resourceGroups/<rg>

# GitHub Actions: log in to Azure with OIDC, NO secret in the repo
#   permissions: { id-token: write, contents: read }
#   - uses: azure/login@v2
#     with: { client-id: ..., tenant-id: ..., subscription-id: ... }

# App Service blue-green: swap staging → production (instant cutover + rollback)
az webapp deployment slot swap -g <rg> -n <app> --slot staging --target-slot production

# Azure Pipelines: a condition that only runs a rollback stage on failure
#   - stage: rollback
#     dependsOn: deploy
#     condition: failed()

# List a pipeline's runs / re-run / approve an environment check
az pipelines runs list --output table
gh run list ; gh run rerun <run-id> ; gh run watch <run-id>

5. War story shapes you'll relive

  • "The deploy stage just didn't run, no error." → a dependency failed (or skipped), so its default succeeded condition wasn't met → it skipped. Skipped ≠ failed. Check the upstream results and the stage's condition.
  • "Rollback fired on a green build." → someone wrote condition: always() where they meant failed(). always runs no matter what; failed only on a real failure.
  • "A fork's PR got our cloud credential." → the federated-credential subject was too broad (matched PRs / any branch). Pin it to ...:ref:refs/heads/main or ...:environment:production; never federate pull_request for privileged deploys.
  • "Our secret leaked in a build log and someone's mining crypto in our sub." → you were storing a long-lived SP secret. The fix isn't "redact logs harder," it's federate — remove the secret entirely.
  • "The bad release took prod down for 40 minutes." → no canary, no health gate, no auto- rollback. A canary would've caught it at 5% and rolled back to 0% with nobody paged.
  • "It only fails on Tuesdays / when re-run." → non-determinism in the build (unsorted parallelism, time/random without a seed). CI must be reproducible.

6. Vocabulary that signals you've shipped

  • DAG / dependsOn / needs — the pipeline is a graph; stages are nodes, deps are edges.
  • Conditionsucceeded/always/failed; the gate that decides skip vs run.
  • Skipped vs blocked vs failed — condition-unmet vs approval-withheld vs ran-and-failed.
  • Environment / approval gate — a protected target that pauses for a human or a check.
  • Matrix — one job spec → the Cartesian product of variables → N parallel jobs.
  • OIDC / workload-identity federation — keyless auth; trust a token's iss/sub/aud, no secret.
  • Federated credential — the (issuer, subject, audience) trust object on an Entra identity.
  • Subject (sub) — the branch/environment the token is for; the security boundary.
  • Blue-green / canary / rolling — swap two slots / ramp+gate / batch-replace.
  • Health gate — the automated check that triggers an automatic rollback.
  • SBOM / provenance / attestation / SLSA — what's in it / who built it / signed proof / the levels.
  • Keyless signing (Sigstore) — sign with the pipeline's OIDC identity, no key to manage.

7. Beginner mistakes that mark you in interviews

  1. Calling a pipeline "a script" — missing that it's a DAG with conditions and gates.
  2. Confusing always() and failed() (and being surprised when rollback runs on success).
  3. Thinking "skipped" means "failed" — they're different statuses with different causes.
  4. Storing a long-lived service-principal secret in CI instead of federating with OIDC.
  5. Setting the federated-credential subject too broad (any branch / PRs) — a fork PR then owns your cloud.
  6. Treating "deploy" as a copy, with no canary/health gate/rollback path defined up front.
  7. Saying "it's signed so it's safe" — signed ≠ attributable to my hardened builder (that's provenance + SLSA).
  8. A matrix that explodes into hundreds of jobs and a surprise bill — prune to what catches bugs.

8. How this phase pays off later

  • The DAG + conditions model is how you reason about every pipeline you'll ever debug, including the IaC pipelines from P01/P02 and the container builds from P07.
  • OIDC federation is the keyless-auth pattern that also underpins managed identities (P12) and is the answer to half of "how do you secure X without a secret."
  • Blue-green / canary + health gates are the reliability primitives that connect straight into P14 (HA, rollback, circuit breakers) and the capstone (P15).
  • The supply-chain framing is the security-design language for any architecture review.

Now read the WARMUP slowly, then build the engine. Once run_pipeline, exchange_oidc_token, and canary_rollout are green, you can explain a deploy that skipped, an auth with no secret, and an automatic rollback — which is the whole Round-2/Round-5 signal for this phase.