sbom signing (#303)

Signed-off-by: Fabian Kammel <fk@edgeless.systems>
This commit is contained in:
Fabian Kammel 2022-10-21 15:19:51 +02:00 committed by GitHub
parent c1b4193791
commit 18ae86c38e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 237 additions and 11 deletions

View File

@ -17,6 +17,15 @@ inputs:
githubToken: githubToken:
description: "GitHub authorization token" description: "GitHub authorization token"
required: true required: true
cosignPublicKey:
description: "Cosign public key"
required: false
cosignPrivateKey:
description: "Cosign private key"
required: false
cosignPassword:
description: "Password for Cosign private key"
required: false
# Linux runner only (Docker required) # Linux runner only (Docker required)
runs: runs:
@ -56,3 +65,12 @@ runs:
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
build-args: | build-args: |
PROJECT_VERSION=${{ inputs.projectVersion }} PROJECT_VERSION=${{ inputs.projectVersion }}
- name: Generate SBOM
uses: ./.github/actions/container_sbom
with:
containerReference: ghcr.io/${{ github.repository }}/${{ inputs.name }}@${{ steps.build-micro-service.outputs.digest }}
cosignPublicKey: ${{ inputs.cosignPublicKey }}
cosignPrivateKey: ${{ inputs.cosignPrivateKey }}
cosignPassword: ${{ inputs.cosignPassword }}
if: ${{ inputs.cosignPublicKey != '' && inputs.cosignPrivateKey != '' && inputs.cosignPassword != '' }}

View File

@ -13,6 +13,15 @@ inputs:
githubToken: githubToken:
description: "GitHub authorization token" description: "GitHub authorization token"
required: true required: true
cosignPublicKey:
description: "Cosign public key"
required: false
cosignPrivateKey:
description: "Cosign private key"
required: false
cosignPassword:
description: "Password for Cosign private key"
required: false
# Linux runner only (Docker required) # Linux runner only (Docker required)
runs: runs:
@ -48,6 +57,7 @@ runs:
type=ref,event=branch type=ref,event=branch
- name: Build and push container image - name: Build and push container image
id: build-image
uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 # tag=v3.2.0 uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 # tag=v3.2.0
with: with:
context: ${{ inputs.sourceDir }} context: ${{ inputs.sourceDir }}
@ -55,6 +65,15 @@ runs:
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
- name: Generate SBOM
uses: ./.github/actions/container_sbom
with:
containerReference: ghcr.io/${{ github.repository }}/${{ inputs.name }}@${{ steps.build-image.outputs.digest }}
cosignPublicKey: ${{ inputs.cosignPublicKey }}
cosignPrivateKey: ${{ inputs.cosignPrivateKey }}
cosignPassword: ${{ inputs.cosignPassword }}
if: ${{ inputs.cosignPublicKey != '' && inputs.cosignPrivateKey != '' && inputs.cosignPassword != '' }}
- name: Bundle for pseudo version - name: Bundle for pseudo version
if: ${{ steps.pseudo-version.outputs.pseudoVersion != '' && inputs.pushTag == '' }} if: ${{ steps.pseudo-version.outputs.pseudoVersion != '' && inputs.pushTag == '' }}
shell: bash shell: bash
@ -84,6 +103,7 @@ runs:
type=ref,event=branch type=ref,event=branch
- name: Build and push bundle image - name: Build and push bundle image
id: build-image-bundle
uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 # tag=v3.2.0 uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 # tag=v3.2.0
with: with:
context: ${{ inputs.sourceDir }} context: ${{ inputs.sourceDir }}
@ -91,6 +111,16 @@ runs:
push: true push: true
tags: ${{ steps.bundle-meta.outputs.tags }} tags: ${{ steps.bundle-meta.outputs.tags }}
- name: Generate Bundle SBOM
uses: ./.github/actions/container_sbom
with:
containerReference: ghcr.io/${{ github.repository }}/${{ inputs.name }}-bundle@${{ steps.build-image-bundle.outputs.digest }}
cosignPublicKey: ${{ inputs.cosignPublicKey }}
cosignPrivateKey: ${{ inputs.cosignPrivateKey }}
cosignPassword: ${{ inputs.cosignPassword }}
if: ${{ inputs.cosignPublicKey != '' && inputs.cosignPrivateKey != '' && inputs.cosignPassword != '' }}
- name: Build and push catalog for pseudo versions - name: Build and push catalog for pseudo versions
if: ${{ steps.pseudo-version.outputs.pseudoVersion != '' && inputs.pushTag == '' }} if: ${{ steps.pseudo-version.outputs.pseudoVersion != '' && inputs.pushTag == '' }}
shell: bash shell: bash

View File

@ -0,0 +1,51 @@
name: Container SBOM
description: Create, vuln-check, sign and upload SBOMs for container images.
inputs:
containerReference:
description: "Full reference to container image, e.g., ghcr.io/org/repo/img:tag"
required: true
cosignPublicKey:
description: "Cosign public key"
required: true
cosignPrivateKey:
description: "Cosign private key"
required: true
cosignPassword:
description: "Password for Cosign private key"
required: true
runs:
using: "composite"
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@7cc35d7fdbe70d4278a0c96779081e6fac665f88 # tag=v2.8.0
if: ${{ inputs.cosignPublicKey != '' && inputs.cosignPrivateKey != '' && inputs.cosignPassword != '' }}
- name: Download syft & grype
run: |
SYFT_VERSION=0.59.0
GRYPE_VERSION=0.51.0
curl -LO https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/syft_${SYFT_VERSION}_linux_amd64.tar.gz
tar -xzf syft_${SYFT_VERSION}_linux_amd64.tar.gz
./syft version
curl -LO https://github.com/anchore/grype/releases/download/v${GRYPE_VERSION}/grype_${GRYPE_VERSION}_linux_amd64.tar.gz
tar -xzf grype_${GRYPE_VERSION}_linux_amd64.tar.gz
./grype version
echo $(pwd) >> $GITHUB_PATH
shell: bash
- name: Generate SBOM
run: |
set -ex
echo "$COSIGN_PRIVATE_KEY" > cosign.key
syft attest --key cosign.key ${{ inputs.containerReference }} -o cyclonedx-json > container-image.att.json
cosign attach attestation ${{ inputs.containerReference }} --attestation container-image.att.json
# TODO: type should be auto-discovered after issue is resolved:
# https://github.com/sigstore/cosign/issues/2264
cosign verify-attestation ${{ inputs.containerReference }} --type 'https://cyclonedx.org/bom' --key env://COSIGN_PUBLIC_KEY
grype ${{ inputs.containerReference }} --fail-on high --only-fixed
shell: bash
env:
# COSIGN_EXPERIMENTAL: 1 # This breaks verification with HTTP 404
COSIGN_PUBLIC_KEY: ${{ inputs.cosignPublicKey }}
COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }}
COSIGN_PASSWORD: ${{ inputs.cosignPassword }}

View File

@ -37,3 +37,6 @@ jobs:
projectVersion: "0.0.0" projectVersion: "0.0.0"
dockerfile: access_manager/Dockerfile dockerfile: access_manager/Dockerfile
githubToken: ${{ secrets.GITHUB_TOKEN }} githubToken: ${{ secrets.GITHUB_TOKEN }}
cosignPublicKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PUBLIC_KEY || secrets.COSIGN_DEV_PUBLIC_KEY }}
cosignPrivateKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PRIVATE_KEY || secrets.COSIGN_DEV_PRIVATE_KEY }}
cosignPassword: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PASSWORD || secrets.COSIGN_DEV_PASSWORD }}

View File

@ -31,3 +31,6 @@ jobs:
name: node-operator name: node-operator
sourceDir: operators/constellation-node-operator sourceDir: operators/constellation-node-operator
githubToken: ${{ secrets.GITHUB_TOKEN }} githubToken: ${{ secrets.GITHUB_TOKEN }}
cosignPublicKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PUBLIC_KEY || secrets.COSIGN_DEV_PUBLIC_KEY }}
cosignPrivateKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PRIVATE_KEY || secrets.COSIGN_DEV_PRIVATE_KEY }}
cosignPassword: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PASSWORD || secrets.COSIGN_DEV_PASSWORD }}

View File

@ -39,3 +39,6 @@ jobs:
projectVersion: "0.0.0" projectVersion: "0.0.0"
dockerfile: joinservice/Dockerfile dockerfile: joinservice/Dockerfile
githubToken: ${{ secrets.GITHUB_TOKEN }} githubToken: ${{ secrets.GITHUB_TOKEN }}
cosignPublicKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PUBLIC_KEY || secrets.COSIGN_DEV_PUBLIC_KEY }}
cosignPrivateKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PRIVATE_KEY || secrets.COSIGN_DEV_PRIVATE_KEY }}
cosignPassword: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PASSWORD || secrets.COSIGN_DEV_PASSWORD }}

View File

@ -38,3 +38,6 @@ jobs:
projectVersion: "0.0.0" projectVersion: "0.0.0"
dockerfile: kms/Dockerfile dockerfile: kms/Dockerfile
githubToken: ${{ secrets.GITHUB_TOKEN }} githubToken: ${{ secrets.GITHUB_TOKEN }}
cosignPublicKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PUBLIC_KEY || secrets.COSIGN_DEV_PUBLIC_KEY }}
cosignPrivateKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PRIVATE_KEY || secrets.COSIGN_DEV_PRIVATE_KEY }}
cosignPassword: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PASSWORD || secrets.COSIGN_DEV_PASSWORD }}

View File

@ -62,3 +62,6 @@ jobs:
dockerfile: ${{ env.microServiceDockerfile }} dockerfile: ${{ env.microServiceDockerfile }}
pushTag: ${{ inputs.imageTag }} pushTag: ${{ inputs.imageTag }}
githubToken: ${{ secrets.GITHUB_TOKEN }} githubToken: ${{ secrets.GITHUB_TOKEN }}
cosignPublicKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PUBLIC_KEY || secrets.COSIGN_DEV_PUBLIC_KEY }}
cosignPrivateKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PRIVATE_KEY || secrets.COSIGN_DEV_PRIVATE_KEY }}
cosignPassword: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PASSWORD || secrets.COSIGN_DEV_PASSWORD }}

View File

@ -5,6 +5,7 @@ on:
inputs: inputs:
imageTag: imageTag:
description: "Container image tag." description: "Container image tag."
required: false
jobs: jobs:
build-operator-manual: build-operator-manual:
@ -29,3 +30,6 @@ jobs:
sourceDir: operators/constellation-node-operator sourceDir: operators/constellation-node-operator
githubToken: ${{ secrets.GITHUB_TOKEN }} githubToken: ${{ secrets.GITHUB_TOKEN }}
pushTag: ${{ inputs.imageTag }} pushTag: ${{ inputs.imageTag }}
cosignPublicKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PUBLIC_KEY || secrets.COSIGN_DEV_PUBLIC_KEY }}
cosignPrivateKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PRIVATE_KEY || secrets.COSIGN_DEV_PRIVATE_KEY }}
cosignPassword: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PASSWORD || secrets.COSIGN_DEV_PASSWORD }}

View File

@ -35,3 +35,6 @@ jobs:
projectVersion: "0.0.0" projectVersion: "0.0.0"
dockerfile: verify/Dockerfile dockerfile: verify/Dockerfile
githubToken: ${{ secrets.GITHUB_TOKEN }} githubToken: ${{ secrets.GITHUB_TOKEN }}
cosignPublicKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PUBLIC_KEY || secrets.COSIGN_DEV_PUBLIC_KEY }}
cosignPrivateKey: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PRIVATE_KEY || secrets.COSIGN_DEV_PRIVATE_KEY }}
cosignPassword: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PASSWORD || secrets.COSIGN_DEV_PASSWORD }}

View File

@ -71,20 +71,29 @@ jobs:
AZURE_SUBSCRIPTION_ID=0d202bbb-4fa7-4af8-8125-58c269a05435 go run . > versions-manifest.json AZURE_SUBSCRIPTION_ID=0d202bbb-4fa7-4af8-8125-58c269a05435 go run . > versions-manifest.json
cat versions-manifest.json cat versions-manifest.json
- name: Build SBOMs - name: Download syft & grype
run: | run: |
SYFT_VERSION=0.55.0 SYFT_VERSION=0.59.0
GRYPE_VERSION=0.50.2
curl -LO https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/syft_${SYFT_VERSION}_linux_amd64.tar.gz curl -LO https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/syft_${SYFT_VERSION}_linux_amd64.tar.gz
tar -xzf syft_${SYFT_VERSION}_linux_amd64.tar.gz tar -xzf syft_${SYFT_VERSION}_linux_amd64.tar.gz
./syft version ./syft version
./syft . --catalogers go-module --file constellation.spdx.sbom -o spdx-json curl -LO https://github.com/anchore/grype/releases/download/v${GRYPE_VERSION}/grype_${GRYPE_VERSION}_linux_amd64.tar.gz
CONTAINER_VERSION=${GITHUB_REF##*/} tar -xzf grype_${GRYPE_VERSION}_linux_amd64.tar.gz
./syft ghcr.io/edgelesssys/constellation/verification-service:${CONTAINER_VERSION} --file verification-service.spdx.sbom -o spdx-json ./grype version
./syft ghcr.io/edgelesssys/constellation/access-manager:${CONTAINER_VERSION} --file access-manager.spdx.sbom -o spdx-json echo $(pwd) >> $GITHUB_PATH
./syft ghcr.io/edgelesssys/constellation/join-service:${CONTAINER_VERSION} --file join-service.spdx.sbom -o spdx-json shell: bash
./syft ghcr.io/edgelesssys/constellation/kmsserver:${CONTAINER_VERSION} --file kmsserver.spdx.sbom -o spdx-json
./syft ghcr.io/edgelesssys/constellation/node-operator:${CONTAINER_VERSION} --file node-operator.spdx.sbom -o spdx-json - name: Build signed SBOMs
if: startsWith(github.ref, 'refs/tags/v') run: |
syft . --catalogers go-module --file constellation.spdx.sbom -o spdx-json
cosign sign-blob --key env://COSIGN_PRIVATE_KEY constellation.spdx.sbom > constellation.spdx.sbom.sig
grype constellation.spdx.sbom --fail-on high --only-fixed
env:
COSIGN_EXPERIMENTAL: 1
cosignPublicKey: ${{ startsWith(github.ref, 'refs/tags/v') && secrets.COSIGN_PUBLIC_KEY || secrets.COSIGN_DEV_PUBLIC_KEY }}
cosignPrivateKey: ${{ startsWith(github.ref, 'refs/tags/v') && secrets.COSIGN_PRIVATE_KEY || secrets.COSIGN_DEV_PRIVATE_KEY }}
cosignPassword: ${{ startsWith(github.ref, 'refs/tags/v') && secrets.COSIGN_PASSWORD || secrets.COSIGN_DEV_PASSWORD }}
- name: Create release with artifacts - name: Create release with artifacts
# GitHub endorsed release project. See: https://github.com/actions/create-release # GitHub endorsed release project. See: https://github.com/actions/create-release
@ -96,4 +105,5 @@ jobs:
build/constellation-* build/constellation-*
build/cosign.pub build/cosign.pub
hack/build-manifest/versions-manifest.json hack/build-manifest/versions-manifest.json
*.spdx.sbom constellation.spdx.sbom
constellation.spdx.sbom.sig

View File

@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- Sign generated SBOMs and store container image SBOMs in registry for easier usage.
### Changed ### Changed
<!-- For changes in existing functionality. --> <!-- For changes in existing functionality. -->

View File

@ -0,0 +1,87 @@
# Consume software bill of materials (SBOMs)
Constellation builds produce a [software bill of materials (SBOM)](https://www.ntia.gov/SBOM) for each generated [artifact](../architecture/components.md).
You can use SBOMs to make informed decisions about dependencies and vulnerabilities in a given application. Enterprises rely on SBOMs to maintain an inventory of used applications, which allows them to take data-driven approaches to managing risks related to vulnerabilities.
SBOMs for Constellation are generated using [Syft](https://github.com/anchore/syft), signed using [Cosign](https://github.com/sigstore/cosign), and stored with the produced artifact.
:::note
The public key for Edgeless Systems' long-term code-signing key is:
```
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEf8F1hpmwE+YCFXzjGtaQcrL6XZVT
JmEe5iSLvG1SyQSAew7WdMKF6o9t8e2TFuCkzlOhhlws2OHWbiFZnFWCFw==
-----END PUBLIC KEY-----
```
The public key is also available for download at https://edgeless.systems/es.pub and in the Twitter profile [@EdgelessSystems](https://twitter.com/EdgelessSystems).
Make sure the key is available in a file named `cosign.pub` to execute the following examples.
:::
## Verify and download SBOMs
The following sections detail how to work with each type of artifact to verify and extract the SBOM.
### Constellation CLI
The SBOM for Constellation CLI is made available on the [GitHub release page](https://github.com/edgelesssys/constellation/releases). The SBOM (`constellation.spdx.sbom`) and corresponding signature (`constellation.spdx.sbom.sig`) are valid for each Constellation CLI for a given version, regardless of architecture and operating system.
```bash
curl -LO https://github.com/edgelesssys/constellation/releases/download/v2.2.0/constellation.spdx.sbom
curl -LO https://github.com/edgelesssys/constellation/releases/download/v2.2.0/constellation.spdx.sbom.sig
cosign verify-blob --key cosign.pub --signature constellation.spdx.sbom.sig constellation.spdx.sbom
```
### Container Images
SBOMs for container images are [attached to the image using Cosign](https://docs.sigstore.dev/cosign/other_types#sboms-software-bill-of-materials) and uploaded to the same registry.
As a consumer, use cosign to download and verify the SBOM:
```bash
# Verify and download the attestation statement
cosign verify-attestation ghcr.io/edgelesssys/constellation/verification-service@v2.2.0 --type 'https://cyclonedx.org/bom' --key cosign.pub --output-file verification-service.att.json
# Extract SBOM from attestation statement
jq -r .payload verification-service.att.json | base64 -d > verification-service.cyclonedx.sbom
```
A successful verification should result in similar output:
```shell-session
$ cosign verify-attestation ghcr.io/edgelesssys/constellation/verification-service@v2.2.0 --type 'https://cyclonedx.org/bom' --key cosign.pub --output-file verification-service.sbom
Verification for ghcr.io/edgelesssys/constellation/verification-service@v2.2.0 --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key
$ jq -r .payload verification-service.sbom | base64 -d > verification-service.cyclonedx.sbom
```
:::note
This example considers only the `verification-service`. The same approach works for all containers in the [Constellation container registry](https://github.com/orgs/edgelesssys/packages?repo_name=constellation).
:::
<!--
TODO: Once mkosi is implemented
## Operating System
-->
## Vulnerability scanning
You can use a plethora of tools to consume SBOMs. This section provides suggestions for tools that are popular and known to produce reliable results, but any tool that consumes [SPDX](https://spdx.dev/) or [CycloneDX](https://cyclonedx.org/) files should work.
Syft is able to [convert between the two formats](https://github.com/anchore/syft#format-conversion-experimental) in case you require a specific type.
### Grype
[Grype](https://github.com/anchore/grype) is a CLI tool that lends itself well for integration into CI/CD systems or local developer machines. It's also able to consume the signed attestation statement directly and does the verification in one go.
```bash
grype att:verification-service.sbom --key cosign.pub --add-cpes-if-none -q
```
### Dependency Track
[Dependency Track](https://dependencytrack.org/) is one of the oldest and most mature solutions when it comes to managing software inventory and vulnerabilities. Once imported, it continuously scans SBOMs for new vulnerabilities. It supports the CycloneDX format and provides direct guidance on how to comply with [U.S. Executive Order 14028](https://docs.dependencytrack.org/usage/executive-order-14028/).

View File

@ -168,6 +168,11 @@ const sidebars = {
label: 'Use Azure trusted launch VMs', label: 'Use Azure trusted launch VMs',
id: 'workflows/trusted-launch', id: 'workflows/trusted-launch',
}, },
{
type: 'doc',
label: 'Consume SBOMs',
id: 'workflows/sbom',
},
{ {
type: 'doc', type: 'doc',
label: 'Troubleshooting', label: 'Troubleshooting',

View File

@ -18,6 +18,7 @@ Filestore
Fulcio Fulcio
Mbps Mbps
Gbps Gbps
Grype
iam iam
IAM IAM
initramfs initramfs
@ -36,6 +37,7 @@ Rekor
resizable resizable
rollout rollout
sigstore sigstore
Syft
systemd systemd
[Uu]nencrypted [Uu]nencrypted
unspoofable unspoofable