CI systems: GitLab and others¶
The CI gates are plain commands with exit codes, so they run in any CI system. Pull request feedback and uploading evidence to the console depend on the system.
What works where¶
Gates (buildscan, iacscan, registry scan) |
PR or MR feedback (ci-feedback) |
Upload evidence to the console | Configuration generated by the console | |
|---|---|---|---|---|
| GitHub Actions on GitHub.com | Yes | Check Run, annotations, code scanning | Yes, with the job's OIDC token | Yes |
| GitLab CI/CD on GitLab.com | Yes | MR note and commit status | Yes, with the job's OIDC token | Yes |
| Self-managed GitLab | Yes | MR note and commit status | No | No |
| GitHub Enterprise Server | Yes | Check Run, if GITHUB_API_URL points at your server |
No | No |
| Jenkins, Buildkite, CircleCI, Azure Pipelines, Bitbucket Pipelines and others | Yes | No | No | No |
Uploads are accepted only with an OIDC token from one of two issuers:
https://token.actions.githubusercontent.com and https://gitlab.com. The service has no
API-key route for CI evidence. From any other system, keep the receipts as build
artifacts and, if you want them in the console, import the JSON on the Build pipeline
page. An imported receipt has no pipeline identity behind it.
Run metadata (trigger, branch, base branch and run URL) is recorded only on GitHub Actions and GitLab CI/CD. On other systems a receipt carries just the commit and run ID you pass.
GitLab CI/CD¶
1. The gate job¶
Install the CLI in the job from the wheel you were given (see Install), run the gate and keep the receipt even when the gate fails:
untrust_build_gate:
stage: test
image: python:3.12-slim
timeout: 10m
allow_failure: false
before_script:
- python -m pip install ./vendor/untrust-<version>-py3-none-any.whl
script:
- >
untrust buildscan
--artifact dist/rootfs.tar --target container
--commit "$CI_COMMIT_SHA" --run-id "$CI_PIPELINE_ID"
--fail-on high
--output .untrust/build.json --sarif .untrust/build.sarif
artifacts:
when: always
expire_in: 1 week
paths:
- .untrust/build.json
- .untrust/build.sarif
--commitmust be the full commit SHA, so use$CI_COMMIT_SHA, not a short SHA.--run-idmust be the plain$CI_PIPELINE_ID. The service compares it with thepipeline_idin the OIDC token when the receipt is uploaded.
For an IaC gate, swap the command for untrust iacscan --plan plan.json ... --output
.untrust/iac.json.
2. Merge request feedback¶
To post one merge request note (edited on re-runs) and a commit status, create a project
access token with the api scope and the Reporter role. CI_JOB_TOKEN can't write notes
or statuses. Store it as a masked, protected CI/CD variable named UNTRUST_GITLAB_TOKEN,
and keep it out of the scanner's environment:
script:
- |
set +e
env -u UNTRUST_GITLAB_TOKEN untrust buildscan \
--artifact dist/rootfs.tar --target container \
--commit "$CI_COMMIT_SHA" --run-id "$CI_PIPELINE_ID" \
--fail-on high --output .untrust/build.json --sarif .untrust/build.sarif
rc=$?
set -e
untrust ci-feedback --kind build --report .untrust/build.json \
--provider gitlab --scanner-exit "$rc" --exit-with-verdict
With --exit-with-verdict, ci-feedback becomes the job's gate and exits 0, 1 or 2
for pass, fail or error.
For regression mode, add --mode regression --fetch-base. The base report is the last
report of the same job on the target branch, fetched with CI_JOB_TOKEN. If it can't be
fetched, the full gate applies. An ERROR is never relaxed.
3. Upload the receipt¶
Register a CI binding for the project
first. Then set two protected CI/CD variables from the saved binding,
UNTRUST_SUBMIT_ENDPOINT and UNTRUST_CI_AUDIENCE, and add a delivery job that asks
GitLab for an ID token with that audience:
untrust_deliver:
stage: deploy
needs: [untrust_build_gate]
image: python:3.12-slim
timeout: 10m
id_tokens:
UNTRUST_CI_ID_TOKEN:
aud: $UNTRUST_CI_AUDIENCE
before_script:
- python -m pip install ./vendor/untrust-<version>-py3-none-any.whl
script:
- python -m untrust.enterprise_ci_client --endpoint "$UNTRUST_SUBMIT_ENDPOINT" --build .untrust/build.json
The client reads the token from UNTRUST_CI_ID_TOKEN. Add --iac .untrust/iac.json for an
IaC receipt.
A binding accepts runs from:
- a branch push (
refs/heads/...); - a run started from the web UI;
- a scheduled pipeline.
It doesn't accept merge request pipelines or tag pipelines.
The binding pins:
- the project and namespace IDs;
- the branch;
- the trigger;
- the pipeline configuration file and its commit.
Copy the subject GitLab issues, for example
project_path:<group>/<project>:ref_type:branch:ref:<branch>, rather than typing it.
The console's pipeline setup can generate this whole configuration for GitLab.com,
including the job templates, for build targets container, nitro, gcp and nvidia,
and for IaC.
Other CI systems¶
On Jenkins, Buildkite, CircleCI, Azure Pipelines, Bitbucket Pipelines or anything else, run the same commands and gate on the exit code:
untrust buildscan \
--artifact dist/rootfs.tar --target container \
--commit "$(git rev-parse HEAD)" --run-id "$BUILD_ID" \
--fail-on high --output build.json --sarif build.sarif
| Exit code | Meaning | What the pipeline should do |
|---|---|---|
0 |
Passed | Continue. |
1 |
Blocked by a finding at or above --fail-on |
Fail the build. |
2 |
Incomplete or error | Fail the build. Never treat it as a pass. |
registry scan uses the same codes: 0 all verified, 1 a confirmed failure, 2 not
assessable.
- Pass the commit as a full SHA. The run ID can be any label of 1 to 200 characters, such
as
$BUILD_IDon Jenkins or$BUILDKITE_BUILD_IDon Buildkite. - Archive the JSON receipt and the SARIF file as build artifacts, even when the step fails. Many systems can display SARIF in their own code scanning view.
- Run
untrust ci-feedback --provider none --summary-file summary.mdto get a Markdown summary of the result without posting anything. - Don't mark the gate step as allowed to fail.
See Environment variables for the variables the gates read, and Troubleshooting for common failures.