GitLab CI: from build to verified release
This tutorial wires cilock into a GitLab pipeline using the reusable template at aflock-ai/cilock-action/gitlab/cilock.gitlab-ci.yml. The shape mirrors the GitHub Actions tutorial, same five-step pattern, same attestation outputs, with CILOCK_* variables instead of action with: inputs.
What you'll buildโ
A GitLab pipeline where each stage is wrapped by cilock and produces a signed in-toto attestation. The template produces a cilock.env dotenv artifact so downstream stages can read the GitOID of the attestation produced by an earlier stage.
Prerequisitesโ
- A GitLab project (the example is Go, but any language works)
- For OIDC keyless signing: GitLab's JWT (
CI_JOB_JWT_V2) orid_tokens:config - Optional: an Archivista instance reachable from the runner
Step 1: Include the templateโ
include:
- remote: 'https://raw.githubusercontent.com/aflock-ai/cilock-action/v1/gitlab/cilock.gitlab-ci.yml'
This pulls in the .cilock job template that downstream jobs extends:.
Step 2: A multi-stage attested pipelineโ
Adapted from cilock-action/examples/gitlab/pipeline.gitlab-ci.yml and the GitLab template README:
include:
- remote: 'https://raw.githubusercontent.com/aflock-ai/cilock-action/v1/gitlab/cilock.gitlab-ci.yml'
stages:
- lint
- test
- build
- publish
- verify
variables:
# File-signer setup (replace with KMS or Sigstore for production)
CILOCK_KEY: "${CI_PROJECT_DIR}/signing-key.pem"
# 1. Lint + secret scan
lint:
stage: lint
extends: .cilock
variables:
CILOCK_STEP: lint
CILOCK_COMMAND: "golangci-lint run ./..."
CILOCK_ATTESTATIONS: "environment git gitlab secretscan"
CILOCK_OUTFILE: attestation-lint
artifacts:
paths:
- attestation-lint*.json
reports:
dotenv: cilock.env
# 2. Tests
test:
stage: test
extends: .cilock
needs: [lint]
variables:
CILOCK_STEP: test
CILOCK_COMMAND: "go test -count=1 -v ./..."
CILOCK_OUTFILE: attestation-test
artifacts:
paths:
- attestation-test*.json
reports:
dotenv: cilock.env
# 3. Build + SBOM in one step
build:
stage: build
extends: .cilock
needs: [test]
variables:
CILOCK_STEP: build
CILOCK_COMMAND: "bash -c 'CGO_ENABLED=0 go build -o bin/myapp ./cmd/myapp && syft bin/myapp -o cyclonedx-json=bin/bom.cdx.json'"
CILOCK_ATTESTATIONS: "environment git gitlab sbom"
CILOCK_OUTFILE: attestation-build
artifacts:
paths:
- bin/
- attestation-build*.json
reports:
dotenv: cilock.env
# 4. Container build
publish:
stage: publish
extends: .cilock
needs: [build]
variables:
CILOCK_STEP: docker-build
CILOCK_COMMAND: "docker buildx build --metadata-file docker-metadata.json -t myapp:test --load ."
CILOCK_ATTESTATIONS: "environment git gitlab docker"
CILOCK_OUTFILE: attestation-publish
artifacts:
paths:
- attestation-publish*.json
- docker-metadata.json
reports:
dotenv: cilock.env
# 5. Verify all attestations against a signed policy
verify:
stage: verify
needs: [publish]
script:
- |
ATTESTATIONS=$(ls attestation-*.json | paste -sd,)
cilock verify \
--policy ./policy-signed.json \
--publickey ./policy-pubkey.pem \
--attestations "$ATTESTATIONS" \
--subjects "sha1:$CI_COMMIT_SHA"
The --attestations flag takes a comma-separated list, so the snippet above globs every attestation-*.json artifact carried forward via dependencies:/needs: and joins them. You can equally pass --attestations a.json,b.json,c.json literally, or repeat -a per file.
The --subjects "sha1:$CI_COMMIT_SHA" flag tells cilock to match attestations whose subject list includes the git commit (every cilock attestation records the commit hash as a subject via the git attestor). Use that instead of --artifactfile bin/myapp: when multi-stage pipelines carry artifacts forward via needs:, the build's output binary often ends up in the build job's materials (because the prior stage's artifact made it visible to the material attestor before the build command ran) rather than its products, so --artifactfile won't find a matching subject. The git-commit subject is reliably present, end-to-end verified against the demo-cilock GitLab pipeline.
Configurable CILOCK_* variablesโ
Sourced from cilock-action/gitlab/README.md:
| Variable | Default | Notes |
|---|---|---|
CILOCK_STEP | required | Step name; matches policy.steps.<name>. |
CILOCK_COMMAND | required | Shell command to wrap. |
CILOCK_VERSION | v1 | cilock-action release version. |
CILOCK_ATTESTATIONS | environment git gitlab | Space-separated attestor list. |
CILOCK_ENABLE_ARCHIVISTA | true | Push to Archivista. |
CILOCK_ARCHIVISTA_SERVER | https://web.platform.testifysec.com | Archivista URL. |
CILOCK_ENABLE_SIGSTORE | false | Off by default, most GitLab teams use file or KMS signing. Set true to use Sigstore Fulcio. |
CILOCK_KEY | (none) | Path to signing key (file signer). |
CILOCK_OUTFILE | (none) | Output path prefix for the signed envelope. |
CILOCK_TRACE | false | Enable Linux ptrace behavioral capture. |
CILOCK_HASHES | sha256 | Hash algorithms. |
For the full reference, see the GitLab component reference.
Differences from the GitHub Actions pipelineโ
| GitHub Action | GitLab template | |
|---|---|---|
| Default attestations | environment git github | environment git gitlab |
Default enable-sigstore | true | false |
| Wrapping another tool | action-ref: input | Not supported, call commands directly |
| OIDC | GH id-token permission | GitLab id_tokens: / CI_JOB_JWT_V2 |
| Inter-step evidence | Action outputs (git_oid, attestation_file) | cilock.env dotenv artifact via dependencies/needs |
Going furtherโ
- The defaults assume file-based signing. For Sigstore keyless signing in GitLab, set
CILOCK_ENABLE_SIGSTORE: "true"and configureid_tokens:in your job. - For policy enforcement, the verify stage above is what gates promotion. See Verify in a release gate.
- For the threat-model walkthrough that motivates this whole shape, see Defending against supply-chain attacks.