From 70ba037106541b41d8e14f7ce5ee0d1443fe9f31 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 25 Sep 2026 18:53:06 +0100 Subject: [PATCH] fix(release): judge RPM extraction by payload; unblock macOS finalize Seal run 36164026957 failed twice: 1. Both musl managed-package lanes hard-failed in inspect_rpm because rpm 4.17's rpm2cpio exits nonzero on valid nFPM 2.47 RPMs while writing a complete, correct payload. verify_rpm's host branch already judged by extracted payload; inspect_rpm (host and docker branches) and verify_rpm's docker branch still trusted the pipeline status. All three now print a NOTE with the cpio log and let the payload-presence and SHA-256 checks stay fail-closed. Reproduced locally against a real nFPM 2.47 aarch64 RPM on rpm 4.17: complete payload with nonzero status now passes; a truncated stream fails closed. docker_rpm_tool verified against fedora:latest in Docker. 2. "Finalize all macOS bytes" never started: the tsm-production-release environment rejected the v1.21.16 tag deployment (branch policy allows main only), and the job hard-requires OP_SERVICE_ACCOUNT_TOKEN for op read, which has never been provisioned. The lane now prefers the 1Password service account and, when absent, falls back to the identical credentials already held by the tsm-production-release environment (the same source finalize-prebuilt-release.yml uses), with unchanged masking and validation. Dispatching from main (same commit the tag points at) satisfies the environment policy. Refs #337 --- .github/scripts/nfpm/build-client-packages.sh | 10 +++-- .../nfpm/tests/test_client_nfpm_native.sh | 33 ++++++++++---- .github/workflows/release-binaries.yml | 43 +++++++++++++++---- 3 files changed, 66 insertions(+), 20 deletions(-) diff --git a/.github/scripts/nfpm/build-client-packages.sh b/.github/scripts/nfpm/build-client-packages.sh index 7c767d3..5088861 100755 --- a/.github/scripts/nfpm/build-client-packages.sh +++ b/.github/scripts/nfpm/build-client-packages.sh @@ -384,12 +384,14 @@ docker_rpm_tool() { # --no-absolute-filenames keeps RPM payload members with # absolute names (Ubuntu 24.04 rpm2cpio / nFPM 2.47) private to # /extract; keep the log off the mounted volume so the host-side - # cleanup trap never meets a root-owned file, and surface it on - # failure instead of discarding stderr. + # cleanup trap never meets a root-owned file. Pipeline status is + # deliberately not the success criterion: the rpm 4.17 rpm2cpio + # exits nonzero on valid nFPM 2.47 RPMs while writing a complete + # payload (see the verify_rpm host branch). Note the status, then + # let the payload/SHA checks below stay fail-closed. if ! rpm2cpio /pkg.rpm | cpio --no-absolute-filenames -idmv >/tmp/rpm-extract.log 2>&1; then - echo "RPM payload extraction failed for /pkg.rpm (rpm2cpio | cpio --no-absolute-filenames -idmv):" >&2 + echo "NOTE: rpm2cpio|cpio returned nonzero for /pkg.rpm; verifying extracted payload" >&2 sed "s/^/ /" /tmp/rpm-extract.log >&2 - exit 1 fi payload="/extract/usr/bin/$2" if test -L "$payload" || ! test -f "$payload" || ! test -s "$payload"; then diff --git a/.github/scripts/nfpm/tests/test_client_nfpm_native.sh b/.github/scripts/nfpm/tests/test_client_nfpm_native.sh index 07a7ed0..03aec0e 100755 --- a/.github/scripts/nfpm/tests/test_client_nfpm_native.sh +++ b/.github/scripts/nfpm/tests/test_client_nfpm_native.sh @@ -232,13 +232,16 @@ inspect_rpm() { if command -v rpm2cpio >/dev/null 2>&1 && command -v rpm >/dev/null 2>&1 && command -v cpio >/dev/null 2>&1; then # --no-absolute-filenames keeps absolute RPM payload member names # (Ubuntu 24.04 rpm2cpio / nFPM 2.47) private to $extract instead of - # writing toward the host's real /usr, and surfaces the extraction - # diagnostics on failure instead of discarding them. + # writing toward the host's real /usr. Pipeline status is deliberately + # not the success criterion: rpm 4.17's rpm2cpio (Ubuntu 24.04 and + # Pop!_OS, i.e. every runner this gate runs on) exits 1 on nFPM 2.47 + # RPMs while writing a complete, correct payload. Note the status, + # then let the payload-presence and SHA-256 checks below stay + # fail-closed. local extract_log="$extract.cpio.log" if ! (cd "$extract" && rpm2cpio "$rpm_pkg" | cpio --no-absolute-filenames -idmv) >"$extract_log" 2>&1; then - echo "RPM payload extraction failed for $rpm_pkg (rpm2cpio | cpio --no-absolute-filenames -idmv):" >&2 + echo "NOTE: rpm2cpio|cpio returned nonzero for $rpm_pkg; verifying extracted payload" >&2 sed 's/^/ /' "$extract_log" >&2 - exit 1 fi { printf 'arch=' @@ -270,10 +273,17 @@ inspect_rpm() { cd /extract # --no-absolute-filenames keeps absolute RPM payload member # names (Ubuntu 24.04 rpm2cpio / nFPM 2.47) private to - # /extract; the log stays off the mounted volume and is - # surfaced on failure instead of discarded. + # /extract. Pipeline status is deliberately not the success + # criterion: the rpm 4.17 rpm2cpio exits 1 on nFPM 2.47 RPMs + # while writing a complete, correct payload. Note the status, + # then let the payload-presence check stay fail-closed (the + # host side SHA-compares the extracted binary afterwards). if ! rpm2cpio /pkg.rpm | cpio --no-absolute-filenames -idmv >/tmp/rpm-extract.log 2>&1; then - echo "RPM payload extraction failed for /pkg.rpm (rpm2cpio | cpio --no-absolute-filenames -idmv):" >&2 + echo "NOTE: rpm2cpio|cpio returned nonzero for /pkg.rpm; verifying extracted payload" >&2 + sed "s/^/ /" /tmp/rpm-extract.log >&2 + fi + if ! test -f "/extract/usr/bin/$1"; then + echo "RPM payload extraction produced no /extract/usr/bin/$1 (rpm2cpio | cpio --no-absolute-filenames -idmv):" >&2 sed "s/^/ /" /tmp/rpm-extract.log >&2 exit 1 fi @@ -287,12 +297,19 @@ inspect_rpm() { printf "\n" } > /metadata chmod -R a+rwX /extract /metadata - ' + ' sh "$bin_name" else echo "BLOCKED: RPM inspection requires host rpm/rpm2cpio/cpio or Docker" >&2 exit 127 fi + # Fail-closed payload judgement for both branches: rpm2cpio|cpio status is + # only advisory (NOTE above), so the extracted binary itself is the + # criterion, SHA-compared immediately after. + [[ -f "$extract/usr/bin/$bin_name" ]] || { + echo "RPM payload extraction produced no $bin_name for $rpm_pkg" >&2 + exit 1 + } actual_sha="$(sha256sum "$extract/usr/bin/$bin_name" | awk '{print $1}')" [[ "$actual_sha" == "$expected_sha" ]] || { echo "RPM payload SHA mismatch expected=$expected_sha actual=$actual_sha" >&2 diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index 5a3c656..a1ddf16 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -549,12 +549,18 @@ jobs: - name: Sign, notarize, and revalidate final macOS binaries env: OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }} + CERT_BASE64: ${{ secrets.CERT_BASE64 }} + CERT_PASSWORD: ${{ secrets.CERT_PASSWORD }} VERSION: ${{ needs.preflight.outputs.version }} run: | set -euo pipefail - load_masked() { - local name="$1" reference="$2" value - value="$(op read "$reference" --no-newline)" + # Shared validation: non-empty, CERT_BASE64 newline-stripped, all + # other values single-line, masked in logs, exported under $name. + normalise_and_mask() { + local name="$1" value="$2" [ -n "$value" ] || { echo "empty credential $name" >&2; exit 1; } if [ "$name" = "CERT_BASE64" ]; then value="${value//$'\r'/}" @@ -570,11 +576,32 @@ jobs: # masking above and the empty-value check are unchanged. export "${name?}" } - load_masked APPLE_ID 'op://TerraphimPlatform/apple.developer.credentials/username' - load_masked APPLE_TEAM_ID 'op://TerraphimPlatform/apple.developer.credentials/APPLE_TEAM_ID' - load_masked APPLE_APP_PASSWORD 'op://TerraphimPlatform/apple.developer.credentials/APPLE_APP_SPECIFIC_PASSWORD' - load_masked CERT_BASE64 'op://TerraphimPlatform/apple.developer.certificate/base64' - load_masked CERT_PASSWORD 'op://TerraphimPlatform/apple.developer.certificate/password' + load_masked() { + local name="$1" reference="$2" + normalise_and_mask "$name" "$(op read "$reference" --no-newline)" + } + load_masked_env() { + local name="$1" + normalise_and_mask "$name" "${!name:-}" + } + # Preferred source is the 1Password service account; when it has not + # been provisioned, the identical credentials held by the reviewed + # tsm-production-release environment (the same source + # finalize-prebuilt-release.yml uses) keep the lane unblocked. + if [ -n "${OP_SERVICE_ACCOUNT_TOKEN:-}" ]; then + load_masked APPLE_ID 'op://TerraphimPlatform/apple.developer.credentials/username' + load_masked APPLE_TEAM_ID 'op://TerraphimPlatform/apple.developer.credentials/APPLE_TEAM_ID' + load_masked APPLE_APP_PASSWORD 'op://TerraphimPlatform/apple.developer.credentials/APPLE_APP_SPECIFIC_PASSWORD' + load_masked CERT_BASE64 'op://TerraphimPlatform/apple.developer.certificate/base64' + load_masked CERT_PASSWORD 'op://TerraphimPlatform/apple.developer.certificate/password' + else + echo "NOTE: OP_SERVICE_ACCOUNT_TOKEN not set; using tsm-production-release environment secrets" >&2 + load_masked_env APPLE_ID + load_masked_env APPLE_TEAM_ID + load_masked_env APPLE_APP_PASSWORD + load_masked_env CERT_BASE64 + load_masked_env CERT_PASSWORD + fi chmod 755 macos/* for path in macos/*; do scripts/sign-macos-binary.sh "$path" "$APPLE_ID" "$APPLE_TEAM_ID" "$APPLE_APP_PASSWORD" "$CERT_BASE64" "$CERT_PASSWORD"