The product is Ch∆In; the old 'F∆I Platform' name survived in module docs/manifests. Generic 'platform' terms left untouched. Signed-off-by: flemming-it <sf@flemming.it>
223 lines
9 KiB
YAML
223 lines
9 KiB
YAML
# F∆I module bundle signing workflow (cosign key-pinning model).
|
|
#
|
|
# Drop-in template for any module repo under fai-modules/.
|
|
# Triggers on tag push (vX.Y.Z) — builds the module, packs the
|
|
# .fai bundle, signs the bundle bytes with the Flemming.AI
|
|
# signing key (ECDSA P-256), and attaches bundle + .sig sidecar
|
|
# to the matching Forgejo Release.
|
|
#
|
|
# The hub-side verifier ships in 0.12.0+: when an operator sets
|
|
# `require_signatures: true`, install downloads the .sig sidecar
|
|
# from `<wasm_url>.sig` and verifies it against the pinned store
|
|
# public key (built-in for `bundled` / `official`, per-store
|
|
# config for everything else).
|
|
#
|
|
# Adopt by copying this file to `.forgejo/workflows/sign.yml` in
|
|
# the module repo and replacing the MODULE_NAME placeholder.
|
|
# Add the signing key to Forgejo secrets as FAI_SIGNING_KEY (PEM,
|
|
# unencrypted, ECDSA P-256). Rotation is a 5-minute key-roll +
|
|
# binary re-release; see infra/cosign/README.md.
|
|
|
|
name: sign-bundle
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*.*.*'
|
|
|
|
env:
|
|
CARGO_TERM_COLOR: always
|
|
RUST_TOOLCHAIN: "1.86"
|
|
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
|
|
|
jobs:
|
|
sign:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write # attach signature to release
|
|
|
|
env:
|
|
# The canonical module name as written in module.yaml.
|
|
# Used in the bundle filename + wasm path.
|
|
MODULE_NAME: http-request
|
|
|
|
steps:
|
|
# See fai/platform CI for background on the manual external
|
|
# checkout: the DinD runner cannot resolve forgejo:3000, so
|
|
# we clone via the external URL with $GITHUB_TOKEN.
|
|
- name: Checkout via external URL
|
|
run: |
|
|
set -eu
|
|
mkdir -p "$GITHUB_WORKSPACE"
|
|
cd "$GITHUB_WORKSPACE"
|
|
git init -q
|
|
git remote add origin \
|
|
"https://x-access-token:${GITHUB_TOKEN}@git.flemming.ai/${GITHUB_REPOSITORY}.git"
|
|
git fetch --depth=1 origin "$GITHUB_SHA"
|
|
git checkout -q FETCH_HEAD
|
|
|
|
# The fai-module-sdk dependency lives in another Forgejo org
|
|
# behind REQUIRE_SIGNIN_VIEW. MODULE_SDK_PAT (org-level
|
|
# secret, read-only on fai/module-sdk) authenticates cargo's
|
|
# git fetch via insteadOf.
|
|
- name: Configure git URL rewrite for SDK fetch
|
|
env:
|
|
SDK_PAT: ${{ secrets.MODULE_SDK_PAT }}
|
|
run: |
|
|
git config --global \
|
|
"url.https://x-access-token:${SDK_PAT}@git.flemming.ai/.insteadOf" \
|
|
"https://git.flemming.ai/"
|
|
|
|
- name: Install system dependencies
|
|
run: |
|
|
apt-get update -qq
|
|
apt-get install -y --no-install-recommends \
|
|
curl \
|
|
ca-certificates \
|
|
build-essential \
|
|
pkg-config \
|
|
libssl-dev \
|
|
git \
|
|
jq \
|
|
openssl
|
|
|
|
- name: Install Rust toolchain
|
|
run: |
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
|
| sh -s -- -y \
|
|
--profile minimal \
|
|
--default-toolchain "$RUST_TOOLCHAIN" \
|
|
--target wasm32-wasip2
|
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
|
|
- name: Build module (wasm32-wasip2)
|
|
run: cargo build --release --target wasm32-wasip2
|
|
|
|
- name: Install fai CLI
|
|
# Pinned to the production channel; signing requires the
|
|
# `fai pack` command. Override FAI_VERSION when bundle
|
|
# format compatibility matters.
|
|
run: |
|
|
curl -fsSL https://get.fai.flemming.ai | sh -s -- --no-bootstrap
|
|
# The installer drops fai at $HOME/.local/bin
|
|
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
export PATH="$HOME/.local/bin:$PATH"
|
|
fai --version
|
|
|
|
- name: Stage bundle inputs
|
|
# The wasm artefact is named after the Cargo package
|
|
# (underscored), which may differ from the module name
|
|
# in module.yaml (dashed). Resolve it from Cargo.toml so
|
|
# the template stays agnostic to the project's naming
|
|
# convention.
|
|
run: |
|
|
set -eu
|
|
CARGO_NAME=$(grep -E '^name *= *"' Cargo.toml \
|
|
| head -1 | sed 's/.*"\(.*\)".*/\1/')
|
|
mkdir -p staging
|
|
cp module.yaml staging/
|
|
cp "target/wasm32-wasip2/release/${CARGO_NAME}.wasm" staging/module.wasm
|
|
if [ -f sbom.cdx.json ]; then cp sbom.cdx.json staging/; fi
|
|
if [ -f MODULE.md ]; then cp MODULE.md staging/; fi
|
|
if [ -f MODULE.de.md ]; then cp MODULE.de.md staging/; fi
|
|
|
|
- name: Pack bundle
|
|
id: pack
|
|
run: |
|
|
export PATH="$HOME/.local/bin:$PATH"
|
|
BUNDLE="${{ env.MODULE_NAME }}-${{ github.ref_name }}.fai"
|
|
fai pack staging --output "$BUNDLE"
|
|
echo "bundle=$BUNDLE" >> "$GITHUB_OUTPUT"
|
|
ls -la "$BUNDLE"
|
|
|
|
- name: Sign bundle (Flemming.AI signing key)
|
|
env:
|
|
FAI_SIGNING_KEY_PEM: ${{ secrets.FAI_SIGNING_KEY }}
|
|
run: |
|
|
if [ -z "${FAI_SIGNING_KEY_PEM:-}" ]; then
|
|
cat >&2 <<'EOF'
|
|
|
|
FAI_SIGNING_KEY secret is empty or undefined.
|
|
|
|
This workflow signs module bundles with the Flemming.AI
|
|
module-signing key (ECDSA P-256, PKCS#8 PEM, unencrypted).
|
|
The matching public key is pinned in the fai-hub binary at
|
|
infra/cosign/official.pub — hubs that require_signatures
|
|
will reject anything not signed with this key.
|
|
|
|
To configure:
|
|
1. Forgejo -> fai-modules org -> Settings -> Secrets
|
|
2. Add secret FAI_SIGNING_KEY (org-level recommended)
|
|
3. Paste the unencrypted PEM (BEGIN PRIVATE KEY ...)
|
|
The encrypted source-of-truth lives on Stefan's Mac at
|
|
~/.fai-secrets/flemming-ai-signing.key (passphrase in
|
|
mSecure under "Ch∆In — Module Signing Key (Private)").
|
|
|
|
See fai/platform infra/cosign/OPERATOR-HANDOFF.md for the
|
|
full setup runbook and rotation process.
|
|
EOF
|
|
exit 1
|
|
fi
|
|
umask 077
|
|
echo "$FAI_SIGNING_KEY_PEM" > /tmp/signing-key.pem
|
|
openssl dgst -sha256 \
|
|
-sign /tmp/signing-key.pem \
|
|
-out "${{ steps.pack.outputs.bundle }}.sig.raw" \
|
|
"${{ steps.pack.outputs.bundle }}"
|
|
base64 < "${{ steps.pack.outputs.bundle }}.sig.raw" \
|
|
> "${{ steps.pack.outputs.bundle }}.sig"
|
|
rm "${{ steps.pack.outputs.bundle }}.sig.raw"
|
|
shred -u /tmp/signing-key.pem || rm -f /tmp/signing-key.pem
|
|
ls -la "${{ steps.pack.outputs.bundle }}.sig"
|
|
|
|
- name: Round-trip verify
|
|
# Sanity-check: the freshly-signed bundle must verify
|
|
# against the well-known public key before we publish it.
|
|
run: |
|
|
curl -fsSL https://git.flemming.ai/fai/platform/raw/branch/main/infra/cosign/official.pub \
|
|
-o /tmp/official.pub
|
|
openssl dgst -sha256 \
|
|
-verify /tmp/official.pub \
|
|
-signature <(base64 -d < "${{ steps.pack.outputs.bundle }}.sig") \
|
|
"${{ steps.pack.outputs.bundle }}"
|
|
|
|
- name: Attach bundle + signature to Forgejo Release
|
|
run: |
|
|
set -eu
|
|
BUNDLE="${{ steps.pack.outputs.bundle }}"
|
|
SIG="$BUNDLE.sig"
|
|
REL_JSON=$(curl -sS \
|
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
|
"https://git.flemming.ai/api/v1/repos/${GITHUB_REPOSITORY}/releases/tags/${GITHUB_REF_NAME}")
|
|
REL_ID=$(echo "$REL_JSON" | jq -r '.id // empty')
|
|
if [ -z "$REL_ID" ] || [ "$REL_ID" = "null" ]; then
|
|
REL_ID=$(curl -sS -X POST \
|
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
"https://git.flemming.ai/api/v1/repos/${GITHUB_REPOSITORY}/releases" \
|
|
-d "{\"tag_name\":\"${GITHUB_REF_NAME}\",\"name\":\"${GITHUB_REF_NAME}\",\"draft\":false,\"prerelease\":false,\"body\":\"Auto-generated by sign-bundle workflow\"}" \
|
|
| jq -r '.id')
|
|
fi
|
|
upload_one() {
|
|
local asset_path="$1" asset
|
|
asset=$(basename "$asset_path")
|
|
EXISTING=$(curl -sS \
|
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
|
"https://git.flemming.ai/api/v1/repos/${GITHUB_REPOSITORY}/releases/${REL_ID}/assets" \
|
|
| jq -r ".[] | select(.name==\"${asset}\") | .id")
|
|
if [ -n "$EXISTING" ]; then
|
|
curl -sS -X DELETE \
|
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
|
"https://git.flemming.ai/api/v1/repos/${GITHUB_REPOSITORY}/releases/${REL_ID}/assets/${EXISTING}" \
|
|
|| true
|
|
fi
|
|
curl -sS -X POST \
|
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
|
-F "attachment=@${asset_path}" \
|
|
"https://git.flemming.ai/api/v1/repos/${GITHUB_REPOSITORY}/releases/${REL_ID}/assets?name=${asset}" \
|
|
> /dev/null
|
|
echo "uploaded $asset"
|
|
}
|
|
upload_one "$BUNDLE"
|
|
upload_one "$SIG"
|
|
echo "release ${GITHUB_REF_NAME} ready: https://git.flemming.ai/${GITHUB_REPOSITORY}/releases/tag/${GITHUB_REF_NAME}"
|