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
15 changes: 8 additions & 7 deletions .github/agent-guides/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ required; this fixture never proves live token generation or project association

**Explicit manifests recovery:** Default-off `enable-signed-recovery` requests only the original version alias, with signed promotion and caller pinning required before any legacy write. Verify the original catalogue identity and all original source/version/run claims, restore only authenticated missing versions, treat matching versions as no-ops, refuse contradictory or ambiguous reads, and always preserve `latest`. Keep recovery boundary, interruption/readback controls and native Flux/registry/cryptographic tests in required CI. The native key fixture proves signed claims and registry bytes, not production OIDC. Cross-repository publisher identity migration remains #242; complete recovery rollout remains #425.

The configuration caller guard approves the publisher workflow and both copied helpers by Git
blob and regular-file mode, while retaining the exact workflow identity, full commit reference,
publication opt-ins and artifact target. Releases with unchanged approved contents need no test-pin
edit; changed contents require review before updating the recorded blobs. Missing commits are read
through a disposable object store. The promotion suite exercises unchanged and changed releases,
helper changes, modes and input refusals. This source check does not replace the deployed verifier's
separate approved-revision gate.
The configuration caller guard separately requires the exact consumer-approved signing revision
and the publisher workflow/helper Git blobs and regular-file modes. Even an unrelated release with
unchanged publisher contents changes its certificate identity. Deploy and independently read back
consumer approval before changing the guard's recorded revision and the caller; never treat content
equivalence as that approval. Changed contents also require review before updating the recorded
blobs. Missing commits are read through a disposable object store. The promotion suite rejects an
unapproved byte-identical release and exercises an explicitly approved release, changed helpers,
modes and unsafe inputs. CI's record does not replace the deployed verifier's exact-revision gate.

**Explicit application recovery:** The same default-off mode verifies both original signatures with all nine paired publication claims before reading both version aliases. Any conflict or unknown read prevents all writes; only missing aliases are restored, with digest readback and matching retries producing no writes. Keep `.github/tests/test-app-recovery.sh` and the native single-platform Buildx/Flux proof required. Never rebuild, re-sign or move `latest` during recovery. Registry writes have no compare-and-set or cross-repository transaction; the shared queue and final readback cannot fence unrelated writers.

Expand Down
18 changes: 12 additions & 6 deletions .github/scripts/check-manifests-caller.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#!/usr/bin/env bash
# Approve publisher contents, so an unrelated release does not require a pin edit here.
# Require independently approved signer identity AND reviewed publisher contents.
set -euo pipefail
fail() { echo "FAIL: configuration caller: $*" >&2; exit 1; }
fail() {
echo "FAIL: configuration caller: $*" >&2
exit 1
}
[[ "$#" == 1 ]] || fail 'expected one caller workflow'
caller="$1"
encoded_ref="$(yq -o=json -I=0 '.jobs["publish-manifests"].uses // ""' "$caller")" ||
Expand All @@ -18,9 +21,10 @@ yq -e '
' "$caller" >/dev/null || fail 'expected verified-promotion inputs'

# The workflow pins every external action and copies exactly these two source helpers.
# All three files are unchanged between the previously approved v10.0.3 and v12.0.1.
# A new release carrying these contents passes; a workflow/helper change requires review
# and an explicit blob update. File modes are part of the approval too.
# Byte equivalence is not signer equivalence: the revision is part of the certificate
# identity. Update this revision only AFTER consumer approval has been deployed and
# read back. Content and file-mode review remain independently required.
approved_publisher_revision='0f390329b590404c02565b27a263ee841fa02bce'
reviewed_workflow_blob='ce9a9897b154486218478734a02c61e5ac3ea479'
reviewed_absence_blob='3566e4067e953a1d9cb34e6a50a0f9b72a5d8bc8'
reviewed_recovery_blob='48dc38f60ddebf90b0976758df9f1983bbb86bfd'
Expand Down Expand Up @@ -51,4 +55,6 @@ $reviewed_workflow_blob .github/workflows/publish-manifests.yaml
$reviewed_absence_blob .github/scripts/require-unpublished-version.sh
$reviewed_recovery_blob .github/scripts/recover-manifests-version.sh
EOF
echo 'ok configuration caller pins the reviewed publisher and helper contents with verified promotion'
[[ "$revision" == "$approved_publisher_revision" ]] ||
fail 'signer revision is not approved by consumers; deploy and verify consumer approval before updating the recorded revision'
echo 'ok configuration caller pins an approved signer with reviewed contents and verified promotion'
45 changes: 32 additions & 13 deletions .github/tests/fixtures/manifests-caller-contract.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ root="$(cd "$(dirname "$0")/../../.." && pwd)"
guard="$root/.github/scripts/check-manifests-caller.sh"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
fail() { echo "FAIL: manifests caller fixture: $*" >&2; exit 1; }
fail() {
echo "FAIL: manifests caller fixture: $*" >&2
exit 1
}
fixture_git() {
git -C "$work/source" -c user.name=fixture -c user.email=fixture@example.invalid \
-c commit.gpgsign=false -c gc.auto=0 -c maintenance.auto=false "$@"
Expand Down Expand Up @@ -47,6 +50,7 @@ fixture_git config uploadpack.allowFilter true
fixture_git config uploadpack.allowAnySHA1InWant true
make_guard() {
sed -e "s|^reviewed_workflow_blob='.*'$|reviewed_workflow_blob='$workflow_blob'|" \
-e "s|^approved_publisher_revision='.*'$|approved_publisher_revision='$reviewed'|" \
-e "s|^reviewed_absence_blob='.*'$|reviewed_absence_blob='$absence_blob'|" \
-e "s|^reviewed_recovery_blob='.*'$|reviewed_recovery_blob='$recovery_blob'|" \
-e "s|^source_remote='.*'$|source_remote='$1'|" "$guard" >"$2"
Expand All @@ -62,23 +66,38 @@ mutate() {
'.jobs["publish-manifests"].uses = ($prefix + "@" + $reviewed) | '"$1" \
"$work/baseline.json" >"$work/caller.json"
}
run_guard() { (cd "$2"; bash "$1" "$work/caller.json"); }
for revision in reviewed release; do
mutate ".jobs[\"publish-manifests\"].uses = (\$prefix + \"@\" + \$$revision)"
run_guard "$work/offline.sh" "$work/source" >"$work/result" 2>&1 || {
cat "$work/result" >&2
fail "rejected $revision content"
}
done
run_guard() { (
cd "$2"
bash "$1" "$work/caller.json"
); }
mutate '.'
run_guard "$work/offline.sh" "$work/source" >"$work/result" 2>&1 || {
cat "$work/result" >&2
fail 'rejected approved publisher'
}
# The two commits carry byte-identical publishers but different signer identities.
# An unrelated release must not become an approved signer implicitly.
# shellcheck disable=SC2016 # jq variables, not shell expansion.
mutate '.jobs["publish-manifests"].uses = ($prefix + "@" + $release)'
if run_guard "$work/offline.sh" "$work/source" >"$work/result" 2>&1; then
fail 'accepted unapproved signer with unchanged publisher contents'
fi
grep -qF 'signer revision is not approved by consumers' "$work/result" || {
cat "$work/result" >&2
fail 'unchanged unapproved signer failed for the wrong reason'
}
# A separately reviewed consumer-first approval admits the exact new identity.
sed "s|^approved_publisher_revision='.*'$|approved_publisher_revision='$release'|" \
"$work/fetching.sh" >"$work/approved-release.sh"
git init --quiet "$work/consumer"
# shellcheck disable=SC2016 # jq variables, not shell expansion.
mutate '.jobs["publish-manifests"].uses = ($prefix + "@" + $release)'
run_guard "$work/fetching.sh" "$work/consumer" >"$work/result" 2>&1 || {
run_guard "$work/approved-release.sh" "$work/consumer" >"$work/result" 2>&1 || {
cat "$work/result" >&2
fail 'rejected fetched unchanged release'
fail 'rejected fetched explicitly approved release'
}
[[ ! -e "$work/consumer/.git/shallow" && ! -s "$work/consumer/.git/FETCH_HEAD" &&
-z "$(git -C "$work/consumer" remote)" ]] || fail 'fetch changed consumer Git state'
-z "$(git -C "$work/consumer" remote)" ]] || fail 'fetch changed consumer Git state'
while IFS=$'\t' read -r label mutation diagnostic; do
mutate "$mutation"
if run_guard "$work/offline.sh" "$work/source" >"$work/result" 2>&1; then
Expand Down Expand Up @@ -110,4 +129,4 @@ if run_guard "$work/fetching.sh" "$work/consumer" >"$work/result" 2>&1; then
fail 'accepted fetched changed helper'
fi
grep -qF 'unreviewed publisher content' "$work/result" || fail 'fetched content refusal had the wrong cause'
echo 'ok caller accepts unchanged releases and refuses unreviewed workflow/helper bytes, modes and unsafe inputs'
echo 'ok caller requires explicit signer approval and reviewed contents, modes and safe inputs'
15 changes: 6 additions & 9 deletions .github/workflows/cd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,14 @@ jobs:
# the SHA must be reachable from a tag, or zizmor flags it as an impostor).
# Dependabot manages this pin (.github/dependabot.yml, github-actions
# ecosystem, no cooldown for devantler-tech/*).
# Keep the ref a 40-hex commit: it is part of the cosign subject the platform
# verifies, so its shape is load-bearing here.
uses: devantler-tech/.github/.github/workflows/publish-manifests.yaml@6f1dee9557a939cc24ca2c562ea775d05e01564d # v12.0.1
# Keep the exact consumer-approved revision, not merely a 40-hex shape or
# byte-identical publisher. CI requires separate signer and content approval;
# deploy and read back consumer approval BEFORE changing this identity.
uses: devantler-tech/.github/.github/workflows/publish-manifests.yaml@0f390329b590404c02565b27a263ee841fa02bce # v10.0.3
with:
oci-name: devantler-tech/github-config
# Without this, a ref the platform verifier does not accept fails nothing
# here: the artifact still publishes, signed under an identity the
# platform rejects, and the `github-config` tenant silently stops
# reconciling until the ref is put back. Enabling the guard turns that
# into a loud pre-signing failure in this workflow instead — the publish
# refuses before the artifact exists, and the error names the fix.
# The publisher refuses mutable signing refs. Consumer approval of the exact
# immutable identity is a separate CI guard, not inferred from this input.
enable-caller-pin: true
# Promote only after the staged digest has been signed and verified.
enable-signed-promotion: true
Loading