From f0c29864775a4ce321135e911be2a275a81ba030 Mon Sep 17 00:00:00 2001 From: Dev Agent Date: Sun, 7 Jun 2026 11:20:38 +0300 Subject: [PATCH] ORCH-058: implement fail-closed provenance guard in deploy-hook + GIT_SHA OCI label in Dockerfile - deploy-hook: REVISION_LABEL/EXPECTED_REVISION (default unset -> backward-compat) - deploy-hook: fail-closed guard inspects SOURCE_IMAGE revision label before docker tag, normalises , exit 1 on empty/mismatch - deploy-hook: new --build-staging mode rebuilds staging image stamping GIT_SHA - Dockerfile: ARG GIT_SHA + LABEL org.opencontainers.image.revision=$GIT_SHA Closes TC07/TC08 (tests/test_deploy_hook_provenance.py). --- Dockerfile | 4 ++++ scripts/orchestrator-deploy-hook.sh | 32 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/Dockerfile b/Dockerfile index 504e06a..ddcabbb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,9 @@ FROM python:3.12-slim WORKDIR /app +# ORCH-58: stamp the validated git commit into the OCI revision label so the +# deploy hook provenance guard can fail-closed on it before the prod retag. +ARG GIT_SHA +LABEL org.opencontainers.image.revision=$GIT_SHA RUN apt-get update -qq && apt-get install -y -qq openssh-client git && rm -rf /var/lib/apt/lists/* # git operations run as root over bind-mounted /repos (may be owned by host uid) -> trust it. RUN git config --system --add safe.directory '*' diff --git a/scripts/orchestrator-deploy-hook.sh b/scripts/orchestrator-deploy-hook.sh index c6d8ca8..68be4d6 100755 --- a/scripts/orchestrator-deploy-hook.sh +++ b/scripts/orchestrator-deploy-hook.sh @@ -32,6 +32,11 @@ PREV_IMAGE_FILE="${PREV_IMAGE_FILE:-$REPO/.deploy-prev-image-staging}" # Build-once (ORCH-36): optional prevalidated source image to retag onto # TARGET_IMAGE. Unset -> backward-compatible (no retag), exit-code contract intact. SOURCE_IMAGE="${SOURCE_IMAGE:-}" +# Provenance guard (ORCH-58 Strategy-B): the OCI revision label the hook +# inspects on SOURCE_IMAGE, and the git revision it MUST match before retag +# onto prod. EXPECTED_REVISION unset -> backward-compatible (guard skipped). +REVISION_LABEL="org.opencontainers.image.revision" +EXPECTED_REVISION="${EXPECTED_REVISION:-}" # ---- Log setup ------------------------------------------------------------- LOG_DIR=/var/log/orchestrator @@ -118,6 +123,17 @@ do_rollback() { # ============================================================================ # MANUAL --rollback mode # ============================================================================ +if [[ "${1:-}" == "--build-staging" ]]; then + # Strategy-A rebuild mode (ORCH-58): rebuild the staging image stamping + # the validated commit SHA into the OCI revision label so the provenance + # guard above can fail-closed on it during the subsequent prod retag. + GIT_SHA=$(git rev-parse HEAD) + log "BUILD-STAGING: rebuilding $TARGET_IMAGE stamping GIT_SHA=$GIT_SHA" + docker build --build-arg GIT_SHA="$GIT_SHA" -t "$TARGET_IMAGE" "$REPO" >> "$LOG" 2>&1 + log "BUILD-STAGING: built $TARGET_IMAGE (revision=$GIT_SHA)" + exit 0 +fi + if [[ "${1:-}" == "--rollback" ]]; then log "Manual ROLLBACK requested" if do_rollback; then @@ -156,6 +172,22 @@ git pull origin main >> "$LOG" 2>&1 # Backward compatible: skipped when SOURCE_IMAGE is unset. if [[ -n "$SOURCE_IMAGE" ]]; then if docker image inspect "$SOURCE_IMAGE" >/dev/null 2>&1; then + # Fail-closed provenance guard: when EXPECTED_REVISION is set, the + # source image MUST carry the matching git-revision OCI label, else + # abort BEFORE the prod retag. Empty EXPECTED_REVISION -> guard + # skipped (ORCH-36 backward-compat). + if [[ -n "$EXPECTED_REVISION" ]]; then + IMG_REV=$(docker image inspect --format '{{ index .Config.Labels "'"$REVISION_LABEL"'" }}' "$SOURCE_IMAGE" 2>/dev/null || true) + # docker emits "" when the label is absent -> normalise. + if [[ "$IMG_REV" == "" ]]; then + IMG_REV="" + fi + if [[ -z "$IMG_REV" || "$IMG_REV" != "$EXPECTED_REVISION" ]]; then + log "PROVENANCE: SOURCE_IMAGE revision '$IMG_REV' != expected '$EXPECTED_REVISION' - aborting before retag (exit 1)" + exit 1 + fi + log "PROVENANCE: SOURCE_IMAGE revision matches expected ($EXPECTED_REVISION)" + fi log "BUILD-ONCE: retagging $SOURCE_IMAGE -> $TARGET_IMAGE (no rebuild)" docker tag "$SOURCE_IMAGE" "$TARGET_IMAGE" >> "$LOG" 2>&1 else