OCI as attestations storage for your packages

Marco Franssen /
12 min read • 2368 words

In my previous blog you can read about securing the software supply chain for Docker images using GitHub actions and Sigstore. We have seen how we can sign our Docker images, as well how to generate an SBOM and build provenance. Using Sigstore/cosign we attached both the signature, SBOM and build provenance to the Docker image. Using Sigstore we get a real nice integration and developer experience to add these security features to our build pipelines for Docker images.
In this blog I want to show an approach to store SBOMs and Provenance in an OCI registry for other software release assets (e.g. npm, maven, nuget, …). To do so we will also utilize cosign
to interact with the registry. The developer experience will not be that nice and integrated as we have with Docker images, but at least this can be an approach to move into a more ideal situation untill there is some more integrated solution with package managers.
Using OCI as a blob storage we can leverage this to distribute and discover attestations for software assets that are not OCI/Docker images. The idea is to come up with some naming convention that can relate for example our npm package to a Blob in the OCI registry. Using this convention we can document and make it transparent for consumers of your assets where they can find the SBOM
as well the build provenance
.
Assuming we already have build provenance
(named provenance.att
) and an SBOM
(named sbom-spdx.json
) for a fictitious npm package, our folder structure might look like this.
$ tree examples/awesome-node-cli
examples/awesome-node-cli
├── bin
│ └── awesome-node-cli.js
├── node_modules
│ └── commander
│ └── …
├── sbom-spdx.json
├── provenance.att
├── package.json
└── yarn.lock
5 directories, 18 files
With this SBOM and provenance generated we can use an OCI registry to distribute and store the SBOM and provenance. See in the next part an example on how to do that using Cosign.
$ cosign upload blob -f provenance.att ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.provenance
Uploading file from [provenance.att] to [ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.provenance] with media type [text/plain]
File [provenance.att] is available directly at [ghcr.io/v2/marcofranssen/slsa-workflow-examples/attestations/blobs/sha256:f21e2217b67da30183139b8db23db829cd8abb7c9a5a66068e57cde07e29a215]
$ cosign sign --key cosign.key ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.provenance
Pushing signature to: ghcr.io/marcofranssen/slsa-workflow-examples/attestations
$ cosign upload blob -f sbom-spdx.json ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.sbom
Uploading file from [examples/awesome-node-cli/sbom-spdx.json] to [ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.sbom] with media type [text/plain]
File [examples/awesome-node-cli/sbom-spdx.json] is available directly at [ghcr.io/v2/marcofranssen/slsa-workflow-examples/attestations/blobs/sha256:ebdbffa2affe75b8ed7ff2e53cb41943a5201b13ff1a22375270f1c46c754f8c]
$ cosign sign --key cosign.key ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.sbom
Pushing signature to: ghcr.io/marcofranssen/slsa-workflow-examples/attestations
For any other package we can do similar by applying the same tagging convention («oci-registry»:«pkg-name»-«version».«attestation-type»
) like we applied in previous example.
With these attestations stored in the OCI registry, consumers of our assets can now easily download the attestations and verify its signature that guarantees its authenticity. We can do this using sget. Once downloaded we can write it to a file or pipe it to other shell tools to inspect/transform and use it for our usecases. More on consuming the stored attesations later in this blog, first lets have a look at a tool called fatt
, which is a proof of concept (POC) to make the process of publishing and discoverability easier, by automating above steps.
Automating the upload and signing steps
To automate the steps of uploading and signing the SBOMs and provenance further we build fatt as a POC. (⚠️ NOTE the signing is not yet implemented in fatt
and still a manual step, see fatt#20).
Fatt requires a few command line arguments to apply the convention we did manually using cosign. To identify the attestation type fatt
requires to define a URL Scheme
format in front of the attestations that will be uploaded (see the example below).
$ fatt publish --repository ghcr.io/marcofranssen/slsa-workflow-examples/attestations --tag-prefix awesome-node-cli --version v0.1.5 sbom://sbom-spdx.json provenance://provenance.att
Publishing attestations…
Uploading file from [examples/awesome-node-cli/sbom-spdx.json] to [ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.sbom] with media type [text/plain]
File [examples/awesome-node-cli/sbom-spdx.json] is available directly at [ghcr.io/v2/marcofranssen/slsa-workflow-examples/attestations/blobs/sha256:ebdbffa2affe75b8ed7ff2e53cb41943a5201b13ff1a22375270f1c46c754f8c]
Uploading file from [provenance.att] to [ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.provenance] with media type [text/plain]
File [provenance.att] is available directly at [ghcr.io/v2/marcofranssen/slsa-workflow-examples/attestations/blobs/sha256:f21e2217b67da30183139b8db23db829cd8abb7c9a5a66068e57cde07e29a215]
Generating attestations.txt based on uploaded attestations…
Uploading file from [attestations.txt] to [ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.discovery] with media type [text/plain]
File [attestations.txt] is available directly at [ghcr.io/v2/marcofranssen/slsa-workflow-examples/attestations/blobs/sha256:a978b71603dfe0e1c2f7b4915059d2aefc4bb051ea8710d200cdd927fe22138c]
# These 3 signing steps can be removed once https://github.com/philips-labs/fatt/issues/20 is implemented.
$ cosign sign --key cosign.key ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-npm-cli-v0.1.5.sbom
Pushing signature to: ghcr.io/marcofranssen/slsa-workflow-examples/attestations
$ cosign sign --key cosign.key ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-npm-cli-v0.1.5.provenance
Pushing signature to: ghcr.io/marcofranssen/slsa-workflow-examples/attestations
$ cosign sign --key cosign.key ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-npm-cli-v0.1.5.discovery
Pushing signature to: ghcr.io/marcofranssen/slsa-workflow-examples/attestations
Fatt uses the tagging convention as described earlier in this blog with the manual cosign
steps. We also added an additional blob, attestations.txt
, that we store in the OCI registry. In this attestations.txt
, fatt
stores the location of SBOM and provenane in purl format. Purls are commonly used in SBOMs to link to other resources. This attestations.txt
is used by fatt
filter capabilities to allow some advanced consumption usecases.
$ fatt list --key cosign.pub ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.discovery
Fetching attestations from ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.discovery…
Verifying signature for ghcr.io/marcofranssen/slsa-workflow-examples/attestations@sha256:252783857d310d36a54086cd1e5035298c98c5db604edc7daa7ce4cdcc03deaf…
pkg:oci/marcofranssen/slsa-workflow-examples/attestations@sha256:68f1d97c61e62228195aedfdfdce74b52123764c0c3b5f71d03b713bc63eb897?repository_url=ghcr.io%2Fmarcofranssen%2Fslsa-workflow-examples%2Fattestations&tag=awesome-node-cli-v0.1.5.sbom
pkg:oci/marcofranssen/slsa-workflow-examples/attestations@sha256:cad11e9b6229631175321b6e35dfbf0124064ef82761e41800bc533ecc3e8f06?repository_url=ghcr.io%2Fmarcofranssen%2Fslsa-workflow-examples%2Fattestations&tag=awesome-node-cli-v0.1.5.provenance
At this stage we have our attestations published, either via cosign or using fatt
as a convenience. As we used a convention it should be more recognizable for consumers of your packages where to find the attestations. This enables consumers to automate the consumption of the attestations. E.g.:
- Check SBOMs for license violations (e.g. GPL)
- Check SBoms for vulnerabilities
- Check provenance if artifact was produced by a trusted build environment
- …
Consuming
We can utilize sget to fetch a specific blob from an OCI registry. Sget is part of the Sigstore tools. It downloads the given resource and automatically verifies the signature of this artifact to proof the authenticity.
COSIGN_EXPERIMENTAL=1 sget ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.discovery
In my case I used the ephemeral keys from sigstore (COSIGN_EXPERIMENTAL=1
). If you used selfsigned keys you will have to use the --key
flag to provide your public key.
SBOM example
Fetching by tag requires signature verification. In this case you will receive 2 json objects. One containing the signature data and one containing your SBOM. To get the SBOM contents we can use jq
.
$ COSIGN_EXPERIMENTAL=1 sget ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.sbom | grep -v '^Certificate' | jq --slurp '.[1] | .packages'
Verification for ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.sbom --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- Any certificates were verified against the Fulcio roots.
[
{
"SPDXID": "SPDXRef-8f4c75ddef2e1d03",
"name": "commander",
"licenseConcluded": "NONE",
"downloadLocation": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceLocator": "cpe:2.3:a:commander:commander:9.0.0:*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
},
{
"referenceCategory": "SECURITY",
"referenceLocator": "cpe:2.3:a:*:commander:9.0.0:*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
},
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceLocator": "pkg:npm/[email protected]",
"referenceType": "purl"
}
],
"filesAnalyzed": false,
"licenseDeclared": "NONE",
"sourceInfo": "acquired package info from installed node module manifest file: yarn.lock",
"versionInfo": "9.0.0"
}
]
In case you fetch the asset by digest you can omit the signature verification. However the digest is less userfriendly to remember and type. Small advantage is you will not receive the signature in the output of the command.
$ sget ghcr.io/marcofranssen/slsa-workflow-examples/attestations@sha256:68f1d97c61e62228195aedfdfdce74b52123764c0c3b5f71d03b713bc63eb897 | jq '.packages'
[
{
"SPDXID": "SPDXRef-8f4c75ddef2e1d03",
"name": "commander",
"licenseConcluded": "NONE",
"downloadLocation": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceLocator": "cpe:2.3:a:commander:commander:9.0.0:*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
},
{
"referenceCategory": "SECURITY",
"referenceLocator": "cpe:2.3:a:*:commander:9.0.0:*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
},
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceLocator": "pkg:npm/[email protected]",
"referenceType": "purl"
}
],
"filesAnalyzed": false,
"licenseDeclared": "NONE",
"sourceInfo": "acquired package info from installed node module manifest file: yarn.lock",
"versionInfo": "9.0.0"
}
]
Provenance example
See here an example of getting the provenance predicate that describes how the awesome-node-cli asset was produced.
$ COSIGN_EXPERIMENTAL=1 sget ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.provenance | grep -v '^Certificate' | jq --slurp '.[1] | .predicate'
Verification for ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.provenance --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- Any certificates were verified against the Fulcio roots.
{
"builder": {
"id": "https://github.com/marcofranssen/slsa-workflow-examples/Attestations/GitHubHostedActions@v1"
},
"buildType": "https://github.com/Attestations/GitHubActionsWorkflow@v1",
"invocation": {
"configSource": {
"entryPoint": "Release NPM",
"uri": "git+https://github.com/marcofranssen/slsa-workflow-examples",
"digest": {
"sha1": "01e93c44cd41e7f6025c815af1f5075a4fcbec8b"
}
},
"parameters": null,
"environment": null
},
"metadata": {
"buildInvocationId": "https://github.com/marcofranssen/slsa-workflow-examples/actions/runs/2070200957",
"buildFinishedOn": "2022-03-31T09:40:17Z",
"completeness": {
"parameters": true,
"environment": false,
"materials": false
},
"reproducible": false
},
"materials": [
{
"uri": "git+https://github.com/marcofranssen/slsa-workflow-examples",
"digest": {
"sha1": "01e93c44cd41e7f6025c815af1f5075a4fcbec8b"
}
}
]
}
Similarly like the SBOM you could also fetch the provenance by digest, which does not require signature checks and slightly changes how you apply jq
in further processing.
Discovery file example
We have seen fatt
was also adding the attestations.txt
as an addition to the cosign
steps (upload and sign). fatt list
is similar to sget when it comes to fetching the ….discovery
tag. It will download the blob, verify the signature and adds some additional capabilities to make more advanced commandline usecases possible. e.g.:
- Filtering
- Transforming purls to oci image urls
- Working directly with the OCI blob
- Traversing the local file system on multiple
attestations.txt
files
See here some examples:
Store the attestations in a local file
$ export COSIGN_EXPERIMENTAL=1
$ fatt list ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.discovery > attestations.txt
Fetching attestations from ghcr.io/marcofranssen/slsa-workflow-examples/attestations:awesome-node-cli-v0.1.5.discovery…
Verifying signature for ghcr.io/marcofranssen/slsa-workflow-examples/attestations@sha256:252783857d310d36a54086cd1e5035298c98c5db604edc7daa7ce4cdcc03deaf…
Recursively search the local file system for attestation.txt and filter by provenance
$ export COSIGN_EXPERIMENTAL=1
$ fatt list -f '{ .IsAttestationType("provenance") }'
Fetching attestations from /Users/marco/code/slsa-workflow-examples/examples/awesome-npm-cli…
pkg:oci/marcofranssen/slsa-workflow-examples/attestations@sha256:cad11e9b6229631175321b6e35dfbf0124064ef82761e41800bc533ecc3e8f06?repository_url=ghcr.io%2Fmarcofranssen%2Fslsa-workflow-examples%2Fattestations&tag=awesome-node-cli-v0.1.5.provenance
Recursively search the local file system for attestation.txt and filter by SBOM and output the OCI blob URLs
$ export COSIGN_EXPERIMENTAL=1
$ fatt list -o oci -f '{ .IsAttestationType("sbom") }'
Fetching attestations from /Users/marco/code/slsa-workflow-examples/examples/awesome-npm-cli…
ghcr.io/marcofranssen/slsa-workflow-examples/attestations@sha256:68f1d97c61e62228195aedfdfdce74b52123764c0c3b5f71d03b713bc63eb897
Specifically this last example is usefull to further automate things to automatically fetch all SBOMs for projects on your local file system (all projects do need to have an attestations.txt
in their workspace and have to share the same root path to allow scanning this filepath recursively).
e.g. if you would use fatt
or sget
to download the attestations.txt
and store them on your local disc you might end up with a folder structure like this example.
$ tree code
code
├── node-cli
│ ├── attestations.txt
│ └── …
├── another-node-cli
│ ├── attestations.txt
│ └── …
└── super-cool-go-cli
├── attestations.txt
└── …
55 directories, 299 files
$ while IFS= read -r line ; do sget "${line}" | grep -v '^Certificate'; done \
<<< "$(fatt list -f '{ .IsAttestationType("sbom") }' -o oci code)" \
| jq --slurp 'map(.packages) | flatten | map({ name: .name, version: .versionInfo, license: .licenseConcluded }) | unique'
Fetching attestations from /Users/marco/code…
[
{
"name": "commander",
"version": "9.0.0",
"license": "NONE"
},
{
"name": "commander",
"version": "8.0.1",
"license": "NONE"
},
{
"name": "github.com/sigstore/cosign",
"version": "1.6.0",
"license": "NONE"
}
]
GitHub actions
To see a full end to end example of a CI pipeline you can see following workflow.
name: Release NPM
on:
push:
tags:
- v**
env:
COSIGN_EXPERIMENTAL: 1
ATT_IMAGE: ghcr.io/marcofranssen/slsa-workflow-examples/attestations
PKG_NAME: awesome-node-cli
SIGSTORE_VERSION: v1.6.0
jobs:
npm-package:
runs-on: ubuntu-20.04
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Checkout
uses: actions/[email protected]
- name: Setup Node
uses: actions/[email protected]
with:
node-version: '16'
cache: 'yarn'
cache-dependency-path: examples/awesome-node-cli
registry-url: 'https://npm.pkg.github.com/'
scope: "@marcofranssen"
- name: Install cosign
uses: sigstore/[email protected]
with:
cosign-release: ${{ env.SIGSTORE_VERSION }}
- name: Install Syft
uses: anchore/sbom-action/[email protected]
- name: Install fatt
uses: philips-labs/fatt/[email protected]
with:
fatt-release: v0.3.1
env:
COSIGN_EXPERIMENTAL: 0
# This workflow assumes that package.json version was updated prior to tagging
# and aligned with the tag you are about to give.
- name: Publish npm package
working-directory: examples/awesome-node-cli
run: |
pkg_json=examples/awesome-node-cli/package.json
version_error='You need to update package.json version to be aligned with your tag (prior to tagging).'
yarn pack
yarn publish marcofranssen-awesome-node-cli-${GITHUB_REF_NAME}.tgz || \
(echo "::error file=${pkg_json},line=3,endLine=3,col=14,title=Version issue::${version_error}" ; exit 1)
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate SBOM
working-directory: examples/awesome-node-cli
run: syft . -o spdx-json=sbom-spdx.json
- name: Generate provenance
uses: philips-labs/[email protected]
with:
command: generate
subcommand: files
arguments: --artifact-path examples/awesome-node-cli/marcofranssen-awesome-node-cli-${GITHUB_REF_NAME}.tgz --output-path provenance.att
env:
COSIGN_EXPERIMENTAL: 0
- name: Login to ghcr.io
uses: docker/login-action@dd4fa0671be5250ee6f50aedf4cb05514abda2c7 #v1.14.1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Upload attestations
run: |
fatt publish \
--repository "${ATT_IMAGE}" \
--tag-prefix "${PKG_NAME}" \
--version "${GITHUB_REF_NAME}" \
sbom://examples/awesome-node-cli/sbom-spdx.json \
provenance://provenance.att
- name: Sign attestations and discovery
run: |
cosign sign "${ATT_IMAGE}:${PKG_NAME}-${GITHUB_REF_NAME}.discovery"
cosign sign "${ATT_IMAGE}:${PKG_NAME}-${GITHUB_REF_NAME}.provenance"
cosign sign "${ATT_IMAGE}:${PKG_NAME}-${GITHUB_REF_NAME}.sbom"
test-attestation-fetching:
runs-on: ubuntu-20.04
needs: [npm-package]
steps:
- name: Install cosign
uses: sigstore/[email protected]
with:
cosign-release: ${{ env.SIGSTORE_VERSION }}
- name: Install sget
run: |
os="${RUNNER_OS,,}"
arch="${RUNNER_ARCH,,}"
[ "$arch" == "x64" ] && arch=amd64
curl -sSLo sget https://github.com/sigstore/cosign/releases/download/${SIGSTORE_VERSION}/sget-${os}-amd64
curl -sSLo sget.sig https://github.com/sigstore/cosign/releases/download/${SIGSTORE_VERSION}/sget-${os}-amd64.sig
curl -sSLo sigstore.pub https://github.com/sigstore/cosign/releases/download/${SIGSTORE_VERSION}/release-cosign.pub
cosign verify-blob --key sigstore.pub --signature sget.sig sget
mkdir -p /tmp/sigstore/bin
mv sget /tmp/sigstore/bin
chmod +x /tmp/sigstore/bin/sget
echo '/tmp/sigstore/bin' >> "${GITHUB_PATH}"
env:
COSIGN_EXPERIMENTAL: 0
- name: Install fatt
uses: philips-labs/fatt/[email protected]
with:
fatt-release: v0.3.1
env:
COSIGN_EXPERIMENTAL: 0
- name: Fetch discovery file
run: |
fatt list "${ATT_IMAGE}:${PKG_NAME}-${GITHUB_REF_NAME}.discovery" > attestations.txt
cat attestations.txt
- uses: actions/[email protected]
with:
name: package-attestations
path: attestations.txt
- name: Fetch provenance
run: |
# grep is a workaround to fix sget bug in v1.6.0, bug is resolved in next releases
sget "${ATT_IMAGE}:${PKG_NAME}-${GITHUB_REF_NAME}-provenance" | grep -v '^Certificate' | jq '.' > provenance.att
- uses: actions/[email protected]
with:
name: awesome-node-cli-sbom-via-sget
path: provenance.att
- name: Fetch SBOM
run: |
# grep is a workaround to fix sget bug in v1.6.0, bug is resolved in next releases
sget "${ATT_IMAGE}:${PKG_NAME}-${GITHUB_REF_NAME}.sbom" | grep -v '^Certificate' | jq '.' > sbom-spdx.json
- uses: actions/[email protected]
with:
name: awesome-node-cli-provenance-via-sget
path: sbom-spdx.json
- name: Example sbom traversal to get license info
run: |
echo Imagine you would have saved all 'attestations.txt' files in all your projects you can easily get all Licenses.
# grep is a workaround to fix sget bug in v1.6.0, bug is resolved in next releases
while IFS= read -r line ; do sget "${line}" | grep -v '^Certificate'; done \
<<< "$(fatt list -f '{ .IsAttestationType("sbom") }' -o oci)" \
| jq --slurp 'map(.packages) | flatten | map({ name: .name, version: .versionInfo, license: .licenseConcluded }) | unique'
- name: Notice
run: echo "::notice title=Attestations::See ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID} on how to fetch attestations."
In this workflow we make a release for our software using npm pack
. Then we utilize syft to generate an SBOM and we use the slsa-provenance-action to generate the provenance for our build. Last but not least we utilize fatt to publish the sbom
and provenance
to an OCI registry.
The second job in the workflow showcases some ways to consume the attestations. This gives you an impression on the possibilities of using these attestations in your workflows. E.g. you could check your dependencies in npm, try to fetch the SBOMs based on a convention, and if the SBOM is provided by the dependency you can perform some license checks in your CI pipeline.
Bonus
Using crane you can easily query the OCI registry to see how the attestations are stored for your software assets.
$ crane ls ghcr.io/marcofranssen/slsa-workflow-examples/attestations
v0.1.4-rc2.sbom
v0.1.4-rc2.provenance
v0.1.4-rc2.discovery
sha256-2303915bddc4a07184bbead2c57cc157eae79529c381c54c3c731a4d37ee8c6c.sig
sha256-9b42b04bad23bedb88b72ece1e69d2548c45d6bf18a92eba205dab975136bed2.sig
sha256-6b4c73905e7065f821aa10f26a6a96fead849c31b928ef51ecc079a7d2eebc8d.sig
awesome-node-cli-v0.1.4-rc3.sbom
awesome-node-cli-v0.1.4-rc3.provenance
awesome-node-cli-v0.1.4-rc3.discovery
sha256-f7bb6199092fa87d26b9a8195842eb87f7f907e80f95db07d21b73616e271aec.sig
sha256-49a0dfce851be9d88d3b73d525b2e9b950d546f1d7d7046050ed1f058a7cefb1.sig
sha256-c6184be9cf4c8d35aa26e4f1975f1fbd9b91d756a5ea2262b258293f369bb49c.sig
awesome-node-cli-v0.1.4.sbom
awesome-node-cli-v0.1.4.provenance
awesome-node-cli-v0.1.4.discovery
sha256-d3f5585432c887e626717dc04df80f97775a4ff4a5f721b62c0466a63b865c60.sig
sha256-84a13d69302341f15b8fb7e8b645b12309956546136fa218adc7225e4f6b5aaf.sig
sha256-1cb77a219aeb2165463660e6fd908dca27e72c1481ff79d5fe49feced2bd7bca.sig
awesome-node-cli-v0.1.5-rc.sbom
awesome-node-cli-v0.1.5-rc.provenance
awesome-node-cli-v0.1.5-rc.discovery
sha256-ad3b62e3793d6354292e63dba82b455decd5c6bcdf93b94f631ea6cba56aeff5.sig
sha256-394cf9cd05af02d2352e5f528a84a0bbb55a9424db6173b968402587b6c67a36.sig
sha256-26be1c1d7d8985ec8f770c9463bc8558c9abbed8b5d8632b2084dbcb063e9808.sig
awesome-node-cli-v0.1.5.sbom
awesome-node-cli-v0.1.5.provenance
awesome-node-cli-v0.1.5.discovery
sha256-252783857d310d36a54086cd1e5035298c98c5db604edc7daa7ce4cdcc03deaf.sig
sha256-cad11e9b6229631175321b6e35dfbf0124064ef82761e41800bc533ecc3e8f06.sig
sha256-68f1d97c61e62228195aedfdfdce74b52123764c0c3b5f71d03b713bc63eb897.sig
As you can see here, we have a signature for each released tag. You can also see that we didn't use the fatt
commandline option --tag-prefix
in an earlier release (v0.1.4-rc2). This commandline option allows you to choose between 2 publishing strategies:
- A repository per package
ghcr.io/marcofranssen/attestations-awesome-node-cli:v0.1.5.sbom
ghcr.io/marcofranssen/attestations-another-node-cli:v0.1.5.sbom
- 1 repository for multiple packages
ghcr.io/marcofranssen/attestations:awesome-node-cli-v0.1.5.sbom
ghcr.io/marcofranssen/attestations:another-node-cli-v0.1.5.sbom
Wrapup
In this blog we have stored attestations (SBOM, build provenance) in an OCI registry. We also have seen how we could automate those steps a little further using fatt
. Aside from the publishing part we also looked at the consumption usecases. The examples where based on an NPM package, however we can apply the same for other package managers like Maven, Nuget, Go modules etc. Also take some time to explore the GitHub actions workflow to incorporate this in your own projects.
What do you think about this approach and the proof of concept using fatt? Do you see any other usecases to be applied or do you have any suggestions on taking this idea next-level? Feel free to reach out at the fatt repository, discuss at Twitter, Reddit or any other Social platform. Looking forward to your feedback.