From 3da2916e0e44dfc2d9f98014c3a6d83370a3248d Mon Sep 17 00:00:00 2001 From: Roberto Alfieri Date: Thu, 27 Aug 2026 17:27:27 +0200 Subject: [PATCH] [ci] Scan Python lockfiles with OSV-Scanner Catch newly introduced dependency CVEs on PRs without failing on the standing inventory forced by OpenStack upper-constraints. Weekly scans upload SARIF to the Security tab. Co-authored-by: Cursor --- .github/dependabot.yml | 9 ++++ .github/workflows/osv-scanner.yml | 89 +++++++++++++++++++++++++++++++ docs/developer-guide.md | 17 ++++++ osv-scanner.toml | 9 ++++ 4 files changed, 124 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/osv-scanner.yml create mode 100644 osv-scanner.toml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..a19e518d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + # Keep GitHub Action SHA pins current. The pip ecosystem is omitted: + # Python versions are pinned by OpenStack upper-constraints and refreshed + # by the update-sources workflow, not by Dependabot. + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml new file mode 100644 index 00000000..5553b3ed --- /dev/null +++ b/.github/workflows/osv-scanner.yml @@ -0,0 +1,89 @@ +name: OSV-Scanner + +# Scan committed pip-compile lockfiles (requirements.lock.). +# pythondeps.txt is unpinned input and is not scanned. +# +# pull_request: fail only on vulnerabilities newly introduced by the PR. +# schedule / push to main: full inventory uploaded to the Security tab. +# SARIF upload is disabled on pull_request so fork PRs keep a read-only token. + +on: + pull_request: + paths: + - .github/workflows/osv-scanner.yml + - containers/**/pythonbuilddeps.txt + - containers/**/pythondeps.txt + - containers/**/requirements.lock* + - containers/**/sources.txt + - osv-scanner.toml + merge_group: + push: + branches: [main] + paths: + - .github/workflows/osv-scanner.yml + - containers/**/pythonbuilddeps.txt + - containers/**/pythondeps.txt + - containers/**/requirements.lock* + - containers/**/sources.txt + - osv-scanner.toml + schedule: + - cron: '30 12 * * 1' + workflow_dispatch: + +permissions: + contents: read + +jobs: + lockfiles: + name: List Python lockfiles + runs-on: ubuntu-latest + outputs: + scan-args: ${{ steps.list.outputs.scan-args }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - id: list + name: Collect requirements.lock.* paths + run: | + set -euo pipefail + mapfile -t files < <(find containers -type f -name 'requirements.lock.*' | sort) + if [[ ${#files[@]} -eq 0 ]]; then + echo "No requirements.lock.* files found under containers/" >&2 + exit 1 + fi + { + echo 'scan-args<> "$GITHUB_OUTPUT" + echo "Scanning ${#files[@]} lockfile(s):" + printf ' %s\n' "${files[@]}" + + scan-pr: + name: PR (new vulnerabilities) + needs: lockfiles + if: github.event_name == 'pull_request' || github.event_name == 'merge_group' + permissions: + actions: read + contents: read + security-events: write + uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@baa4139e56d6312335d899e6ba045fa16d1d3d0b # v2.5.1 + with: + scan-args: ${{ needs.lockfiles.outputs.scan-args }} + upload-sarif: false + + scan-scheduled: + name: Inventory (SARIF) + needs: lockfiles + if: github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' + permissions: + actions: read + contents: read + security-events: write + uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@baa4139e56d6312335d899e6ba045fa16d1d3d0b # v2.5.1 + with: + scan-args: ${{ needs.lockfiles.outputs.scan-args }} + fail-on-vuln: false + upload-sarif: true diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 30c44697..1c1712bc 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -568,6 +568,23 @@ default branch. Linters always run. Manual `workflow_dispatch` runs everything. When build or push runs, **all** images are built together so every container for a commit shares the same `master-` tag and consistent OS packages. +## Python vulnerability scanning + +GitHub Actions runs [OSV-Scanner](https://google.github.io/osv-scanner/) against +committed `requirements.lock.` files (the `pip-compile` output that +images actually install). Unpinned `pythondeps.txt` files are not scanned. + +- **Pull requests** compare the target branch to the PR and fail only when the + change **introduces** new vulnerabilities. Existing findings inherited from + OpenStack upper-constraints do not block the PR. +- **Weekly (Monday) and pushes to `main`** run a full inventory and upload SARIF + to the repository **Security → Code scanning** tab. That job does not fail the + workflow, so known issues stay visible without turning `main` red. + +Document accepted exceptions in [`osv-scanner.toml`](../osv-scanner.toml) with a +reason and an `ignoreUntil` date. Image and RPM CVE scanning remains a Konflux +concern; this workflow covers declared Python dependencies only. + ## Adding a new service 1. Create the project directory structure: diff --git a/osv-scanner.toml b/osv-scanner.toml new file mode 100644 index 00000000..c0f11539 --- /dev/null +++ b/osv-scanner.toml @@ -0,0 +1,9 @@ +# Applied to every scanned lockfile via --config in the OSV-Scanner workflow. +# https://google.github.io/osv-scanner/configuration/ +# +# Ignore a finding only with a reason, and preferably an expiry: +# +# [[IgnoredVulns]] +# id = "GHSA-xxxx-yyyy-zzzz" +# ignoreUntil = 2027-12-31 +# reason = "No fix available within OpenStack upper-constraints"