Skip to content
Merged
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
18 changes: 18 additions & 0 deletions .github/scripts/attach-sbom.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ set -euo pipefail
ref="${1:?usage: attach-sbom.sh <image-ref> <sbom-file>}"
sbom="${2:?usage: attach-sbom.sh <image-ref> <sbom-file>}"

# oras does not apply the docker CLI's implicit docker.io default: a bare
# "repo/name:tag" is parsed with registry = "repo" (DNS lookup fails, attach is
# lost). Normalise to a fully-qualified reference following Docker's rules so oras
# resolves and authenticates correctly:
# - no "/" -> official image, docker.io/library/<ref>
# - first segment contains "." or ":" or is "localhost" -> already a registry host, keep as-is
# (ghcr.io/..., localhost/..., localhost:5000/...)
# - otherwise (namespace/repo[:tag]) -> bare Docker Hub ref, docker.io/<ref>
case "$ref" in
*/*)
case "${ref%%/*}" in
*.*|*:*|localhost) ;;
*) ref="docker.io/${ref}" ;;
esac
;;
*) ref="docker.io/library/${ref}" ;;
esac

if [ ! -s "$sbom" ]; then
echo "::warning::SBOM '$sbom' missing or empty; skipping attach for ${ref}"
exit 0
Expand Down
25 changes: 23 additions & 2 deletions .github/scripts/tests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,27 @@ assert_contains "$out" "Attached" "success prints Attached"
orasCallLog="$(cat "$orasLog" 2>/dev/null)"
assert_contains "$orasCallLog" "attach --artifact-type application/spdx+json" "oras invoked with attach --artifact-type application/spdx+json"
assert_contains "$orasCallLog" "${work}/s.spdx.json:application/spdx+json" "oras blob arg carries :application/spdx+json media-type suffix"
# oras needs a fully-qualified registry: a bare Docker Hub ref must be prefixed docker.io/
# (else oras reads "pimcore" as the registry host and the attach is silently lost).
assert_contains "$orasCallLog" "attach --artifact-type application/spdx+json docker.io/pimcore/pimcore:php8.5-v5-amd64" "bare Docker Hub ref qualified to docker.io for oras"

# a ref that already carries a registry host (ghcr.io/...) is left untouched
ghLog="$(mktemp)"; tmpdirs+=("$ghLog")
STUB_ORAS=ok STUB_LOG="$ghLog" "${ROOT}/.github/scripts/attach-sbom.sh" ghcr.io/pimcore/pimcore:php8.5-v5-amd64 "${work}/s.spdx.json" >/dev/null 2>&1
ghCallLog="$(cat "$ghLog" 2>/dev/null)"
assert_contains "$ghCallLog" "attach --artifact-type application/spdx+json ghcr.io/pimcore/pimcore:php8.5-v5-amd64" "ghcr.io ref left unqualified (already a registry host)"
assert_not_contains "$ghCallLog" "docker.io/ghcr.io" "ghcr.io ref not double-prefixed with docker.io"

# single-component ref (no slash) is an official image -> docker.io/library/<name>
scLog="$(mktemp)"; tmpdirs+=("$scLog")
STUB_ORAS=ok STUB_LOG="$scLog" "${ROOT}/.github/scripts/attach-sbom.sh" alpine:latest "${work}/s.spdx.json" >/dev/null 2>&1
assert_contains "$(cat "$scLog")" "attach --artifact-type application/spdx+json docker.io/library/alpine:latest" "single-component ref -> docker.io/library/"

# localhost is a registry host -> left as-is (not rewritten to Docker Hub)
lhLog="$(mktemp)"; tmpdirs+=("$lhLog")
STUB_ORAS=ok STUB_LOG="$lhLog" "${ROOT}/.github/scripts/attach-sbom.sh" localhost/repo:tag "${work}/s.spdx.json" >/dev/null 2>&1
assert_contains "$(cat "$lhLog")" "attach --artifact-type application/spdx+json localhost/repo:tag" "localhost recognized as registry host (left as-is)"
assert_not_contains "$(cat "$lhLog")" "docker.io/localhost" "localhost ref not prefixed with docker.io"

# failure path is swallowed
out="$(STUB_ORAS=fail "${ROOT}/.github/scripts/attach-sbom.sh" pimcore/pimcore:php8.5-v5-amd64 "${work}/s.spdx.json" 2>&1)"; rc=$?
Expand Down Expand Up @@ -262,8 +283,8 @@ oras_attaches="$(grep -c 'attach' "$logS" 2>/dev/null || true)"
# Exact subject binding: each referrer must target the LOGICAL tag (no arch suffix),
# keyed to the matching per-arch SBOM. Catches a regression that attaches to the child
# (…-amd64/…-arm64) ref instead of the index -- which would defeat the whole feature.
assert_contains "$(cat "$logS")" "attach --artifact-type application/spdx+json pimcore/pimcore:php8.5-v5.2 sboms/php8.5-v5.2-amd64.spdx.json" "S amd64 SBOM attached to the LOGICAL tag"
assert_contains "$(cat "$logS")" "attach --artifact-type application/spdx+json pimcore/pimcore:php8.5-v5.2 sboms/php8.5-v5.2-arm64.spdx.json" "S arm64 SBOM attached to the LOGICAL tag"
assert_contains "$(cat "$logS")" "attach --artifact-type application/spdx+json docker.io/pimcore/pimcore:php8.5-v5.2 sboms/php8.5-v5.2-amd64.spdx.json" "S amd64 SBOM attached to the LOGICAL tag (docker.io-qualified)"
assert_contains "$(cat "$logS")" "attach --artifact-type application/spdx+json docker.io/pimcore/pimcore:php8.5-v5.2 sboms/php8.5-v5.2-arm64.spdx.json" "S arm64 SBOM attached to the LOGICAL tag (docker.io-qualified)"

echo; [ "$fail" = "0" ] && echo "ALL TESTS PASSED" || echo "TESTS FAILED"
exit "$fail"
16 changes: 11 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -376,16 +376,22 @@ jobs:
path: aggregated_tags.txt
if-no-files-found: ignore

- name: Fail if severity gate failed
- name: Report severity gate results
if: ${{ matrix.build.hardened }}
run: |
# Hardened publishing is best-effort: a gate / Copa-patch failure skips the
# -hardened tag for that variant but must NOT fail the job -- the plain image
# already shipped, and some stable lines (e.g. EOL Debian releases whose fixed
# packages are no longer served) can never be fully patched. The failure is
# surfaced as a warning and in the step summary (scan-patch-gate.sh records the
# reason there), not as a red pipeline.
if compgen -G '.docker-state/*/gate_failed.txt' > /dev/null; then
echo "The following variants failed the severity gate; their -hardened tags were NOT published:"
echo "The following variants failed the severity gate; their -hardened tags were NOT published (plain images shipped as usual):"
grep -H . .docker-state/*/gate_failed.txt
echo "::error::One or more variants failed the severity gate; only their -hardened tags were skipped (plain image handling is unaffected)"
exit 1
echo "::warning::One or more variants failed the severity gate; their -hardened tags were skipped (plain image handling is unaffected)."
else
echo "All hardened variants passed the severity gate."
fi
echo "All hardened variants passed the severity gate."

process-tags:
runs-on: ubuntu-22.04
Expand Down
Loading