Skip to content
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

Add support for SBOM attestations #730

Closed
wants to merge 3 commits into from
Closed
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
24 changes: 21 additions & 3 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,18 @@ The list of images can be separated by commas or by newlines.
value: img1@sha256:digest1, img2@sha256:digest2
```

When processing a `TaskRun`, Chains will parse through the list, then sign and attest each image.
Chains also suports signing SBOMs. If using the `IMAGE_URL` and `IMAGE_DIGEST` combination, the
lcarva marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to consolidating type hinting docs in #913. Not sure which PR will be merged first. But this section may be better to live in the new "how to chain with pipeline" doc?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chains supports multiple images type hinting by just appending a prefix before IMAGE_URL and IMAGE_DIGEST. Does SBOM generation support this? If so, can we mention this in the doc as well?

`TaskRun` can also emit the `IMAGE_SBOM_URL` and the `IMAGE_SBOM_FORMAT` results. The first contains
lcarva marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

@chitrangpatel chitrangpatel Aug 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be in a separate PR but we should probably also add support for Object Results, like we have for Artifact Inputs.

xxxARTIFACTS_SBOM:
    url:...
    format:... 

a reference to an *unsigned* SBOM image. This reference must include the digest of the SBOM image.
The second holds the desired predicate for the SBOM attestation. All four results must be included,
otherwise SBOM signing is skipped.

When processing a `TaskRun`, Chains will parse through the results, then sign and attest each image.
Optionally, it may also create signed SBOM attestations if the expected results are present.

When processing a `PipelineRun`, Chains will only attest each image. Thus, if both `TaskRun` and
`PipelineRun` produce type hint results, each image will have one signature and two attestations.
SBOM-releated results are ignored for `PipelineRuns`.

For in-toto attestations, see [intoto.md](intoto.md) for description
of in-toto specific type hinting.
Expand Down Expand Up @@ -64,9 +73,9 @@ Supported keys include:
| `artifacts.pipelinerun.format` | The format to store `PipelineRun` payloads in. | `in-toto`, `slsa/v1`| `in-toto` |
| `artifacts.pipelinerun.storage` | The storage backend to store `PipelineRun` signatures in. Multiple backends can be specified with comma-separated list ("tekton,oci"). To disable the `PipelineRun` artifact input an empty string (""). | `tekton`, `oci`, `gcs`, `docdb`, `grafeas` | `tekton` |
| `artifacts.pipelinerun.signer` | The signature backend to sign `PipelineRun` payloads with. | `x509`, `kms` | `x509` |
| `artifacts.pipelinerun.enable-deep-inspection` | This boolean option will configure whether Chains should inspect child taskruns in order to capture inputs/outputs within a pipelinerun. `"false"` means that Chains only checks pipeline level results, whereas `"true"` means Chains inspects both pipeline level and task level results. | `"true"`, `"false"` | `"false"` |
| `artifacts.pipelinerun.enable-deep-inspection` | This boolean option will configure whether Chains should inspect child taskruns in order to capture inputs/outputs within a pipelinerun. `"false"` means that Chains only checks pipeline level results, whereas `"true"` means Chains inspects both pipeline level and task level results. | `"true"`, `"false"` | `"false"` |

> NOTE:
> NOTE:
> - For grafeas storage backend, currently we only support Container Analysis. We will make grafeas server address configurabe within a short time.
> - `slsa/v1` is an alias of `in-toto` for backwards compatibility.

Expand All @@ -78,6 +87,15 @@ Supported keys include:
| `artifacts.oci.storage` | The storage backend to store `OCI` signatures in. Multiple backends can be specified with comma-separated list ("oci,tekton"). To disable the `OCI` artifact input an empty string ("").| `tekton`, `oci`, `gcs`, `docdb`, `grafeas` | `oci` |
| `artifacts.oci.signer` | The signature backend to sign `OCI` payloads with. | `x509`, `kms` | `x509` |

### SBOM Configuration

| Key | Description | Supported Values | Default |
| :--- | :--- | :--- | :--- |
| `artifacts.sbom.format` | The statement format to store `SBOM` payloads in. | `in-toto` | `in-toto` |
| `artifacts.sbom.storage` | The storage backend to store `SBOM` payloads in. Multiple backends can be specified with comma-separated list ("oci,tekton"). To disable the `OCI` artifact input an empty string ("").| `tekton`, `oci`, `gcs`, `docdb`, `grafeas` | `oci` |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we support all the storage backend for sbom currently? If not, should we limit this list to what we currently support?

| `artifacts.sbom.signer` | The signature backend to sign `SBOM` payloads with. | `x509`, `kms` | `x509` |
| `artifacts.sbom.maxbytes` | The maximum size, in bytes, allowed for SBOMs. | `integer` | `10485760`, 10 MB |

### KMS Configuration

| Key | Description | Supported Values | Default |
Expand Down
175 changes: 175 additions & 0 deletions docs/tutorials/signed-sbom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<!--
---
linkTitle: "Tutorial: Signed SBOMs"
weight: 300
---
-->

# Chains Signed SBOM Tutorial

This tutorial details the steps required to use Tekton Chains to sign an SBOM for an image. It adds
SBOM generation to a minimal clone-build-push Pipeline.

At the end of this tutorial, the built image will contain a signed SBOM attestation. NOTE: This is
different than an SBOM attachment. SBOM attestations have a securer link to the described image as
well as the flexibility to provide multiple SBOMs for the same image.

## Prerequisites

A Kubernetes cluster with the following installed:

* Tekton Chains
* Tekton Pipelines

## Generate a Key Pair

First, we'll generate an encrypted x509 keypair and save it as a Kubernetes secret. Install
[cosign](https://github.com/sigstore/cosign) and run the following:

```shell
cosign generate-key-pair k8s://tekton-chains/signing-secrets
```

`cosign` will prompt you for a password, which will be stored in a Kubernetes secret named
`signing-secrets` in the `tekton-chains` namespace.

The public key will be written to a local file called `cosign.pub`.

## Set up Authentication

There are two forms of authentication that need to be set up:

1. The Chains controller will push signatures and attestations to an OCI registry using the
credentials linked to your `TaskRun`'s service account. See our [authentication
doc](../authentication.md)
2. The build and sbom Tasks will build and push content to the OCI registry.

Both of those can be setup by creating a `docker-registry` secret and linking it to the
ServiceAccount used by the TaskRuns. This tutorial assumes the ServiceAccount is `default`.

```shell
# Create a secret based on your local docker config.
kubectl create secret docker-registry tutorial-secret \
--from-file=.dockerconfigjson=$HOME/.docker/config.json

# Link secret to service account
kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "tutorial-secret"}]}'
kubectl patch serviceaccount default -p '{"secrets": [{"name": "tutorial-secret"}]}'
```

## Configure Tekton Chains

You'll need to make these changes to the Tekton Chains Config:

* `artifacts.sbom.format=in-toto`
* `artifacts.sbom.storage=oci`
* `transparency.enabled=true`

You can set these fields by running:

```shell
kubectl patch configmap chains-config -n tekton-chains -p='{"data":{"artifacts.sbom.format": "in-toto"}}'
kubectl patch configmap chains-config -n tekton-chains -p='{"data":{"artifacts.sbom.storage": "oci"}}'
kubectl patch configmap chains-config -n tekton-chains -p='{"data":{"transparency.enabled": "true"}}'
```

This tells Chains to generate an in-toto attestation for the SBOM and store it in the image's OCI
registry. The SBOM signature will also be stored in [rekor](https://github.com/sigstore/rekor) since
transparency is enabled.

## Create SBOM Task

Similar to image signatures, Tekton Chains requires that the task producing the SBOM emits results
[named in a certain way](https://tekton.dev/docs/chains/config/#chains-type-hinting). This section
creates a sample Tekton Task that meets these requirements.

The [sample sbom Task](../../examples/sbom/sbom-task.yaml) uses
[syft](https://github.com/anchore/syft) to generate an SBOM in the CycloneDX format for a given
image. It then stores the generated SBOM as blob in the OCI registry. When the Task completes,
Tekton Chains downloads the SBOM blob and uses it as the payload of a new signed attestation that is
then attached to the image. To create it:

```shell
kubectl apply -f examples/sbom/sbom-task.yaml
```

It is possible to run this Task to see SBOM signing in action. But let's use it in a Pipeline for a
more realistic example.

## Build Pipeline

The [sample build pipeline](../../examples/sbom/sbom-pipeline.yaml) uses git to clone an application
repository, buildah to build an application container image, and syft to create an SBOM for the
image. To create it:

```shell
kubectl apply -f examples/sbom/sbom-pipeline.yaml
```

## Running

Let's use the tkn CLI to run the build pipeline. To do so, you'll need an OCI registry to push the
image to and a git repository that contains a valid Dockerfile in its root. For example:

```shell
# Set these accordingly
GIT_REPO=https://github.com/user/example
GIT_REVISION=main
OCI_REPO=quay.io/user/example

tkn -n minimal-container pipeline start simple-build \
--param git-repo=${GIT_REPO} --param git-revision=${GIT_REVISION} \
--param output-image=${OCI_REPO}:latest --param sbom-repo=${OCI_REPO} \
--workspace name=shared,pvc,claimName="tekton-build" \
--showlog
```

NOTE: The above assumes you have an existing PVC named `tekton-build`.

Towards the end of the log, there should a line like this:

```text
[sbom : store-sbom-blob] Digest: sha256:bb19013e908abf6d0d024d82c8990c30e84bea29796a361d828f78499b7ddf12
```

Make a note of the digest. This is the expected digest of the contents of the SBOM.

The digest can also be retrieved from the TaskRun's result:

```shell
kubectl get taskrun $TASK_RUN_NAME -o yaml | \
yq '.status.results[] | select(.name == "IMAGE_SBOM_URL") | .value'
```

## Verification

At this point, the image is built and should have an SBOM attestation attached to it. Use cosign to
inspect it:

```shell
cosign download attestation ${OCI_REPO}:latest | \
jq '.payload | @base64d | fromjson | select(.predicateType == "https://cyclonedx.org/schema") | .predicate'
```

Pipe the command above through `sha256sum`. The digest should match the digest of the SBOM retrieved
in the previous section.

You can also use cosign to verify that the SBOM is in fact signed:

```shell
cosign verify-attestation --key cosign.pub \
--type 'https://cyclonedx.org/schema' ${OCI_REPO}:latest | \
jq '.payload | @base64d | fromjson | .predicate' | sha256sum
```

NOTE: The above assumes you still have the `cosign.pub` file from the previous step. If you don't,
you can load the public key directly from Kubernetes by using `k8s://tekton-chains/signing-secrets`
as the value for the `--key` flag.

NOTE: If you do not set `transparency.enabled` to `true` in `chains-config`, you must use the flag
`--insecure-ignore-tlog` in the command above.

## Large SBOMs

Because Tekton Chains has to read the SBOM in order to sign it, it has a default maxium size of
10MB. This can be adjusted via the `artifacts.sbom.maxbytes` property in `chains-config`.
98 changes: 98 additions & 0 deletions examples/sbom/sbom-pipeline.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Copyright 2023 The Tekton Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: simple-build
spec:
params:
- description: Repository URL to clone from.
name: git-repo
type: string
- default: main
description: Revision to checkout. (branch, tag, sha, ref, etc...)
name: git-revision
type: string
- description: Reference of the image the pipeline will produce.
name: output-image
type: string
- description: OCI repo to temporarily store the SBOM blob.
name: sbom-repo
type: string
results:
- description: Reference of the image the pipeline will produce.
name: IMAGE_URL
value: $(tasks.build.results.IMAGE_URL)
- description: Digest of the image the pipeline will produce.
name: IMAGE_DIGEST
value: $(tasks.build.results.IMAGE_DIGEST)
- description: Repository URL used for buiding the image.
name: CHAINS-GIT_URL
value: $(tasks.clone.results.url)
- description: Repository commit used for building the image.
name: CHAINS-GIT_COMMIT
value: $(tasks.clone.results.commit)
tasks:
- name: clone
taskRef:
resolver: git
params:
- name: url
value: https://github.com/tektoncd/catalog.git
- name: revision
value: main
- name: pathInRepo
value: task/git-clone/0.9/git-clone.yaml
params:
- name: url
value: $(params.git-repo)
- name: revision
value: $(params.git-revision)
workspaces:
- name: output
workspace: shared
- name: build
runAfter:
- clone
taskRef:
resolver: git
params:
- name: url
value: https://github.com/tektoncd/catalog.git
- name: revision
value: main
- name: pathInRepo
value: task/buildah/0.5/buildah.yaml
params:
- name: IMAGE
value: $(params.output-image)
workspaces:
- name: source
workspace: shared
- name: sbom
runAfter:
- build
taskRef:
kind: Task
name: sbom
params:
- name: IMAGE_URL
value: $(params.output-image)
- name: IMAGE_DIGEST
value: $(tasks.build.results.IMAGE_DIGEST)
- name: SBOM_REPO
value: $(params.sbom-repo)
workspaces:
- name: shared
70 changes: 70 additions & 0 deletions examples/sbom/sbom-task.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright 2023 The Tekton Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: sbom
spec:
params:
- name: IMAGE_URL
description: Reference to the image the SBOM will be generated for.
- name: IMAGE_DIGEST
description: Digest of the image the SBOM will be generated for.
- name: SBOM_REPO
description: OCI repo to temporarily push the SBOM blob to.
- name: HOMEDIR
type: string
description: Value for the HOME environment variable.
default: /tekton/home
results:
- name: IMAGE_URL
description: Reference to the image the SBOM will be generated for.
- name: IMAGE_DIGEST
description: Digest of the image the SBOM will be generated for.
- name: IMAGE_SBOM_URL
description: Reference, including digest, to the SBOM blob.
- name: IMAGE_SBOM_FORMAT
description: The SBOM format.
type: string
Comment on lines +32 to +40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Chains takes this approach, I am curious if we should work with catalog team to make sure the syft task can produce right results so that chains can sign? https://github.com/tektoncd/catalog/tree/main/task/syft/0.1

stepTemplate:
env:
- name: HOME
value: "$(params.HOMEDIR)"
steps:
- name: make-sbom
image: docker.io/anchore/syft:v0.86.1
args:
- $(params.IMAGE_URL)@$(params.IMAGE_DIGEST)
- --output
- cyclonedx-json
- --file
- $(params.HOMEDIR)/sbom.json
- name: emit-results
image: docker.io/busybox:1.36
script: |
sbom_digest="$(sha256sum $(params.HOMEDIR)/sbom.json | cut -d' ' -f1)"
echo -n "$(params.SBOM_REPO)@sha256:${sbom_digest}" | tee $(results.IMAGE_SBOM_URL.path)
echo -n 'https://cyclonedx.org/schema' | tee $(results.IMAGE_SBOM_FORMAT.path)
echo -n '$(params.IMAGE_URL)' | tee $(results.IMAGE_URL.path)
echo -n '$(params.IMAGE_DIGEST)' | tee $(results.IMAGE_DIGEST.path)
- name: store-sbom-blob
image: docker.io/bitnami/oras:latest
args:
- blob
- push
- --registry-config
- /tekton/creds/.docker/config.json
- $(params.SBOM_REPO)
- $(params.HOMEDIR)/sbom.json
Loading