Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
fa349fa
feat: port podvm build/deploy workflows from fortress
alhassankhedr-cohere Apr 22, 2026
844b0ec
fix: address zizmor workflow security findings
alhassankhedr-cohere Apr 22, 2026
65a2d56
chore: use cvm-measure feature branch until PR #12 merges
alhassankhedr-cohere Apr 22, 2026
464c0f2
fix: group GITHUB_OUTPUT redirects to satisfy shellcheck SC2129
alhassankhedr-cohere Apr 22, 2026
a31d854
test: temporarily add feature branch to push trigger
alhassankhedr-cohere Apr 22, 2026
81ff7f7
fix: pass GITHUB_TOKEN to cvm-measure clone
alhassankhedr-cohere Apr 22, 2026
8949b4b
fix: use CVM_MEASURE_TOKEN to clone private cvm-measure repo
alhassankhedr-cohere Apr 22, 2026
8fed5be
feat: always deploy to GCP, overwrite on branch pushes
alhassankhedr-cohere Apr 22, 2026
26587fc
feat: build both release and debug images in parallel
alhassankhedr-cohere Apr 22, 2026
711031d
fix: add --break-system-packages to pip install for Ubuntu 24.04
alhassankhedr-cohere Apr 22, 2026
d5f53dc
fix: quote variable in git clone URL to satisfy shellcheck SC2086
alhassankhedr-cohere Apr 22, 2026
d4cf7d6
chore: add TODO reminders for pre-merge cleanup
alhassankhedr-cohere Apr 22, 2026
bde0a9f
Merge branch 'cohere' into alhassankhedr/cc-192-cloud-api-adaptor-por…
alhassankhedr-cohere Apr 22, 2026
098eb08
refactor: combine deploy-gcp-release/debug into a single matrix job
alhassankhedr-cohere Apr 22, 2026
5f6b5c5
fix: respect replace_existing_image input on workflow_dispatch
alhassankhedr-cohere Apr 22, 2026
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
334 changes: 334 additions & 0 deletions .github/workflows/build-podvm-cohere.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
name: Build PodVM Image (Cohere)

on:
push:
tags: ["podvm-v*"]
branches: ["cohere"]
workflow_dispatch:
inputs:
image_profile:
description: "Image profile"
required: false
type: choice
options: [release, debug]
default: release
distro:
description: "PodVM distro"
required: false
type: choice
options: [ubuntu, fedora]
default: ubuntu
ssh_public_key:
description: "SSH public key for debug images (optional)"
required: false
type: string
guest_components_repo:
description: "guest-components repo (default: cohere-ai/guest-components)"
required: false
type: string
default: "https://github.com/cohere-ai/guest-components.git"
guest_components_ref:
description: "guest-components ref (default: cohere)"
required: false
type: string
default: "cohere"
custom_gc_binaries:
description: "guest-components binaries to build from source"
required: false
type: string
default: "attestation-agent,api-server-rest"
aa_features:
description: "attestation-agent cargo features"
required: false
type: string
default: "bin,ttrpc,kbs,coco_as,rust-crypto,tdx-attester,nvidia-attester"
deploy_gcp:
description: "Deploy to GCP after build"
required: false
type: boolean
default: true
replace_existing_image:
description: "Replace existing GCP image if it exists"
required: false
type: boolean
default: false

permissions:
id-token: write # OIDC token for build provenance attestation
Comment thread
alhassankhedr-cohere marked this conversation as resolved.
Dismissed
attestations: write # actions/attest-build-provenance
Comment thread
alhassankhedr-cohere marked this conversation as resolved.
Dismissed
contents: read # actions/checkout
packages: write # push OCI artifact to GHCR
Comment thread
alhassankhedr-cohere marked this conversation as resolved.
Dismissed

env:
TEE_PLATFORM: tdx
OCI_IMAGE: ghcr.io/${{ github.repository }}/podvm
VERSIONS_YAML: src/cloud-api-adaptor/versions.yaml

jobs:
build:
name: Build PodVM OS Image
runs-on: ubuntu-latest
outputs:
image_name: ${{ steps.meta.outputs.image_name }}
image_tag: ${{ steps.meta.outputs.image_tag }}
oci_ref: ${{ steps.push.outputs.oci_ref }}
oci_digest: ${{ steps.push.outputs.digest }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
with:
persist-credentials: false

- name: Free up runner disk space
run: |
echo "=== Before cleanup ==="
df -h /
sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc \
/usr/local/share/boost /opt/hostedtoolcache/CodeQL
sudo apt-get purge -y google-cloud-cli azure-cli microsoft-edge-stable \
dotnet-* aspnetcore-* 2>/dev/null || true
sudo apt-get autoremove -y
echo "=== After cleanup ==="
df -h /

- name: Install system dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
alien binutils bubblewrap dnf git make mtools qemu-utils uidmap
sudo apt-get clean
sudo rm -rf /var/lib/apt/lists/*

command -v yq || sudo snap install yq
yq --version

- name: Tune kernel for mkosi
run: |
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true

- name: Install ORAS
run: |
ORAS_VERSION=$(yq '.tools.oras' "$VERSIONS_YAML")
curl -fsSLO "https://github.com/oras-project/oras/releases/download/v${ORAS_VERSION}/oras_${ORAS_VERSION}_linux_amd64.tar.gz"
tar -xzf "oras_${ORAS_VERSION}_linux_amd64.tar.gz" oras
rm -f "oras_${ORAS_VERSION}_linux_amd64.tar.gz"
sudo mv oras /usr/local/bin/
oras version

- name: Install cvm-measure
run: |
git clone --depth 1 --branch alhassankhedr/cc-167-tdx-measurement-toolkit \
Comment thread
alhassankhedr-cohere marked this conversation as resolved.
https://github.com/cohere-ai/cvm-measure.git /tmp/cvm-measure
Comment thread
alhassankhedr-cohere marked this conversation as resolved.
Outdated
pip install /tmp/cvm-measure
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
rm -rf /tmp/cvm-measure
cvm-measure --version

- name: Compute image metadata
id: meta
env:
PROFILE: ${{ inputs.image_profile || 'release' }}
DISTRO: ${{ inputs.distro || 'ubuntu' }}
run: |
if [[ "$GITHUB_REF" == refs/tags/podvm-v* ]]; then
TAG="${GITHUB_REF#refs/tags/podvm-}"
else
TAG="dev-$(date +%Y%m%d)-${GITHUB_SHA::8}"
fi
TAG="${TAG//./-}"
IMAGE_NAME="podvm-${DISTRO}-${TEE_PLATFORM}-${PROFILE}-${TAG}"
OCI_TAG="${TAG}-${DISTRO}-${PROFILE}"
{
echo "image_name=$IMAGE_NAME"
echo "image_tag=$OCI_TAG"
echo "profile=$PROFILE"
echo "distro=$DISTRO"
echo "caa_commit=$GITHUB_SHA"
echo "caa_commit_short=${GITHUB_SHA::7}"
echo "caa_ref=${GITHUB_REF_NAME}"
} >> "$GITHUB_OUTPUT"
echo "Building: $IMAGE_NAME (tag: $TAG)"

- name: Inject SSH key for debug image
if: inputs.image_profile == 'debug' && inputs.ssh_public_key != ''
working-directory: src/cloud-api-adaptor/podvm-mkosi
env:
SSH_PUBLIC_KEY: ${{ inputs.ssh_public_key }}
run: |
mkdir -p resources
printf '%s\n' "$SSH_PUBLIC_KEY" > resources/authorized_keys
chmod 0400 resources/authorized_keys
echo "SSH key injected for debug image"

- name: Build binaries
working-directory: src/cloud-api-adaptor/podvm-mkosi
env:
GH_TOKEN: ${{ github.token }}
PODVM_DISTRO: ${{ steps.meta.outputs.distro }}
AA_FEATURES: ${{ inputs.aa_features || 'bin,ttrpc,kbs,coco_as,rust-crypto,tdx-attester,nvidia-attester' }}
GC_REPO: ${{ inputs.guest_components_repo || 'https://github.com/cohere-ai/guest-components.git' }}
GC_REF: ${{ inputs.guest_components_ref || 'cohere' }}
GC_CUSTOM_BINARIES: ${{ inputs.custom_gc_binaries || 'attestation-agent,api-server-rest' }}
run: |
MAKE_ARGS=(
"VERIFY_PROVENANCE=yes"
"TEE_PLATFORM=$TEE_PLATFORM"
"PODVM_DISTRO=$PODVM_DISTRO"
)
if [ -n "$GC_CUSTOM_BINARIES" ]; then
MAKE_ARGS+=("CUSTOM_GC_BINARIES=$GC_CUSTOM_BINARIES")
MAKE_ARGS+=("GUEST_COMPONENTS_REPO=$GC_REPO")
MAKE_ARGS+=("GUEST_COMPONENTS_REF=$GC_REF")
[ -n "$AA_FEATURES" ] && MAKE_ARGS+=("AA_FEATURES=$AA_FEATURES")
fi
make "${MAKE_ARGS[@]}" binaries
docker system prune -af --volumes 2>/dev/null || true
echo "Disk after binaries build:"
df -h /

- name: Build OS image
working-directory: src/cloud-api-adaptor/podvm-mkosi
env:
PROFILE: ${{ steps.meta.outputs.profile }}
PODVM_DISTRO: ${{ steps.meta.outputs.distro }}
run: |
TARGET="image"
if [ "$PROFILE" = "debug" ]; then
TARGET="image-debug"
fi
TEE_PLATFORM="$TEE_PLATFORM" \
PODVM_DISTRO="$PODVM_DISTRO" \
make "$TARGET"

ls -lh build/

- name: Free disk space
run: |
echo "=== Before cleanup ==="
df -h /
mv src/cloud-api-adaptor/podvm-mkosi/build/system.raw /tmp/disk.raw
docker system prune -af --volumes 2>/dev/null || true
echo "=== After cleanup ==="
df -h /

- name: Extract UKI and predict RTMR2
id: measure
env:
IMAGE_NAME: ${{ steps.meta.outputs.image_name }}
CAA_COMMIT: ${{ steps.meta.outputs.caa_commit }}
CAA_REF: ${{ steps.meta.outputs.caa_ref }}
DISTRO: ${{ steps.meta.outputs.distro }}
PROFILE: ${{ steps.meta.outputs.profile }}
run: |
cvm-measure extract-uki --disk /tmp/disk.raw --output /tmp/BOOTX64.EFI

RTMR2=$(python3 -c "
import hashlib
from cvm_measure.tdx.pe import pe_extract_section
from cvm_measure.tdx.rtmr import extend_rtmr, SHA384_SIZE
uki = open('/tmp/BOOTX64.EFI', 'rb').read()
sections = ['linux', 'osrel', 'cmdline', 'initrd', 'ucode', 'uname', 'sbat', 'pcrpkey']
rtmr2 = bytes(SHA384_SIZE)
for name in sections:
data = pe_extract_section(uki, f'.{name}', use_virtual_size=True)
if data is None:
continue
name_hash = hashlib.sha384(f'.{name}\0'.encode('ascii')).digest()
rtmr2 = extend_rtmr(rtmr2, name_hash)
content_hash = hashlib.sha384(data).digest()
rtmr2 = extend_rtmr(rtmr2, content_hash)
print(rtmr2.hex())
")

KERNEL=$(python3 -c "
from cvm_measure.tdx.pe import pe_extract_section
data = pe_extract_section(open('/tmp/BOOTX64.EFI','rb').read(), '.uname', use_virtual_size=True)
print(data.decode(errors='replace').strip('\x00') if data else 'unknown')
")

jq -n \
--arg image_name "$IMAGE_NAME" \
--arg rtmr2_sha384 "$RTMR2" \
--arg kernel "$KERNEL" \
--arg caa_commit "$CAA_COMMIT" \
--arg caa_ref "$CAA_REF" \
--arg caa_version "$CAA_REF" \
--arg distro "$DISTRO" \
--arg profile "$PROFILE" \
--arg tee_platform "$TEE_PLATFORM" \
--arg build_date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'$ARGS.named' > /tmp/measurements.json

cat /tmp/measurements.json

- name: Prepare artifacts
run: |
tar -czf /tmp/disk.tar.gz -C /tmp disk.raw
rm /tmp/disk.raw
echo "Compressed size: $(du -sh /tmp/disk.tar.gz | cut -f1)"

- name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Push artifact to GHCR
id: push
env:
OCI_TAG: ${{ steps.meta.outputs.image_tag }}
IMAGE_TITLE: ${{ steps.meta.outputs.image_name }}
DISTRO: ${{ steps.meta.outputs.distro }}
PROFILE: ${{ steps.meta.outputs.profile }}
CAA_COMMIT: ${{ steps.meta.outputs.caa_commit }}
run: |
OCI_REF="${OCI_IMAGE}:${OCI_TAG}"
RTMR2=$(jq -r '.rtmr2_sha384' /tmp/measurements.json)

cd /tmp
oras push "$OCI_REF" \
disk.tar.gz:application/vnd.cohere.podvm.disk.tar+gzip \
measurements.json:application/vnd.cohere.podvm.measurements+json \
--annotation "org.opencontainers.image.title=${IMAGE_TITLE}" \
--annotation "org.opencontainers.image.description=PodVM OS image (${DISTRO}/${TEE_PLATFORM}/${PROFILE})" \
--annotation "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}" \
--annotation "org.opencontainers.image.revision=${GITHUB_SHA}" \
--annotation "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--annotation "com.cohere.caa.commit=${CAA_COMMIT}" \
--annotation "com.cohere.caa.version=${GITHUB_REF_NAME}" \
--annotation "com.cohere.rtmr2=${RTMR2}" \
--format json > oras-output.json

cat oras-output.json
DIGEST=$(jq -r '.digest' oras-output.json)

{
echo "digest=$DIGEST"
echo "oci_ref=${OCI_REF}@${DIGEST}"
echo "oci_tag=$OCI_TAG"
} >> "$GITHUB_OUTPUT"
echo "Pushed: $OCI_REF @ $DIGEST"

- name: Attest build provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4
with:
subject-name: ${{ env.OCI_IMAGE }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

# ---------------------------------------------------------------------------
# Deploy to GCP (reusable workflow) — runs on tag pushes or when requested
# ---------------------------------------------------------------------------
deploy-gcp:
name: Create image
needs: build
if: |
always() && needs.build.result == 'success' &&
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/podvm-v') ||
github.event_name == 'workflow_dispatch' && inputs.deploy_gcp)
uses: ./.github/workflows/deploy-gcp-cohere.yaml
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
with:
image_tag: ${{ needs.build.outputs.image_tag }}
replace_existing_image: ${{ inputs.replace_existing_image || false }}
secrets:
GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
GCP_SERVICE_ACCOUNT: ${{ secrets.GCP_SERVICE_ACCOUNT }}
Loading
Loading