-
Notifications
You must be signed in to change notification settings - Fork 36
feat: add make sbom / install-sbom / uninstall-sbom targets #410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| name: SBOM Test | ||
|
|
||
| # Full SBOM generation for wolfProvider using the vendored wolfGlass toolkit | ||
| # under tools/sbom/. This job builds the OpenSSL + wolfSSL + wolfProvider | ||
| # stack, so it is not a cheap PR check. | ||
| # | ||
| # Triggers (Aidan, PR #410): | ||
| # * Nightly, via nightly-osp.yml (out-of-wave, like static-analysis). | ||
| # * On a PR, add the `ci:sbom` label (pr-osp-select.yml). | ||
| # * Manual: workflow_dispatch. | ||
| # | ||
| # A per-PR smoke that only ran `make sbom` + pyspdxtools would still pay the | ||
| # full stack build, so it is not cheaper than this job. Use nightly + label. | ||
|
|
||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| wolfssl_ref: | ||
| description: 'wolfSSL git ref to build and link (not the generator)' | ||
| required: false | ||
| type: string | ||
| default: 'master' | ||
| workflow_dispatch: | ||
| inputs: | ||
| wolfssl_ref: | ||
| description: 'wolfSSL git ref to build and link (not the generator)' | ||
| required: false | ||
| default: 'master' | ||
|
|
||
| # run-scoped: a reusable workflow's group keys off the caller, so a shared | ||
| # group would let one nightly run cancel another's SBOM job. | ||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }}-${{ github.run_id }} | ||
| cancel-in-progress: false | ||
|
|
||
| # This workflow only reads the repo and uploads artefacts; no API writes. | ||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| sbom: | ||
| name: wolfProvider SBOM generation (linux) | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 45 | ||
| steps: | ||
| - name: Checkout wolfprovider | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Verify vendored wolfGlass generator | ||
| run: | | ||
| test -f tools/sbom/gen-sbom | ||
| test -f tools/sbom/sbom.am | ||
| test -f tools/sbom/.wolfglass-rev | ||
| python3 -m py_compile tools/sbom/gen-sbom | ||
| echo "wolfGlass pin: $(cat tools/sbom/.wolfglass-rev) $(cat tools/sbom/VERSION)" | ||
|
|
||
| - name: Install build tooling and SBOM validator (pyspdxtools) | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y build-essential autoconf automake libtool \ | ||
| pkg-config | ||
| python3 -m pip install --user 'spdx-tools==0.8.*' | ||
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | ||
|
|
||
| # Build the full stack (OpenSSL + wolfSSL + wolfProvider) from source using | ||
| # the project's maintained script. WOLFPROV_SKIP_TEST keeps it a | ||
| # build-only run (the SBOM only needs the built libwolfprov artifact). | ||
| # This also runs autogen + ./configure in-tree, so `make sbom` picks up the | ||
| # AC_PATH_PROG(PYTHON3/PYSPDXTOOLS/GIT) substitutions. wolfSSL defaults to | ||
| # master; override with WOLFSSL_TAG / the wolfssl_ref input. | ||
| - name: Build wolfProvider stack (openssl + wolfssl + wolfprovider) | ||
| env: | ||
| WOLFSSL_TAG: ${{ inputs.wolfssl_ref || 'master' }} | ||
| run: WOLFPROV_SKIP_TEST=1 ./scripts/build-wolfprovider.sh | ||
|
|
||
| # Record the versions of the wolfSSL and OpenSSL that were actually | ||
| # linked, so the SBOM's dependency versions match the deployed binaries. | ||
| - name: Resolve linked wolfSSL / OpenSSL versions | ||
| id: vers | ||
| run: | | ||
| wv="" | ||
| if [ -f wolfssl-source/wolfssl/version.h ]; then | ||
| wv=$(sed -n 's/.*LIBWOLFSSL_VERSION_STRING[ \t]*"\([^"]*\)".*/\1/p' \ | ||
| wolfssl-source/wolfssl/version.h) | ||
| fi | ||
| ov="" | ||
| if [ -x openssl-install/bin/openssl ]; then | ||
| # Pass the raw string (may include BUILD_METADATA, e.g. | ||
| # 3.5.4+wolfProvider-nonfips). gen-sbom keeps it in versionInfo | ||
| # and drops the +suffix from CPE 2.3 and PURL. | ||
| ov=$(openssl-install/bin/openssl version | awk '{print $2}') | ||
| fi | ||
| echo "wolfssl=$wv" >> "$GITHUB_OUTPUT" | ||
| echo "openssl=$ov" >> "$GITHUB_OUTPUT" | ||
| echo "linked wolfSSL=$wv openssl=$ov" | ||
|
|
||
| - name: Generate SBOM | ||
| run: | | ||
| make sbom \ | ||
| WOLFSSL_DIR="$GITHUB_WORKSPACE/wolfssl-source" \ | ||
| SBOM_WOLFSSL_VERSION="${{ steps.vers.outputs.wolfssl }}" \ | ||
| SBOM_OPENSSL_VERSION="${{ steps.vers.outputs.openssl }}" | ||
|
|
||
| - name: Outputs exist and SPDX validates | ||
| run: | | ||
| ls wolfprovider-*.cdx.json wolfprovider-*.spdx.json wolfprovider-*.spdx | ||
| pyspdxtools --infile wolfprovider-*.spdx.json | ||
| python3 tools/sbom/validate_sbom.py \ | ||
| --name-prefix wolfprovider \ | ||
| --require-dep-version wolfssl \ | ||
| --require-dep-version openssl \ | ||
| wolfprovider-*.cdx.json wolfprovider-*.spdx.json | ||
|
|
||
| - name: CycloneDX identity and licence | ||
| run: | | ||
| python3 - <<'PY' | ||
| import glob, json | ||
| cdx = json.load(open(glob.glob('wolfprovider-*.cdx.json')[0])) | ||
| assert cdx['bomFormat'] == 'CycloneDX', cdx.get('bomFormat') | ||
| assert cdx['specVersion'] == '1.6', cdx.get('specVersion') | ||
| m = cdx['metadata']['component'] | ||
| assert m['name'] == 'wolfprovider', m['name'] | ||
| # purl-spec: github namespace and name are lowercased. | ||
| assert m['purl'].startswith('pkg:github/wolfssl/wolfprovider@'), m['purl'] | ||
| # Default override must land as GPL-3.0-or-later (matches source headers). | ||
| ids = [l.get('license', {}).get('id') for l in m.get('licenses', [])] | ||
| assert 'GPL-3.0-or-later' in ids, ids | ||
| # Identity is the hashed library artifact. | ||
| assert {h['alg'] for h in m.get('hashes', [])}, 'no component hash' | ||
| print('CDX ok:', m['name'], m['purl'], ids) | ||
| PY | ||
|
|
||
| - name: Assert no host path leak | ||
| run: | | ||
| if grep -REn '"/(home|Users|root)/' wolfprovider-*.cdx.json \ | ||
| wolfprovider-*.spdx.json; then | ||
| echo "ERROR: absolute host path found in SBOM (scrub failed)." >&2 | ||
| exit 1 | ||
| fi | ||
| echo "OK: no host path leak." | ||
|
|
||
| - name: Reproducible across two runs (SOURCE_DATE_EPOCH) | ||
| run: | | ||
| rm -f wolfprovider-*.cdx.json wolfprovider-*.spdx.json wolfprovider-*.spdx | ||
| SOURCE_DATE_EPOCH=1700000000 make sbom \ | ||
| WOLFSSL_DIR="$GITHUB_WORKSPACE/wolfssl-source" \ | ||
| SBOM_WOLFSSL_VERSION="${{ steps.vers.outputs.wolfssl }}" \ | ||
| SBOM_OPENSSL_VERSION="${{ steps.vers.outputs.openssl }}" | ||
| sha256sum wolfprovider-*.cdx.json wolfprovider-*.spdx.json > /tmp/a.sums | ||
| rm -f wolfprovider-*.cdx.json wolfprovider-*.spdx.json wolfprovider-*.spdx | ||
| SOURCE_DATE_EPOCH=1700000000 make sbom \ | ||
| WOLFSSL_DIR="$GITHUB_WORKSPACE/wolfssl-source" \ | ||
| SBOM_WOLFSSL_VERSION="${{ steps.vers.outputs.wolfssl }}" \ | ||
| SBOM_OPENSSL_VERSION="${{ steps.vers.outputs.openssl }}" | ||
| sha256sum wolfprovider-*.cdx.json wolfprovider-*.spdx.json > /tmp/b.sums | ||
| diff /tmp/a.sums /tmp/b.sums | ||
|
|
||
| - name: wolfssl recorded as a dependency | ||
| run: | | ||
| python3 - <<'PY' | ||
| import glob, json | ||
| d = json.load(open(glob.glob('wolfprovider-*.spdx.json')[0])) | ||
| assert 'wolfssl' in {p['name'] for p in d['packages']}, \ | ||
| [p['name'] for p in d['packages']] | ||
| rels = [(r['spdxElementId'], r['relationshipType'], | ||
| r['relatedSpdxElement']) for r in d['relationships']] | ||
| assert ('SPDXRef-Package-wolfprovider', 'DEPENDS_ON', | ||
| 'SPDXRef-Package-wolfssl') in rels, rels | ||
| print('wolfssl dependency ok') | ||
| PY | ||
|
|
||
| - name: openssl recorded as a dependency | ||
| run: | | ||
| python3 - <<'PY' | ||
| import glob, json | ||
| d = json.load(open(glob.glob('wolfprovider-*.spdx.json')[0])) | ||
| pkgs = {p['name']: p for p in d['packages']} | ||
| assert 'openssl' in pkgs, list(pkgs) | ||
| rels = [(r['spdxElementId'], r['relationshipType'], | ||
| r['relatedSpdxElement']) for r in d['relationships']] | ||
| assert ('SPDXRef-Package-wolfprovider', 'DEPENDS_ON', | ||
| 'SPDXRef-Package-openssl') in rels, rels | ||
| openssl = pkgs['openssl'] | ||
| # versionInfo keeps BUILD_METADATA when openssl version prints it. | ||
| # CPE 2.3 and PURL must not contain a raw '+'. | ||
| refs = {r['referenceType']: r['referenceLocator'] | ||
| for r in openssl.get('externalRefs', [])} | ||
| cpe = refs.get('cpe23Type', '') | ||
| purl = refs.get('purl', '') | ||
| assert cpe.startswith('cpe:2.3:a:openssl:openssl:'), cpe | ||
| assert '+' not in cpe, cpe | ||
| assert purl.startswith('pkg:github/openssl/openssl@openssl-'), purl | ||
| assert '+' not in purl, purl | ||
| print('openssl dependency ok:', openssl.get('versionInfo'), cpe, purl) | ||
| PY | ||
|
|
||
| - name: Upload SBOM artefacts | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: wolfprovider-sbom-${{ github.sha }} | ||
| path: | | ||
| wolfprovider-*.cdx.json | ||
| wolfprovider-*.spdx.json | ||
| wolfprovider-*.spdx | ||
| if-no-files-found: warn | ||
| retention-days: 90 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ noinst_HEADERS = | |
| check_PROGRAMS = | ||
| dist_noinst_SCRIPTS = | ||
| DISTCLEANFILES = | ||
| CLEANFILES = | ||
| pkginclude_HEADERS = | ||
| EXTRA_DIST = | ||
|
|
||
|
|
@@ -52,3 +53,38 @@ test: check | |
| # The '--with-wolfssl' doesn't get propagated during a distcheck either, but it | ||
| # is necessary when they are installed somewhere other than /usr/local. | ||
| AM_DISTCHECK_CONFIGURE_FLAGS=CPPFLAGS="-I@abs_top_srcdir@/include" --with-openssl=@OPENSSL_INSTALL_DIR@ --with-wolfssl=@WOLFSSL_INSTALL_DIR@ | ||
|
|
||
| # SBOM generation (CRA compliance). The recipe is the vendored wolfGlass | ||
| # fragment at tools/sbom/sbom.am. wolfProvider declares what it is (a shared | ||
| # library that links wolfSSL and OpenSSL) and includes it. gen-sbom is vendored | ||
| # next to the fragment, so `make sbom` does not need a wolfSSL source tree. | ||
| # Set WOLFSSL_DIR only when you want the SBOM to read the linked wolfSSL | ||
| # version from wolfssl/version.h. | ||
| SBOM_PKGNAME = wolfprovider | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SBOM package name (wolfprovider) diverges from autoconf package (wolfprov)
Fix: Confirm |
||
| SBOM_LICENSE_FILE = $(srcdir)/COPYING | ||
| SBOM_LIB_STEM = libwolfprov | ||
| SBOM_DEP_WOLFSSL = yes | ||
| SBOM_DEP_OPENSSL = yes | ||
|
|
||
| # AC_CONFIG_HEADERS puts our config.h in include/, not the build root, so the | ||
| # fragment's default would silently miss every AC_DEFINE feature macro: the | ||
| # -include is $(wildcard)-guarded, so a wrong path drops out without an error | ||
| # and the SBOM records an empty feature set. | ||
| SBOM_CONFIG_H = $(abs_builddir)/include/config.h | ||
|
|
||
| # wolfProvider is GPLv3-or-later (per the per-file source headers: "either | ||
| # version 3 of the License, or (at your option) any later version") or | ||
| # commercial. Pin the header-accurate SPDX id here so the SBOM is correct | ||
| # regardless of the gen-sbom version's licence detection; commercial licensees | ||
| # can override it (e.g. LicenseRef-wolfSSL-Commercial). | ||
| SBOM_LICENSE_OVERRIDE ?= GPL-3.0-or-later | ||
|
|
||
| # Automake inlines this include into Makefile. Do not assign SBOM_GEN or | ||
| # SBOM_VENDOR_DIR here: Automake -Werror treats a second assignment in | ||
| # tools/sbom/sbom.am as fatal and skips Makefile.in. The fragment default | ||
| # SBOM_VENDOR_DIR is $(srcdir)/tools/sbom. | ||
|
|
||
| EXTRA_DIST += tools/sbom | ||
|
|
||
| include tools/sbom/sbom.am | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -89,6 +89,50 @@ Without an enable flag no PQC code is compiled, regardless of what wolfSSL enabl | |
| * ML-DSA (FIPS 204): ML-DSA-44, ML-DSA-65, ML-DSA-87 (signatures, pure mode with empty context per FIPS 204 sec 5.2) | ||
|
|
||
|
|
||
| ## SBOM / EU CRA Compliance | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not the right spot for this documentation I would suggest creating a SBOM.md or better a chapter in the documentation repo for WP |
||
|
|
||
| wolfProvider generates a Software Bill of Materials (SBOM) in CycloneDX 1.6 and | ||
| SPDX 2.3 formats to support compliance with the EU Cyber Resilience Act (CRA). | ||
| The generator is the vendored [wolfGlass](https://github.com/wolfSSL/wolfGlass) | ||
| toolkit under `tools/sbom/`. The SBOM records the configured build options, | ||
| hashes the built `libwolfprov` library artifact (shared or static; ELF, Mach-O, | ||
| or PE), and lists both wolfSSL and OpenSSL as dependencies so vulnerability | ||
| scanners can associate wolfSSL and OpenSSL advisories with a wolfProvider | ||
| deployment. Output is reproducible: set `SOURCE_DATE_EPOCH` (or build from a | ||
| git checkout, which uses the last commit time) and repeated runs are | ||
| byte-identical. | ||
|
|
||
| ```sh | ||
| make sbom | ||
| ``` | ||
|
|
||
| Requires `python3` and `pyspdxtools` (`pip install spdx-tools`). The generator | ||
| is vendored, so you do not need a wolfSSL source tree to run `make sbom`. Set | ||
| `WOLFSSL_DIR` only when you want the SBOM to read the linked wolfSSL version | ||
| from `wolfssl/version.h`. | ||
|
|
||
| Output: `wolfprovider-<version>.cdx.json`, `wolfprovider-<version>.spdx.json`, `wolfprovider-<version>.spdx` | ||
|
|
||
| Optional overrides: | ||
|
|
||
| - `SBOM_LICENSE_OVERRIDE` - SPDX expression to use instead of the licence | ||
| parsed from `COPYING` (e.g. `LicenseRef-wolfSSL-Commercial` for commercial | ||
| licensees). Defaults to `GPL-3.0-or-later` (the per-file header licence). | ||
| - `SBOM_LICENSE_TEXT` - path to the licence text for any `LicenseRef-*` used in | ||
| `SBOM_LICENSE_OVERRIDE` (required by SPDX 2.3). | ||
| - `SBOM_WOLFSSL_VERSION` - version recorded for the wolfSSL dependency; | ||
| auto-detected from `WOLFSSL_DIR/wolfssl/version.h` (or wolfSSL's `pkg-config` | ||
| entry) when unset. | ||
| - `SBOM_OPENSSL_VERSION` - version recorded for the OpenSSL dependency; | ||
| resolved via OpenSSL's `pkg-config` entry when unset. | ||
|
|
||
| ```sh | ||
| make install-sbom # installs to $(datadir)/doc/wolfprov/ | ||
| make uninstall-sbom | ||
| ``` | ||
|
|
||
| For further CRA guidance see [wolfssl/doc/CRA.md](https://github.com/wolfSSL/wolfssl/blob/master/doc/CRA.md). | ||
|
|
||
| ## Support | ||
|
|
||
| - [GitHub Issues](https://github.com/wolfssl/wolfProvider/issues) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.