Skip to content

Attest Models

Attest Models #26

Workflow file for this run

name: Attest Models
on:
workflow_dispatch:
inputs:
version:
description: "Release version (e.g. v0.0.1)"
required: true
type: string
reason:
description: "Reason for this release"
required: false
type: string
models:
description: "Comma-separated model names to attest (leave empty for all)"
required: false
type: string
ita_slot:
description: "ITA policy slot override (auto = blue/green rotation)"
required: false
default: "auto"
type: choice
options:
- auto
- slot-a
- slot-b
permissions:
id-token: write
contents: write
attestations: write
packages: read
env:
KATA_VERSION: "3.27.0"
CVM_MEASURE_REPO: "https://github.com/cohere-ai/cvm-measure.git"
# TODO: remove this branch pin once the cvm-measure CLI/UKI extraction branch merges to main.
CVM_MEASURE_REF: "alhassankhedr/cc-167-tdx-measurement-toolkit"
PODVM_IMAGE: "ghcr.io/cohere-ai/cloud-api-adaptor/podvm"
BASELINES_REPO: "cohere-ai/cohere-cc-baselines"
# TODO: vendor this file into the repo once the fortress settings are stable,
# then remove this runtime fetch.
FORTRESS_REPO: "cohere-ai/fortress"
FORTRESS_REF: "0ecb69b460b5ca9741f61a1915e7ea560a1fb1ac"
FORTRESS_GENPOLICY_SETTINGS: "components/kata-policy/configs/genpolicy-settings-base.json"
jobs:
attest:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
# ----------------------------------------------------------------
# Setup: tools and dependencies
# ----------------------------------------------------------------
- name: Install Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install Python dependencies
run: pip install pyyaml
- name: Install cvm-measure
run: |
git clone --depth 1 --branch "${CVM_MEASURE_REF}" "${CVM_MEASURE_REPO}" /tmp/cvm-measure
pip install /tmp/cvm-measure
- name: Install cosign
uses: sigstore/cosign-installer@v3
- name: Install oras
uses: oras-project/setup-oras@v2
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y mtools zstd
- name: Authenticate to GCP
uses: google-github-actions/auth@v3
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- name: Configure Docker for Artifact Registry
run: |
gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
- name: Fetch genpolicy binary
run: |
./scripts/fetch-genpolicy.sh \
--version "$KATA_VERSION" \
--output /usr/local/bin/genpolicy
echo "GENPOLICY_VERSION=${KATA_VERSION}" >> "$GITHUB_ENV"
- name: Get cvm-measure version
run: |
CVM_MEASURE_VERSION=$(pip show cvm-measure 2>/dev/null | grep '^Version:' | cut -d' ' -f2 || echo "unknown")
echo "CVM_MEASURE_VERSION=$CVM_MEASURE_VERSION" >> "$GITHUB_ENV"
# ----------------------------------------------------------------
# Resolve model list
# ----------------------------------------------------------------
- name: Resolve models
id: models
run: |
if [[ -n "${{ inputs.models }}" ]]; then
MODELS="${{ inputs.models }}"
else
MODELS=$(ls -1 models/ | tr '\n' ',' | sed 's/,$//')
fi
echo "list=$MODELS" >> "$GITHUB_OUTPUT"
echo "Attesting models: $MODELS"
# ----------------------------------------------------------------
# Resolve per-model CVM config
# ----------------------------------------------------------------
- name: Resolve per-model CVM config
run: |
python3 scripts/resolve-cvm-config.py \
--models "${{ steps.models.outputs.list }}" \
--models-dir models/ \
--output-dir cvm-artifacts/
# ----------------------------------------------------------------
# Fetch CVM artifacts (per model, deduplicated)
# ----------------------------------------------------------------
- name: Fetch baselines
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
run: |
python3 scripts/fetch-baselines.py \
--models "${{ steps.models.outputs.list }}" \
--cvm-artifacts-dir cvm-artifacts/
- name: Fetch OVMF firmware
run: |
python3 scripts/fetch-firmware.py \
--models "${{ steps.models.outputs.list }}" \
--cvm-artifacts-dir cvm-artifacts/
- name: Log in to GHCR
run: echo "${{ github.token }}" | oras login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Extract UKI from podvm images
run: |
python3 scripts/fetch-uki.py \
--models "${{ steps.models.outputs.list }}" \
--cvm-artifacts-dir cvm-artifacts/
- name: Fetch genpolicy settings from fortress
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
run: |
gh api "/repos/${FORTRESS_REPO}/contents/${FORTRESS_GENPOLICY_SETTINGS}?ref=${FORTRESS_REF}" \
--jq '.content' | base64 -d > cvm-artifacts/genpolicy-settings-base.json
echo "Downloaded genpolicy settings from ${FORTRESS_REPO}@${FORTRESS_REF}"
# ----------------------------------------------------------------
# Per-model loop: genpolicy -> initdata -> podspec -> cvm-measure
# ----------------------------------------------------------------
- name: Generate policies and measurements
run: |
python3 scripts/generate-policies-and-measurements.py \
--models "${{ steps.models.outputs.list }}" \
--models-dir models/ \
--artifacts-dir artifacts/ \
--cvm-artifacts-dir cvm-artifacts/ \
--base-rules rules/rules.rego \
--base-settings cvm-artifacts/genpolicy-settings-base.json
# ----------------------------------------------------------------
# Generate ITA policy (single policy, all models)
# ----------------------------------------------------------------
- name: Generate ITA attestation policy
run: |
python3 scripts/generate-ita-policy.py \
--measurements-dir artifacts/ \
--template attestation-policy/template.rego \
--static-ref-vals attestation-policy/tdx-static-ref-vals.yaml \
--nonce "${{ github.run_id }}" \
--output artifacts/ita-attestation-policy.rego
# ----------------------------------------------------------------
# Upload to ITA
# ----------------------------------------------------------------
- name: Upload policy to ITA
id: ita
env:
GH_TOKEN: ${{ github.token }}
ITA_API_URL: ${{ secrets.ITA_API_URL }}
ITA_ADMIN_API_KEY: ${{ secrets.ITA_ADMIN_API_KEY }}
run: |
# Sets ITA_POLICY_SLOT, ITA_POLICY_ID, ITA_POLICY_NAME
if [[ "${{ inputs.ita_slot }}" != "auto" ]]; then
eval "$(ITA_SLOT_OVERRIDE=${{ inputs.ita_slot }} ./scripts/select-ita-slot.sh)"
else
eval "$(./scripts/select-ita-slot.sh)"
fi
echo "ITA_POLICY_SLOT=$ITA_POLICY_SLOT"
echo "ITA_POLICY_ID=$ITA_POLICY_ID"
echo "ITA_POLICY_NAME=$ITA_POLICY_NAME"
if [[ -n "$ITA_API_URL" && -n "$ITA_ADMIN_API_KEY" ]]; then
./scripts/upload-ita-policy.sh \
--policy-file artifacts/ita-attestation-policy.rego \
--policy-name "$ITA_POLICY_NAME" \
--policy-id "$ITA_POLICY_ID" \
--api-url "$ITA_API_URL" \
--api-key "$ITA_ADMIN_API_KEY"
else
echo "WARNING: ITA credentials not configured, skipping upload"
fi
echo "policy_id=$ITA_POLICY_ID" >> "$GITHUB_OUTPUT"
echo "policy_slot=$ITA_POLICY_SLOT" >> "$GITHUB_OUTPUT"
# ----------------------------------------------------------------
# Build predicates and sign with Sigstore (per model)
# ----------------------------------------------------------------
- name: Fetch previous release log index
id: chain
run: |
PREV_INDEX=0
LATEST=$(gh release view --json tagName -q .tagName 2>/dev/null || echo "")
if [[ -n "$LATEST" ]]; then
echo "Previous release: $LATEST"
# TODO: extract Rekor log index from previous release's attestation bundle
# For now, use 0 (genesis)
fi
echo "previous_log_index=$PREV_INDEX" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
- name: Build predicates
run: |
python3 scripts/build-predicate.py \
--artifacts-dir artifacts/ \
--cvm-artifacts-dir cvm-artifacts/ \
--policy-id "${{ steps.ita.outputs.policy_id }}" \
--version "${{ inputs.version }}" \
--genpolicy-version "${{ env.GENPOLICY_VERSION }}" \
--cvm-measure-version "${{ env.CVM_MEASURE_VERSION }}" \
--previous-log-index "${{ steps.chain.outputs.previous_log_index }}"
- name: Sign attestations with Sigstore (per model)
run: |
IFS=',' read -ra MODEL_ARRAY <<< "${{ steps.models.outputs.list }}"
for MODEL in "${MODEL_ARRAY[@]}"; do
MODEL=$(echo "$MODEL" | xargs)
MEAS_FILE="artifacts/${MODEL}/measurements.json"
PREDICATE_FILE="artifacts/${MODEL}/predicate.json"
if [[ ! -f "$MEAS_FILE" || ! -f "$PREDICATE_FILE" ]]; then
echo "Skipping $MODEL (missing artifacts)"
continue
fi
echo "=========================================="
echo "Signing attestation for: $MODEL"
echo "=========================================="
cosign attest-blob \
--predicate "$PREDICATE_FILE" \
--type "https://cohere.com/attestation-policy-ledger/v1" \
--bundle "artifacts/${MODEL}/attestation.sigstore.json" \
-y \
"$MEAS_FILE"
echo "Bundle written: artifacts/${MODEL}/attestation.sigstore.json"
done
# ----------------------------------------------------------------
# Create GitHub Release
# ----------------------------------------------------------------
- name: Prepare release assets
id: assets
run: |
mkdir -p release-assets
IFS=',' read -ra MODEL_ARRAY <<< "${{ steps.models.outputs.list }}"
RELEASE_BODY="# Model Attestation Release ${{ inputs.version }}\n\n"
if [[ -n "${{ inputs.reason }}" ]]; then
RELEASE_BODY+="**Reason:** ${{ inputs.reason }}\n\n"
fi
RELEASE_BODY+="**ITA Policy ID:** \`${{ steps.ita.outputs.policy_id }}\`\n"
RELEASE_BODY+="**ITA Policy Slot:** \`${{ steps.ita.outputs.policy_slot }}\`\n\n"
RELEASE_BODY+="## Per-Model Artifacts\n\n"
for MODEL in "${MODEL_ARRAY[@]}"; do
MODEL=$(echo "$MODEL" | xargs)
mkdir -p "release-assets/${MODEL}"
for f in measurements.json policy.rego podspec_with_initdata.yaml predicate.json attestation.sigstore.json; do
if [[ -f "artifacts/${MODEL}/${f}" ]]; then
cp "artifacts/${MODEL}/${f}" "release-assets/${MODEL}/${f}"
fi
done
MEAS=$(cat "artifacts/${MODEL}/measurements.json" 2>/dev/null || echo "{}")
RELEASE_BODY+="### ${MODEL}\n\n"
RELEASE_BODY+="\`\`\`json\n${MEAS}\n\`\`\`\n\n"
done
cp artifacts/ita-attestation-policy.rego release-assets/
echo -e "$RELEASE_BODY" > release-body.md
- name: Create release
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ inputs.version }}"
# Collect all asset files
ASSET_FLAGS=""
for f in $(find release-assets -type f); do
ASSET_FLAGS="$ASSET_FLAGS $f"
done
gh release create "$TAG" \
--title "Model Attestation $TAG" \
--notes-file release-body.md \
$ASSET_FLAGS