diff --git a/docs/tutorials/signed-sbom.md b/docs/tutorials/signed-sbom.md new file mode 100644 index 0000000000..a0f5fe66b2 --- /dev/null +++ b/docs/tutorials/signed-sbom.md @@ -0,0 +1,175 @@ + + +# 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`. diff --git a/examples/sbom/sbom-pipeline.yaml b/examples/sbom/sbom-pipeline.yaml new file mode 100644 index 0000000000..9529d11c07 --- /dev/null +++ b/examples/sbom/sbom-pipeline.yaml @@ -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 diff --git a/examples/sbom/sbom-task.yaml b/examples/sbom/sbom-task.yaml new file mode 100644 index 0000000000..0eff308794 --- /dev/null +++ b/examples/sbom/sbom-task.yaml @@ -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 + 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