From 7f9b5514b159ab91d211143fd96e61e31a5b28a9 Mon Sep 17 00:00:00 2001 From: JorisJonkers Agent Date: Thu, 9 Jul 2026 16:56:47 +0000 Subject: [PATCH] fix: add GHCR auth and fix idempotency check in deploy-artifact publish Two issues in publish.sh: 1. No GHCR authentication before oras push / cosign sign. GITHUB_TOKEN and GITHUB_ACTOR are now passed explicitly via env to publish.sh, which logs in to ghcr.io via both oras and docker at startup. 2. The idempotency check (oras manifest fetch) matched the container image tag rather than the deploy artifact, because both share the same OCI ref (ghcr.io//:). Adding --media-type to the fetch call restricts the match to deploy artifacts only, preventing false-positive idempotency hits that silently used the container image digest as the artifact digest. Also removes the syft SBOM step: SBOM is only meaningful for container images (handled in container-publish.yml), not for YAML/tar deploy artifacts. --- .github/workflows/deploy-artifact.yml | 2 ++ actions/deploy-artifact/publish.sh | 40 +++++++++++++++++---------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/.github/workflows/deploy-artifact.yml b/.github/workflows/deploy-artifact.yml index 30c4565..685afd5 100644 --- a/.github/workflows/deploy-artifact.yml +++ b/.github/workflows/deploy-artifact.yml @@ -125,6 +125,8 @@ jobs: ARTIFACT_NAME: ${{ inputs.artifact-name }} ARTIFACT_VERSION: ${{ inputs.artifact-version }} RENDER_HASH: ${{ steps.render.outputs.render-hash }} + GITHUB_TOKEN: ${{ github.token }} + GITHUB_ACTOR: ${{ github.actor }} run: bash .github-workflows/actions/deploy-artifact/publish.sh - name: Attest build provenance diff --git a/actions/deploy-artifact/publish.sh b/actions/deploy-artifact/publish.sh index d21e267..23ddbc4 100755 --- a/actions/deploy-artifact/publish.sh +++ b/actions/deploy-artifact/publish.sh @@ -48,6 +48,18 @@ main() { local artifact_ref artifact_ref="ghcr.io/jorisjonkers-dev/${artifact_name}:${artifact_version}" + # (0) Authenticate to GHCR so oras push and cosign sign can write packages. + # GITHUB_TOKEN is always injected by GitHub Actions (packages: write on the + # deploy-artifact.yml caller grants write access to the service's packages). + if [[ -n "${GITHUB_TOKEN:-}" ]]; then + printf '%s' "$GITHUB_TOKEN" | oras login ghcr.io \ + --username "${GITHUB_ACTOR:-github-actions}" \ + --password-stdin + printf '%s' "$GITHUB_TOKEN" | docker login ghcr.io \ + --username "${GITHUB_ACTOR:-github-actions}" \ + --password-stdin + fi + # (1) Build deterministic tar (SC-9: sort, mtime=0, uid=0, gid=0, numeric) tar \ --sort=name \ @@ -60,14 +72,18 @@ main() { local artifact_digest - # (2) Check if tag already exists (idempotent publish) - if oras manifest fetch "$artifact_ref" > /dev/null 2>&1; then - # Tag exists: verify render-hash matches; fail on mismatch (content collision) + # (2) Check if a deploy artifact tag already exists (idempotent publish). + # Use --media-type to distinguish deploy artifacts from container images that + # share the same tag (container images have a different manifest media type + # and will not match, preventing false-positive idempotency hits). + if oras manifest fetch "$artifact_ref" \ + --media-type "application/vnd.jorisjonkers.deployment.artifact.v1+tar" \ + > /dev/null 2>&1; then + # Deploy artifact tag exists: verify render-hash; fail on mismatch. local existing_digest existing_digest=$(oras resolve "$artifact_ref" 2>/dev/null) local existing_hash="" - # Attempt to extract artifact-contract from the stored artifact blob local existing_contract if existing_contract=$(oras blob fetch \ "ghcr.io/jorisjonkers-dev/${artifact_name}@${existing_digest}" \ @@ -82,7 +98,6 @@ main() { fail "E_PUBLISH_RACE_TAG_MOVED: tag=${artifact_ref} existing-hash=${existing_hash} new-hash=${render_hash} (different content published under same version tag)" fi - # Hashes match (or could not extract existing hash): idempotent re-use of existing artifact artifact_digest="$existing_digest" warn "artifact already exists with matching render-hash; skipping push (idempotent)" else @@ -107,19 +122,16 @@ main() { artifact_digest="$pushed_digest" fi - # (5) SBOM via syft → attach - syft "ghcr.io/jorisjonkers-dev/${artifact_name}@${artifact_digest}" \ - -o spdx-json=sbom.spdx.json - - oras attach "${artifact_ref}@${artifact_digest}" \ - --artifact-type "application/vnd.syft.sbom.spdx+json" \ - sbom.spdx.json + # NOTE: SBOM via syft is intentionally omitted here. This artifact is a + # YAML/tar package (deployment configuration), not a container image; there + # are no software dependencies to enumerate. Container-image SBOM is handled + # separately by the container-publish.yml workflow. - # (6) Keyless cosign sign (SC-10) + # (5) Keyless cosign sign (SC-10) cosign sign --yes \ "ghcr.io/jorisjonkers-dev/${artifact_name}@${artifact_digest}" - # (7) SLSA build provenance attestation + # (6) SLSA build provenance attestation # Uses the GitHub Actions attest action injected via the workflow permissions printf '%s\n' "artifact-ref=${artifact_ref}@${artifact_digest}" >> "${GITHUB_OUTPUT:-/dev/null}" printf '%s\n' "artifact-digest=${artifact_digest}" >> "${GITHUB_OUTPUT:-/dev/null}"