`cosign attach sbom` is deprecated (sigstore/cosign#2755) and pushes the SBOM UNSIGNED. Migrate the release-tools SBOM task to `cosign attest --predicate <sbom> --type cyclonedx --key`, signing the CycloneDX SBOM with the same cosign key the image-signing task uses. The signing key was already mounted in this task (previously unused by `attach`). - .tekton/release-tools/tasks/cosign-attach-sbom.yaml: attest instead of attach; refresh displayName/description/comments. - README.md: consumer block now verifies + extracts the signed attestation via `cosign verify-attestation --type cyclonedx`; `cosign download sbom` no longer applies. Closes forgejo-mcp-aa6. Follow-up forgejo-mcp-3y1 tracks the governed OpenSpec update (release-tools-image spec still cites `download sbom`).
171 lines
7.6 KiB
YAML
171 lines
7.6 KiB
YAML
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 <cosign.pub> <image-ref>`
|
|
(or `cosign download attestation <image-ref>` 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}"
|