Skip to content

Commit 28624d7

Browse files
committed
📝 Add SBOM
1 parent 3eb9e8a commit 28624d7

8 files changed

Lines changed: 314 additions & 17 deletions

File tree

docs/intro.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ From Chapter 2, the tutorial follows the prototype of a research project:
5454
<productive/licensing>`, :doc:`tests <productive/testing>` and
5555
:doc:`python-basics:logging/index`. Finally, the chapter includes advice on
5656
:doc:`improving code quality <productive/qa/index>` and :doc:`secure
57-
operation <productive/security>`.
57+
operation <productive/security/index>`.
5858
#. :doc:`web/index` can either generate dashboards from Jupyter notebooks or
5959
require more comprehensive application logic, such as demonstrated in
6060
:doc:`pyviz:bokeh/embedding-export/flask`, or provide data via a `RESTful

docs/productive/git/advanced/gitlab/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ Python version of the project <ci-cd/docker>`.
1919
If a PaaS solution such as `Kubernetes
2020
<https://en.wikipedia.org/wiki/Kubernetes>`_ is already in use, GitLab-CI/CD can
2121
be used to automatically deploy, test and scale apps. The
22-
:doc:`/productive/security` of your project can also be checked automatically.
22+
:doc:`/productive/security/index` of your project can also be checked
23+
automatically.
2324

2425
GitLab is a completely packaged platform, while GitHub can be extended with apps
2526
from the Marketplace. However, this does not mean that GitLab cannot be

docs/productive/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,4 @@ hand for :doc:`testing`, :doc:`python-basics:logging/index`, :doc:`documenting
8080
testing
8181
logging
8282
qa/index
83-
security
83+
security/index

docs/productive/licensing.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -630,8 +630,8 @@ Alternatives
630630

631631
* generates `OWASP CycloneDX <https://cyclonedx.org>`_, `SPDX Software Bill
632632
of Materials (SBOM)
633-
<https://github.com/opensbom-generator/spdx-sbom-generator>`_ or custom
634-
FOSS attribution documentation for your software project
633+
<https://spdx.github.io/spdx-spec/v3.0.1/model/Software/Classes/Sbom/>`_
634+
or custom FOSS attribution documentation for your software project
635635
* automates your FOSS policy to check your software project and its
636636
dependencies for licensing, security vulnerabilities, source code and
637637
technical standards
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
stages:
2+
- build
3+
- sbom
4+
5+
build:
6+
stage: build
7+
script:
8+
- uv build
9+
artifacts:
10+
paths:
11+
- dist/
12+
13+
include:
14+
- template: Security/Dependency-Scanning.gitlab-ci.yml
15+
16+
generate-sbom:
17+
stage: test
18+
image: ghcr.io/sbomify/sbomify-action
19+
variables:
20+
LOCK_FILE: uv.lock
21+
OUTPUT_FILE: gl-sbom-report.cdx.json
22+
UPLOAD: "false"
23+
ENRICH: "true"
24+
script:
25+
- /sbomify.sh
26+
artifacts:
27+
paths:
28+
- gl-sbom-report.cdx.json
29+
reports:
30+
cyclonedx: gl-sbom-report.cdx.json
31+
32+
generate-sbom:
33+
stage: sbom
34+
image: ghcr.io/sbomify/sbomify-action
35+
variables:
36+
LOCK_FILE: uv.lock
37+
OUTPUT_FILE: sbom.cdx.json
38+
COMPONENT_NAME: myapp
39+
COMPONENT_VERSION: $CI_COMMIT_TAG
40+
UPLOAD: "false"
41+
ENRICH: "true"
42+
script:
43+
- /sbomify.sh
44+
artifacts:
45+
paths:
46+
- sbom.cdx.json
47+
reports:
48+
cyclonedx: sbom.cdx.json
Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ or better still:
9090
<https://docs.astral.sh/uv/reference/settings/#audit_ignore-until-fixed>`_
9191

9292
You can also add the vulnerability analysis using ``uv-audit`` to your
93-
:doc:`prek <git/advanced/hooks/prek>` checks:
93+
:doc:`prek <../git/advanced/hooks/prek>` checks:
9494

9595
.. code-block:: yaml
9696
@@ -235,8 +235,8 @@ into the codebase.
235235
.. _bandit:
236236

237237
`Bandit <https://github.com/PyCQA/bandit>`__, which you can use with
238-
:doc:`qa/ruff`, allows you to check for the following vulnerabilities, amongst
239-
others:
238+
:doc:`../qa/ruff`, allows you to check for the following vulnerabilities,
239+
amongst others:
240240

241241
+--------+-----------------------------------------------------------------------+
242242
| Rule | Description |
@@ -260,7 +260,7 @@ others:
260260
261261
You can also integrate Bandit into Jupyter Notebooks, IDEs and prek.
262262

263-
In addition, you can use :doc:`qa/pysa` for `taint
263+
In addition, you can use :doc:`../qa/pysa` for `taint
264264
<https://en.wikipedia.org/wiki/Taint_checking>`_ analyses.
265265

266266
For GitHub repositories you can also use `CodeQL <https://codeql.github.com>`_;
@@ -288,9 +288,10 @@ Risk: High
288288
With :ref:`protected Git branches <protected_branches>`, rules can be defined
289289
for the adoption of changes in standard and release branches, for example
290290
automated `static code analyses
291-
<https://en.wikipedia.org/wiki/Static_program_analysis>`_ with :doc:`qa/flake8`,
292-
:doc:`qa/pysa`, :doc:`qa/wily` and :ref:`code reviews <code_reviews>` via
293-
:doc:`merge requests <git/advanced/gitlab/merge-requests>`.
291+
<https://en.wikipedia.org/wiki/Static_program_analysis>`_ with
292+
:doc:`../qa/flake8`, :doc:`../qa/pysa`, :doc:`../qa/wily` and :ref:`code reviews
293+
<code_reviews>` via :doc:`merge requests
294+
<../git/advanced/gitlab/merge-requests>`.
294295

295296
.. seealso::
296297
* `Reproducible Builds <https://reproducible-builds.org>`_
@@ -335,17 +336,17 @@ In your project, dependencies used during the build and release process should
335336
be pinned. A pinned dependency should be explicitly set to a specific hash and
336337
not just to a mutable version or version range.
337338

338-
:doc:`envs/spack/index` writes these hashes for the respective environment in
339-
:ref:`spack_lock`, :doc:`envs/uv/index` in :ref:`uv_lock`.
339+
:doc:`../envs/spack/index` writes these hashes for the respective environment in
340+
:ref:`spack_lock`, :doc:`../envs/uv/index` in :ref:`uv_lock`.
340341

341342
.. tip::
342343
Üblicherweise verwalte ich diese Dateien jedoch nur bei
343-
:doc:`python-basics:packs/apps` in :doc:`git/index`. Bei
344+
:doc:`python-basics:packs/apps` in :doc:`../git/index`. Bei
344345
:doc:`python-basics:libs/index` schränke ich üblicherweise lediglich den
345346
Versionsbereich der Abhängigkeiten in der :file:`pyproject.toml`-Datei ein.
346347

347-
:doc:`envs/spack/index` writes these hashes for the respective environment in
348-
:ref:`spack_lock`, :doc:`envs/uv/index` in :ref:`uv_lock`. These files should
348+
:doc:`../envs/spack/index` writes these hashes for the respective environment in
349+
:ref:`spack_lock`, :doc:`../envs/uv/index` in :ref:`uv_lock`. These files should
349350
therefore also be checked in with the source code.
350351

351352
This can reduce the following security risks for
@@ -371,3 +372,10 @@ reduce this risk by
371372
.. _S324: https://docs.astral.sh/ruff/rules/hashlib-insecure-hash-function/
372373
.. _S608: https://docs.astral.sh/ruff/rules/hardcoded-sql-expression/
373374
.. _S608: https://docs.astral.sh/ruff/rules/hardcoded-sql-expression/
375+
376+
.. toctree::
377+
:hidden:
378+
:titlesonly:
379+
:maxdepth: 0
380+
381+
sbom

docs/productive/security/sbom.rst

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
.. SPDX-FileCopyrightText: 2026 cusy GmbH
2+
..
3+
.. SPDX-License-Identifier: BSD-3-Clause
4+
5+
Software Bill of Materials (SBOM)
6+
=================================
7+
8+
A Software Bill of Materials (SBOM) is a document used to exchange information
9+
about software and its composition. This format is primarily used in the
10+
security field to check software and its dependencies for vulnerabilities using
11+
vulnerability databases such as `CVE <https://www.cve.org/>`_ and `OSV
12+
<https://osv.dev/>`_. The SBOM format used by the CPython project is `SPDX
13+
<https://spdx.github.io/spdx-spec/v3.0.1/model/Software/Classes/Sbom/>`_, which
14+
can be converted to other formats as needed. The SBOM file for the dependencies
15+
included in CPython is maintained at `Misc/sbom.spdx.json
16+
<https://github.com/python/cpython/blob/main/Misc/sbom.spdx.json>`_. The file is
17+
generated using `Tools/build/generate_sbom.py
18+
<https://github.com/python/cpython/blob/main/Tools/build/generate_sbom.py>`_.
19+
20+
Generating an SBOM File
21+
-----------------------
22+
23+
… with uv
24+
~~~~~~~~~
25+
26+
:term:`uv` provides an easy way to generate an SBOM file in the CycloneDX v1.5
27+
format using:
28+
29+
.. code-block:: console
30+
31+
$ uv export --format='cyclonedx1.5' > sbom.cdx.json
32+
33+
However, the file contains only very basic information; for example, for
34+
`cusy.tasks <https://github.com/cusyio/cusy.tasks>`_:
35+
36+
.. code-block:: json
37+
38+
"component": {
39+
"type": "library",
40+
"bom-ref": "cusy-tasks-1@26.2.0",
41+
"name": "cusy-tasks",
42+
"version": "26.2.0",
43+
"properties": [
44+
{
45+
"name": "uv:package:is_project_root",
46+
"value": "true"
47+
}
48+
]
49+
}
50+
51+
Using ``uv export --all-groups --format='cyclonedx1.5' > sbom.cdx.json``, you
52+
can also include all dependency groups in the SBOM file.
53+
54+
… using CycloneDX Python
55+
~~~~~~~~~~~~~~~~~~~~~~~~
56+
57+
The output from `CycloneDX Python
58+
<https://cyclonedx-bom-tool.readthedocs.io/en/latest/index.html>`_ is
59+
considerably more comprehensive:
60+
61+
.. code-block:: json
62+
63+
{
64+
"bom-ref": "cusy-tasks==26.2.0",
65+
"description": "",
66+
"externalReferences": [
67+
{
68+
"comment": "PackageSource: Local",
69+
"type": "distribution",
70+
"url": "file:///Users/veit/cusy/prj/cusy.tasks"
71+
},
72+
{
73+
"comment": "from packaging metadata Project-URL: Documentation",
74+
"type": "documentation",
75+
"url": "https://tasks.cusy.io/"
76+
},
77+
{
78+
"comment": "from packaging metadata Project-URL: Mastodon",
79+
"type": "other",
80+
"url": "https://mastodon.social/@Python4DataScience"
81+
},
82+
{
83+
"comment": "from packaging metadata Project-URL: GitHub",
84+
"type": "vcs",
85+
"url": "https://github.com/cusyio/cusy.tasks"
86+
}
87+
],
88+
"licenses": [
89+
{
90+
"license": {
91+
"acknowledgement": "declared",
92+
"id": "BSD-3-Clause"
93+
}
94+
}
95+
],
96+
"name": "cusy-tasks",
97+
"type": "library",
98+
"version": "26.2.0"
99+
}
100+
101+
The command to generate the file is:
102+
103+
.. code-block:: console
104+
105+
$ uvx --from cyclonedx-bom cyclonedx-py environment .venv --output-file sbom.cdx.json
106+
107+
.. warning::
108+
CycloneDX Python generates the SBOM file from the current :file:`.venv`
109+
directory. So if you run ``cyclonedx-bom`` in your development environment,
110+
you will also find all your development tools listed in your SBOM file.
111+
112+
… with sbomify
113+
~~~~~~~~~~~~~~
114+
115+
sbomify provides a GitHub Action for generating the SBOM file, which uses
116+
CycloneDX Python under the bonnet. The file can then be attested using
117+
`actions/attest-sbom <https://github.com/actions/attest-sbom>`_:
118+
119+
.. literalinclude:: sbomify.yml
120+
:caption: .github/workflows/sbomify.yml
121+
:language: yaml
122+
123+
In GitLab CI, you can use sbomify as follows:
124+
125+
.. literalinclude:: .gitlab-ci.yml
126+
:caption: .gitlab-ci.yml
127+
:language: yaml
128+
:lines: 1-12, 32-
129+
130+
You can also integrate dependency scanning:
131+
132+
.. literalinclude:: .gitlab-ci.yml
133+
:caption: .gitlab-ci.yml
134+
:language: yaml
135+
:lines: 13-30
136+
137+
.. seealso::
138+
* `SBOM Generation in CI/CD Pipelines <https://sbomify.com/guides/ci-cd/>`_
139+
* `Dependency scanning by using SBOM
140+
<https://docs.gitlab.com/user/application_security/dependency_scanning/dependency_scanning_sbom/>`_
141+
142+
PEP 770 – SBOMs in Python packages
143+
----------------------------------
144+
145+
:pep:`770` standardises the inclusion of SBOMs in Python wheels via the
146+
:file:`.dist-info/sboms/` directory. If you publish Python packages on
147+
:term:`PyPI`, this means that your users will automatically receive the SBOM
148+
file when they install your package, for example:
149+
150+
.. code-block:: console
151+
152+
myapp-26.2.0.dist-info
153+
├── METADATA
154+
├── RECORD
155+
└── sboms/
156+
└── myapp.cdx.json
157+
158+
Build backends such as Hatchling ≥ 1.28 already support this via the
159+
``sbom-files`` configuration:
160+
161+
.. code-block:: toml
162+
:caption: pyproject.toml
163+
164+
[tool.hatch.build.targets.wheel]
165+
sbom-files = ["myapp.cdx.json"]
166+
167+
Instead of generating the SBOM file from scratch during the build, a minimal
168+
CycloneDX SBOM file can be checked into the repository:
169+
170+
.. code-block:: json
171+
172+
{
173+
"bomFormat": "CycloneDX",
174+
"specVersion": "1.6",
175+
"version": 1,
176+
"metadata": {
177+
"component": {
178+
"type": "library",
179+
"name": "mylib",
180+
"version": "0.0.0-placeholder"
181+
}
182+
},
183+
"components": []
184+
}
185+
186+
In the :term:`CI` workflow, the placeholder SBOM file is then checked out along
187+
with the code. sbomify then enriches it with the latest information. Hatchling
188+
subsequently builds the wheel using the latest SBOM file, and finally the :term:`wheel` is published to :term:`PyPI`.
189+
190+
Analysis
191+
--------
192+
193+
There are numerous tools available for security and licence checks, each
194+
focusing on different problem areas. Two open-source tools for SBOM analysis are
195+
`Dependency Track <https://dependencytrack.org>`_ and `GUAC <https://guac.sh>`_.
196+
With ``sbomify-action``, you can upload SBOMs directly from the :term:`CI`
197+
pipeline to your Dependency Track instance using:
198+
199+
.. code-block:: yaml
200+
201+
UPLOAD_DESTINATIONS=dependency-track

0 commit comments

Comments
 (0)