Skip to content
andrew.dunn.dev

pipeline

Source Catalog Releases Adoption profile

pipeline is the CI/CD catalog I maintain at GitLab Public Sector, the group behind egress, manifold and bulkhead. It is a set of GitLab CI/CD components for supply-chain-hardened builds. A consumer’s .gitlab-ci.yml includes binary or container, then container-sbom, container-attest, verify and release, each pinned to the catalog major, and gets a pipeline that cross-compiles or builds an image, signs it with cosign through the job’s own GitLab OIDC identity, attaches a CycloneDX SBOM and SLSA v1.0 provenance, verifies the signature, and links all of it into a GitLab release. The same repository carries the images those jobs run in, the shared Renovate presets consumers extend, an adoption profile that says what a conforming consumer looks like, and the catalog’s own test harness. tach and assay are the reference consumers, one Go and one Rust.

include @6CATALOGpipelinetemplates + linterUBI job images, signedsim harnessRenovate presetsone lockstep tag vX.Y.ZCONSUMER PIPELINEbuildbinary · imagesign · attestcosign · SBOM · SLSAverifyOIDC identityreleaseassets linkedSIGSTOREFulcio + Rekorkeyless, bound to the job identityCONTRACTpin the major • semver tags, no digests • gate on the merge path • fail closed
Catalog components in a consumer pipeline
  • A consumer pipeline includes components from the catalog at a pinned major.
  • The catalog ships templates, images, a sim harness and lockstep tags.
  • The consumer’s jobs build, sign and attest through Sigstore with the GitLab OIDC identity, verify, and release.

Every component is one templates/<name>/template.yml with a spec: block that declares its inputs: a type, a description, and a default or none, with options for closed value sets and regex for version pins and job names. Components define jobs only, never default:, workflow: or stages:. scripts/lint-templates.py checks that shape on every merge request: every input referenced is declared and every input declared is referenced, the README documents no input the template lacks, a job that runs cosign sign declares its id_tokens, an image: that names a variable defines it in the same job, and a template parses under a strict loader that errors on a duplicate mapping key.

Every template ships at the same catalog tag, and the images the jobs run in, built on Red Hat UBI bases with cosign, syft, buildah and the language toolchains baked in, are built, signed and pushed under that same tag. A component’s default image: resolves through $[[ component.version ]] to the image built from its own version, so no job installs a tool at run time. The catalog’s own pipeline runs the image stages, then a sim stage in which each sim consumer includes a component at $CI_COMMIT_SHA and exercises it against the images just built, then create-release, which reads the annotated tag’s message as the release notes and publishes the version to the CI/CD Catalog.

ADOPTION.md is the shape a consumer conforms to, in three invariants. Every version reference is one Renovate can track: the catalog major pinned and minors floating, everything else pinned exactly by semver tag and never by digest. The gate runs on the path that merges: osv-verdict scans the merge base and the head and fails only on findings the head introduces, and automerge-gate consumes the per-check verdicts and fails closed, so an absent verdict blocks and a customer-shipping class reports GATE_DECISION=human-review. And CI discovers files rather than enumerating them, so a renamed directory cannot leave a check passing while it covers less.

The catalog is infrastructure for the group’s own projects: the presets, the adoption profile and the gate assume an estate where third-party dependency updates are a human decision and artifacts ship into controlled-egress and air-gapped environments.