ci(sgx): signed SGX release image build via Secret Manager + WIF - #705
ci(sgx): signed SGX release image build via Secret Manager + WIF#705kimo-ice wants to merge 3 commits into
Conversation
Fetches the SGX enclave signing key (raiko-enclave-key) from GCP Secret Manager at build time via script/publish-image.sh, using Workload Identity Federation. No engineer needs direct access to the key, and it never lands in an image layer (passed as a BuildKit --secret). Auth uses a dedicated least-privilege GSA (raiko-enclave-signer@evmchain) that holds secretAccessor on only raiko-enclave-key. Provider/SA/secret/project are wired via repo Actions variables.
There was a problem hiding this comment.
Pull request overview
Adds a manually-triggered GitHub Actions workflow to build and push the signed SGX release image by authenticating to GCP via Workload Identity Federation and letting script/publish-image.sh fetch the enclave signing key from GCP Secret Manager at build time.
Changes:
- Introduces
.github/workflows/release-sgx-image.ymlwithworkflow_dispatchinputs (tag,edmm) to drive signed SGX image publishing. - Uses
google-github-actions/auth@v2(OIDC/WIF) +setup-gcloud@v2to enable Secret Manager access during the build.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| run: | | ||
| # publish-image.sh prompts for proof type (0=tee/SGX, 1=zk); feed via stdin. | ||
| # args: $1 = EDMM flag, $2 = tag | ||
| echo "0" | ./script/publish-image.sh "${{ inputs.edmm }}" "${{ inputs.tag }}" |
| - uses: google-github-actions/setup-gcloud@v2 | ||
|
|
||
| # (registry login step here — your existing release convention) | ||
|
|
||
| - name: Build, sign & push SGX image |
| build-sign-push: | ||
| runs-on: [taiko-runner] # SGX-capable runner (same as ci-sgx-docker) | ||
| timeout-minutes: 90 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 77fc8d9182
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| run: | | ||
| # publish-image.sh prompts for proof type (0=tee/SGX, 1=zk); feed via stdin. | ||
| # args: $1 = EDMM flag, $2 = tag | ||
| echo "0" | ./script/publish-image.sh "${{ inputs.edmm }}" "${{ inputs.tag }}" |
There was a problem hiding this comment.
Move dispatch inputs out of the shell script
When someone who can manually dispatch this workflow provides tag or edmm containing shell syntax such as $(...), GitHub substitutes the input into this run block before bash parses it, so the command executes in a job that has already impersonated the enclave signer and has the Secret Manager identifiers in its environment. Pass the inputs through env variables and quote those variables, or validate them against a strict Docker-tag/EDMM pattern before invoking the script.
Useful? React with 👍 / 👎.
| run: | | ||
| # publish-image.sh prompts for proof type (0=tee/SGX, 1=zk); feed via stdin. | ||
| # args: $1 = EDMM flag, $2 = tag | ||
| echo "0" | ./script/publish-image.sh "${{ inputs.edmm }}" "${{ inputs.tag }}" |
There was a problem hiding this comment.
When this workflow_dispatch job reaches the end of script/publish-image.sh, the script asks a second interactive question before pushing (read -p "Do you want to push..."), but this pipeline only supplies the proof-type answer. Because the script runs with set -e, that second read hits EOF and exits nonzero before docker push, so the release workflow fails after the build instead of publishing the image; feed the push confirmation too or add a noninteractive publish mode.
Useful? React with 👍 / 👎.
DOCKER_REPOSITORY in publish-image.sh is us-docker.pkg.dev/evmchain/images, so add gcloud auth configure-docker for that AR host (reuses the WIF credential, no registry secret) and feed both interactive prompts (proof type + push confirm) via stdin. Signer GSA granted artifactregistry.writer on the evmchain images repo.
… pins)
Addresses adversarial review findings:
- Critical: stop interpolating ${{ inputs.* }} into run: (shell injection).
Pass via env + validate; constrain edmm to a choice input.
- High: signer WIF re-scoped to THIS workflow file on refs/heads/main via a
dedicated pool (github-raiko-signer), replacing the repo-wide binding on the
shared terraform-pool. Provider locked to repository == taikoxyz/raiko.
- High: gate the job on a protected environment (admin must add reviewers +
restrict to main).
- Low: pin actions to commit SHAs.
What
Adds a
workflow_dispatchrelease workflow that builds & pushes the signed SGX release image, pulling the enclave signing key from GCP Secret Manager at build time — so no engineer ever needs direct access toraiko-enclave-key.Why
Engineers don't (and shouldn't) have access to the GCE/Secret Manager secret
raiko-enclave-keyin theevmchainproject. Butscript/publish-image.shalready knows how to fetch it itself: whenGCP_ENCLAVE_KEY_SECRET/GCP_ENCLAVE_KEY_PROJECTare set it runsgcloud secrets versions access --out-file <tmpfile>, passes it to the build as a BuildKit--secret id=enclave_key(never baked into a layer), puts only the public key SHA256 in a build-arg, and deletes the tmpfile on exit.So the only thing needed is for the CI identity to have
secretAccessoron that one secret.GCP wiring (already provisioned, least-privilege)
[email protected].roles/secretmanager.secretAccessoron onlyraiko-enclave-key(secret-level, not project-wide).roles/artifactregistry.writeron only theimagesrepo (us) — for the image push.roles/iam.workloadIdentityUserscoped to exactly this workflow file onrefs/heads/main—principalSet://…/github-raiko-signer/attribute.job_workflow_ref/taikoxyz/raiko/.github/workflows/release-sgx-image.yml@refs/heads/main. This lives in a dedicated WIF pool (github-raiko-signer) whose provider is locked toassertion.repository == 'taikoxyz/raiko'— deliberately not the sharedterraform-poolthat federates taiko-mono. No JSON keys.The push target is Artifact Registry (
DOCKER_REPOSITORY=us-docker.pkg.dev/evmchain/images, hardcoded inpublish-image.sh), so the workflow authenticates Docker withgcloud auth configure-docker us-docker.pkg.devusing the same WIF credential — no registry secret needed. Both interactive prompts (proof type + push confirm) are fed via stdin, so it runs unattended.Repo config
These are set as repo Actions variables (non-secret):
GCP_WORKLOAD_IDENTITY_PROVIDERprojects/790288402401/…/workloadIdentityPools/github-raiko-signer/providers/githubGCP_ENCLAVE_SIGNER_SA[email protected]GCP_ENCLAVE_KEY_SECRETraiko-enclave-keyGCP_ENCLAVE_KEY_PROJECTevmchainSecurity hardening (from an adversarial review of this PR)
Applied in this branch / on the GCP side:
run:script. They pass throughenv:and are regex-validated;edmmis a constrainedchoice.main, in a dedicated pool isolated fromterraform-pool/taiko-mono (see above). Consequence: dispatch only works from themainbranch after merge — it will not run from this feature branch by design.environment: sgx-release-signing.Needs a repo admin / follow-up (can't do with push access)
sgx-release-signingenvironment: required reviewers, disallow self-review, restrict deployment branch tomain. The environment referenced here is inert until these rules are set.taiko-runneris a persistent self-hosted pool; the MRSIGNER key lands on it as a tmpfile. Ideally sign on a single-use runner not shared with PR/CI workloads (or a non-exportable signing service).publish-image.sh's build+push flow.publish-image.shhardening (upstream tooling, not this PR): install the key-cleanuptrapimmediately aftermktempand coverHUP INT TERM; drop globalset -xfor the key's lifetime.Builds tee/SGX by default (stdin feeds
0); flip to1for a zk variant. Runs on[taiko-runner]to matchci-sgx-docker.yml.