Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 32 additions & 4 deletions .github/workflows/build-podvm-cohere.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ on:
required: false
type: boolean
default: true
deploy_azure:
description: "Publish to Azure Compute Gallery (needs Azure CC infra)."
required: false
type: boolean
default: false

permissions:
actions: read # download smoke measurements artifact in the GCP smoke workflow
Expand All @@ -30,10 +35,11 @@ permissions:
packages: write # push OCI artifact to GHCR

env:
# tdx-nvidia selects the AA OCI tag suffix (<sha>-tdx-nvidia_x86_64) that
# bundles libnvat.so* alongside the binary. The fork's publish-artifacts
# workflow produces this variant; upstream guest-components does not.
TEE_PLATFORM: tdx-nvidia
# tdx-snp-nvidia selects the AA OCI tag suffix
# (<sha>-tdx-snp-nvidia_x86_64).
# The Cohere fork's variant bundles TDX + SNP + Azure SNP-vTPM + NVIDIA GPU
# attesters and includes libnvat.so* alongside the binary.
TEE_PLATFORM: tdx-snp-nvidia
OCI_IMAGE: ghcr.io/${{ github.repository }}/podvm
VERSIONS_YAML: src/cloud-api-adaptor/versions.yaml
SMOKE_MACHINE_TYPE: a3-highgpu-1g
Expand Down Expand Up @@ -455,3 +461,25 @@ jobs:
GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
GCP_SERVICE_ACCOUNT: ${{ secrets.GCP_SERVICE_ACCOUNT }}
DEBUG_SSH_PRIVATE_KEY: ${{ secrets.DEBUG_SSH_PRIVATE_KEY }}

# ---------------------------------------------------------------------------
# Deploy to Azure Compute Gallery — opt-in; needs the Azure CC infra.
# ---------------------------------------------------------------------------
deploy-azure:
name: Deploy ${{ matrix.profile }} to Azure
needs: [meta, build]
if: |
needs.build.result == 'success' &&
github.event_name == 'workflow_dispatch' &&
inputs.deploy_azure
strategy:
fail-fast: false
matrix:
profile: [release, debug]
uses: ./.github/workflows/deploy-azure-cohere.yaml
with:
image_tag: ${{ matrix.profile == 'release' && needs.meta.outputs.image_tag_release || needs.meta.outputs.image_tag_debug }}
secrets:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
254 changes: 254 additions & 0 deletions .github/workflows/deploy-azure-cohere.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
name: Deploy PodVM to Azure (Cohere)

# Publishes a podvm artifact to an Azure Compute Gallery image version flagged
# ConfidentialVmSupported (x86_64/Gen2) for SEV-SNP VMs (e.g. NCCadsH100v5).
# Requires (infra-azure):
# secrets: AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID (OIDC app
# with Contributor + Storage Blob Data Contributor)
# vars: AZURE_RESOURCE_GROUP, AZURE_PODVM_GALLERY_NAME,
# AZURE_PODVM_STORAGE_ACCOUNT, AZURE_PODVM_BLOB_CONTAINER,
# AZURE_LOCATION

on:
workflow_call:
inputs:
image_tag:
description: "OCI artifact tag in GHCR to deploy"
required: true
type: string
secrets:
AZURE_CLIENT_ID:
required: true
AZURE_TENANT_ID:
required: true
AZURE_SUBSCRIPTION_ID:
required: true

workflow_dispatch:
inputs:
image_tag:
description: "OCI artifact tag in GHCR to deploy"
required: true
type: string
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

permissions: {}

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

jobs:
deploy:
name: Azure
runs-on: ubuntu-latest
# Serialize publishes per image tag: gallery versions are immutable and the
# patch is time-based, so overlapping runs could collide on the same version.
concurrency:
group: deploy-azure-cohere-${{ inputs.image_tag }}
cancel-in-progress: false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gallery version collision across tags

Medium Severity

Azure gallery versions use minute-granularity Major.Minor.Patch, but concurrency is keyed only on inputs.image_tag, so two deploy jobs for different tags that share the same image_family can run in parallel and fight over the same immutable version string.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6f4974a. Configure here.

permissions:
id-token: write # Azure login via OIDC federated credentials
contents: read # actions/checkout
packages: read # pull OCI artifact from GHCR
attestations: read # gh attestation verify
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Install dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends qemu-utils
command -v yq || sudo snap install yq
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
sudo mv oras /usr/local/bin/
oras version
qemu-img --version

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

- name: Verify attestation
id: verify
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IMAGE_TAG: ${{ inputs.image_tag }}
run: |
IMAGE_DIGEST=$(oras resolve "${OCI_IMAGE}:${IMAGE_TAG}")
IMAGE_REF="${OCI_IMAGE}@${IMAGE_DIGEST}"
gh attestation verify \
"oci://${IMAGE_REF}" \
--repo "${GITHUB_REPOSITORY}"
echo "image_ref=$IMAGE_REF" >> "$GITHUB_OUTPUT"
echo "Attestation verified for ${IMAGE_REF}"

- name: Pull artifact and read measurements
id: pull
env:
IMAGE_REF: ${{ steps.verify.outputs.image_ref }}
run: |
mkdir -p /tmp/podvm
cd /tmp/podvm
oras pull "${IMAGE_REF}"
ls -lh

if [ ! -f measurements.json ]; then
echo "ERROR: measurements.json not found in artifact - build is broken"
exit 1
fi
cat measurements.json

IMAGE_NAME=$(jq -r '.image_name' measurements.json)
IMAGE_FAMILY=$(jq -r '.image_family // ""' measurements.json)
if [ -z "$IMAGE_NAME" ] || [ "$IMAGE_NAME" = "null" ] || [ -z "$IMAGE_FAMILY" ]; then
echo "ERROR: image_name/image_family missing in measurements.json" >&2
exit 1
fi

# Definition names: [A-Za-z0-9._-], <=80 chars.
IMAGE_DEF=$(echo "$IMAGE_FAMILY" | sed 's/[^A-Za-z0-9._-]/-/g' | cut -c1-80)

# Versions need numeric Major.Minor.Patch, no leading zeros.
MAJOR=$(date -u +%Y)
MINOR=$(date -u +%m%d | sed 's/^0*//'); MINOR=${MINOR:-0}
PATCH=$(date -u +%H%M | sed 's/^0*//'); PATCH=${PATCH:-0}
IMAGE_VERSION="${MAJOR}.${MINOR}.${PATCH}"

{
echo "image_name=$IMAGE_NAME"
echo "image_def=$IMAGE_DEF"
echo "image_version=$IMAGE_VERSION"
echo "tee_platform=$(jq -r '.tee_platform // "tdx"' measurements.json)"
echo "distro=$(jq -r '.distro' measurements.json)"
echo "profile=$(jq -r '.profile' measurements.json)"
echo "caa_commit=$(jq -r '.caa_commit' measurements.json)"
} >> "$GITHUB_OUTPUT"

- name: Extract raw disk and convert to fixed VHD
id: vhd
run: |
cd /tmp/podvm
tar -xzf disk.tar.gz
# Azure needs a fixed-format VHD whose virtual size is a whole number
# of MiB. mkosi's raw image isn't necessarily 1 MiB-aligned, so round
# it up to the next whole MiB first; qemu-img's force_size then keeps
# that exact aligned size in the fixed VHD footer. Without this, the
# gallery image-version create fails with "unsupported virtual size".
MB=$((1024 * 1024))
size=$(qemu-img info -f raw --output json disk.raw | jq '.["virtual-size"]')
rounded=$(( ((size + MB - 1) / MB) * MB ))
if [ "$rounded" -ne "$size" ]; then
echo "Rounding raw image $size -> $rounded bytes ($((rounded / MB)) MiB) for Azure alignment"
qemu-img resize -f raw disk.raw "$rounded"
fi
qemu-img convert -f raw -O vpc -o subformat=fixed,force_size \
disk.raw disk.vhd
ls -lh disk.vhd

- name: Azure login (OIDC)
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: Publish confidential gallery image version
env:
RESOURCE_GROUP: ${{ vars.AZURE_RESOURCE_GROUP }}
GALLERY: ${{ vars.AZURE_PODVM_GALLERY_NAME }}
STORAGE_ACCOUNT: ${{ vars.AZURE_PODVM_STORAGE_ACCOUNT }}
BLOB_CONTAINER: ${{ vars.AZURE_PODVM_BLOB_CONTAINER }}
LOCATION: ${{ vars.AZURE_LOCATION }}
IMAGE_NAME: ${{ steps.pull.outputs.image_name }}
IMAGE_DEF: ${{ steps.pull.outputs.image_def }}
IMAGE_VERSION: ${{ steps.pull.outputs.image_version }}
TEE_PLATFORM: ${{ steps.pull.outputs.tee_platform }}
CAA_COMMIT: ${{ steps.pull.outputs.caa_commit }}
run: |
set -euo pipefail
for v in RESOURCE_GROUP GALLERY STORAGE_ACCOUNT BLOB_CONTAINER LOCATION; do
if [ -z "${!v}" ]; then
echo "ERROR: required Azure var '$v' is empty - configure the infra-azure prerequisites" >&2
exit 1
fi
done

BLOB_NAME="${IMAGE_NAME}.vhd"
BLOB_URI="https://${STORAGE_ACCOUNT}.blob.core.windows.net/${BLOB_CONTAINER}/${BLOB_NAME}"

echo "Uploading staging VHD page blob: $BLOB_URI"
az storage blob upload \
--account-name "$STORAGE_ACCOUNT" \
--container-name "$BLOB_CONTAINER" \
--name "$BLOB_NAME" \
--file /tmp/podvm/disk.vhd \
--type page \
--auth-mode login \
--overwrite true

# Create the ConfidentialVmSupported definition on first use.
if ! az sig image-definition show \
--resource-group "$RESOURCE_GROUP" \
--gallery-name "$GALLERY" \
--gallery-image-definition "$IMAGE_DEF" >/dev/null 2>&1; then
echo "Creating image definition $IMAGE_DEF (ConfidentialVmSupported)"
az sig image-definition create \
--resource-group "$RESOURCE_GROUP" \
--gallery-name "$GALLERY" \
--gallery-image-definition "$IMAGE_DEF" \
--publisher cohere \
--offer podvm \
--sku "$IMAGE_DEF" \
--os-type Linux \
--os-state Generalized \
--hyper-v-generation V2 \
--architecture x64 \
--features SecurityType=ConfidentialVmSupported
fi

if az sig image-version show \
--resource-group "$RESOURCE_GROUP" \
--gallery-name "$GALLERY" \
--gallery-image-definition "$IMAGE_DEF" \
--gallery-image-version "$IMAGE_VERSION" >/dev/null 2>&1; then
echo "ERROR: image version $IMAGE_DEF:$IMAGE_VERSION already exists" >&2
echo "Gallery versions are immutable; re-run to bump the time-based patch." >&2
exit 1
Comment thread
cursor[bot] marked this conversation as resolved.
fi

echo "Creating gallery image version $IMAGE_DEF:$IMAGE_VERSION"
az sig image-version create \
--resource-group "$RESOURCE_GROUP" \
--gallery-name "$GALLERY" \
--gallery-image-definition "$IMAGE_DEF" \
--gallery-image-version "$IMAGE_VERSION" \
--location "$LOCATION" \
--os-vhd-storage-account "$STORAGE_ACCOUNT" \
--os-vhd-uri "$BLOB_URI" \
--tags "tee=${TEE_PLATFORM}" "image_name=${IMAGE_NAME}" "caa_commit=${CAA_COMMIT}"

echo "Cleaning up staging VHD blob"
az storage blob delete \
--account-name "$STORAGE_ACCOUNT" \
--container-name "$BLOB_CONTAINER" \
--name "$BLOB_NAME" \
--auth-mode login || true

{
echo "### PodVM Azure gallery image published"
echo ""
echo "| Field | Value |"
echo "| --- | --- |"
echo "| Gallery | \`${GALLERY}\` |"
echo "| Definition | \`${IMAGE_DEF}\` |"
echo "| Version | \`${IMAGE_VERSION}\` |"
echo "| Security type | ConfidentialVmSupported |"
echo "| TEE | ${TEE_PLATFORM} |"
} >> "$GITHUB_STEP_SUMMARY"
31 changes: 28 additions & 3 deletions .github/workflows/deploy-gcp-cohere.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_call:
inputs:
image_tag:
description: "OCI artifact tag in GHCR (e.g. podvm-ubuntu-tdx-nvidia-release-v1-0-0)"
description: "OCI artifact tag in GHCR (e.g. podvm-ubuntu-tdx-snp-nvidia-release-v1-0-0)"
required: true
type: string
secrets:
Expand All @@ -27,7 +27,6 @@ permissions:
attestations: read # gh attestation verify

env:
TEE_PLATFORM: tdx
OCI_IMAGE: ghcr.io/${{ github.repository }}/podvm
PODVM_GCP_IMAGE_PROJECT: cohere-artifacts
VERSIONS_YAML: src/cloud-api-adaptor/versions.yaml
Expand Down Expand Up @@ -118,6 +117,7 @@ jobs:
echo "caa_commit_short=$(jq -r '.caa_commit[:7]' measurements.json)"
echo "caa_version=$(jq -r '.caa_version' measurements.json)"
echo "caa_ref=$(jq -r '.caa_ref' measurements.json)"
echo "tee_platform=$(jq -r '.tee_platform' measurements.json)"
echo "rtmr2=$(jq -r '.rtmr2_sha384 // ""' measurements.json)"
echo "kernel=$(jq -r '.kernel // ""' measurements.json)"
echo "build_date=$(jq -r '.build_date // ""' measurements.json)"
Expand Down Expand Up @@ -156,6 +156,7 @@ jobs:
CAA_COMMIT_SHORT: ${{ steps.pull.outputs.caa_commit_short }}
CAA_REF: ${{ steps.pull.outputs.caa_ref }}
PROFILE: ${{ steps.pull.outputs.profile }}
TEE_PLATFORM: ${{ steps.pull.outputs.tee_platform }}
RTMR2: ${{ steps.pull.outputs.rtmr2 }}
KERNEL: ${{ steps.pull.outputs.kernel }}
BUILD_DATE: ${{ steps.pull.outputs.build_date }}
Expand All @@ -174,6 +175,30 @@ jobs:
exit 1
fi

if [ -z "$TEE_PLATFORM" ] || [ "$TEE_PLATFORM" = "null" ]; then
echo "ERROR: tee_platform is missing from measurements.json" >&2
exit 1
fi

case "$TEE_PLATFORM" in
tdx-snp-nvidia)
GUEST_OS_FEATURES="TDX_CAPABLE,SEV_CAPABLE,SEV_SNP_CAPABLE,UEFI_COMPATIBLE,GVNIC"
;;
tdx)
GUEST_OS_FEATURES="TDX_CAPABLE,UEFI_COMPATIBLE,GVNIC"
;;
snp|amd)
GUEST_OS_FEATURES="SEV_SNP_CAPABLE,UEFI_COMPATIBLE,GVNIC"
;;
sev)
GUEST_OS_FEATURES="SEV_CAPABLE,UEFI_COMPATIBLE,GVNIC"
;;
*)
echo "ERROR: unsupported tee_platform '$TEE_PLATFORM'" >&2
exit 1
;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing legacy tee_platform mapping

Low Severity

GCP guest OS feature selection rejects any tee_platform not listed in the new case statement. Artifacts built before this change recorded tdx-nvidia in measurements.json, so manual redeploy of those GHCR tags via deploy-gcp-cohere.yaml now fails even though the prior workflow always published them as TDX-capable.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit edc4a45. Configure here.

esac

RAW_IMAGE="${IMAGE_NAME}-import"
if gcloud compute images describe "$RAW_IMAGE" --project="$GCP_PROJECT" &>/dev/null; then
echo "Deleting stale intermediate image $RAW_IMAGE"
Expand Down Expand Up @@ -204,7 +229,7 @@ jobs:
--family="$IMAGE_FAMILY" \
--description="podvm: distro=${DISTRO} tee=${TEE_PLATFORM} profile=${PROFILE} class=${IMAGE_CLASS} caa=${CAA_VERSION} commit=${CAA_COMMIT} rtmr2=${RTMR2} oci=${OCI_IMAGE}:${IMAGE_TAG}" \
--labels="$LABELS" \
--guest-os-features=TDX_CAPABLE,UEFI_COMPATIBLE,GVNIC
--guest-os-features="$GUEST_OS_FEATURES"

gcloud compute images delete "$RAW_IMAGE" \
--project="$GCP_PROJECT" --quiet
Expand Down
6 changes: 3 additions & 3 deletions src/cloud-api-adaptor/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
ARG BUILD_TYPE=dev
# golang:1.25.10 (based on Debian 13 trixie)
ARG BUILDER_BASE=golang@sha256:3965b9511a9bed199f0b4eb146f99c31971a5c0adb3240d9f25e233cbd9e01c8
# debian:trixie-slim (matches golang:1.25.10 base for library compatibility)
# golang:1.25.11 (based on Debian 13 trixie)
ARG BUILDER_BASE=golang@sha256:f188e8c16ea47a8b22d2bdcf6d9bcd07b63ea7876c199749c07bf31e0ab33bad
# debian:trixie-slim (matches golang:1.25.11 base for library compatibility)
# Multi-arch manifest list digest supporting amd64, arm64, ppc64le,, s390x
ARG BASE=debian@sha256:109e2c65005bf160609e4ba6acf7783752f8502ad218e298253428690b9eaa4b

Expand Down
Loading
Loading