Once the BOMnipotent Server is up and running, you can begin uploading documents. A typical workflow begins with generating an SBOM, an inventory of what components your product contains. This step should be automatically triggered upon creating a new release. Then this inventory is scanned for known vulnerabilities. This should also be an automatic step, but it should be triggered periodically. If a vulnerability is discovered, it is time to analyse it. This task needs expertise, and thus cannot be done automatically. The result of this analysis is a CSAF document, which tells your users if and how they have to react to this new development.
To let BOMnipotent Server directly communicate with your email server, set up the
smtp part
of your configuration file to look roughly like this:
[smtp]
user = "you@yourdomain.com"endpoint = "your.smtp.host"secret = "${SMTP_SECRET}"
The exact form will strongly depend on your email provider. Some may require the full email address as the user, others may not.
Clients like Mozilla Thunderbird are usually very good at deducing the required parameters. If you’re at a loss, look for them there.
Communication via SMTP Relay
If you have more than one service that sends email it can be benficial to locally run an SMTP relay station. It offers a single endpoint for your setup to communicate with the mail server.
There are several docker containers that offer SMTP relay functionality. This tutorial focuses on
crazymax/msmtpd
, because it has the best security posture among the lightweight solutions.
Running Relay via Docker Compose
Add the following service to your compose.yaml file:
smtp_relay:
container_name: smtp_relaydeploy:
resources:
limits:
cpus: "0.5"memory: "512M"environment:
TZ: Europe/Berlin# Replace with your preferred timezoneSMTP_HOST: your.smtp.host# Replace with the correct endpointSMTP_PORT: 465SMTP_TLS: onSMTP_STARTTLS: offSMTP_TLS_CHECKCERT: onSMTP_AUTH: loginSMTP_USER: you@yourdomain.com# Replace with your usernameSMTP_FROM: you@yourdomain.com# Replace with your email addressSMTP_PASSWORD: ${SMTP_PASSWORD}SMTP_DOMAIN: localhosthealthcheck:
test: ["CMD", "msmtp", "--version"]
interval: 30stimeout: 10sretries: 3image: crazymax/msmtpdlogging:
driver: localoptions:
max-size: "10m"max-file: "3"networks:
- smtp_networkrestart: unless-stopped
This will spin up the container, connecting to Port 465 (the default for SMTPS protocol) of the SMTP Host, encrypting with TLS and not STARTTLS. It will listen on port 2500, which is not obvious from the input but the default behaviour of msmtp.
The modification of your compose file is not yet done, though!
Under networks, you have to declare the smtp_network:
You also need to add the network to any container that is supposed to contact it. You may also want for these containers to depend on the smtp_relay, so that they don’t start before the relay station is ready:
Aside from the modifications to the compose file, your .env file or your environment needs to provide the secret or password for your mail provider:
SMTP_PASSWORD=eHD5B6S8Kze3
export SMTP_PASSWORD=eHD5B6S8Kze3
set SMTP_PASSWORD=eHD5B6S8Kze3
$env:SMTP_PASSWORD = "eHD5B6S8Kze3"
In your BOMnipotent Server config file, you can now modify your smtp section to connect to the relay via the docker network:
[smtp_config]
user = "you@yourdomain.com"endpoint = "smtp://smtp_relay:2500"
Running Relay in standalone Docker Container
If your setup does not have a compose file, you can instead run the container using Docker directly. Make sure that your environment provides a value for SMTP_PASSWORD, and then run
This does basically the same as the section suggested for the compose file. You again need to replace the values for TZ, SMTP_HOST, SMTP_USER and SMTP_FROM with the ones for your email provider.
The command above exposes the port 2500 to localhost, which is why your BOMnipotent config needs to be as follows:
[smtp_config]
user = "you@yourdomain.com"endpoint = "smtp://localhost:2500"
To stop the container, run:
docker stop smtp_relay
SBOMs with Syft
A Software Bill of Material (SBOM) is a list of all software components used in your product. In the contex of supply chain security, it serves as a machine-readable list of items to compare to whenever a new vulnerability surfaces.
Several tools exist to automatically generate such an SBOM. This tutorial focuses on Anchore’s Syft, an open source command line tool.
Setup
The official
Syft GitHub repo
contains installation instructions. It is available via a shell script or via various package managers.
On some linux systems you may want to change the install path (the very last argument to the shell command) to “~/.local/bin”, because “/usr/local/bin” requires root permissions to modify.
Usage
The basic usage of Syft is:
syft <target> [options]
Additionally, some configuration can be made using environment variables.
Syft supports lockfiles, directories, container images and more as targets.
‘SYFT_FORMAT_PRETTY=1’ is makes this call with an environment variable that tells Syft to use prettified output. This only serves to make the resulting json easier readable for humans. See
here
for a full list of configurations.
‘syft’ cals the Syft program.
‘Cargo.lock’ tells Syft to analyse the lockfile of the Rust ecosystem.
‘–output cyclonedx-json=./sbom.cdx.json’ specifies that the output is to be stored in the
CycloneDx
JSON format in the file ‘./sbom.cdx.json’.
‘–source-name=“BOMnipotent”’ explains to Syft that these are the sources for the BOMnipotent component, which it may not automatically detect in all cases.
The CycloneDX schema may not require a component name, but BOMnipotent does.
Likewise ‘–source-version=“1.0.0”’ tells Syft the current version of your project.
If you do not provide a version, BOMnipotent will try to use the timestamp as a version string instead.
Syft supports a wide range of ecosystems, which is listed on their
GitHub repo
.
Directory
Letting Syft loose on a whole directory is possible, but overdoes it in most situations. It will go through all subdirectories and collect everything that looks remotely like a lockfile, including all your test dependencies, development scripts and GitHub Actions.
syft . --output cyclonedx-json=./dev_env_sbom.cdx.json --source-name="BOMnipotent Development Environment" --source-version=1.2.3
syft . -o cyclonedx-json=./dev_env_sbom.cdx.json --source-name="BOMnipotent Development Environment" --source-version=1.2.3
Container
If you have a docker container exported as a ‘.tar’ file you can also specify that as a target:
For compiled languages the results will be vastly different, because most information about the components that went into compilation is lost. On the other hand, this SBOM contains information about the environment that your product may later run in.
Final remarks
When deciding on a target, it is important to think about the scope of your application: What do you ship to the customer? Up to which extend are you responsible for the supply chain of your product? If in doubt, there’s no harm in uploading more than one variant of a BOM, as long as product name or version are different.
Once your SBOM is generated, it is time to continuously scan it for vulnerabilities. Note that some laws, for example the EU’s Cyber Resiliance Act, require that products are released without any known vulnerability. The first scan should therefore happen before a release.
There are several tools for scanning a product for supply chain vulnerabilities. This tutorial uses Anchore’s Grype, because it integrates well with Anchore’s Syft from the
SBOM tutorial
. Like Syft, Grype is an open source command line utility.
Setup
The official
Grype GitHub repo
contains installation instructions. Like for Syft, you may want to change the install path (the very last argument to the shell command) to ‘~/.local/bin’, because ‘/usr/local/bin’ requires root permissions to modify.
Usage
With an SBOM at hand, scanning for vulnerabilities is very easy:
grype sbom:./sbom.cdx.json --fail-on low
grype sbom:./sbom.cdx.json -f low
✔ Scanned for vulnerabilities [2 vulnerability matches]
├── by severity: 0 critical, 0 high, 2 medium, 0 low, 0 negligible
└── by status: 2 fixed, 0 not-fixed, 0 ignored
NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY
ring 0.17.8 0.17.12 rust-crate GHSA-4p46-pwfr-66x6 Medium
rustls 0.23.15 0.23.18 rust-crate GHSA-qg5g-gv98-5ffh Medium
[0000] ERROR discovered vulnerabilities at or above the severity threshold
When running this command, Grype checks
several vulnerability databases
for matches with the components provided in the sbom. The ‘fail-on’ option specifies that it exits with a non-zero error code if any with severity ’low’ or higher is discovered.
The syntax to export a vulnerability report consumable by BOMnipotent is similar to Syft:
Grype integrates well with BOMnipotent. You can use the “bom get” command of BOMnipotent Client to directly print the contents of a BOM to the console output, and then pipe it to grype:
bomnipotent_client bom get <BOM-NAME> <BOM-VERSION> | grype --output cyclonedx-json=./vuln.cdx.json
bomnipotent_client bom get <BOM-NAME> <BOM-VERSION> | grype -o cyclonedx-json=./vuln.cdx.json
CI/CD
BOMnipotent is built with automation in mind. By design, it never prompts for an interactive user input and is thus fully scriptable.
This page describes how to use BOMnipotent Client inside your CI/CD pipeline to
upload BOMs
with every release, and to
check for vulnerabilities
on a daily basis.
Weichwerke Heidrich Software has created several ready-to-use GitHub A to integrate BOMnipotent into your pipeline. Most of them are intentionally based on bash scripts, to make the transfer to other pipeline environments as easy and seamless as possible.
Prerequisites
This setup assumes that you have a BOMnipotent Server instance up and running. Consult the
setup instructions
if that is not yet the case.
The pipeline further requires that the server contains an approved robot user account with the BOM_MANAGEMENT and VULN_MANAGEMENT permissions. The sections on
account creation
and
access management
contain more information on how to creat one.
Setup BOMnipotent Client
The task of making executables available is where various pipeline environments show the most differences. Luckily, it is also the task that DevOps engineers figure out very early on. This task is therefore currently only described for GitHub Actions, where a ready-to-use step exists.
The three parameters are optional, but recommended:
domain: Provide the full domain (including subdomain) to your BOMnipotent Server instance. It will be stored in a session, so that you do not have to provide in subsequent calls.
user: Store the username of your robot user, which you have set up during the
prerequisites
section.
secret-key: Provide a reference to the secret key used to authenticate the robot user. You can make it available to your pipeline via <your repo> → Settings → Secrets and variables → Actions → New repository secret. In the example above, it is named “CLIENT_SECRET_KEY”.
Caution: Do not directly store your secret key inside the pipeline, or any other versioned file. This is what the GitHub secret mechanism is designed to handle.
After running this action, the command “bomnipotent_client.exe” (Windows) or “bomnipotent_client” (UNIX) is available for the rest of the job. Because different jobs run inside different containers, it may be necessary to call the setup action several times throughout the workflow.
Upload BOMs
A Bill of Materials (BOM) is a list of the components of a product. It is a static document, and thus each BOM is closely associated with a specific release of the product. Whenever the components of the (released) product change, it should be tagged with a new version, and associated with a new BOM.
This is why the step of uploading a BOM to the server should be run as part of the release pipeline.
Following the single responsibility principle, the action to upload BOMs has some prerequisites:
The BOMnipotent Client command has to be available. There is a
separate action
that ensures just that.
The action requires a BOM in CycloneDX format as input, which has to be generated in an earlier step.
The task of generating a BOM is highly specific to the product. The ideal tool depends on the ecosystem and how it is used.
Syft
is one tool that can assist, and that offers a ready-to-use
GitHub action
. It is by far not the only option: The
CycloneDX Tool Center
offers many alternatives.
GitHub Action
An often used pattern is to trigger the release pipeline upon pushing a tag that corresponds to a semantic version:
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
After setting up BOMnipotent Client and generating the BOM, it can be uploaded with the following snippet:
bom: This is the only mandatory argument, it needs to point to an existing file containing a BOM in CycloneDX format. In this example, the bom was stored under “./bom.cdx.json”.
name / version: The CycloneDX standard does not require a BOM to have a name or version, but BOMnipotent does. If the tool used to create the BOM does not offer to set the name and/or version, you can overwrite them via arguments. In the example above, the repository name is used as the product name, and the triggering tag as the version. You may instead also specify a path to a file: The line version: './version.txt' for example would tell the action to read the file “./version.txt” and use the contents as the version string.
tlp: You can specify a TLP label to classify the uploaded BOM. If not provided, the server’s
default TLP
applies.
The GitHub action is merely a wrapper for a bash script. To upload a BOM using your pipeline infrastructure you can download and use the
script
from the repo.
if [ ! -f ./upload_bom.sh ]; then
curl https://raw.githubusercontent.com/Weichwerke-Heidrich-Software/upload-bom-action/refs/heads/main/upload_bom.sh > ./upload_bom.sh
chmod +x ./upload_bom.sh
fi
./upload_bom.sh <bom.cdx.json> <optional args...>
The script takes the same arguments as the action does, except that the bom argument is positional, and that optional arguments need to be prefixed with a double hyphen:
While BOMs are static objects documenting the composition of a product, vulnerabilities associated with a BOM need to be checked regularly. Most vulnerability databases are updated on a daily basis, and vulnerability checks should match that frequency.
The BOMnipotent vulnerability action has two substeps:
Update the known vulnerabilities associated with all BOMs on the server.
Check the server for new, unassesed vulnerabilities.
Both steps can be skipped individually, in case they do not fit your use-case.
GitHub Action
To run the vulnerability check every day at 3:00 am (for example), add the following trigger to your workflow yaml:
on:
schedule:
- cron: '0 3 * * *'# Runs the workflow every day at 03:00 UTC
Once you have
set up BOMnipotent Client
on the agent, the snippet for handling the vulnerability checks is very simple:
The script downloads all BOMs accessible to the robot user, checks it against several databases of known vulnerabilities, and updates them on the server.
It then checks if there are any unassesed vulnerabilities. A vulnerability is unassessed if BOMnipotent Server does not contain a
CSAF document
associated with it.
Other Pipeline
Analogously to the
upload step
, the action is mostly a wrapper for a bash script. You can reference the
script
in the repo, or download and use it directly:
if [ ! -f ./upload_bom.sh ]; then
curl https://raw.githubusercontent.com/Weichwerke-Heidrich-Software/vulnerability-action/refs/heads/main/update_vulns.sh > ./update_vulns.sh
chmod +x ./update_vulns.sh
fi
./update_vulns.sh
The script internally uses
grype
to check for vulnerabilities. If you use the script directly, you will need to ensure that the program is installed.