From 138b7bfc943143be4bd61d9e51baba54feff1185 Mon Sep 17 00:00:00 2001 From: Valera Satsura Date: Sat, 11 Jul 2026 15:25:39 +0300 Subject: [PATCH] Add reusable scan workflow for consumer repos --- .github/workflows/scan.yml | 96 ++++++++++++++++++++++++++++++++++++++ README.md | 20 ++++++-- 2 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/scan.yml diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml new file mode 100644 index 0000000..26108d3 --- /dev/null +++ b/.github/workflows/scan.yml @@ -0,0 +1,96 @@ +# Reusable Cerberus scan + merge gate. +# +# Call it from a repository like this: +# +# jobs: +# security: +# uses: startmatter/cerberus/.github/workflows/scan.yml@main +# with: +# runner: self-hosted +# dns: 1.1.1.1,8.8.8.8 # self-hosted runners behind a VPN resolver +# secrets: +# K_SARIF_URL: ${{ secrets.K_SARIF_URL }} +# K_SARIF_SECRET: ${{ secrets.K_SARIF_SECRET }} +# +# The default branch reports (tasks are created, fixed findings close); every +# other branch and pull request runs a read-only check that gates the merge on +# findings the change introduces. The existing backlog never blocks anyone. +name: Cerberus + +on: + workflow_call: + inputs: + runner: + description: "Runner label: ubuntu-latest, or self-hosted" + type: string + default: ubuntu-latest + required: false + mode: + description: "auto | report | check | off. auto reports on the default branch and checks elsewhere." + type: string + default: auto + required: false + dns: + description: "Comma-separated DNS servers for the scan container. Needed when the runner's resolver is unreachable from containers — without it Semgrep fetches no rules and Trivy no vulnerability database, and both quietly produce nothing." + type: string + default: "" + required: false + path: + description: "Directory to scan." + type: string + default: "." + required: false + image: + description: "Cerberus image to run." + type: string + default: ghcr.io/startmatter/cerberus:latest + required: false + # Declared explicitly, not inherited: `secrets: inherit` does not cross + # organization boundaries, and callers may live in a different org. + secrets: + K_SARIF_URL: + required: false + K_SARIF_SECRET: + required: false + # Only needed when the image is private and the caller is in another + # organization — the job's own token cannot read it then. + REGISTRY_TOKEN: + required: false + +jobs: + scan: + runs-on: ${{ inputs.runner }} + permissions: + contents: read + packages: read + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Gitleaks scans the whole history + + # Merging this workflow before the ingest secrets exist must not turn + # every pipeline red — say so and skip instead. + - name: Check configuration + id: cfg + env: + URL: ${{ secrets.K_SARIF_URL }} + SECRET: ${{ secrets.K_SARIF_SECRET }} + run: | + if [ -z "$URL" ] || [ -z "$SECRET" ]; then + echo "::warning::K_SARIF_URL / K_SARIF_SECRET are not set — skipping the scan" + echo "configured=false" >> "$GITHUB_OUTPUT" + else + echo "configured=true" >> "$GITHUB_OUTPUT" + fi + + - name: Run Cerberus + if: steps.cfg.outputs.configured == 'true' + uses: startmatter/cerberus@main + with: + url: ${{ secrets.K_SARIF_URL }} + secret: ${{ secrets.K_SARIF_SECRET }} + mode: ${{ inputs.mode }} + dns: ${{ inputs.dns }} + path: ${{ inputs.path }} + image: ${{ inputs.image }} + registry-token: ${{ secrets.REGISTRY_TOKEN || github.token }} diff --git a/README.md b/README.md index fc19695..53ae185 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,18 @@ include: - remote: https://raw.githubusercontent.com/startmatter/cerberus/main/templates/gitlab-ci.yml ``` -**GitHub** — add the action: +**GitHub** — call the reusable workflow: + +```yaml +jobs: + security: + uses: startmatter/cerberus/.github/workflows/scan.yml@main + secrets: + K_SARIF_URL: ${{ secrets.K_SARIF_URL }} + K_SARIF_SECRET: ${{ secrets.K_SARIF_SECRET }} +``` + +Or drive the action directly when you need control over the surrounding job: ```yaml - uses: actions/checkout@v4 @@ -71,8 +82,11 @@ runner has to authenticate: - **GitLab** — add a `DOCKER_AUTH_CONFIG` CI variable (group-level): `{"auths":{"ghcr.io":{"auth":""}}}` where the token is a GitHub PAT with `read:packages`. -- **GitHub** — nothing to do: the action logs in with the job's `GITHUB_TOKEN`. Give the job - `permissions: packages: read`. +- **GitHub, same organization** — nothing to do: the action logs in with the job's `GITHUB_TOKEN`. + Give the job `permissions: packages: read`. +- **GitHub, another organization** — a repository's own token cannot read another org's private + package. Pass a PAT with `read:packages` as the `REGISTRY_TOKEN` secret (reusable workflow) or + `registry-token` input (action). Configuration lives in [`cerberus.yml`](cerberus.example.yml) in the repo root — scanners, extra args, custom SARIF-emitting checks, gate policy.