Checkpoint Lab — Secrets Management, External Secret Providers, Protected Data, Rotation, and Least-Privilege Patterns
The checkpoint implements a fully synthetic local secret provider with one authorized principal and one denied principal. You will rotate from version v1 to v2, prove that old material is no longer retrievable, capture only non-secret evidence, document incident/revocation actions, and clean up every temporary file created by the lab.
Learning objectives
- Run one authorized and one denied synthetic-provider job with no real credentials and no secret values in logs or artifacts.
- Rotate a generated secret from v1 to v2 and prove v1 becomes unavailable while v2 remains retrievable to the authorized principal.
- Inject a controlled authorization or stale-version failure and repair the causal trust/rotation state rather than weakening access controls.
- Produce an evidence packet containing source/ref/SHA, pipeline/job/runner identity, provider/principal/version metadata, digests, denial evidence, assumptions, and cleanup.
- Document the incident steps that would be required if a real secret were exposed and bridge the model into Chapter 08 rules and conditional execution.
1. Checkpoint goal and acceptance evidence
Prove five independent states:
- The pipeline/job identity is known: source/ref/SHA, pipeline ID, job ID, runner.
- An authorized principal can retrieve only the active synthetic secret version.
- An unauthorized principal is denied and receives no secret file.
- After rotation v1 → v2, v1 is denied and v2 succeeds for the authorized principal.
- All temporary secret material is deleted while non-secret evidence remains.
2. Checkpoint pipeline: same provider model, separate authorization outcomes
stages: [verify]
default:
before_script:
- set -eu
- mkdir -p ch07-evidence
- printf 'source=%s\n' "$CI_PIPELINE_SOURCE" | tee ch07-evidence/context.txt
- printf 'ref=%s\n' "$CI_COMMIT_REF_NAME" | tee -a ch07-evidence/context.txt
- printf 'sha=%s\n' "$CI_COMMIT_SHA" | tee -a ch07-evidence/context.txt
- printf 'pipeline=%s job=%s\n' "$CI_PIPELINE_ID" "$CI_JOB_ID" | tee -a ch07-evidence/context.txt
provider_checkpoint:
stage: verify
script:
- |
PROVIDER_DIR="$(mktemp -d)"
trap 'rm -rf "$PROVIDER_DIR"' EXIT
mkdir -p "$PROVIDER_DIR/versions"
umask 077
openssl rand -hex 32 > "$PROVIDER_DIR/versions/v1"
openssl rand -hex 32 > "$PROVIDER_DIR/versions/v2"
printf 'v1\n' > "$PROVIDER_DIR/active"
provider_fetch() {
principal="$1"; requested="$2"; output="$3"
active="$(cat "$PROVIDER_DIR/active")"
if [ "$principal" != 'project/ch07:deploy' ]; then return 77; fi
if [ "$requested" != "$active" ]; then return 78; fi
cp "$PROVIDER_DIR/versions/$requested" "$output"
chmod 600 "$output"
}
# 1) Authorized v1 succeeds.
provider_fetch 'project/ch07:deploy' 'v1' "$PROVIDER_DIR/authorized-v1"
printf 'authorized_v1=allowed\n' | tee ch07-evidence/results.txt
printf 'authorized_v1_sha256=%s\n' "$(sha256sum "$PROVIDER_DIR/authorized-v1" | awk '{print $1}')" | tee -a ch07-evidence/results.txt
rm -f "$PROVIDER_DIR/authorized-v1"
# 2) Unauthorized principal is denied.
set +e
provider_fetch 'fork/untrusted' 'v1' "$PROVIDER_DIR/denied"
denied_rc=$?
set -e
test "$denied_rc" -eq 77
test ! -e "$PROVIDER_DIR/denied"
printf 'unauthorized_v1=denied rc=%s\n' "$denied_rc" | tee -a ch07-evidence/results.txt
# 3) Rotate to v2.
printf 'v2\n' > "$PROVIDER_DIR/active"
# 4) v1 is now revoked.
set +e
provider_fetch 'project/ch07:deploy' 'v1' "$PROVIDER_DIR/revoked"
old_rc=$?
set -e
test "$old_rc" -eq 78
test ! -e "$PROVIDER_DIR/revoked"
printf 'authorized_v1_after_rotation=revoked rc=%s\n' "$old_rc" | tee -a ch07-evidence/results.txt
# 5) v2 succeeds.
provider_fetch 'project/ch07:deploy' 'v2' "$PROVIDER_DIR/authorized-v2"
printf 'authorized_v2=allowed\n' | tee -a ch07-evidence/results.txt
printf 'authorized_v2_sha256=%s\n' "$(sha256sum "$PROVIDER_DIR/authorized-v2" | awk '{print $1}')" | tee -a ch07-evidence/results.txt
rm -f "$PROVIDER_DIR/authorized-v2"
artifacts:
when: always
paths:
- ch07-evidence/context.txt
- ch07-evidence/results.txt
expire_in: 1 day
mktemp and are deleted by the
trap. Never reuse this digest policy for low-entropy real passwords
without a threat-model review.
3. Predict the state transitions before running
| Step | Expected provider state | Expected evidence |
|---|---|---|
| Authorized v1 | active=v1; allowed principal requests v1. | Allowed; temporary file exists only until explicit removal. |
| Denied v1 | active=v1; untrusted principal requests v1. | Return 77; no output file. |
| Rotate | active pointer changes v1 → v2. | No secret value logged. |
| Old v1 after rotation | allowed principal requests non-active v1. | Return 78; no output file. |
| Authorized v2 | active=v2; allowed principal requests v2. | Allowed; new synthetic digest differs from v1. |
4. Run on a disposable branch and preserve immutable identity
LAB_BRANCH="ch07/checkpoint"
git switch -c "$LAB_BRANCH"
git add -- .gitlab-ci.yml
git diff --cached --check
git commit -m "ch07: secret lifecycle checkpoint"
LAB_SHA="$(git rev-parse HEAD)"
printf '%s\n' "$LAB_SHA"
git push -u origin "$LAB_BRANCH"
In GitLab, record the pipeline ID, job ID, pipeline source, ref,
SHA, runner identity, and job status. Confirm the job SHA equals the
recorded LAB_SHA. A green job proves the assertions in
this synthetic provider simulation; it does not prove a real
provider’s policy or external target state.
5. Inspect the evidence packet without opening secret material
Download or browse only the two evidence text files. Expected result lines resemble:
authorized_v1=allowed
authorized_v1_sha256=<synthetic-digest>
unauthorized_v1=denied rc=77
authorized_v1_after_rotation=revoked rc=78
authorized_v2=allowed
authorized_v2_sha256=<different-synthetic-digest>
The digests prove the synthetic v1/v2 values differ without retaining the values. The denial codes prove policy outcomes in the simulation.
6. Failure injection: stale version or wrong principal
Run exactly one controlled failure before the successful checkpoint:
-
Change the first authorized request from
v1tov2while active is still v1. Expected return 78. -
Or change the authorized principal to
project/ch07:build. Expected return 77.
Preserve the failing job ID and non-secret result. Repair only the
requested version/principal mapping. Do not broaden
provider_fetch policy.
7. Real-world provider mapping and assumptions
If this were Vault/AWS/Azure/GCP/GitLab Secrets Manager, the checkpoint would additionally record:
- provider/offering/tier and exact integration mode;
- GitLab/Runner version assumptions where the feature requires them;
- ID-token audience and expected trust-policy claims (never token value);
- secret logical reference/version and provider policy identity;
- provider audit event or denial status;
- rotation/revocation record and owner.
The course does not require those paid/external systems; this is the architecture mapping only.
8. Incident drill: what if the secret had been real?
Write a short incident note containing these actions in order:
- Mark the credential as potentially compromised and preserve pipeline/job/source identifiers.
- Revoke or disable the exposed credential at the authoritative provider.
- Issue/activate replacement material only for the intended workload.
- Prove old material is denied and new material succeeds.
- Review provider/service audit logs for suspicious use during the exposure window.
- Remove secret-bearing logs/artifacts/caches where authorized and fix the source code/configuration that caused exposure.
- Document owner, timeline, affected resources, and preventive control.
9. Cleanup and rollback
- The job trap deletes the provider temp directory automatically; the job should not artifact it.
-
Delete the disposable
ch07/checkpointbranch only after recording its SHA and pipeline/job IDs. -
Remove any synthetic
LAB_FAKE_CREDENTIALproject variable or protected lab branch created in Lesson 2. - Keep only the non-secret evidence packet and assumptions/limitations note.
10. Required evidence packet
Project/path, pipeline source, ref, immutable SHA, pipeline ID.
Job ID/name/status and first-failure job ID before repair.
Runner ID/description/version/executor where visible.
Local synthetic provider, active version, allowed principal, denial code meanings.
Allowed result + temporary-file lifecycle + synthetic digest; no value.
Unauthorized principal, expected denial code, proof no output file existed.
v1 allowed before rotation, v1 denied after rotation, v2 allowed.
Free/disposable simulation; no external provider; no real credentials; current docs verified 2026-09-11.
Temporary provider removed; synthetic settings/branch removed as appropriate.
Real-world revocation, audit, replacement, cleanup, and preventive-control sequence.
11. What Chapter 07 adds to the operating model
You can now separate sensitive-value mechanics from true secret lifecycle. A secure pipeline knows which job identity is authorized, requests only the necessary secret reference, avoids log/artifact exposure, uses short-lived/federated identity where possible, rotates and revokes old material, and preserves non-secret evidence.
Chapter 08 moves into workflow:rules and job
rules. That topic becomes much safer once you
understand why pipeline-source/ref decisions determine which
jobs—and therefore which secret capabilities—can exist at all.
Knowledge check
What is the most important checkpoint proof after rotating v1 to v2?
That v2 succeeds for the intended principal and v1 is explicitly denied; merely creating v2 is not enough.
Why does the denied job not need access to a real secret to teach authorization?
The learning objective is the provider decision and absence of secret material, which a synthetic provider can model safely.
What should be retained from the secret retrieval job?
Non-secret identifiers, provider/reference/version metadata, authorization result, source/ref/SHA, runner context, and carefully chosen synthetic evidence—not reusable secret bytes.
If the real credential had appeared in an artifact, what comes before deleting the artifact?
Revoke/rotate the credential and preserve incident identifiers; deletion alone does not invalidate a copied credential.
How does Chapter 07 prepare for rules in Chapter 08?
Rules decide whether a pipeline/job exists in a given source/ref context, which directly controls whether a secret-capable job can ever reach its authorization/retrieval path.
Official references and version notes
- Pipeline security — current guidance that CI/CD variables are less secure than dedicated secret-management providers and that sensitive values should use stronger secret-management controls where possible.
- CI/CD variables — masking, hiding, protection, fork/MR behavior, file variables, and explicit warnings that masking is not a defense against malicious job code.
- External secrets in CI/CD — current supported provider integrations and tier/offering requirements.
- OIDC authentication using ID tokens — job-scoped ID tokens, audiences, claims, and third-party trust boundaries.
-
HashiCorp Vault secrets
—
id_tokens,secrets:vault, file materialization, and provider-side authorization. - GitLab Secrets Manager — current limited/beta availability, permissions, branch/environment scoping, Runner requirements, rotation reminders, and file-by-default behavior.
- Secret detection — defense-in-depth for accidentally committed credentials; detection is not a substitute for rotation after exposure.
Secret-management behavior in this chapter was rechecked against current primary GitLab documentation on 2026-09-11. External-secret integrations documented by GitLab are currently Premium/Ultimate; ID tokens are available on Free/Premium/Ultimate. GitLab Secrets Manager is availability/billing sensitive and currently requires GitLab Runner 19.0 or later for CI access, so it is discussed as an optional current-platform path rather than a mandatory lab dependency. The mandatory exercises use generated synthetic values and a local provider simulation.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.