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
213 changes: 150 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,109 @@
[![Coverage Status](https://coveralls.io/repos/github/erlef/rebar3_sbom/badge.svg?branch=main)](https://coveralls.io/github/erlef/rebar3_sbom?branch=main)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/11547/badge)](https://www.bestpractices.dev/projects/11547)

Generates a Software Bill-of-Materials (SBoM) in CycloneDX format
Generates a Software Bill-of-Materials (SBoM) for [Rebar3](https://rebar3.org)
projects, that follows the [CycloneDX](https://cyclonedx.org) specification
([1.6](https://spec.cyclonedx.org/1.6/)).

## Use
[CycloneDX](https://cyclonedx.org) is an open SBOM standard (maintained by the
[OWASP Foundation](https://owasp.org)) with broad support in security scanners,
dependency trackers, and compliance tools.

Add rebar3_sbom to your rebar config, either in a project or globally in
`~/.config/rebar3/rebar.config`:
## What is a Software Bill of Materials (SBoM) and why use it?

In manufacturing, a Bill of Materials (BOM) is the list of raw materials, parts,
and sub-assemblies needed to build a product, so you know exactly what goes into
it, in what quantity, and from where.

A **Software Bill of Materials (SBoM)** does the same for software: it **lists
the components of your application** (packages and libraries) along with their
`versions`, `source locations` (e.g. Hex, GitHub), `checksums` (cryptographic
hashes for integrity), and `licensing information`.

That visibility supports **supply chain tracking** (what is in the software and
where it came from), faster response to vulnerabilities by **matching components
against vulnerability databases to respond to CVEs**, and **licence and compliance
reporting** (e.g. audits and due diligence). That is why **many organisations
and regulations expect or require an SBoM for critical software** — e.g.
safety-critical sectors, government procurement, or customers requiring supply
chain transparency.

> #### Info {: .info}
>
> An SBoM is an inventory, not a certification. It does not claim that software is secure or compliant. It provides a factual inventory that other tools and processes use for security and compliance assessments.

## Installation

Add the plugin to your Rebar3 config, per project (recommended) or globally.

**1. Project (recommended)**. In the project's `rebar.config`:

```erlang
{plugins, [rebar3_sbom]}.
```

**2. Global**. In `~/.config/rebar3/rebar.config`:

```erlang
{plugins, [rebar3_sbom]}.
```

Then run the `sbom` task on a project:
Now, when you run any rebar3 command (e.g. `rebar3 compile` or `rebar3 sbom`), the plugin will be fetched.

## Usage

From the project directory (where your `rebar.config` lives), run:

```bash
rebar3 sbom
```

Result:

```
$ rebar3 sbom
===> Verifying dependencies...
===> CycloneDX SBoM written to bom.xml
```

By default the SBoM is written to `bom.xml` (or `bom.json` if you use `--format json`).


See [Available options](#available-options) for all flags.

### Choosing which profiles to include

Only dependencies in the `default` profile are included by default. To generate an SBoM that also includes development dependencies (e.g. from the `test` or `docs` profiles), specify the profiles using `as`:

```bash
rebar3 as default,test,docs sbom -o dev_bom.xml
```

## Available options

The following command line options are supported:

| Option | Short | Description | Default |
|--------|-------|-------------|---------|
| `--format` | `-F` | The file format of the SBoM output: `xml` or `json` | `xml` |
| `--output` | `-o` | The full path to the SBoM output file | `./bom.xml` or `./bom.json` |
| `--force` | `-f` | Overwrite existing files without prompting for confirmation | `false` |
| `--strict_version` | `-V` | When true, the BOM version is incremented only if the set of components differs from the existing BOM file (if any). Changes to metadata, timestamp, or other fields do not trigger a version increment. | `true` |
| `--author` | `-a` | The author of the SBoM | `GITHUB_ACTOR` env var; if unset, `authors` from `.app.src` file|

To see all options and their descriptions, run: `rebar3 help sbom`.


## Configuration

You can configure additional SBoM metadata in your `rebar.config`:
Optional SBoM metadata, like `sbom_manufacturer` or `sbom_licenses`, is set in
`rebar.config` under the `rebar3_sbom` key. Both options are optional; omit them
if you don't need them.

Example:

```erlang
{rebar3_sbom, [
{sbom_manufacturer, #{ % Optional, all fields inside are optional
{sbom_manufacturer, #{ % Optional; all fields inside are optional
name => "Your Organization",
url => ["https://example.com", "https://another-example.com"],
address => #{
Expand All @@ -55,63 +132,34 @@ You can configure additional SBoM metadata in your `rebar.config`:
phone => "123456789"}
]
}},
{sbom_licenses, ["Apache-2.0"]} % Optional
{sbom_licenses, ["Apache-2.0"]} % Optional; licenses for the SBoM document itself
]}.
```
- `sbom_manufacturer` — **Who is producing the SBoM** (e.g. your organisation or
CI). If omitted, the `manufacturer` field is not included in the SBOM. All
fields inside are optional.
- `sbom_licenses` — Licenses for the SBoM document itself (the metadata), not
your project. If omitted, it defaults to your project's licenses from
`.app.src`. In the generated SBoM, these appear under `metadata.licenses`.
Your project's licenses are always read from `.app.src` and appear in
`metadata.component.licenses`.

**Note:**
- `sbom_manufacturer` (optional) identifies who is **producing the SBoM document**
(typically your organization or CI environment), not necessarily who developed
the software. If omitted, the manufacturer field is not included in the SBoM.
- `sbom_licenses` (optional) specifies the licenses for the **SBoM document itself**
(the metadata), not your project. If omitted, it defaults to the same licenses
as your project (from the `.app.src` file).
- Your project's licenses are automatically read from the `.app.src` file and
appear in `metadata.component.licenses`.


## Command Line Options

The following command line options are supported:

-F, --format the file format of the SBoM output, [xml|json], [default: xml]
-o, --output the full path to the SBoM output file [default: ./bom.[xml|json]]
-f, --force overwite existing files without prompting for confirmation
[default: false]
-V, --strict_version modify the version number of the BoM only when the content changes
[default: true]
-a --author the author of the SBoM

**Author Fallback:** If `--author` is not specified, the plugin will fall back
to the `GITHUB_ACTOR` environment variable. If that variable isn't set, it will
use the authors from the project's `.app.src` file.

By default only dependencies in the 'default' profile are included. To
generate an SBoM covering development environments specify the relevant
profiles using 'as':

```bash
$ rebar3 as default,test,docs sbom -o dev_bom.xml
```

## Hash Generation
## Hash generation

For the main component (`metadata.component`), the plugin computes the SHA-256 hash of the release tarball (`<name>-<version>.tar.gz`) found in the release directory.
For the main component (the root application described by the BOM, in CycloneDX `metadata.component`), the plugin computes the `SHA-256` hash of the release tarball (`<name>-<version>.tar.gz`). The tarball is looked for under `rel/<app_name>/` relative to the project base directory (e.g. after `rebar3 release` or `rebar3 tar`).

If the tarball does not exist (e.g., because `rebar3 tar` hasn't been run), no hash is included for the main component, and a warning is logged.
If the tarball does not exist, no hash is included for the main component, and a warning is logged.

## CPE Generation

The plugin automatically generates a CPE (Common Platform Enumeration) identifier for the main component (`metadata.component`) using the GitHub link from your project's `.app.src` file. If no GitHub link is present, the CPE field is omitted from the SBoM.
The plugin automatically generates a CPE (Common Platform Enumeration) identifier for the main component (`metadata.component`) using the GitHub link from your project's `.app.src` file. Dependencies also get a CPE when the package has a GitHub link (e.g. from Hex metadata). If no GitHub link is present, the CPE field is omitted for that component.

To ensure CPE generation, add a GitHub link to your `.app.src` file. For example:
To ensure CPE generation for the main component, add a GitHub link to your `.app.src` file. For example:

```erlang
{application, my_app, [
...
{links, [
{"GitHub", "https://github.com/your-org/my_app"}
]}
{links, [{"GitHub", "https://github.com/your-org/my_app"}]}
]}.
```

Expand All @@ -121,13 +169,17 @@ The plugin supports external references for components, which are automatically

All standard CycloneDX external reference types are supported. Additionally, for convenience, the plugin supports common field names used by the Erlang/Elixir community, which are automatically mapped to their CycloneDX equivalents:

- `"GitHub"` → `"vcs"`
- `"Homepage"` → `"website"`
- `"Changelog"` → `"release-notes"`
- `"Issues"` → `"issue-tracker"`
- `"Documentation"` → `"documentation"`
| Link name | CycloneDX type |
|------------------|--------------------|
| `"GitHub"` | `"vcs"` |
| `"Homepage"` | `"website"` |
| `"Changelog"` | `"release-notes"` |
| `"Issues"` | `"issue-tracker"` |
| `"Documentation"`| `"documentation"` |

**Note:** The plugin treats the names (i.e., `"Homepage"`, `"GitHub"`, etc.) in the `links` field as case-insensitive, so `"homepage"` and `"HOMEPAGE"` will also map to `"website"`, for example.
> #### Info {: .info}
>
> The plugin treats the names (i.e., `"Homepage"`, `"GitHub"`, etc.) in the `links` field as case-insensitive, so `"homepage"` and `"HOMEPAGE"` will also map to `"website"`, for example.

You can use either the standard CycloneDX type names or the community convention names in your `.app.src` file:

Expand All @@ -144,14 +196,49 @@ You can use either the standard CycloneDX type names or the community convention
]}.
```

Development
-----------
## Merging and other ecosystems

This plugin only considers `Rebar3/Hex` and `Git` (GitHub, Bitbucket) dependencies. For a full deployment SBoM (e.g. including NPM or OS packages), generate multiple CycloneDX BOMs and merge them with a tool such as [CycloneDX CLI](https://github.com/CycloneDX/cyclonedx-cli) using its merge command (e.g. `cyclonedx merge --input-files bom1.xml bom2.xml --output-file merged.xml`).

## Development

### Development environment

You can get Erlang, Rebar3, and the tools used by this project in either of these ways:

- **devenv**: From the repository root, run `devenv shell` to enter a shell
with Erlang, Rebar3, CycloneDX CLI, and other tools (see [devenv](https://devenv.sh)).
- **asdf**: With [asdf](https://asdf-vm.com/) installed, run `asdf install` in
the repository root; versions are defined in [`.tool-versions`](https://github.com/erlef/rebar3_sbom/blob/main/.tool-versions).

Linting is configured in [`elvis.config`](elvis.config). Run `rebar3 lint` to
check code style before submitting.

### Contributing

For guidelines on how to contribute (bug reports, feature proposals, pull
requests), see the [Contributing guide](https://github.com/erlef/rebar3_sbom/blob/main/.github/CONTRIBUTING.md).

### Generating documentation

With Erlang/OTP 27+ and Rebar3 available (e.g. `devenv shell` or `.tool-versions`):

```bash
rebar3 compile
rebar3 ex_doc
```

Open `doc/index.html` in a browser.

### Running tests

To run the full test suite locally, you need the CycloneDX CLI (`cyclonedx-cli`) available on your `PATH`, as it is used by `rebar3_sbom_validation_SUITE` to validate generated SBOMs.

For example, on macOS or Linux with Homebrew:

brew tap cyclonedx/cyclonedx
brew install cyclonedx/cyclonedx/cyclonedx-cli
```bash
brew tap cyclonedx/cyclonedx
brew install cyclonedx/cyclonedx/cyclonedx-cli
```

More informations about the tool: https://github.com/CycloneDX/cyclonedx-cli
More information about the tool: https://github.com/CycloneDX/cyclonedx-cli
9 changes: 9 additions & 0 deletions src/rebar3_sbom.erl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@

-include("rebar3_sbom.hrl").

-moduledoc """
Entry point for the Rebar3 SBoM plugin.
Registers the `sbom` provider (see `m:rebar3_sbom_prv`).

This module also exports the types used for CycloneDX SBoM structures
(`sbom/0`, `component/0`, `metadata/0`, etc.) for use by other modules.
""".

-export([init/1]).

-export_type([
Expand All @@ -30,6 +38,7 @@
-type dependency() :: #dependency{}.
-type sbom() :: #sbom{}.

-doc "Registers the `sbom` provider with Rebar3.".
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
{ok, State1} = rebar3_sbom_prv:init(State),
Expand Down
11 changes: 11 additions & 0 deletions src/rebar3_sbom_cpe.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
% Includes
-include("rebar3_sbom.hrl").

-moduledoc """
Builds CPE (Common Platform Enumeration) identifiers for SBoM components. Uses
the component name, version, and (for GitHub deps) vendor from the source URL.

Used by the provider when building components (see `m:rebar3_sbom_prv`).
""".

%--- Macros --------------------------------------------------------------------
-define(CPE_PREFIX, <<"cpe:", ?CPE_VERSION/binary>>).
% Includes the fields:
Expand All @@ -28,6 +35,10 @@

%--- API -----------------------------------------------------------------------

-doc """
Returns a CPE string for the component, or `undefined` if no GitHub URL
for unknown names. CPE version is defined in `rebar3_sbom.hrl`.
""".
-spec cpe(Name, Version, Url) -> CPE when
Name :: bitstring(),
Version :: bitstring(),
Expand Down
9 changes: 9 additions & 0 deletions src/rebar3_sbom_cyclonedx.erl
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,18 @@

-include("rebar3_sbom.hrl").

-moduledoc """
Builds a CycloneDX SBoM from the application, its dependencies, and metadata.

License IDs are resolved via `m:rebar3_sbom_license`.
""".

-doc "Builds a CycloneDX SBoM; serial (UUID) is generated automatically.".
-doc #{equiv => bom / 6}.
bom(FileInfo, IsStrictVersion, App, Plugin, MetadataInfo) ->
bom(FileInfo, IsStrictVersion, App, Plugin, uuid(), MetadataInfo).

-doc "Builds a CycloneDX SBoM with the given serial (UUID).".
bom({FilePath, _} = FileInfo, IsStrictVersion, App, Plugin, Serial, MetadataInfo) ->
{AppInfo, RawComponents} = App,
{PluginInfo, PluginDepsInfo} = Plugin,
Expand Down
2 changes: 2 additions & 0 deletions src/rebar3_sbom_json.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

-include("rebar3_sbom.hrl").

-moduledoc "Encodes and decodes the SBoM to and from CycloneDX JSON.".

-define(SCHEMA, <<"http://cyclonedx.org/schema/bom-1.6.schema.json">>).

encode(SBoM) ->
Expand Down
11 changes: 11 additions & 0 deletions src/rebar3_sbom_license.erl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@

-export([spdx_id/1]).

-moduledoc """
Maps license strings (e.g. from Hex or `.app.src`) to SPDX license identifiers.

Used when filling component and metadata licenses in the SBoM
(see `m:rebar3_sbom_cyclonedx`).
""".

-define(SPDX_ID, #{
"0bsd" => "0BSD",
"aal" => "AAL",
Expand Down Expand Up @@ -408,6 +415,10 @@
"zlib-acknowledgement" => "zlib-acknowledgement"
}).

-doc """
Returns the SPDX license ID for a given string if known; otherwise the original
string.
""".
spdx_id(Id) ->
maps:get(normalize(Id), ?SPDX_ID, undefined).

Expand Down
Loading