Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions .github/workflows/generate_sbom.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# SPDX-FileCopyrightText: Copyright 2025 Siemens AG
#
# SPDX-License-Identifier: Apache-2.0

# Generates a CycloneDX SBOM from the locked dependencies and publishes it so it
# can always be fetched for analysis: on push to main the SBOM is committed back
# to the repository, and on a published release it is attached as a release asset.

name: Generate SBOM

# Triggered when the dependencies (pyproject.toml) or this workflow change, on
# every published release, and manually.
on:
push:
branches:
- main
paths:
- pyproject.toml
- .github/workflows/generate_sbom.yml
pull_request:
paths:
- pyproject.toml
- .github/workflows/generate_sbom.yml
release:
types: [published]
workflow_dispatch:

permissions:
contents: read

# Serialize runs per ref so two commits to main can't race their git push.
# Releases and PRs use separate groups; a run is never cancelled mid push.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
# Generate a docker image URL where all the characters are lower-case, for situations where an external contributor
# has a username that begins with a capital letter. This is a limitation of GHCR, which only allows lower-case
# characters. Subsequent jobs use this URL.
prepare_env:
runs-on: ubuntu-24.04
outputs:
image_url: ${{ steps.setenv.outputs.image_url }}
steps:
- name: Set lowercase image name
id: setenv
# Pass via env, not inline ${{ }}, so the value can't be parsed as shell.
env:
REPO_OWNER: ${{ github.repository_owner }}
run: |
OWNER_LC=$(echo "$REPO_OWNER" | tr '[:upper:]' '[:lower:]')
echo "image_url=ghcr.io/${OWNER_LC}/cmp-test-dev:latest" >> "$GITHUB_OUTPUT"

generate_sbom:
needs: prepare_env
name: Generate CycloneDX SBOM
runs-on: ubuntu-24.04
container:
image: ${{ needs.prepare_env.outputs.image_url }}
steps:
- uses: actions/checkout@v6.0.3
with:
# Read-only job: don't leave the token in git config for later steps.
persist-credentials: false

- name: Generate SBOM in CycloneDX 1.5 format
# The container image already resolves and locks the dependencies at build
# time (data/dockerfiles/Dockerfile.dev: `WORKDIR /app` + `uv sync`, which
# writes /app/uv.lock). The checked-out workspace has no committed uv.lock,
# so point --project at /app to reuse the image's lockfile. Output still
# goes to the workspace so the upload-artifact step can find it.
run: uv --project /app export --format cyclonedx1.5 --locked -o "$GITHUB_WORKSPACE/sbom.cdx.json"

- name: Upload SBOM artifact
uses: actions/upload-artifact@v7.0.1
with:
name: sbom-cdx
path: ${{ github.workspace }}/sbom.cdx.json
retention-days: 30

# Only on direct pushes to main, never from PRs/forks (which get a read-only
# token), so the committed-back SBOM always reflects merged dependencies.
commit_sbom:
needs: generate_sbom
name: Commit SBOM to main
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v6.0.3
with:
ref: main

- name: Download SBOM artifact
uses: actions/download-artifact@v8.0.1
with:
name: sbom-cdx
path: .

- name: Commit and push updated SBOM
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add sbom.cdx.json
if git diff --staged --quiet; then
echo "SBOM unchanged, nothing to commit"
else
# [skip ci] keeps the bot commit from re-triggering CI.
git commit -m "chore: update SBOM [skip ci]"
# Rebase in case main advanced since checkout; only this job edits
# the SBOM, so the rebase can't conflict.
git pull --rebase origin main
git push
fi

# Only on published releases; attaches the SBOM to that release's assets.
release_sbom:
needs: generate_sbom
name: Attach SBOM to release
if: github.event_name == 'release'
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- name: Download SBOM artifact
uses: actions/download-artifact@v8.0.1
with:
name: sbom-cdx
path: .

- name: Upload SBOM to GitHub release
# Pass the tag via env, not inline ${{ }}, to avoid shell injection;
# --clobber lets re-runs replace the asset instead of failing.
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.event.release.tag_name }}
run: gh release upload "$TAG" sbom.cdx.json --clobber --repo "$GITHUB_REPOSITORY"
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ help:
@echo ' verify - Run a bunch of checks, to see if there are any obvious deficiencies in the code '
@echo ' verifyformat - Check formatting only '
@echo ' show-outdated - Show outdated packages (depth 1)'
@echo ' sbom - Generate Software Bill of Materials (SBOM) in CycloneDX 1.5 format'
@echo ' start-mock-ca - Start the mock CA server, so that it can listens to requests '
@echo ' test-mock-ca - Run the test against the mock CA server '
@echo ' test-mock-ca-verbose - Run all tests against the mock CA server '
Expand Down Expand Up @@ -108,6 +109,12 @@ verifyformat:
show-outdated:
uv tree --outdated --depth 1

sbom:
uv sync --all-extras
uv lock --locked
uv export --format cyclonedx1.5 --locked -o sbom.cdx.json
@echo "SBOM generated: sbom.cdx.json"
Comment thread
Guiliano99 marked this conversation as resolved.

dryrun:
robot --dryrun --pythonpath=./ --variable environment:$(env) tests tests_pq_and_hybrid tests_mock_ca

Expand Down
1 change: 1 addition & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ path = [
"scripts/**", "pq_logic/fips/frodokem.py",
"pyrightconfig.json", "VERSION",
"client_tests/certs/**",
"sbom.cdx.json"
]
precedence = "override"
SPDX-FileCopyrightText = "Copyright 2024 Siemens AG"
Expand Down
Loading