Skip to content

nuconstruct-ltd/automata-linux

 
 

Repository files navigation

    

License GitHub Release

A command-line tool for deploying and managing Confidential Virtual Machines (CVMs) across AWS, GCP, and Azure.

📑 Table of Contents

Getting Started

Clone the repository and use ./toolkit directly:

git clone --recurse-submodules https://github.com/nuconstruct-ltd/automata-linux
cd automata-linux
./toolkit --help

The ./toolkit script automatically installs missing dependencies (curl, jq, unzip, openssl) on first run.

Prerequisites

  • Ensure that you have enough permissions on your account on either GCP, AWS or Azure to create virtual machines, disks, networks, firewall rules, buckets/storage accounts and service roles.

Downloading and Verifying Disk Images

The deployment scripts automatically download pre-built disk images from GitHub Releases. By default, the latest release is used. To use a specific release, set RELEASE_TAG (e.g., export RELEASE_TAG=v1.0.0).

All disk images include SLSA Build Level 2 provenance attestations. To verify:

./toolkit get-disk aws
./toolkit download-build-provenance
./toolkit verify-build-provenance aws_disk.vmdk

For complete verification instructions, see docs/ATTESTATION_VERIFICATION.md.

Quickstart

1. Deploying the CVM

To quickly deploy the CVM with the example workload:

# Deploy to GCP
./toolkit deploy-gcp workload-example

# Deploy to AWS
./toolkit deploy-aws workload-example

# Deploy to Azure
./toolkit deploy-azure workload-example

Note

The script will automatically download the latest disk image from GitHub Releases.
If you want to use a specific release version, set the RELEASE_TAG environment variable (see Prerequisites).
If another developer has given you a custom disk, you can use it instead by:

  • Placing the custom disk file in the root of this folder.
  • Making sure the file is named exactly as follows, depending on which cloud provider you plan to deploy on:
    • GCP: gcp_disk.tar.gz
    • AWS: aws_disk.vmdk
    • Azure: azure_disk.vhd

2. Get logs from the CVM

At the end of the previous step, you should have the following output:

✅ Golden measurements saved to _artifacts/golden-measurements/gcp-cvm-test.json
✨ Deployment complete! Your VM Name: cvm-test

Using the provided VM name, you can retrieve logs from the VM like this:

# ./toolkit get-logs <cloud-provider> <vm-name> [service-names...]
# <cloud-provider> = "aws" or "gcp" or "azure"
./toolkit get-logs gcp cvm-test

# Get logs for specific services only
./toolkit get-logs gcp cvm-test prometheus node-exporter

3. Destroy the VM

Finally, when you're ready to delete the VM and remove all the components that are deployed with it, you can run the following command:

# ./toolkit cleanup <cloud-provider> <vm-name>
# <cloud-provider> = "aws" or "gcp" or "azure"
./toolkit cleanup gcp cvm-test

Deploying the CVM with your Workload

Workload Directory Structure

This repository includes two example workload directories:

  • workload-example/ - A minimal monitoring stack with Prometheus, node-exporter, and an nginx metrics proxy. Use this as a starting point for simple deployments.
  • workload-tool-0/ - A full tool-node setup with Ethereum execution and consensus clients, network isolation controller, and supporting services.

Each workload directory contains:

  • docker-compose.yml - Standard docker compose file defining your workload. Most docker compose files work fine. Caveats:
    • Container images hosted on Docker's official registry must be prefixed with docker.io/.
    • Podman does not support depends_on.condition = service_completed_successfully.
  • config/ - Files mounted by containers that will be measured by the cvm-agent into the TPM PCR before containers run.
  • secrets/ - Files mounted by containers that should NOT be measured (e.g., private keys, database credentials, .env file).

Caution

Never commit secrets/* to git. This directory contains sensitive credentials, private keys, and environment variables. The workload-example/ directory includes sample secrets only to provide a working example — in production, always add secrets/ to your .gitignore.

Caution

Remember to build your container images for X86_64, especially if you're using an ARM64 machine!

Note

If you wish to load container images that are not published to any container registry, simply put the .tar files for the container images into the workload directory itself. This will be automatically detected and loaded at runtime.

1. Configure your workload

Copy the example environment file and set your values:

cp workload-example/secrets/.env.example workload-example/secrets/.env

2. Edit the Security Policy

The CVM agent runs inside the CVM and is responsible for VM management, workload measurement, and related tasks. The tasks that it is allowed to perform depends on a security policy, which can be configured by the user.

By default, the CVM will use the default security policy found in your workload's config/cvm_agent/cvm_agent_policy.json. There are 3 settings that you must configure:

  • firewall.allowed_ports: By default, all incoming traffic on all ports are blocked by nftables, except for CVM agent ports 7999 and 8000. If your workload requires incoming traffic on other ports (eg. you need a p2p port on 30000), please follow the given example and add the ports you require.
  • workload_config.services.allow_update: This list specifies which services in your docker-compose.yml are allowed to be updated remotely via the cvm-agent API /update-workload. You must list the names of your services in your docker-compose.yml if you wish to allow remote updates. Otherwise, set it to an empty list [] to disallow remote updates.
  • workload_config.services.skip_measurement: This list specifies which services the CVM agent will avoid measuring. This includes skipping its image signature checking, if it is enabled. Set it to an empty list [] to measure all services.

The other settings not mentioned can be left as its default values. If you wish to modify the other settings, a detailed description of each policy option can be found in this document.

3. Deploy the CVM

In this example, we assume that you're deploying a workload that requires opening a peer‑to‑peer port on 30000 and attaching an additional 20 GB persistent data disk. If your workload does not need either of these resources, you can omit both --additional_ports "30000" and --attach-disk mydisk --disk-size 20.

The --additional_ports option configures the cloud provider's firewall to allow inbound traffic on port 30000; it does not modify the nftables firewall inside the CVM, which is managed by the security policy you defined earlier.

The --attach-disk mydisk flag instructs the CLI to attach (or create, if it does not already exist) a persistent data disk named mydisk to the CVM. When used with --disk-size 20, the CLI creates a 20 GB disk if mydisk is not already present. This disk is independent of the VM's boot volume, so data written to it is preserved across reboots, redeployments, and VM replacements.

Note

After cvm is launched, the cvm will automatically detect the unmounted disk and setup the filesystem if the disk is not initialized and mount the disk at /data/datadisk-1.

# Deploy to GCP
./toolkit deploy-gcp workload-tool-0 --additional_ports "30000" --attach-disk mydisk --disk-size 20

# Deploy to AWS
./toolkit deploy-aws workload-tool-0 --additional_ports "30000" --attach-disk mydisk --disk-size 20

# Deploy to Azure
./toolkit deploy-azure workload-tool-0 --additional_ports "30000" --attach-disk mydisk --disk-size 20

At the end of the deployment, you should be able to see the name of the deployed CVM in the shell, and the location where the golden measurement of this CVM is stored:

✅ Golden measurements saved to _artifacts/golden-measurements/gcp-cvm-test.json
✨ Deployment complete! Your VM Name: cvm-test

Note

Please see the detailed walkthrough if you wish to do the following:

  • Customise other settings, like the vm name, or where the vm is deployed.
  • Check on best practices regarding the golden measurement, or how to use it in remote attestation.
  • If you only want to build a disk with your workload and distribute it to others.
  • If you wish to enable kernel livepatching.

4. Managing the CVM

We've scripted some convenience commands that you can run to manage your CVM.

Get Logs

Use this command to get all logs from all running containers in the CVM.

# ./toolkit get-logs <cloud-provider> <vm-name>
./toolkit get-logs gcp cvm-test

Update the workload

In the scenario where you have updated your app version and made a new container image for it, you can update your workload directory and upload it onto the existing CVM using this command:

# ./toolkit update-workload <workload-dir> <cloud-provider> <vm-name>
./toolkit update-workload workload-tool-0 gcp cvm-test

When the script is finished, the golden measurements will be automatically regenerated for you.

Note

If you are having troubles updating the workload, you might have forgotten to set the workload_config.services.allow_update. Please see the above section on editing the security policy.

Deleting the VM:

Use this command to delete the VM once you no longer need it.

# ./toolkit cleanup <cloud-provider> <vm-name>
./toolkit cleanup gcp cvm-test

Cleaning Up Local Artifacts

Use this command to remove all locally downloaded disk images, build provenance, and other artifacts.

./toolkit cleanup-local

(Advanced) Kernel Livepatching

Use this command to deploy a livepatch onto the CVM. Please checkout our kernel livepatch guide for more details.

# ./toolkit livepatch <cloud-provider> <vm-name> <path-to-livepatch>
./toolkit livepatch gcp cvm-test /path/to/livepatch.ko

Workload Stack

The workload-tool-0/ directory runs a full tool-node stack with the following services:

Service Description
tool-node Ethereum execution client with TEE relay support
lighthouse Ethereum beacon chain (consensus) client
controller Network isolation controller — enforces mutual exclusion between WAN and Tool Node access using nftables
operator SSH-accessible management container sharing the controller's network namespace
caddy Reverse proxy with automatic HTTPS via Let's Encrypt
promtail Log shipper — collects podman container logs and forwards to a remote Loki instance
node-exporter Prometheus node metrics exporter

For a simpler starting point, workload-example/ provides just Prometheus, node-exporter, and an nginx metrics proxy.

Configuration

Copy workload-tool-0/secrets/.env.example to workload-tool-0/secrets/.env and set your values. Key variables:

NETWORK=hoodi                              # Ethereum network (mainnet, hoodi, etc.)
LOKI_HOST=loki.example.com                 # Remote Loki host for log collection
LOKI_USER=                                 # Loki basic auth credentials
LOKI_PASSWORD=
CONTROLLER_API_KEY=                        # Bearer token for controller maintenance API
SSH_PUBLIC_KEY_FILE=~/.ssh/id_ed25519.pub  # SSH key for operator access
CADDY_RPC_DOMAIN=rpc.example.com           # Domain for automatic TLS (optional)

Network Isolation (Controller)

The controller enforces two mutually exclusive network modes:

  • Tool-Node mode (default): Operator can reach the Tool Node. WAN and inbound SSH are blocked.
  • Internet mode (maintenance): Operator has WAN access and SSH is allowed. Tool Node access is blocked.

Switch to Internet mode via the controller API:

VM_IP=$(cat _artifacts/gcp_<vm-name>_ip)

curl -X POST \
  -H "Authorization: Bearer $CONTROLLER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"enable"}' \
  http://$VM_IP:8080/maintenance

To restore Tool-Node mode:

curl -X POST \
  -H "Authorization: Bearer $CONTROLLER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"disable"}' \
  http://$VM_IP:8080/maintenance

See controller/README.md for the full API reference, nftables rules, and security details.

SSH Access

SSH into the operator container is only available in Internet mode (maintenance enabled):

ssh -p 2200 root@$VM_IP

The authorized key is configured via SSH_PUBLIC_KEY_FILE in .env and auto-populated by ./toolkit at deploy time.

Log Collection (Promtail → Loki → Grafana)

Promtail automatically discovers all running podman containers and ships their logs to a remote Loki instance with a container label (e.g. tool-node, lighthouse, controller). Set LOKI_HOST, LOKI_USER, and LOKI_PASSWORD in .env.


Live Demo

Here is a short demo video showing how to deploy workload using our cvm-image on AZURE in action.

Watch the demo

Instructions to recreate the demo setup in your own environment are available here:

git clone https://github.com/nuconstruct-ltd/automata-linux.git

cd automata-linux

cat workload-example/docker-compose.yml

cat workload-example/config/cvm_agent/cvm_agent_policy.json

./toolkit deploy-azure workload-example --additional_ports "30000"

./toolkit get-logs azure cvm-test

./toolkit update-workload workload-example azure cvm-test

./toolkit cleanup azure cvm-test

Detailed Walkthrough

A detailed walkthrough of what can be customized and any other features available can be found in this doc.

Architecture

Details of our CVM trust chain and attestation architecture can be found in this doc.

Troubleshooting

Running into trouble deploying the CVM? We have some common Q&A in this doc.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Shell 87.2%
  • Rust 7.7%
  • Python 1.9%
  • Makefile 1.7%
  • Other 1.5%