Chapter 34Lesson 03~420 minutes

Production Capstone: Build and Govern a Secure GitLab Software Delivery Platform: Security, Governance, and Reliability Validation

Validate the platform as an interacting control system, prove commit-to-artifact traceability, test tier-sensitive governance assumptions, and map every requirement to independent evidence and an accountable owner.

ValidationSecurityGovernanceReliabilityEvidence Matrix

Learning objectives

  • Validate access, merge, pipeline, runner, variable, environment, registry, scanner, API, webhook, and release controls as interacting boundaries.
  • Prove commit-to-artifact-to-release traceability with independent checks rather than trusting pipeline success alone.
  • Distinguish GitLab Free controls from Premium/Ultimate enforcement and from Self-Managed/Dedicated responsibilities.
  • Evaluate reliability and cost behavior including redundant pipelines, artifact/cache retention, runner capacity, retries, rate limits, registry cleanup, and external dependencies.
  • Produce an evidence matrix mapping every invariant to a GitLab resource, verification method, failure signal, and accountable remediation owner.
Availability baseline — verified 2026-08-22 against GitLab 19.3. Validation must use the controls actually present in the learner’s environment. GitLab Free approvals are optional; required approval rules and Code Owners enforcement require Premium/Ultimate. Protected environments/deployment approvals require Premium/Ultimate. Security policies and dependency scanning are Ultimate. Basic SAST and pipeline secret-detection report artifacts remain available across tiers with supported runners. Never mark a simulated policy “PASS — enforced”; mark it “SIMULATED / DESIGN VERIFIED.”

1. Validate control interactions, not isolated checkboxes

Most production failures happen between controls. A protected branch can still be undermined by an overly permissive matching rule. A protected variable can still be exposed to trusted-branch code running on an unsafe persistent runner. A security scanner can run successfully but cover the wrong source or produce evidence that no policy consumes. A release can exist while pointing to the wrong artifact.

Boundary pair Dangerous assumption Validation question
Branch protection + approvals “Protected means reviewed.” Can a user push directly? Is review actually required or merely requested? Which tier enforces it?
Pipeline source + variables “Masked/protected means safe.” Which pipeline sources can reach the variable, and can untrusted code execute in those pipelines?
Runner + job token “Ephemeral token means low risk.” Can the runner or job access another project, network, registry, or host data beyond intent?
Artifact + release tag “Same version label means same bytes.” Does the release manifest contain the digest of the exact built artifact from the tagged commit?
Scanner + policy “Scanner ran, so merge is safe.” What was scanned, what report was produced, and what actually blocks/records a risky result?
Webhook + API “Signed event means authorized action.” Did the receiver validate scope and perform an idempotent, least-privilege mutation?

2. Prove the release tuple independently

The core traceability tuple is tag → commit SHA → pipeline/source → artifact digest → release record. Each link should be independently observable. If any link disagrees, stop promotion until the discrepancy is explained.

from pathlib import Path
import json, hashlib, subprocess
root=Path('gitlab-ch34-capstone'); ev=root/'evidence'
artifact=json.loads((ev/'artifact_manifest.json').read_text())
release=json.loads((ev/'release_manifest.json').read_text())
actual=hashlib.sha256((root/artifact['file']).read_bytes()).hexdigest()
checks={
  'artifact_digest_matches_bytes': actual == artifact['sha256'],
  'release_digest_matches_artifact': release['artifact_sha256'] == artifact['sha256'],
  'security_report_hash_matches': release['security_report_sha256'] == (ev/'security_report.sha256').read_text().strip(),
}
try:
    tag_sha=subprocess.check_output(['git','-C',str(root),'rev-list','-n','1','v0.1.0-capstone'],text=True).strip()
    checks['tag_resolves_to_release_commit']= release['commit_sha'] in {'LOCAL_COMMIT_UNKNOWN',tag_sha} or release['commit_sha']==tag_sha
except Exception:
    checks['tag_resolves_to_release_commit']='TAG_NOT_AVAILABLE_IN_LOCAL_FALLBACK'
print(json.dumps(checks,indent=2))
if any(v is False for v in checks.values()): raise SystemExit('traceability validation failed')

A GitLab pipeline ID alone does not prove that the retained artifact came from that pipeline. The digest binds the bytes. A Git tag alone does not bind an external deployment target to those bytes. The deployment record or manifest must carry the immutable identity forward.

3. Access and merge-governance validation

Review the effective member roles at the project and inherited group levels. The target invariant is least privilege: normal application delivery should not require Owner, and elevated roles should be deliberate. Then inspect branch rules. GitLab’s current protection-rule semantics matter: when multiple rules match, the most permissive rule can determine push/merge behavior, while Code Owner approval uses a different, restrictive combination rule where that paid feature is enabled.

ACCESS VALIDATION RECORD
Project: asterline-lab/delivery-platform/app
Expected Developer capability: branch/MR/pipeline work
Expected Maintainer capability: repository/project configuration and release governance
Owner required for routine delivery: NO
Inherited memberships reviewed: YES
main direct push allowed: NO
release tag creation: restricted to intended release role
Required approvals enforced by GitLab: NO on Free path / verify on paid path
CODEOWNERS enforcement: NO on Free path / verify on paid path
Evidence IDs: <member snapshot>, <branch rule>, <MR>, <tag rule>

4. Pipeline, variable, job-token, and runner validation

Review four separate dimensions. First, pipeline source: does the configuration run the intended jobs for merge-request, default-branch, tag, scheduled, or triggered pipelines? Second, variables: are sensitive values protected, hidden/masked where appropriate, and scoped only to eligible pipelines/environments? Third, CI_JOB_TOKEN: is cross-project access constrained to the documented allowlist/resource model? Fourth, runner trust: could repository-controlled code reach a persistent host, sibling project, Docker socket, or production network?

Check Pass condition Failure signal
Pipeline source Only documented sources create release-capable jobs unexpected pipeline type contains release/deploy job
Protected variable Unavailable to unprotected refs/untrusted source secret-dependent job runs from untrusted ref
Job token scope Only intended GitLab resources accessible cross-project API/registry access succeeds outside allowlist intent
Runner executor Appropriate isolation; no privileged shared host for untrusted code host mount/Docker socket/shell executor exposes cross-job state
Runner selection Sensitive jobs require specific protected/trusted runner path release job lands on generic runner
Masked is not a secret-management guarantee. A job can deliberately transform or exfiltrate a value. The decisive boundary is whether untrusted code can obtain the credential at all.

5. Validate security evidence without overclaiming coverage

For the local fixture, inspect the report hash and scope. For GitLab-native SAST or pipeline secret detection, capture analyzer job, report artifact, commit SHA, and coverage assumptions. On Free/Premium, downloadable report artifacts are useful evidence even when Ultimate-only vulnerability-management dashboards or approval policies are unavailable.

from pathlib import Path
import json, hashlib
p=Path('gitlab-ch34-capstone/evidence/security_report.json')
report=json.loads(p.read_text())
print('scanner =',report['scanner'])
print('scope   =',report['scope'])
print('findings=',len(report['findings']))
print('sha256  =',hashlib.sha256(p.read_bytes()).hexdigest())
print('ASSERTION: this report covers only its declared scope; it is not proof of vulnerability absence')

6. Registry and release controls: mutable names versus immutable identity

A package version, container tag, or release name is useful for humans but may be mutable or re-creatable depending on the registry and policy. Use a digest/checksum for exact identity. If a container image is used, record the OCI manifest digest. If using the GitLab Container Registry, cleanup and protection rules affect who can mutate or delete tags/repositories; cleanup is destructive and should never be the only retention mechanism for release evidence.

Object Human label Immutable/stronger identity Retention question
Job artifact job name / filename checksum + job/pipeline/commit IDs Does expiry remove evidence needed for audit/recovery?
Generic package package name + version package file checksum Can the version be overwritten or deleted?
Container image tag such as v0.1.0 OCI digest Can mutable tag movement hide a provenance change?
GitLab Release release/tag name tag commit SHA + evidence + linked artifact digest Will deleting tag/release break required evidence?

7. Validate tier/offering assumptions explicitly

Control Current baseline Capstone treatment
Protected branches Free/Premium/Ultimate; all offerings Use on mandatory path
MR approvals optional approvals on Free; required rules on Premium/Ultimate Record process approval on Free; optional enforced rule on paid tier
Code Owners Premium/Ultimate File documents intent on Free; enforcement optional
Protected environments / deployment approvals Premium/Ultimate Use approval fixture on Free; optional live enforcement
Basic SAST / downloadable report Free/Premium/Ultimate Optional live scanner or local report fixture
Pipeline secret-detection report artifact Free/Premium/Ultimate with supported runner Optional live scanner or fixture
Security policies / dependency scanning Ultimate Policy design and evidence fixture only unless licensed
Releases / release evidence Free/Premium/Ultimate Use disposable release path
Self-Managed backup/restore Self-Managed Fixture/disposable instance only
Duo/Agent Platform feature/add-on/credits/status dependent No mandatory AI; preserve governance constraints from Chapter 33

8. Test the major architectural tradeoffs

Choice Benefit Cost/risk Decision rule
Central governance consistent minimum controls, easier audit can slow teams; policy mistakes have broad blast radius centralize invariants, allow team-local implementation where risk permits
Team autonomy fast local decisions drift and uneven evidence grant autonomy inside protected platform boundaries
GitLab-hosted runner lower operations burden quota/cost and shared execution model constraints use for low-trust general work when acceptable
Self-managed runner custom network/tooling operator owns isolation, patching, capacity, credential exposure use only with an explicit trust class and lifecycle
Deep paid security enforcement strong centralized guardrails license cost + configuration complexity use where risk/compliance justifies it; preserve scanner evidence on Free
Minimal pipeline fast/cheap less evidence and coverage remove redundant work, not essential validation
Long retention better historical evidence storage cost and sensitive-data footprint retain only what policy/recovery requires

9. Reliability and cost review

Efficiency is a reliability property when it reduces queueing and resource exhaustion. Inspect duplicate branch/MR pipelines, unnecessary retries, artifact/cache retention, runner concurrency, serial deployment needs, API pagination/rate limits, registry growth, and Self-Managed dependencies. Do not “optimize” by deleting the evidence required to investigate failure.

RELIABILITY / COST REVIEW
Pipelines
  [ ] workflow:rules prevents accidental duplicate sources
  [ ] interruptible only where cancellation is safe
  [ ] retry used only for transient/idempotent failures
Runners
  [ ] capacity and trust class documented
  [ ] no privileged persistent runner for untrusted code
Artifacts / cache
  [ ] artifact retention matches evidence need
  [ ] cache is acceleration, not release evidence
API / hooks
  [ ] pagination and rate limits handled
  [ ] webhook duplicate delivery is idempotent
Registry
  [ ] retention/cleanup preserves release identities
Self-Managed
  [ ] DB, Gitaly, object storage, registry, Sidekiq, network/TLS dependencies mapped

10. Build the evidence matrix

from pathlib import Path
import csv
root=Path('gitlab-ch34-capstone'); ev=root/'evidence'
rows=[
 ['least_privilege','group/project membership','membership snapshot','unexpected inherited elevated role','group owner'],
 ['reviewed_default_branch','branch rule + MR','direct-push test + MR record','direct push succeeds / review absent','maintainer'],
 ['pipeline_identity','.gitlab-ci.yml + pipeline','source + commit SHA','release job on unintended source','platform'],
 ['runner_isolation','runner config/metadata','trust-class review','sensitive job on unsafe runner','runner operator'],
 ['artifact_identity','artifact manifest','independent SHA-256','checksum mismatch','release owner'],
 ['release_traceability','tag/release/manifest','tag->commit->digest check','tuple disagreement','release owner'],
 ['security_evidence','scanner/report','report hash + scope','missing/stale/out-of-scope report','security'],
 ['webhook_integrity','webhook receiver','signature + scope + duplicate test','invalid/stale/duplicate event causes action','integration owner'],
 ['recovery','backup/runbook','restore drill evidence','backup exists but restore fails','Self-Managed operator'],
]
with (ev/'evidence_matrix.csv').open('w',newline='') as f:
    w=csv.writer(f); w.writerow(['invariant','resource','verification','failure_signal','owner']); w.writerows(rows)
print((ev/'evidence_matrix.csv').read_text())

Knowledge check

Why can two branch protection rules create a surprising result?

What is the strongest evidence that a released tarball is the one produced by the reviewed commit?

Why is an empty SAST report insufficient to declare the application secure?

What should an evidence matrix include beyond “pass/fail”?

When should a cache be used as release evidence?

11. Lesson summary and bridge

  • GitLab controls are validated at their interactions: source, authorization, runner, credential, artifact, policy, and evidence boundaries.
  • Traceability requires an immutable identity chain rather than confidence in labels or green pipeline status.
  • Tier-gated controls are explicitly separated from Free-path process simulations.
  • Reliability and cost reviews optimize duplicate work, retention, capacity, and retries without deleting necessary evidence.
  • The evidence matrix turns architecture requirements into operational accountability.

Next, you will deliberately break four parts of the platform, preserve evidence, diagnose root cause, apply the least destructive correction, and prove recovery.

Primary sources and version notes

These lessons were finalized against current official GitLab documentation on 2026-08-22 with GitLab 19.3 as the reference release. Availability can vary by GitLab.com, Self-Managed, or Dedicated offering; Free, Premium, or Ultimate tier; namespace settings; administrator policy; runner type; and feature status. Re-check current documentation before applying a production design. Current protection-rule behavior and paid approval/environment-policy boundaries are especially important in this lesson because a visual “protected” or “approved” state can still be misinterpreted if overlapping rules or tier limitations are ignored.

Next lesson

Failure Injection, Troubleshooting, and Recovery Drill

Break four trust domains deliberately, preserve evidence, diagnose root cause, recover safely, and verify the repaired state.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.