Learning objectives
- Exchange a short-lived GitLab OIDC ID token for AWS access.
- Apply the immutable plan artifact from the same commit, tool, account, region, and backend configuration.
- Use manual approval, serialization, cancellation evidence, and read-only drift checks.
Pipeline contract
| Stage | Safety property |
|---|---|
| Format, validate, native test | No cloud mutation. |
| Plan | OIDC identity preflight and one binary plan artifact. |
| Apply | Manual protected production action that consumes exactly that artifact. |
| Verify and drift | Read outputs, then use -refresh-only; record cancellation or partial-failure evidence. |
The example is opt-in. The root GitLab Pages pipeline does not include it and therefore cannot execute Terraform, OpenTofu, or AWS commands.
OIDC is a bounded identity
GitLab issues a short-lived ID token with audience sts.amazonaws.com. AWS verifies the OIDC issuer and the CI role trust restricts both audience and the subject claim for one project path and protected ref. The role starts without generic administrator permissions. Add only the exact state, KMS, and service actions supported by the reviewed plan.
Lab: review the sample locally
Read the opt-in README and inspect the YAML. Do not add it to the root pipeline or run cloud commands for this course task.
# Terraform command pair for the native mock test.
terraform -chdir=learn-terraform/capstone/infra/modules/network test -test-directory=tests
# OpenTofu command pair for the same native mock test.
tofu -chdir=learn-terraform/capstone/infra/modules/network test -test-directory=tests
# Static review only: prove the Pages pipeline has no include.
python3 -m unittest tests/test_course.py -v
python3 tools/check_course.py learn-terraform index.htmlReview before enabling
- Confirm the account-level OIDC provider already exists and its issuer is publicly reachable.
- Check trust restricts issuer,
aud, project path, branch ref, and protected production environment. - Confirm the plan job writes a binary plan plus commit, tool, region, account, and backend digest metadata with a short expiry.
- Confirm
resource_groupserializes plan, apply, verify, and drift for the production state. - Confirm the apply job is manual, has no automatic rule, and invokes only
apply tfplan.
Failure drill
Verify the controls
- Run format checks for both CLIs and the native mocked module tests.
- Confirm each pinned Linux AMD64 ZIP passes its official SHA256 check before
unzip. - Parse the YAML with the GitLab CI linter before any include is proposed.
- Check no static AWS access-key variables, no cleanup job, and no automatic apply exist.
- Confirm the drift job is
-refresh-onlyand its result is evidence, not a mutation.
Cleanup
Disable IAC_PIPELINE or remove the local include used for an experiment. Let the one-hour plan and evidence artifacts expire. Do not add a CI cleanup job: resource removal is a separate reviewed change with dependency, retention, and recovery checks.
Quick check
Why does the apply job not run plan again?
It must apply the reviewed plan artifact, not recalculate a potentially different change.
Does a manual button make a role safe by itself?
No. The trust claims, least-privilege permissions, protected environment, artifact checks, and state serialization all matter.
Recap and next
CI is a narrow execution path: ephemeral identity, one reviewed artifact, one protected operator action, and evidence when the path stops.
The AWS production foundation is complete. Next, the capstone can add EKS access with the same state and identity boundaries.