TDX confidential VM images for Seismic nodes.
Fork of flashbots/flashbots-images, extended with a modules/seismic/ module that bundles the Seismic node stack into a reproducible mkosi-built image deployable to Azure and GCP Confidential VMs.
Requires Lima on macOS/Linux. For alternatives (native Nix, etc.), see DEVELOPMENT.md.
make build IMAGE=seismicProduces in build/:
| File | Purpose |
|---|---|
seismic_{VERSION}.efi |
Unified Kernel Image — the boot artifact, shared across clouds |
seismic_{VERSION}.vhd |
Azure Confidential VM disk image |
seismic_{VERSION}.tar.gz |
GCP Confidential VM custom-image tarball |
seismic_{VERSION}.manifest |
JSON SBOM of every Debian package in the image |
seismic_{VERSION}.{initrd,vmlinuz} |
Debug extracts of the UKI's contents |
{VERSION} is {commit-date}.{commit-hash}[-dirty] — see mkosi.version.
Generate expected measurement values (RTMRs, MRTD) for the built UKI so a verifier can attest that a running node booted from exactly this image:
make measure # Azure-style measurements -> build/measurements.json
make measure-gcp # GCP-style measurements -> build/gcp_measurements.jsonThe two formats differ because Azure and GCP expose TDX quotes through different mechanisms — Azure via tpm2-tools + the Microsoft attestation service, GCP via the dstack toolchain. The deploy tooling (and the Seismic enclave's attestation path) consume these files to verify that deployed nodes match a known-good image.
| Component | Purpose | Source |
|---|---|---|
tdx-init |
First-boot LUKS provisioning; writes /persistent/conf/node.json |
SeismicSystems/tdx-init |
seismic-enclave-server |
Shielded-tx decryption + key derivation; runs in-TEE | SeismicSystems/enclave |
seismic-reth |
Execution client | SeismicSystems/seismic-reth |
summit |
Consensus client | SeismicSystems/summit |
nginx + certbot |
HTTPS termination with Let's Encrypt for public RPC/WS/metrics | Debian |
Source-built pins are in modules/seismic/mkosi.build.
nginx terminates TLS (Let's Encrypt) and reverse-proxies the following paths to in-TEE services. See modules/seismic/mkosi.extra/etc/nginx/node-template.conf.
| Route | Backend | Purpose | Public? |
|---|---|---|---|
/rpc |
reth :8545 |
Ethereum JSON-RPC (shielded tx support via TxSeismic) | ✅ intended public |
/ws |
reth :8546 |
Ethereum WebSocket RPC | ✅ intended public |
/summit |
summit :3030 |
Consensus REST API, incl. /summit/get_deposit_signature/... used by the staking UI |
|
/enclave |
seismic-enclave-server :7878 |
Enclave API (attestation quotes, public key queries, shielded-tx decryption, key derivation, signing) | |
/metrics/reth |
reth :9001 |
Prometheus metrics | |
/metrics/summit |
summit :9002 |
Prometheus metrics |
Everything above listens on the single public :443. The intended long-term fix is to split nginx into two tiers — a public server block with only the endpoints that should reach the open internet (/rpc, /ws, a narrowed /summit/get_deposit_signature, attestation-quote queries on /enclave), and an internal server block on a separate port with the rest (/metrics/*, the full /summit/* query surface, operator-facing /enclave/*). The deploy tooling would then configure cloud firewall rules (Azure NSG / GCP firewall) to allow the public port from 0.0.0.0/0 and restrict the internal port to the VPC CIDR. Until that split lands, the concrete issues are:
/summit/*is a blanket proxy. Summit exposes a JSON-RPC surface (viajsonrpsee) with ~20 methods: mostly read-only state queries (getCheckpoint,getValidatorBalance,getDeposit, etc. — analogous toeth_*reads and safe to expose), plusgetDepositSignaturewhich causes a BLS signature in the enclave, plussendGenesison the genesis-setup API which must never be public at runtime. Narrowing requires JSON-RPC-method-level filtering (all calls arePOST /, so you can't gate by URL path alone).getDepositSignatureadditionally has no rate limiting — trivially DoS-able — and should gainlimit_reqregardless of network controls./enclave/*is a blanket proxy. Only endpoints designed to be public should be reachable (attestation quotes, public-key queries). Operation-causing endpoints (decrypt calldata, sign consensus messages, key derivation) are meant for reth/summit over localhost only. Current config doesn't enforce this; we rely on the enclave's own auth (if any) to reject external callers. Pending the public/internal port split, or narrowing via an explicit allowlist./metrics/*is unauthenticated. Operationally safe on a locked-down network (the cloud firewall rule above is the right fix), but anyone who can reach the port can scrape sync status, peer info, and resource usage. Do not expose the internal port to the open internet without adding basic auth or an IP allowlist on top.
You can track diff with upstream by comparing the seismic branch with origin/main (which is pinned to the upstream commit we most recently rebased on).
- DEVELOPMENT.md — generic mkosi module/kernel-config/reproducibility guidance (from upstream)
modules/seismic/mkosi.conf— Debian packages in the imagemodules/seismic/mkosi.build— pinned commits forreth/enclave/summit/tdx-initmodules/seismic/mkosi.postinst— systemd services enabled on bootmodules/seismic/mkosi.extra/— per-service unit files and configs- Deploy tooling lives in a separate repo.
Boot a built image under QEMU for smoke testing (without TDX). TDX-enabled invocation and troubleshooting are in the upstream README.
Uses the Lima VM you already run for builds. Nested virt isn't available, so boot goes through software emulation (TCG) — slow (~10 min to reach systemd) but works without any extra setup on the host.
Enter the Lima VM:
limactl shell <your-lima-vm> # e.g. tee-builder-<hash>
cd ~/mnt # where the repo is mountedEverything below runs inside that shell.
One-time setup:
sudo apt-get install -y qemu-system-x86 ovmf
cp /usr/share/OVMF/OVMF_VARS.fd /tmp/OVMF_VARS.fd # writable NVRAMBoot:
qemu-system-x86_64 \
-accel tcg -machine type=q35,smm=on -m 1024M -nographic \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE.secboot.fd \
-drive if=pflash,format=raw,file=/tmp/OVMF_VARS.fd \
-kernel build/latest.efi \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:8080 \
-device virtio-net-pci,netdev=net0Exit QEMU with Ctrl-a x.
TODO: not validated in this repo. The upstream-inherited command below assumes bare-metal Linux with KVM access and should boot in seconds, but hasn't been tested in our fork — please update this section if you run it successfully.
qemu-system-x86_64 \
-enable-kvm -machine type=q35,smm=on -m 16384M -nographic \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE.secboot.fd \
-drive if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS.fd \
-kernel build/latest.efi \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:8080 \
-device virtio-net-pci,netdev=net0Built on flashbots/flashbots-images. Thanks to the Flashbots team for the mkosi-based TDX image tooling that this repo forks.