apiVersion: tekton.dev/v1 kind: Task metadata: name: release-tools-cosign-attach-sbom annotations: tekton.dev/displayName: "syft scan + cosign attest (signed CycloneDX SBOM) for release-tools image" spec: description: | Generates a CycloneDX SBOM via syft and binds it to the published image manifest as a SIGNED in-toto attestation using `cosign attest --type cyclonedx` (stored as an OCI referrer). Consumers verify and retrieve it via `cosign verify-attestation --type cyclonedx --key ` (or `cosign download attestation ` for the raw envelope). Migrated from `cosign attach sbom` (forgejo-mcp-aa6): `attach sbom` is deprecated (sigstore/cosign#2755) and pushes the SBOM UNSIGNED. `attest` signs the SBOM with the same cosign key used by the image-signing task, so the SBOM is tamper-evident. Registry: codeberg.org/operate-first/release-tools (Path A — reuses codeberg-pusher Secret in op1st-pipelines for both push and attach). Image choice: ubi9-minimal + curl-installed syft + cosign (NOT the release-tools image itself). Same reasoning as cosign-sign-image-by-digest: avoids circular reference at v1.0.0, has shell, install-then-run pattern. params: - name: IMAGE_REPO_DIGEST description: Full digest reference to attach SBOM to (e.g. codeberg.org/operate-first/release-tools@sha256:abc123). type: string - name: SYFT_VERSION description: syft CLI version pinned for this Task. type: string default: v1.18.1 - name: COSIGN_VERSION description: cosign CLI version pinned for this Task. type: string default: v2.4.1 workspaces: - name: source mountPath: /workspace/source - name: registry-credentials mountPath: /workspace/registry-creds optional: false description: | Dockerconfigjson Secret with write auth on the release-tools registry path. cosign attest pushes the signed SBOM attestation as an OCI referrer object; needs the same write auth as push-image-by-digest. volumes: - name: cosign-signing-key-images secret: secretName: cosign-signing-key-images optional: false results: - name: SBOM_DIGEST description: sha256 digest of the attached SBOM artifact. steps: - name: generate-and-attach-sbom image: registry.access.redhat.com/ubi9/ubi-minimal:latest workingDir: $(workspaces.source.path) volumeMounts: - name: cosign-signing-key-images mountPath: /workspace/cosign-key readOnly: true env: - name: COSIGN_KEY_PATH value: /workspace/cosign-key/cosign.key - name: COSIGN_PASSWORD valueFrom: secretKeyRef: name: cosign-signing-key-images key: cosign.password optional: false - name: REGISTRY_AUTH_FILE value: /workspace/registry-creds/.dockerconfigjson script: | #!/usr/bin/env sh set -eu # curl-minimal is pre-installed in ubi9-minimal; only tar+gzip need adding. microdnf install -y tar gzip # Install syft from the pinned release tag (not main branch). curl -sSfL "https://raw.githubusercontent.com/anchore/syft/$(params.SYFT_VERSION)/install.sh" \ | sh -s -- -b /usr/local/bin "$(params.SYFT_VERSION)" syft version # Install cosign. Verify against release checksum manifest before chmod +x. curl -sSfLo /tmp/cosign-linux-amd64 \ "https://github.com/sigstore/cosign/releases/download/$(params.COSIGN_VERSION)/cosign-linux-amd64" curl -sSfLo /tmp/cosign-checksums.txt \ "https://github.com/sigstore/cosign/releases/download/$(params.COSIGN_VERSION)/cosign_checksums.txt" cd /tmp && grep " cosign-linux-amd64$" cosign-checksums.txt | sha256sum -c - mv /tmp/cosign-linux-amd64 /usr/local/bin/cosign rm /tmp/cosign-checksums.txt chmod +x /usr/local/bin/cosign cosign version # Registry auth file fallback (.dockerconfigjson vs config.json). if [ ! -f "${REGISTRY_AUTH_FILE}" ]; then REGISTRY_AUTH_FILE=/workspace/registry-creds/config.json fi if [ ! -f "${REGISTRY_AUTH_FILE}" ]; then echo "ERROR: no registry auth file found in /workspace/registry-creds/" >&2 ls -la /workspace/registry-creds/ >&2 exit 1 fi export REGISTRY_AUTH_FILE IMAGE_REF="$(params.IMAGE_REPO_DIGEST)" SBOM_FILE="$(workspaces.source.path)/release-tools-sbom.cdx.json" # --- credential resolution shim ------------------------------------ # `cosign attest` pushes the signed SBOM attestation as an OCI referrer, # so it needs the same registry WRITE auth as the image push. cosign/ggcr # resolve # auth by HOST only, whereas skopeo longest-prefix-matches path-scoped # entries. The codeberg-pusher Secret carries a host-level auth # (op1st-gitops, no goern package-write) plus a path-scoped goern auth. # Without this, attaching to codeberg.org/goern/* gets # "401 Unauthorized: reqPackageAccess". Re-key the longest-prefix match # under the bare host so cosign finds the right token. No-op for # operate-first/release-tools (its longest match IS the host key). # See cosign-sign-image-by-digest.yaml for the full rationale. JQ_VERSION=jq-1.7.1 JQ_SHA256=5942c9b0934e510ee61eb3e30273f1b3fe2590df93933a93d7c58b81d19c8ff5 curl -sSfLo /tmp/jq \ "https://github.com/jqlang/jq/releases/download/${JQ_VERSION}/jq-linux-amd64" echo "${JQ_SHA256} /tmp/jq" | sha256sum -c - chmod +x /tmp/jq REPO="${IMAGE_REF%@*}" HOST="${REPO%%/*}" BEST_KEY="" for k in $(/tmp/jq -r '.auths | keys[]' "${REGISTRY_AUTH_FILE}"); do if [ "${REPO}" = "${k}" ] || [ "${REPO#"${k}"/}" != "${REPO}" ]; then [ ${#k} -gt ${#BEST_KEY} ] && BEST_KEY="${k}" fi done if [ -z "${BEST_KEY}" ]; then echo "ERROR: no auth entry in ${REGISTRY_AUTH_FILE} matches ${REPO}" >&2 /tmp/jq -r '.auths | keys[]' "${REGISTRY_AUTH_FILE}" >&2 || true exit 1 fi echo "Auth entry '${BEST_KEY}' re-keyed under host '${HOST}' for cosign" mkdir -p /tmp/cosign-docker /tmp/jq -c --arg key "${BEST_KEY}" --arg host "${HOST}" \ '{auths: {($host): .auths[$key]}}' "${REGISTRY_AUTH_FILE}" \ > /tmp/cosign-docker/config.json export DOCKER_CONFIG=/tmp/cosign-docker export REGISTRY_AUTH_FILE=/tmp/cosign-docker/config.json # ------------------------------------------------------------------- echo "Generating CycloneDX SBOM for ${IMAGE_REF}..." syft "${IMAGE_REF}" --output cyclonedx-json="${SBOM_FILE}" echo "SBOM written to ${SBOM_FILE}" echo "Attesting signed SBOM for ${IMAGE_REF}..." # `attest` signs the SBOM with the cosign key (COSIGN_KEY_PATH / # COSIGN_PASSWORD already in env) and pushes it as an in-toto # attestation OCI referrer. Replaces deprecated `cosign attach sbom`, # which pushed the SBOM unsigned (sigstore/cosign#2755). cosign attest \ --predicate="${SBOM_FILE}" \ --type=cyclonedx \ --key="${COSIGN_KEY_PATH}" \ --yes \ "${IMAGE_REF}" SBOM_DIGEST=$(sha256sum "${SBOM_FILE}" | cut -d' ' -f1) printf '%s' "sha256:${SBOM_DIGEST}" > "$(results.SBOM_DIGEST.path)" echo "Signed SBOM attestation pushed. Local SBOM digest: sha256:${SBOM_DIGEST}"