mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-12 09:33:23 +00:00
Not one action reference in this repository was pinned. Every uses: line named a mutable tag, and one named a branch. That includes the jobs holding the AUR deploy key, the jobs with release write scope, and the packaging jobs that run with a signing key in the environment, so whoever controls one of those action repositories could repoint a tag into a job holding our credentials. Sixty-two of the sixty-six references are now full commit SHAs with the original tag kept as a trailing comment, so a reader can still tell which release a pin is. Each SHA was resolved from the upstream peeled tag. Four references are left unpinned and justified in one place rather than silently: two actions select the tool they install from the ref name itself, so a bare SHA hands them a hex string where a toolchain name belongs and the step fails. Pinning those means moving the selection into with:, which changes what resolves, and that is a separate decision from pinning. A guard enforces the form on every sweep, wired into the parity job and the local runner beside the existing checkers. It accepts only owner/repo@40-hex with a mandatory trailing comment, treats an unreadable tree as exit 2 rather than as a pass, and its header names what it does not cover: the actions that pinned actions themselves invoke, the pip and cargo installs that are version pinned at best, and anything fetched at run time. The sharper hole was not the tags. The OpenWrt packaging workflow fetched a helper binary straight from a release URL with no verification, in two jobs that hold a signing key, which is code execution from a third-party host into a credentialed job and needs nobody to retag anything. That download now goes through a shared script with per-architecture pinned SHA-256 constants, modelled on the zig block already in that workflow. Upstream publishes no checksum document, so the provenance comment records the asset URL and the date the hashes were taken by downloading rather than pretending they were verified against a published sum.
362 lines
12 KiB
YAML
362 lines
12 KiB
YAML
name: macOS Package
|
|
on:
|
|
push:
|
|
branches:
|
|
- master
|
|
- maint
|
|
- next
|
|
tags:
|
|
- "v*"
|
|
pull_request:
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
CARGO_TERM_COLOR: always
|
|
|
|
jobs:
|
|
determine-versioning:
|
|
runs-on: macos-latest
|
|
outputs:
|
|
macos_package_version: ${{ steps.macos_version.outputs.macos_package_version }}
|
|
steps:
|
|
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Derive macOS package version
|
|
id: macos_version
|
|
shell: bash
|
|
run: |
|
|
: ${GITHUB_OUTPUT:=/tmp/github_output}
|
|
|
|
BASE_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
|
|
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
|
|
VERSION="${GITHUB_REF_NAME#v}"
|
|
else
|
|
BRANCH=$(echo "$GITHUB_REF_NAME" | sed 's|[^A-Za-z0-9]|.|g; s/\.\.+/./g; s/^\.//; s/\.$//')
|
|
HEIGHT=$(git rev-list --count HEAD)
|
|
HASH=$(git rev-parse --short HEAD)
|
|
if [[ -z "$BRANCH" ]]; then
|
|
BRANCH="ref"
|
|
fi
|
|
VERSION="${BASE_VERSION}+${BRANCH}.${HEIGHT}.${HASH}"
|
|
fi
|
|
|
|
echo "macos_package_version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
|
|
build:
|
|
name: Build macOS package (${{ matrix.arch }})
|
|
runs-on: ${{ matrix.os }}
|
|
needs: determine-versioning
|
|
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: macos-latest
|
|
arch: arm64
|
|
target: aarch64-apple-darwin
|
|
- os: macos-latest
|
|
arch: x86_64
|
|
target: x86_64-apple-darwin
|
|
|
|
steps:
|
|
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Set SOURCE_DATE_EPOCH from git
|
|
run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV"
|
|
|
|
- name: Install Rust toolchain
|
|
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
|
|
with:
|
|
target: ${{ matrix.target }}
|
|
cache: false
|
|
rustflags: ''
|
|
|
|
- name: Cache Cargo registry + build
|
|
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
target
|
|
key: macos-release-${{ matrix.arch }}-${{ hashFiles('**/Cargo.lock') }}
|
|
restore-keys: |
|
|
macos-release-${{ matrix.arch }}-
|
|
|
|
- name: Build release binaries
|
|
run: cargo build --release --target ${{ matrix.target }}
|
|
|
|
- name: Build macOS package
|
|
run: |
|
|
packaging/macos/build-pkg.sh \
|
|
--version "${{ needs.determine-versioning.outputs.macos_package_version }}" \
|
|
--target ${{ matrix.target }} \
|
|
--no-build
|
|
|
|
- name: Resolve macOS asset path
|
|
id: macos-assets
|
|
shell: bash
|
|
run: |
|
|
: ${GITHUB_OUTPUT:=/tmp/github_output}
|
|
set -euo pipefail
|
|
|
|
# build-pkg.sh names the package from the build target, so each
|
|
# matrix leg produces a distinctly named, arch-correct asset.
|
|
# Assert that here: a regression in that naming then fails loudly
|
|
# at the build stage instead of as a silent collision when the
|
|
# release job merges both artifacts into one directory.
|
|
EXPECTED="deploy/fips-${{ needs.determine-versioning.outputs.macos_package_version }}-macos-${{ matrix.arch }}.pkg"
|
|
if [[ ! -f "$EXPECTED" ]]; then
|
|
echo "Expected package $EXPECTED was not produced" >&2
|
|
echo "deploy/ contains:" >&2
|
|
ls -la deploy >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
echo "pkg=$EXPECTED" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Verify .pkg structural correctness
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
PKG="${{ steps.macos-assets.outputs.pkg }}"
|
|
EXPAND_DIR="$(mktemp -d)/expanded"
|
|
PAYLOAD_DIR="$(mktemp -d)/payload"
|
|
fail=0
|
|
|
|
echo "==> Verifying $PKG"
|
|
|
|
# 1) Flat-package expansion
|
|
if pkgutil --expand "$PKG" "$EXPAND_DIR"; then
|
|
echo "PASS: pkgutil --expand"
|
|
else
|
|
echo "FAIL: pkgutil --expand"
|
|
fail=1
|
|
fi
|
|
|
|
# Extract the cpio.gz Payload so we can inspect installed file layout
|
|
PAYLOAD_FILE="$(find "$EXPAND_DIR" -name Payload -type f | head -n 1)"
|
|
if [[ -z "$PAYLOAD_FILE" ]]; then
|
|
echo "FAIL: no Payload file inside expanded pkg"
|
|
fail=1
|
|
else
|
|
mkdir -p "$PAYLOAD_DIR"
|
|
(cd "$PAYLOAD_DIR" && gzip -dc "$PAYLOAD_FILE" | cpio -i --quiet)
|
|
echo "PASS: extracted Payload to $PAYLOAD_DIR"
|
|
fi
|
|
|
|
# 2) Binary at canonical install path (./usr/local/bin/fips inside payload)
|
|
BIN_PATH="$PAYLOAD_DIR/usr/local/bin/fips"
|
|
if [[ -f "$BIN_PATH" ]]; then
|
|
echo "PASS: binary present at usr/local/bin/fips"
|
|
else
|
|
echo "FAIL: binary missing at usr/local/bin/fips"
|
|
echo " fallback search:"
|
|
find "$PAYLOAD_DIR" -name fips -type f -print || true
|
|
fail=1
|
|
fi
|
|
for extra in fipsctl fipstop; do
|
|
if [[ -f "$PAYLOAD_DIR/usr/local/bin/$extra" ]]; then
|
|
echo "PASS: binary present at usr/local/bin/$extra"
|
|
else
|
|
echo "FAIL: binary missing at usr/local/bin/$extra"
|
|
fail=1
|
|
fi
|
|
done
|
|
|
|
# 3) LaunchDaemon plist at canonical location
|
|
PLIST_PATH="$PAYLOAD_DIR/Library/LaunchDaemons/com.fips.daemon.plist"
|
|
if [[ -f "$PLIST_PATH" ]]; then
|
|
echo "PASS: plist present at Library/LaunchDaemons/com.fips.daemon.plist"
|
|
else
|
|
echo "FAIL: plist missing at Library/LaunchDaemons/com.fips.daemon.plist"
|
|
echo " fallback search:"
|
|
find "$PAYLOAD_DIR" -name '*.plist' -print || true
|
|
fail=1
|
|
fi
|
|
|
|
# 4) plutil -lint on the plist
|
|
if [[ -f "$PLIST_PATH" ]]; then
|
|
if plutil -lint "$PLIST_PATH"; then
|
|
echo "PASS: plutil -lint"
|
|
else
|
|
echo "FAIL: plutil -lint"
|
|
fail=1
|
|
fi
|
|
fi
|
|
|
|
if [[ "$fail" -ne 0 ]]; then
|
|
echo "==> .pkg verification FAILED"
|
|
exit 1
|
|
fi
|
|
echo "==> .pkg verification PASSED"
|
|
|
|
- name: SHA-256 hash and sidecar
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
PKG="${{ steps.macos-assets.outputs.pkg }}"
|
|
echo "==> macOS release asset:"
|
|
# Capture the SHA-256 of the verified .pkg on the macOS runner and
|
|
# write it to a sidecar file next to the .pkg, in the standard
|
|
# `<hash> <basename>` shasum format. The verify-handoff and release
|
|
# jobs re-check the downloaded bytes against this value, so any
|
|
# corruption introduced after this point is detected before
|
|
# publication.
|
|
( cd "$(dirname "$PKG")" && shasum -a 256 "$(basename "$PKG")" | tee "$(basename "$PKG").sha256" )
|
|
|
|
- name: Upload artifact
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: fips_${{ needs.determine-versioning.outputs.macos_package_version }}_${{ matrix.arch }}_macos
|
|
path: |
|
|
${{ steps.macos-assets.outputs.pkg }}
|
|
${{ steps.macos-assets.outputs.pkg }}.sha256
|
|
retention-days: 30
|
|
|
|
- name: Build summary
|
|
run: |
|
|
echo "Build Summary for macOS/${{ matrix.arch }}:"
|
|
echo " Package: ${{ steps.macos-assets.outputs.pkg }}"
|
|
|
|
verify-handoff:
|
|
name: Verify macOS package handoff integrity
|
|
runs-on: ubuntu-latest
|
|
needs: build
|
|
|
|
steps:
|
|
- name: Download macOS artifacts
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
path: dist
|
|
merge-multiple: true
|
|
|
|
- name: Verify .pkg integrity across the handoff
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
cd dist
|
|
|
|
pkgs=$(find . -maxdepth 1 -type f -name '*.pkg' | LC_ALL=C sort)
|
|
if [[ -z "$pkgs" ]]; then
|
|
echo "FAIL: no .pkg artifacts were downloaded" >&2
|
|
exit 1
|
|
fi
|
|
|
|
fail=0
|
|
while IFS= read -r pkg; do
|
|
base=$(basename "$pkg")
|
|
sidecar="${pkg}.sha256"
|
|
if [[ ! -f "$sidecar" ]]; then
|
|
echo "FAIL: missing SHA-256 sidecar for $base" >&2
|
|
fail=1
|
|
continue
|
|
fi
|
|
expected=$(awk '{print $1}' "$sidecar")
|
|
actual=$(sha256sum "$pkg" | awk '{print $1}')
|
|
if [[ "$expected" != "$actual" ]]; then
|
|
echo "FAIL: $base SHA-256 mismatch across the artifact handoff" >&2
|
|
echo " expected (macOS runner): $expected" >&2
|
|
echo " actual (downloaded): $actual" >&2
|
|
fail=1
|
|
continue
|
|
fi
|
|
echo "PASS: $base matches the macOS-runner SHA-256 ($actual)"
|
|
done <<<"$pkgs"
|
|
|
|
if [[ "$fail" -ne 0 ]]; then
|
|
echo "==> macOS package handoff verification FAILED" >&2
|
|
exit 1
|
|
fi
|
|
echo "==> macOS package handoff verification PASSED"
|
|
|
|
release:
|
|
name: Publish macOS assets to GitHub Release
|
|
runs-on: ubuntu-latest
|
|
needs: [build, verify-handoff]
|
|
if: startsWith(github.ref, 'refs/tags/')
|
|
permissions:
|
|
contents: write
|
|
|
|
steps:
|
|
- name: Download macOS artifacts
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
path: dist
|
|
merge-multiple: true
|
|
|
|
- name: Validate .pkg bytes before publishing
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
cd dist
|
|
|
|
pkgs=$(find . -maxdepth 1 -type f -name '*.pkg' | LC_ALL=C sort)
|
|
if [[ -z "$pkgs" ]]; then
|
|
echo "FAIL: no .pkg artifacts were downloaded" >&2
|
|
exit 1
|
|
fi
|
|
|
|
fail=0
|
|
while IFS= read -r pkg; do
|
|
base=$(basename "$pkg")
|
|
sidecar="${pkg}.sha256"
|
|
if [[ ! -f "$sidecar" ]]; then
|
|
echo "FAIL: missing SHA-256 sidecar for $base" >&2
|
|
fail=1
|
|
continue
|
|
fi
|
|
expected=$(awk '{print $1}' "$sidecar")
|
|
actual=$(sha256sum "$pkg" | awk '{print $1}')
|
|
if [[ "$expected" != "$actual" ]]; then
|
|
echo "FAIL: $base SHA-256 mismatch on the bytes about to be published" >&2
|
|
echo " expected (macOS runner): $expected" >&2
|
|
echo " actual (downloaded): $actual" >&2
|
|
fail=1
|
|
continue
|
|
fi
|
|
echo "PASS: $base matches the macOS-runner SHA-256 ($actual)"
|
|
done <<<"$pkgs"
|
|
|
|
if [[ "$fail" -ne 0 ]]; then
|
|
echo "==> pre-publish .pkg verification FAILED; not publishing" >&2
|
|
exit 1
|
|
fi
|
|
echo "==> pre-publish .pkg verification PASSED"
|
|
|
|
- name: Generate macOS release checksums
|
|
run: |
|
|
cd dist
|
|
find . -maxdepth 1 -type f -name '*.pkg' -printf '%P\n' \
|
|
| LC_ALL=C sort \
|
|
| xargs sha256sum \
|
|
> checksums-macos.txt
|
|
|
|
- name: Wait for tag release
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
for attempt in $(seq 1 20); do
|
|
if gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
|
|
exit 0
|
|
fi
|
|
echo "Release ${GITHUB_REF_NAME} not available yet; waiting..."
|
|
sleep 15
|
|
done
|
|
|
|
echo "Timed out waiting for release ${GITHUB_REF_NAME}" >&2
|
|
exit 1
|
|
|
|
- name: Upload macOS assets
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
gh release upload "${GITHUB_REF_NAME}" \
|
|
dist/*.pkg \
|
|
dist/checksums-macos.txt \
|
|
--clobber \
|
|
--repo "${GITHUB_REPOSITORY}"
|