Software Bill-of-Materials (SBOM)

Eine Software Bill-of-Materials (SBOM) ist ein Dokument zum Austausch von Informationen über Software und deren Zusammensetzung. Dieses Format wird vor allem im Sicherheitsbereich verwendet, um Software und ihre Abhängigkeiten mithilfe von Schwachstellendatenbanken wie CVE und OSV auf Schwachstellen zu überprüfen. Das vom CPython-Projekt verwendete SBOM-Format ist SPDX, das bei Bedarf in andere Formate konvertiert werden kann. Die SBOM-Datei für die in CPython enthaltenen Abhängigkeiten wird unter Misc/sbom.spdx.json verwaltet. Die Datei wird erstellt mit Tools/build/generate_sbom.py.

SBOM-Datei erstellen

… mit uv

uv bietet eine einfache Möglichkeit, eine SBOM-Datei im Format CycloneDX v1.5 zu erstellen mit:

$ uv export --format='cyclonedx1.5' > sbom.cdx.json

Die Datei enthält jedoch nur sehr rudimentäre Angaben, z. B. für cusy.tasks:

"component": {
  "type": "library",
  "bom-ref": "cusy-tasks-1@26.2.0",
  "name": "cusy-tasks",
  "version": "26.2.0",
  "properties": [
    {
      "name": "uv:package:is_project_root",
      "value": "true"
    }
  ]
}

Mit uv export  --all-groups --format='cyclonedx1.5' > sbom.cdx.json könnt ihr auch alle Dependency-Groups in die SBOM-Datei übernehmen.

… mit CycloneDX Python

Deutlich umfangreicher ist die Ausgabe von CycloneDX Python:

{
  "bom-ref": "cusy-tasks==26.2.0",
  "description": "",
  "externalReferences": [
    {
      "comment": "PackageSource: Local",
      "type": "distribution",
      "url": "file:///Users/veit/cusy/prj/cusy.tasks"
    },
    {
      "comment": "from packaging metadata Project-URL: Documentation",
      "type": "documentation",
      "url": "https://tasks.cusy.io/"
    },
    {
      "comment": "from packaging metadata Project-URL: Mastodon",
      "type": "other",
      "url": "https://mastodon.social/@Python4DataScience"
    },
    {
      "comment": "from packaging metadata Project-URL: GitHub",
      "type": "vcs",
      "url": "https://github.com/cusyio/cusy.tasks"
    }
  ],
  "licenses": [
    {
      "license": {
        "acknowledgement": "declared",
        "id": "BSD-3-Clause"
      }
    }
  ],
  "name": "cusy-tasks",
  "type": "library",
  "version": "26.2.0"
}

Der Kommandozeilenaufruf zum Erstellen der Datei ist:

$ uvx --from cyclonedx-bom cyclonedx-py environment .venv --output-file sbom.cdx.json

Warnung

CycloneDX Python erstellt die SBOM-Datei aus dem aktuellen .venv-Verzeichnis. Ruft ihr cyclonedx-bom also in eurer Entwicklungsumgebung auf, werdet ihr auch alle Entwicklungswerkzeuge in eurer SBOM-Datei wiederfinden.

… mit sbomify

sbomify stellt eine GitHub Action zum Erstellen der SBOM-Datei bereit, die unter der Haube CycloneDX Python verwendet. Mit actions/attest-sbom kann die Datei dann attestiert werden:

.github/workflows/sbomify.yml
---
name: Build with SBOM Attestation

on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write
  attestations: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false

      - name: Build
        run: uv build

      - name: Generate SBOM
        uses: sbomify/sbomify-action@f38411f20fe2cc52e5bb7abd4d094ffc9dc5f17c # v26.7.0
        env:
          LOCK_FILE: uv.lock
          OUTPUT_FILE: sbom.cdx.json
          COMPONENT_NAME: myapp
          COMPONENT_VERSION: ${{ github.sha }}
          ENRICH: true
          UPLOAD: false

      - name: Attest SBOM
        uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0
        with:
          subject-path: './dist'
          sbom-path: './sbom.cdx.json'

In GitLab CI könnt ihr sbomify folgendermaßen verwenden:

.gitlab-ci.yml
stages:
  - build
  - sbom

build:
  stage: build
  script:
    - uv build
  artifacts:
    paths:
      - dist/

generate-sbom:
  stage: sbom
  image: ghcr.io/sbomify/sbomify-action
  variables:
    LOCK_FILE: uv.lock
    OUTPUT_FILE: sbom.cdx.json
    COMPONENT_NAME: myapp
    COMPONENT_VERSION: $CI_COMMIT_TAG
    UPLOAD: "false"
    ENRICH: "true"
  script:
    - /sbomify.sh
  artifacts:
    paths:
      - sbom.cdx.json
    reports:
      cyclonedx: sbom.cdx.json

Ihr könnt auch Dependency Scanning integrieren:

.gitlab-ci.yml
include:
  - template: Security/Dependency-Scanning.gitlab-ci.yml

generate-sbom:
  stage: test
  image: ghcr.io/sbomify/sbomify-action
  variables:
    LOCK_FILE: uv.lock
    OUTPUT_FILE: gl-sbom-report.cdx.json
    UPLOAD: "false"
    ENRICH: "true"
  script:
    - /sbomify.sh
  artifacts:
    paths:
      - gl-sbom-report.cdx.json
    reports:
      cyclonedx: gl-sbom-report.cdx.json

PEP 770 – SBOMs in Python-Paketen

PEP 770 standardisiert die Einbindung von SBOMs in Python-Wheels über das Verzeichnis .dist-info/sboms/. Wenn ihr Python-Pakete auf PyPI veröffentlicht, bedeutet dies, dass eure User die SBOM-Datei automatisch erhalten, wenn sie euer Paket installieren, z. B.:

myapp-26.2.0.dist-info
├── METADATA
├── RECORD
└── sboms/
    └── myapp.cdx.json

Build-Backends wie Hatchling≥1.28 unterstützen dies bereits mit der sbom-files-Konfiguration:

pyproject.toml
[tool.hatch.build.targets.wheel]
sbom-files = ["myapp.cdx.json"]

Anstatt die SBOM-Datei beim Build von Grund auf neu zu generieren, kann eine minimale CycloneDX-SBOM-Datei in das Repository eingecheckt werden:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "version": 1,
  "metadata": {
    "component": {
      "type": "library",
      "name": "mylib",
      "version": "0.0.0-placeholder"
    }
  },
  "components": []
}

Im CI-Workflow wird die Platzhalter-SBOM-Datei dann mit dem Code ausgecheckt. sbomify reichert sie dann mit den aktuellen Informationen an. Anschließend baut hatchling das Wheel mit der aktuellen SBOM-Datei und schließlich wird das Wheel auf PyPI veröffentlicht.

Analyse

Zur Sicherheits- und Lizenzprüfung stehen zahlreiche Tools zur Verfügung, die sich auf unterschiedliche Problembereiche konzentrieren. Zwei Open-Source-Tools für die SBOM-Analyse sind Dependency Track und GUAC. Mit sbomify-action könnt ihr SBOMs aus der CI-Pipeline direkt in eure Dependency Track-Instanz hochladen mit:

UPLOAD_DESTINATIONS=dependency-track