diff --git a/.github/scripts/attach-sbom.sh b/.github/scripts/attach-sbom.sh index befc890..98b7245 100755 --- a/.github/scripts/attach-sbom.sh +++ b/.github/scripts/attach-sbom.sh @@ -6,6 +6,24 @@ set -euo pipefail ref="${1:?usage: attach-sbom.sh }" sbom="${2:?usage: attach-sbom.sh }" +# 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/ +# - 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/ +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 diff --git a/.github/scripts/tests/run.sh b/.github/scripts/tests/run.sh index c015845..effb080 100755 --- a/.github/scripts/tests/run.sh +++ b/.github/scripts/tests/run.sh @@ -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/ +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=$? @@ -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" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f17fb9c..8174283 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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