├── .cursorrules ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Makefile ├── README.md ├── attestation.md ├── basefiles ├── app-compose.service ├── app-compose.sh ├── dstack-guest-agent.service ├── dstack-prepare.service ├── dstack-prepare.sh ├── journald.conf ├── llmnr.conf ├── tdx-attest.conf ├── wg-checker.service └── wg-checker.sh ├── cargo-check-all.sh ├── cc-eventlog ├── Cargo.toml ├── samples │ └── ccel.bin └── src │ ├── codecs.rs │ ├── lib.rs │ ├── snapshots │ ├── cc_eventlog__tests__parse_ccel-2.snap │ └── cc_eventlog__tests__parse_ccel.snap │ └── tcg.rs ├── cert-client ├── Cargo.toml └── src │ └── lib.rs ├── certbot ├── .gitignore ├── Cargo.toml ├── cli │ ├── Cargo.toml │ └── src │ │ └── main.rs └── src │ ├── acme_client.rs │ ├── acme_client │ └── tests.rs │ ├── bot.rs │ ├── bot │ └── tests.rs │ ├── dns01_client.rs │ ├── dns01_client │ └── cloudflare.rs │ ├── lib.rs │ └── workdir.rs ├── ct_monitor ├── Cargo.toml └── src │ └── main.rs ├── docs ├── assets │ ├── app-board.png │ ├── app-deploy.png │ ├── app-dns-a.png │ ├── app-dns-txt.png │ ├── appid.png │ ├── arch.png │ ├── certbot-caa.png │ ├── gateway-accountid.png │ ├── gateway.png │ ├── guest-agent.png │ ├── kms-auth-set-info.png │ ├── kms-bootstrap-result.png │ ├── kms-bootstrap.png │ ├── org-contributors-2024-12-26.png │ ├── secret.png │ ├── tproxy-add-wildcard-domain.jpg │ └── vmm.png ├── deployment.md ├── dstack-gateway.md └── faq.md ├── dstack-types ├── Cargo.toml └── src │ ├── lib.rs │ └── shared_filenames.rs ├── dstack-util ├── Cargo.toml └── src │ ├── crypto.rs │ ├── host_api.rs │ ├── main.rs │ ├── parse_env_file.rs │ ├── system_setup.rs │ └── utils.rs ├── gateway ├── Cargo.toml ├── dstack-app │ ├── .gitignore │ ├── deploy-to-vmm.sh │ ├── docker-compose.yaml │ └── entrypoint.sh ├── gateway.toml ├── rpc │ ├── Cargo.toml │ ├── build.rs │ ├── proto │ │ └── gateway_rpc.proto │ └── src │ │ ├── generated.rs │ │ └── lib.rs ├── src │ ├── admin_service.rs │ ├── config.rs │ ├── main.rs │ ├── main_service.rs │ ├── main_service │ │ ├── auth_client.rs │ │ ├── snapshots │ │ │ ├── dstack_gateway__main_service__tests__config-2.snap │ │ │ ├── dstack_gateway__main_service__tests__config-3.snap │ │ │ ├── dstack_gateway__main_service__tests__config.snap │ │ │ └── dstack_gateway__main_service__tests__empty_config.snap │ │ ├── sync_client.rs │ │ └── tests.rs │ ├── models.rs │ ├── proxy.rs │ ├── proxy │ │ ├── io_bridge.rs │ │ ├── sni.rs │ │ ├── tls_passthough.rs │ │ └── tls_terminate.rs │ ├── web_routes.rs │ └── web_routes │ │ └── route_index.rs └── templates │ ├── dashboard.html │ ├── rproxy.yaml │ └── wg.conf ├── guest-agent ├── Cargo.toml ├── dstack.toml ├── rpc │ ├── Cargo.toml │ ├── build.rs │ ├── proto │ │ └── agent_rpc.proto │ └── src │ │ ├── generated.rs │ │ └── lib.rs ├── src │ ├── config.rs │ ├── guest_api_service.rs │ ├── http_routes.rs │ ├── main.rs │ ├── models.rs │ └── rpc_service.rs └── templates │ ├── dashboard.html │ └── metrics.tpl ├── guest-api ├── Cargo.toml ├── build.rs ├── proto │ └── guest_api.proto └── src │ ├── client.rs │ ├── generated │ └── mod.rs │ └── lib.rs ├── host-api ├── Cargo.toml ├── build.rs ├── proto │ └── host_api.proto └── src │ ├── client.rs │ ├── generated │ └── mod.rs │ └── lib.rs ├── http-client ├── Cargo.toml └── src │ ├── hyper_vsock.rs │ ├── lib.rs │ └── prpc.rs ├── iohash ├── Cargo.toml └── src │ └── main.rs ├── key-provider-build ├── Cargo.lock ├── Dockerfile.aesmd ├── Dockerfile.key-provider ├── docker-compose.yaml ├── entrypoint-aesmd.sh ├── entrypoint-key-provider.sh ├── run.sh └── sgx_default_qcnl.conf ├── key-provider-client ├── Cargo.toml └── src │ ├── host.rs │ └── lib.rs ├── kms ├── Cargo.toml ├── README.md ├── auth-eth │ ├── .gitignore │ ├── .openzeppelin │ │ └── unknown-2035.json │ ├── contracts │ │ ├── AppAuth.sol │ │ ├── IAppAuth.sol │ │ └── KmsAuth.sol │ ├── hardhat.config.ts │ ├── jest.config.js │ ├── jest.integration.config.js │ ├── lib │ │ └── deployment-helpers.ts │ ├── package-lock.json │ ├── package.json │ ├── run-tests.sh │ ├── scripts │ │ ├── deploy.ts │ │ ├── upgrade.ts │ │ └── verify.ts │ ├── src │ │ ├── ethereum.ts │ │ ├── main.ts │ │ ├── server.ts │ │ └── types.ts │ ├── test │ │ ├── AppAuth.test.ts │ │ ├── ethereum.integration.test.ts │ │ ├── ethereum.test.ts │ │ ├── main.test.ts │ │ └── setup.ts │ ├── tsconfig.json │ └── typechain-types │ │ ├── @openzeppelin │ │ ├── contracts-upgradeable │ │ │ ├── access │ │ │ │ ├── OwnableUpgradeable.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── proxy │ │ │ │ ├── index.ts │ │ │ │ └── utils │ │ │ │ │ ├── Initializable.ts │ │ │ │ │ ├── UUPSUpgradeable.ts │ │ │ │ │ └── index.ts │ │ │ └── utils │ │ │ │ ├── ContextUpgradeable.ts │ │ │ │ └── index.ts │ │ ├── contracts │ │ │ ├── index.ts │ │ │ ├── interfaces │ │ │ │ ├── IERC1967.ts │ │ │ │ ├── draft-IERC1822.sol │ │ │ │ │ ├── IERC1822Proxiable.ts │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── proxy │ │ │ │ ├── ERC1967 │ │ │ │ │ ├── ERC1967Utils.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── beacon │ │ │ │ │ ├── IBeacon.ts │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ └── utils │ │ │ │ ├── Address.ts │ │ │ │ ├── Errors.ts │ │ │ │ └── index.ts │ │ └── index.ts │ │ ├── common.ts │ │ ├── contracts │ │ ├── AppAuth.ts │ │ ├── IAppAuth.ts │ │ ├── KmsAuth.ts │ │ └── index.ts │ │ ├── factories │ │ ├── @openzeppelin │ │ │ ├── contracts-upgradeable │ │ │ │ ├── access │ │ │ │ │ ├── OwnableUpgradeable__factory.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── proxy │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils │ │ │ │ │ │ ├── Initializable__factory.ts │ │ │ │ │ │ ├── UUPSUpgradeable__factory.ts │ │ │ │ │ │ └── index.ts │ │ │ │ └── utils │ │ │ │ │ ├── ContextUpgradeable__factory.ts │ │ │ │ │ └── index.ts │ │ │ ├── contracts │ │ │ │ ├── index.ts │ │ │ │ ├── interfaces │ │ │ │ │ ├── IERC1967__factory.ts │ │ │ │ │ ├── draft-IERC1822.sol │ │ │ │ │ │ ├── IERC1822Proxiable__factory.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── proxy │ │ │ │ │ ├── ERC1967 │ │ │ │ │ │ ├── ERC1967Utils__factory.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── beacon │ │ │ │ │ │ ├── IBeacon__factory.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ │ └── utils │ │ │ │ │ ├── Address__factory.ts │ │ │ │ │ ├── Errors__factory.ts │ │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── contracts │ │ │ ├── AppAuth__factory.ts │ │ │ ├── IAppAuth__factory.ts │ │ │ ├── KmsAuth__factory.ts │ │ │ └── index.ts │ │ └── index.ts │ │ ├── hardhat.d.ts │ │ └── index.ts ├── dstack-app │ ├── .gitignore │ ├── compose-dev.yaml │ ├── deploy-to-vmm.sh │ ├── docker-compose.yaml │ └── entrypoint.sh ├── kms.toml ├── rpc │ ├── Cargo.toml │ ├── build.rs │ ├── proto │ │ └── kms_rpc.proto │ └── src │ │ ├── .gitignore │ │ ├── generated.rs │ │ └── lib.rs └── src │ ├── config.rs │ ├── crypto.rs │ ├── ct_log.rs │ ├── main.rs │ ├── main_service.rs │ ├── main_service │ └── upgrade_authority.rs │ ├── onboard_service.rs │ └── www │ └── onboard.html ├── load_config ├── Cargo.toml └── src │ └── lib.rs ├── lspci ├── Cargo.toml └── src │ ├── lib.rs │ └── snapshots │ └── lspci__lspci.snap ├── mod-tdx-guest ├── Kconfig ├── Makefile ├── mod.c ├── tdcall.S ├── tdx-guest.h └── tdx.h ├── python ├── .gitignore └── ct_monitor │ ├── ct_monitor.py │ └── pyproject.toml ├── ra-rpc ├── Cargo.toml └── src │ ├── client.rs │ ├── lib.rs │ └── rocket_helper.rs ├── ra-tls ├── Cargo.toml ├── assets │ └── tdx_quote └── src │ ├── attestation.rs │ ├── cert.rs │ ├── kdf.rs │ ├── lib.rs │ ├── oids.rs │ └── traits.rs ├── rocket-vsock-listener ├── Cargo.toml └── src │ └── lib.rs ├── run-tests.sh ├── run.sh ├── sdk ├── README.md ├── curl │ ├── api-tappd.md │ └── api.md ├── go │ ├── README.md │ ├── dstack │ │ ├── client.go │ │ └── client_test.go │ ├── go.mod │ ├── go.sum │ └── tappd │ │ ├── client.go │ │ └── client_test.go ├── js │ ├── .gitignore │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── bun.lockb │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── index.test.ts │ │ │ ├── solana.test.ts │ │ │ └── viem.test.ts │ │ ├── encrypt-env-vars.ts │ │ ├── index.ts │ │ ├── solana.ts │ │ └── viem.ts │ └── tsconfig.json ├── python │ ├── .gitignore │ ├── README.md │ ├── pdm.lock │ ├── pyproject.toml │ ├── src │ │ └── dstack_sdk │ │ │ ├── __init__.py │ │ │ ├── dstack_client.py │ │ │ ├── ethereum.py │ │ │ └── solana.py │ └── tests │ │ ├── __init__.py │ │ ├── test_client.py │ │ ├── test_ethereum.py │ │ └── test_solana.py ├── rust │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ ├── dstack_client.rs │ │ ├── ethereum.rs │ │ └── lib.rs │ └── tests │ │ ├── test_client.rs │ │ └── test_eth.rs └── simulator │ ├── .gitignore │ ├── app-compose.json │ ├── appkeys.json │ ├── build.sh │ ├── dstack.toml │ ├── eventlog.json │ ├── quote.hex │ └── sys-config.json ├── serde-duration ├── Cargo.toml └── src │ └── lib.rs ├── sodiumbox ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── supervisor ├── Cargo.toml ├── client │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── main.rs ├── src │ ├── lib.rs │ ├── main.rs │ ├── process.rs │ ├── supervisor.rs │ └── web_api.rs ├── supervisor.toml └── tests │ ├── test-cli.sh │ └── test.sh ├── tdx-attest-sys ├── Cargo.lock ├── Cargo.toml ├── bindings.h ├── build.rs ├── csrc │ ├── qgs_msg_lib.cpp │ ├── qgs_msg_lib.h │ ├── tdx_attest.c │ └── tdx_attest.h └── src │ └── lib.rs ├── tdx-attest ├── Cargo.lock ├── Cargo.toml └── src │ ├── dummy.rs │ ├── lib.rs │ ├── linux.rs │ └── snapshots │ ├── tdx_attest__eventlog__tests__parse_ccel-2.snap │ └── tdx_attest__eventlog__tests__parse_ccel.snap ├── test-scripts ├── get-app-key.sh └── inspect-cert.sh └── vmm ├── Cargo.toml ├── requirements.txt ├── rpc ├── Cargo.toml ├── build.rs ├── proto │ └── vmm_rpc.proto └── src │ ├── generated.rs │ └── lib.rs ├── src ├── app.rs ├── app │ ├── id_pool.rs │ ├── image.rs │ └── qemu.rs ├── config.rs ├── console.html ├── guest_api_service.rs ├── host_api_service.rs ├── main.rs ├── main_routes.rs ├── main_service.rs ├── setup-user.sh ├── vmm-cli.py └── x25519.js ├── venv.sh └── vmm.toml /.cursorrules: -------------------------------------------------------------------------------- 1 | Don't capitalize the first letter for log messages and error messages. -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust checks 2 | 3 | on: 4 | push: 5 | branches: [ master, next, dev-* ] 6 | pull_request: 7 | branches: [ master, next, dev-* ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | rust-checks: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Install Rust 19 | uses: dtolnay/rust-toolchain@1.86 20 | with: 21 | components: clippy, rustfmt 22 | 23 | - name: Run Clippy 24 | run: cargo clippy -- -D warnings --allow unused_variables 25 | 26 | - name: Cargo fmt check 27 | run: cargo fmt --check --all 28 | 29 | - name: Run tests 30 | run: ./run-tests.sh 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /certs 3 | /build-config.sh 4 | /build 5 | generated/ 6 | node_modules/ 7 | /.cargo 8 | .venv 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOMAIN := local 2 | TO := ./certs 3 | 4 | .PHONY: clean run all certs 5 | 6 | all: 7 | 8 | certs: ${TO} 9 | 10 | ${TO}: 11 | mkdir -p ${TO} 12 | cargo run --bin certgen -- generate --domain ${DOMAIN} --output-dir ${TO} 13 | 14 | run: 15 | $(MAKE) -C mkguest run 16 | 17 | clean: 18 | rm -rf ${TO} 19 | -------------------------------------------------------------------------------- /basefiles/app-compose.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=App Compose Service 3 | Wants=docker.service 4 | After=docker.service tboot.service dstack-guest-agent.service 5 | 6 | [Service] 7 | Type=oneshot 8 | RemainAfterExit=true 9 | EnvironmentFile=-/dstack/.host-shared/.decrypted-env 10 | WorkingDirectory=/dstack 11 | ExecStart=/bin/app-compose.sh 12 | ExecStop=/bin/docker compose stop 13 | StandardOutput=journal+console 14 | StandardError=journal+console 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /basefiles/app-compose.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HOST_SHARED_DIR="/dstack/.host-shared" 4 | SYS_CONFIG_FILE="$HOST_SHARED_DIR/.sys-config.json" 5 | CFG_PCCS_URL=$([ -f "$SYS_CONFIG_FILE" ] && jq -r '.pccs_url//""' "$SYS_CONFIG_FILE" || echo "") 6 | export PCCS_URL=${PCCS_URL:-$CFG_PCCS_URL} 7 | 8 | if [ $(jq 'has("pre_launch_script")' app-compose.json) == true ]; then 9 | echo "Running pre-launch script" 10 | dstack-util notify-host -e "boot.progress" -d "pre-launch" || true 11 | source <(jq -r '.pre_launch_script' app-compose.json) 12 | fi 13 | 14 | RUNNER=$(jq -r '.runner' app-compose.json) 15 | case "$RUNNER" in 16 | "docker-compose") 17 | echo "Starting containers" 18 | dstack-util notify-host -e "boot.progress" -d "starting containers" || true 19 | if ! [ -f docker-compose.yaml ]; then 20 | jq -r '.docker_compose_file' app-compose.json >docker-compose.yaml 21 | fi 22 | dstack-util remove-orphans -f docker-compose.yaml || true 23 | chmod +x /usr/bin/containerd-shim-runc-v2 24 | systemctl restart docker 25 | 26 | if ! docker compose up --remove-orphans -d --build; then 27 | dstack-util notify-host -e "boot.error" -d "failed to start containers" 28 | exit 1 29 | fi 30 | echo "Pruning unused images" 31 | docker image prune -af 32 | echo "Pruning unused volumes" 33 | docker volume prune -f 34 | ;; 35 | "bash") 36 | chmod +x /usr/bin/containerd-shim-runc-v2 37 | echo "Running main script" 38 | dstack-util notify-host -e "boot.progress" -d "running main script" || true 39 | jq -r '.bash_script' app-compose.json | bash 40 | ;; 41 | *) 42 | echo "ERROR: unsupported runner: $RUNNER" >&2 43 | dstack-util notify-host -e "boot.error" -d "unsupported runner: $RUNNER" 44 | exit 1 45 | ;; 46 | esac 47 | 48 | dstack-util notify-host -e "boot.progress" -d "done" || true 49 | -------------------------------------------------------------------------------- /basefiles/dstack-guest-agent.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Dstack Guest Agent Service 3 | After=network.target tboot.service 4 | Before=docker.service 5 | 6 | [Service] 7 | OOMScoreAdjust=-1000 8 | ExecStart=/bin/dstack-guest-agent --watchdog -c /dstack/agent.json 9 | Restart=always 10 | User=root 11 | Group=root 12 | Type=notify 13 | WatchdogSec=30s 14 | StandardOutput=journal+console 15 | StandardError=journal+console 16 | Environment=RUST_LOG=warn 17 | 18 | [Install] 19 | WantedBy=multi-user.target 20 | -------------------------------------------------------------------------------- /basefiles/dstack-prepare.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=DStack Guest Preparation Service 3 | After=network.target chronyd.service 4 | Before=app-compose.service dstack-guest-agent.service docker.service 5 | OnFailure=reboot.target 6 | 7 | [Service] 8 | Type=oneshot 9 | ExecStart=/bin/dstack-prepare.sh 10 | RemainAfterExit=yes 11 | StandardOutput=journal+console 12 | StandardError=journal+console 13 | FailureAction=reboot 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /basefiles/dstack-prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | WORK_DIR="/var/volatile/dstack" 5 | DATA_MNT="$WORK_DIR/persistent" 6 | 7 | # Prepare volatile dirs 8 | mount_overlay() { 9 | local src=$1 10 | local dst=/var/volatile/$1 11 | mkdir -p $dst/upper $dst/work 12 | mount -t overlay overlay -o lowerdir=$src,upperdir=$dst/upper,workdir=$dst/work $src 13 | } 14 | mount_overlay /etc/wireguard 15 | mount_overlay /etc/docker 16 | mount_overlay /usr/bin 17 | mount_overlay /home/root 18 | 19 | # Disable the containerd-shim-runc-v2 temporarily to prevent the containers from starting 20 | # before docker compose removal orphans. It will be enabled in app-compose.sh 21 | chmod -x /usr/bin/containerd-shim-runc-v2 22 | 23 | # Make sure the system time is synchronized 24 | echo "Syncing system time..." 25 | # Let the chronyd correct the system time immediately 26 | chronyc makestep 27 | 28 | modprobe tdx-guest 29 | 30 | # Setup dstack system 31 | echo "Preparing dstack system..." 32 | dstack-util setup --work-dir $WORK_DIR --device /dev/vdb --mount-point $DATA_MNT 33 | 34 | echo "Mounting docker dirs to persistent storage" 35 | # Mount docker dirs to DATA_MNT 36 | mkdir -p $DATA_MNT/var/lib/docker 37 | mount --bind $DATA_MNT/var/lib/docker /var/lib/docker 38 | mount --bind $WORK_DIR /dstack 39 | -------------------------------------------------------------------------------- /basefiles/journald.conf: -------------------------------------------------------------------------------- 1 | [Journal] 2 | SystemMaxUse=128M 3 | SystemKeepFree=1G 4 | SystemMaxFileSize=10M 5 | SystemMaxFiles=10 6 | RuntimeMaxUse=0 7 | ReadKMsg=no 8 | -------------------------------------------------------------------------------- /basefiles/llmnr.conf: -------------------------------------------------------------------------------- 1 | [Resolve] 2 | LLMNR=no -------------------------------------------------------------------------------- /basefiles/tdx-attest.conf: -------------------------------------------------------------------------------- 1 | port=4050 -------------------------------------------------------------------------------- /basefiles/wg-checker.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=WireGuard Endpoint Checker Service 3 | After=network-online.target tboot.service 4 | Wants=network-online.target 5 | 6 | [Service] 7 | Type=simple 8 | ExecStart=/bin/wg-checker.sh 9 | Restart=always 10 | RestartSec=10 11 | StandardOutput=journal+console 12 | StandardError=journal+console 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /basefiles/wg-checker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | get_conf_endpoint() { 4 | grep "Endpoint" /etc/wireguard/wg0.conf | awk "{print \$3}" 5 | } 6 | 7 | get_current_endpoint() { 8 | wg show wg0 endpoints | awk "{print \$2}" 9 | } 10 | 11 | check_endpoint() { 12 | CONF_ENDPOINT=$(get_conf_endpoint) 13 | CURRENT_ENDPOINT=$(get_current_endpoint) 14 | 15 | if [ "$CURRENT_ENDPOINT" != "$CONF_ENDPOINT" ]; then 16 | echo "Wg endpoint changed from $CONF_ENDPOINT to $CURRENT_ENDPOINT." 17 | wg syncconf wg0 <(wg-quick strip wg0) 18 | fi 19 | } 20 | 21 | while true; do 22 | if [ -f /etc/wireguard/wg0.conf ]; then 23 | check_endpoint 24 | fi 25 | sleep 10 26 | done 27 | -------------------------------------------------------------------------------- /cargo-check-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | find . -name Cargo.toml -exec dirname {} \; | while read dir; do 3 | echo "Checking $dir..." 4 | (cd "$dir" && cargo check) 5 | done 6 | -------------------------------------------------------------------------------- /cc-eventlog/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cc-eventlog" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | fs-err.workspace = true 11 | hex.workspace = true 12 | scale.workspace = true 13 | serde.workspace = true 14 | serde-human-bytes.workspace = true 15 | serde_json.workspace = true 16 | sha2.workspace = true 17 | 18 | [dev-dependencies] 19 | insta.workspace = true 20 | -------------------------------------------------------------------------------- /cc-eventlog/samples/ccel.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/cc-eventlog/samples/ccel.bin -------------------------------------------------------------------------------- /cc-eventlog/src/codecs.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use scale::{Decode, Input}; 4 | 5 | #[derive(Clone, Debug, PartialEq, Eq)] 6 | pub struct VecOf { 7 | len: I, 8 | inner: Vec, 9 | } 10 | 11 | impl Default for VecOf { 12 | fn default() -> Self { 13 | Self { 14 | len: I::default(), 15 | inner: Vec::default(), 16 | } 17 | } 18 | } 19 | 20 | impl + Copy, T: Decode> Decode for VecOf { 21 | fn decode(input: &mut In) -> Result { 22 | let decoded_len = I::decode(input)?; 23 | let len = decoded_len.into() as usize; 24 | let mut inner = Vec::with_capacity(len); 25 | for _ in 0..len { 26 | inner.push(T::decode(input)?); 27 | } 28 | Ok(Self { 29 | len: decoded_len, 30 | inner, 31 | }) 32 | } 33 | } 34 | 35 | impl VecOf { 36 | pub fn into_inner(self) -> Vec { 37 | self.inner 38 | } 39 | 40 | pub fn length(&self) -> I 41 | where 42 | I: Clone, 43 | { 44 | self.len.clone() 45 | } 46 | } 47 | 48 | impl Deref for VecOf { 49 | type Target = Vec; 50 | 51 | fn deref(&self) -> &Self::Target { 52 | &self.inner 53 | } 54 | } 55 | 56 | impl From<(I, Vec)> for VecOf { 57 | fn from((len, vec): (I, Vec)) -> Self { 58 | Self { len, inner: vec } 59 | } 60 | } 61 | 62 | impl AsRef<[T]> for VecOf { 63 | fn as_ref(&self) -> &[T] { 64 | &self.inner 65 | } 66 | } 67 | 68 | impl From> for Vec { 69 | fn from(value: VecOf) -> Self { 70 | value.inner 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /cert-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cert-client" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | dstack-types.workspace = true 11 | dstack-kms-rpc.workspace = true 12 | ra-rpc = { workspace = true, features = ["client"] } 13 | ra-tls.workspace = true 14 | serde_json.workspace = true 15 | tdx-attest.workspace = true 16 | -------------------------------------------------------------------------------- /certbot/.gitignore: -------------------------------------------------------------------------------- 1 | /test-workdir 2 | -------------------------------------------------------------------------------- /certbot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "certbot" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | bon.workspace = true 11 | enum_dispatch.workspace = true 12 | fs-err.workspace = true 13 | hickory-resolver.workspace = true 14 | instant-acme.workspace = true 15 | path-absolutize.workspace = true 16 | rcgen.workspace = true 17 | reqwest.workspace = true 18 | serde.workspace = true 19 | serde_json.workspace = true 20 | time.workspace = true 21 | tokio.workspace = true 22 | tracing.workspace = true 23 | x509-parser.workspace = true 24 | 25 | [dev-dependencies] 26 | rand.workspace = true 27 | tokio = { workspace = true, features = ["full"] } 28 | tracing-subscriber.workspace = true 29 | -------------------------------------------------------------------------------- /certbot/cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "certbot-cli" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [[bin]] 9 | name = "certbot" 10 | path = "src/main.rs" 11 | 12 | [dependencies] 13 | anyhow.workspace = true 14 | certbot.workspace = true 15 | clap.workspace = true 16 | documented.workspace = true 17 | fs-err.workspace = true 18 | serde.workspace = true 19 | tokio = { workspace = true, features = ["full"] } 20 | toml_edit.workspace = true 21 | tracing-subscriber.workspace = true 22 | rustls.workspace = true 23 | -------------------------------------------------------------------------------- /certbot/src/acme_client/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(test))] 2 | 3 | use super::*; 4 | 5 | async fn new_acme_client() -> Result { 6 | let dns01_client = Dns01Client::new_cloudflare( 7 | std::env::var("CLOUDFLARE_ZONE_ID").expect("CLOUDFLARE_ZONE_ID not set"), 8 | std::env::var("CLOUDFLARE_API_TOKEN").expect("CLOUDFLARE_API_TOKEN not set"), 9 | ); 10 | let credentials = 11 | std::env::var("LETSENCRYPT_CREDENTIAL").expect("LETSENCRYPT_CREDENTIAL not set"); 12 | AcmeClient::load(dns01_client, &credentials).await 13 | } 14 | 15 | #[tokio::test] 16 | async fn test_request_new_certificate() { 17 | tracing_subscriber::fmt::try_init().ok(); 18 | 19 | let test_domain = std::env::var("TEST_DOMAIN").expect("TEST_DOMAIN not set"); 20 | let domains = vec![test_domain.clone(), format!("*.{}", test_domain)]; 21 | let bot = new_acme_client().await.unwrap(); 22 | println!("account credentials: {}", bot.dump_credentials().unwrap()); 23 | let key = KeyPair::generate().unwrap(); 24 | let key_pem = key.serialize_pem(); 25 | let cert = bot 26 | .request_new_certificate(&key_pem, &domains) 27 | .await 28 | .expect("Failed to get cert"); 29 | println!("key:\n{}", key_pem); 30 | println!("cert:\n{}", cert); 31 | } 32 | -------------------------------------------------------------------------------- /certbot/src/bot/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(test))] 2 | 3 | use instant_acme::LetsEncrypt; 4 | 5 | use super::*; 6 | 7 | async fn new_certbot() -> Result { 8 | let cf_zone_id = std::env::var("CLOUDFLARE_ZONE_ID").expect("CLOUDFLARE_ZONE_ID not set"); 9 | let cf_api_token = std::env::var("CLOUDFLARE_API_TOKEN").expect("CLOUDFLARE_API_TOKEN not set"); 10 | let domains = vec![std::env::var("TEST_DOMAIN").expect("TEST_DOMAIN not set")]; 11 | let config = CertBotConfig::builder() 12 | .acme_url(LetsEncrypt::Staging.url()) 13 | .auto_create_account(true) 14 | .credentials_file("./test-workdir/credentials.json") 15 | .cf_zone_id(cf_zone_id) 16 | .cf_api_token(cf_api_token) 17 | .cert_dir("./test-workdir/backup") 18 | .cert_file("./test-workdir/live/cert.pem") 19 | .key_file("./test-workdir/live/key.pem") 20 | .cert_subject_alt_names(domains) 21 | .renew_interval(Duration::from_secs(30)) 22 | .renew_timeout(Duration::from_secs(120)) 23 | .renew_expires_in(Duration::from_secs(7772187)) 24 | .auto_set_caa(false) 25 | .build(); 26 | config.build_bot().await 27 | } 28 | 29 | #[tokio::test] 30 | async fn test_certbot() { 31 | tracing_subscriber::fmt::try_init().ok(); 32 | 33 | let bot = new_certbot().await.unwrap(); 34 | bot.run().await; 35 | } 36 | -------------------------------------------------------------------------------- /certbot/src/dns01_client.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use cloudflare::CloudflareClient; 3 | use enum_dispatch::enum_dispatch; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | mod cloudflare; 7 | 8 | #[derive(Debug, Deserialize, Serialize)] 9 | /// Represents a DNS record 10 | pub(crate) struct Record { 11 | /// Unique identifier for the record 12 | pub id: String, 13 | /// The name of the DNS record (e.g., "_acme-challenge.example.com") 14 | pub name: String, 15 | /// The content of the DNS record (e.g., the TXT value for ACME challenges) 16 | pub content: String, 17 | /// The type of DNS record (e.g., "TXT" for ACME challenges) 18 | pub r#type: String, 19 | } 20 | 21 | #[enum_dispatch] 22 | pub(crate) trait Dns01Api { 23 | /// Creates a TXT DNS record with the given domain and content. 24 | /// 25 | /// Returns the ID of the created record. 26 | async fn add_txt_record(&self, domain: &str, content: &str) -> Result; 27 | 28 | /// Add a CAA record for the given domain. 29 | async fn add_caa_record( 30 | &self, 31 | domain: &str, 32 | flags: u8, 33 | tag: &str, 34 | value: &str, 35 | ) -> Result; 36 | 37 | /// Remove a DNS record. 38 | /// 39 | /// Deletes a DNS record using its unique identifier. 40 | async fn remove_record(&self, record_id: &str) -> Result<()>; 41 | 42 | /// Get all records for a domain. 43 | async fn get_records(&self, domain: &str) -> Result>; 44 | 45 | /// Remove TXT DNS records by domain. 46 | /// 47 | /// Deletes all TXT DNS records matching the given domain. 48 | async fn remove_txt_records(&self, domain: &str) -> Result<()> { 49 | for record in self.get_records(domain).await? { 50 | if record.r#type == "TXT" { 51 | self.remove_record(&record.id).await?; 52 | } 53 | } 54 | Ok(()) 55 | } 56 | } 57 | 58 | /// A DNS-01 client. 59 | #[derive(Debug, Serialize, Deserialize)] 60 | #[enum_dispatch(Dns01Api)] 61 | #[serde(rename_all = "lowercase")] 62 | pub enum Dns01Client { 63 | Cloudflare(CloudflareClient), 64 | } 65 | 66 | impl Dns01Client { 67 | pub fn new_cloudflare(zone_id: String, api_token: String) -> Self { 68 | Self::Cloudflare(CloudflareClient::new(zone_id, api_token)) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /certbot/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A CertBot client for requesting certificates from Let's Encrypt. 2 | //! 3 | //! This library provides a simple interface for requesting and managing SSL/TLS certificates 4 | //! using the ACME protocol with Let's Encrypt as the Certificate Authority. 5 | //! 6 | //! # Features 7 | //! 8 | //! - Automatic certificate issuance and renewal 9 | //! - DNS-01 challenge support (currently implemented for Cloudflare) 10 | //! - Easy integration with existing Rust applications 11 | //! 12 | //! For more detailed information on the available methods and their usage, please refer 13 | //! to the documentation of individual structs and functions. 14 | 15 | pub use acme_client::AcmeClient; 16 | pub use bot::{CertBot, CertBotConfig}; 17 | pub use dns01_client::Dns01Client; 18 | pub use workdir::WorkDir; 19 | 20 | mod acme_client; 21 | mod bot; 22 | mod dns01_client; 23 | mod workdir; 24 | -------------------------------------------------------------------------------- /certbot/src/workdir.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use fs_err as fs; 3 | use std::{ 4 | collections::BTreeSet, 5 | path::{Path, PathBuf}, 6 | }; 7 | 8 | use crate::acme_client::Credentials; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct WorkDir { 12 | workdir: PathBuf, 13 | } 14 | 15 | impl WorkDir { 16 | pub fn new(workdir: impl AsRef) -> Self { 17 | Self { 18 | workdir: workdir.as_ref().to_path_buf(), 19 | } 20 | } 21 | 22 | pub fn workdir(&self) -> &PathBuf { 23 | &self.workdir 24 | } 25 | 26 | pub fn account_credentials_path(&self) -> PathBuf { 27 | self.workdir.join("credentials.json") 28 | } 29 | 30 | pub fn backup_dir(&self) -> PathBuf { 31 | self.workdir.join("backup") 32 | } 33 | 34 | pub fn live_dir(&self) -> PathBuf { 35 | self.workdir.join("live") 36 | } 37 | 38 | pub fn cert_path(&self) -> PathBuf { 39 | self.live_dir().join("cert.pem") 40 | } 41 | 42 | pub fn key_path(&self) -> PathBuf { 43 | self.live_dir().join("key.pem") 44 | } 45 | 46 | pub fn list_certs(&self) -> Result> { 47 | crate::bot::list_certs(self.backup_dir()) 48 | } 49 | 50 | pub fn acme_account_uri(&self) -> Result { 51 | let encoded_credentials = fs::read_to_string(self.account_credentials_path())?; 52 | let credentials: Credentials = serde_json::from_str(&encoded_credentials)?; 53 | Ok(credentials.account_id) 54 | } 55 | 56 | pub fn list_cert_public_keys(&self) -> Result>> { 57 | crate::bot::list_cert_public_keys(self.backup_dir()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ct_monitor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ct_monitor" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | clap = { workspace = true, features = ["derive"] } 11 | hex_fmt.workspace = true 12 | regex.workspace = true 13 | reqwest = { workspace = true, default-features = false, features = ["json", "rustls-tls", "charset", "hickory-dns"] } 14 | serde = { workspace = true, features = ["derive"] } 15 | serde_json.workspace = true 16 | tokio = { workspace = true, features = ["full"] } 17 | tracing.workspace = true 18 | tracing-subscriber.workspace = true 19 | x509-parser.workspace = true 20 | 21 | dstack-gateway-rpc.workspace = true 22 | ra-rpc = { workspace = true, default-features = false, features = ["client"] } 23 | -------------------------------------------------------------------------------- /docs/assets/app-board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/app-board.png -------------------------------------------------------------------------------- /docs/assets/app-deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/app-deploy.png -------------------------------------------------------------------------------- /docs/assets/app-dns-a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/app-dns-a.png -------------------------------------------------------------------------------- /docs/assets/app-dns-txt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/app-dns-txt.png -------------------------------------------------------------------------------- /docs/assets/appid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/appid.png -------------------------------------------------------------------------------- /docs/assets/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/arch.png -------------------------------------------------------------------------------- /docs/assets/certbot-caa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/certbot-caa.png -------------------------------------------------------------------------------- /docs/assets/gateway-accountid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/gateway-accountid.png -------------------------------------------------------------------------------- /docs/assets/gateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/gateway.png -------------------------------------------------------------------------------- /docs/assets/guest-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/guest-agent.png -------------------------------------------------------------------------------- /docs/assets/kms-auth-set-info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/kms-auth-set-info.png -------------------------------------------------------------------------------- /docs/assets/kms-bootstrap-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/kms-bootstrap-result.png -------------------------------------------------------------------------------- /docs/assets/kms-bootstrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/kms-bootstrap.png -------------------------------------------------------------------------------- /docs/assets/org-contributors-2024-12-26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/org-contributors-2024-12-26.png -------------------------------------------------------------------------------- /docs/assets/secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/secret.png -------------------------------------------------------------------------------- /docs/assets/tproxy-add-wildcard-domain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/tproxy-add-wildcard-domain.jpg -------------------------------------------------------------------------------- /docs/assets/vmm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/docs/assets/vmm.png -------------------------------------------------------------------------------- /docs/dstack-gateway.md: -------------------------------------------------------------------------------- 1 | # Setup dstack-gateway for Production 2 | 3 | To set up dstack-gateway for production, you need a wildcard domain and SSL certificate. 4 | 5 | ## Step 1: Setup wildcard domain 6 | 7 | Set up a second-level wildcard domain using Cloudflare; make sure to disable proxy mode and use **DNS Only**. 8 | 9 | ![add-wildcard-domain](./assets/tproxy-add-wildcard-domain.jpg) 10 | 11 | ## Step 2: Request a Wildcard Domain SSL Certificate with Certbot 12 | 13 | You need to get a Cloudflare API Key and ensure the API can manage this domain. 14 | 15 | You can check your Cloudflare API key and get `cf_zone_id` using this command: 16 | 17 | ```shell 18 | curl -X GET "https://api.cloudflare.com/client/v4/zones" -H "Authorization: Bearer " -H "Content-Type: application/json" | jq . 19 | ``` 20 | 21 | Open your `certbot.toml`, and update these fields: 22 | 23 | - `acme_url`: change to `https://acme-v02.api.letsencrypt.org/directory` 24 | - `cf_api_token`: Obtain from Cloudflare 25 | - `cf_zone_id`: Obtain from the API call above 26 | 27 | ## Step 3: Run Certbot Manually and Get First SSL Certificates 28 | 29 | ```shell 30 | ./certbot set-caa 31 | ./certbot renew 32 | ``` 33 | 34 | ## Step 4: Update `gateway.toml` 35 | 36 | Focus on these five fields in the `core.proxy` section: 37 | 38 | - `cert_chain` & `cert_key`: Point to the certificate paths from the previous step 39 | - `base_domain`: The wildcard domain for proxy 40 | - `listen_addr` & `listen_port`: Listen to `0.0.0.0` and preferably `443` in production. If using another port, specify it in the URL 41 | 42 | For example, if your base domain is `gateway.example.com`, app ID is ``, listening on `80`, and dstack-gateway is on port 7777, the URL would be `https://-80.gateway.example.com:7777` 43 | 44 | ## Step 5: Adjust Configuration in `vmm.toml` 45 | 46 | Open `vmm.toml` and adjust dstack-gateway configuration in the `gateway` section: 47 | 48 | - `base_domain`: Same as `base_domain` from `gateway.toml`'s `core.proxy` section 49 | - `port`: Same as `listen_port` from `gateway.toml`'s `core.proxy` section -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## CVM status turns to `exited` immediately 4 | 5 | First, check the stderr output of the CVM. 6 | 7 | > [!TIP] 8 | > To view the CVM's stderr, append `ch=stderr` to the end of the log URL. 9 | > If the log URL is `/logs?id=&follow=true&ansi=false&lines=20` 10 | > The stderr URL would be `/logs?id=&follow=true&ansi=false&lines=20&ch=stderr`. 11 | 12 | If you see an error message in CVM's stderr output: 13 | 14 | ``` 15 | Could not access KVM kernel module: Permission denied 16 | gemu-system-x86_64: -accel kvm: failed to initialize kvm: Permission denied 17 | ``` 18 | 19 | This means your supervisor is not running with an account that belongs to the `libvirt` and `kvm` groups. You need to ensure your account is added to these two groups. You can check this by running the following command: 20 | 21 | ```shell 22 | id 23 | ``` 24 | 25 | If you are not in these groups, you likely won't have the necessary privileges to run QEMU. 26 | 27 | Once you have the required privileges, make sure the supervisor process is shut down: 28 | 29 | ```shell 30 | ps aux | grep supervisor | grep $(whoami) | grep -v grep 31 | ``` 32 | 33 | Log out of all your sessions and log back in. Check your groups with the `id` command, and this should resolve the issue. -------------------------------------------------------------------------------- /dstack-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dstack-types" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | serde = { workspace = true, features = ["derive"] } 10 | serde-human-bytes.workspace = true 11 | -------------------------------------------------------------------------------- /dstack-types/src/shared_filenames.rs: -------------------------------------------------------------------------------- 1 | pub const APP_COMPOSE: &str = "app-compose.json"; 2 | pub const APP_KEYS: &str = ".appkeys.json"; 3 | pub const SYS_CONFIG: &str = ".sys-config.json"; 4 | pub const USER_CONFIG: &str = ".user-config"; 5 | pub const ENCRYPTED_ENV: &str = ".encrypted-env"; 6 | pub const DECRYPTED_ENV: &str = ".decrypted-env"; 7 | pub const DECRYPTED_ENV_JSON: &str = ".decrypted-env.json"; 8 | pub const INSTANCE_INFO: &str = ".instance_info"; 9 | pub const HOST_SHARED_DIR: &str = "/dstack/.host-shared"; 10 | pub const HOST_SHARED_DIR_NAME: &str = ".host-shared"; 11 | 12 | pub mod compat_v3 { 13 | pub const SYS_CONFIG: &str = "config.json"; 14 | pub const ENCRYPTED_ENV: &str = "encrypted-env"; 15 | } 16 | -------------------------------------------------------------------------------- /dstack-util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dstack-util" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | aes-gcm.workspace = true 10 | anyhow.workspace = true 11 | clap.workspace = true 12 | curve25519-dalek.workspace = true 13 | fs-err.workspace = true 14 | getrandom = { workspace = true, features = ["std"] } 15 | hex.workspace = true 16 | hex_fmt.workspace = true 17 | regex.workspace = true 18 | scale = { workspace = true, features = ["derive"] } 19 | schnorrkel.workspace = true 20 | serde.workspace = true 21 | serde-human-bytes.workspace = true 22 | serde_json.workspace = true 23 | sha2.workspace = true 24 | tokio = { workspace = true, features = ["full"] } 25 | tracing.workspace = true 26 | tracing-subscriber.workspace = true 27 | x25519-dalek.workspace = true 28 | 29 | dstack-kms-rpc.workspace = true 30 | ra-rpc = { workspace = true, features = ["client"] } 31 | ra-tls.workspace = true 32 | dstack-gateway-rpc.workspace = true 33 | tdx-attest.workspace = true 34 | host-api = { workspace = true, features = ["client"] } 35 | cmd_lib.workspace = true 36 | toml.workspace = true 37 | dcap-qvl.workspace = true 38 | k256 = { workspace = true, features = ["ecdsa"] } 39 | dstack-types.workspace = true 40 | rand.workspace = true 41 | sha3.workspace = true 42 | cert-client.workspace = true 43 | x509-parser.workspace = true 44 | serde_yaml2.workspace = true 45 | bollard.workspace = true 46 | sodiumbox.workspace = true 47 | 48 | [dev-dependencies] 49 | rand.workspace = true 50 | -------------------------------------------------------------------------------- /dstack-util/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use anyhow::{Context, Result}; 4 | use fs_err as fs; 5 | use serde::de::DeserializeOwned; 6 | 7 | pub use dstack_types::{AppCompose, AppKeys, KeyProviderKind, SysConfig}; 8 | 9 | pub fn deserialize_json_file(path: impl AsRef) -> Result { 10 | let data = fs::read_to_string(path).context("Failed to read file")?; 11 | serde_json::from_str(&data).context("Failed to parse json") 12 | } 13 | 14 | pub fn sha256(data: &[u8]) -> [u8; 32] { 15 | use sha2::Digest; 16 | let mut sha256 = sha2::Sha256::new(); 17 | sha256.update(data); 18 | sha256.finalize().into() 19 | } 20 | 21 | pub fn sha256_file(path: impl AsRef) -> Result<[u8; 32]> { 22 | let data = fs::read(path).context("Failed to read file")?; 23 | Ok(sha256(&data)) 24 | } 25 | -------------------------------------------------------------------------------- /gateway/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dstack-gateway" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | rocket = { workspace = true, features = ["mtls"] } 10 | tracing.workspace = true 11 | tracing-subscriber.workspace = true 12 | anyhow.workspace = true 13 | serde = { workspace = true, features = ["derive"] } 14 | ipnet = { workspace = true, features = ["serde"] } 15 | fs-err.workspace = true 16 | clap = { workspace = true, features = ["derive", "string"] } 17 | shared_child.workspace = true 18 | tokio = { workspace = true, features = ["full"] } 19 | rustls.workspace = true 20 | tokio-rustls = { workspace = true, features = ["ring"] } 21 | rinja.workspace = true 22 | hex.workspace = true 23 | parcelona.workspace = true 24 | hickory-resolver.workspace = true 25 | pin-project.workspace = true 26 | serde_json.workspace = true 27 | rand.workspace = true 28 | git-version.workspace = true 29 | ra-rpc = { workspace = true, features = ["client", "rocket"] } 30 | dstack-gateway-rpc.workspace = true 31 | certbot.workspace = true 32 | bytes.workspace = true 33 | safe-write.workspace = true 34 | smallvec.workspace = true 35 | futures.workspace = true 36 | cmd_lib.workspace = true 37 | load_config.workspace = true 38 | dstack-kms-rpc.workspace = true 39 | ra-tls.workspace = true 40 | dstack-guest-agent-rpc.workspace = true 41 | http-client = { workspace = true, features = ["prpc"] } 42 | sha2.workspace = true 43 | dstack-types.workspace = true 44 | serde-duration.workspace = true 45 | reqwest = { workspace = true, features = ["json"] } 46 | 47 | [target.'cfg(unix)'.dependencies] 48 | nix = { workspace = true, features = ["resource"] } 49 | 50 | [dev-dependencies] 51 | insta.workspace = true 52 | -------------------------------------------------------------------------------- /gateway/dstack-app/.gitignore: -------------------------------------------------------------------------------- 1 | /.app-compose.json 2 | /.prelaunch.sh 3 | /.env 4 | /.venv 5 | /.app_env 6 | -------------------------------------------------------------------------------- /gateway/dstack-app/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | gateway: 3 | build: 4 | context: . 5 | dockerfile_inline: | 6 | FROM rust:1.86.0@sha256:300ec56abce8cc9448ddea2172747d048ed902a3090e6b57babb2bf19f754081 7 | WORKDIR /src 8 | RUN apt-get update && apt-get install -y \ 9 | git build-essential libssl-dev protobuf-compiler \ 10 | libprotobuf-dev wireguard-tools iproute2 libclang-dev jq 11 | RUN git clone https://github.com/Dstack-TEE/dstack.git && \ 12 | cd dstack && \ 13 | git checkout ${GIT_REV} 14 | WORKDIR /src/dstack/gateway/dstack-app 15 | RUN cargo build --release -p dstack-gateway 16 | ENTRYPOINT ["/src/dstack/gateway/dstack-app/entrypoint.sh"] 17 | CMD ["/src/dstack/target/release/dstack-gateway", "-c", "/data/gateway/gateway.toml"] 18 | volumes: 19 | - /var/run/dstack.sock:/var/run/dstack.sock 20 | - /dstack:/dstack 21 | - data:/data 22 | network_mode: host 23 | privileged: true 24 | environment: 25 | - WG_ENDPOINT=${WG_ENDPOINT} 26 | - SRV_DOMAIN=${SRV_DOMAIN} 27 | - CF_API_TOKEN=${CF_API_TOKEN} 28 | - CF_ZONE_ID=${CF_ZONE_ID} 29 | - MY_URL=${MY_URL} 30 | - BOOTNODE_URL=${BOOTNODE_URL} 31 | - ACME_STAGING=${ACME_STAGING} 32 | - SUBNET_INDEX=${SUBNET_INDEX} 33 | - RUST_LOG=info,certbot=debug 34 | - PCCS_URL=${PCCS_URL} 35 | restart: always 36 | 37 | volumes: 38 | data: 39 | -------------------------------------------------------------------------------- /gateway/gateway.toml: -------------------------------------------------------------------------------- 1 | workers = 8 2 | max_blocking = 64 3 | ident = "Dstack Gateway" 4 | temp_dir = "/tmp" 5 | keep_alive = 10 6 | log_level = "info" 7 | port = 8010 8 | 9 | [core] 10 | kms_url = "" 11 | state_path = "./gateway-state.json" 12 | # auto set soft ulimit to hard ulimit 13 | set_ulimit = true 14 | rpc_domain = "" 15 | run_in_dstack = true 16 | 17 | [core.auth] 18 | enabled = false 19 | url = "http://localhost/app-auth" 20 | timeout = "5s" 21 | 22 | [core.admin] 23 | enabled = false 24 | port = 8011 25 | 26 | [core.certbot] 27 | enabled = false 28 | workdir = "/etc/certbot" 29 | acme_url = "https://acme-staging-v02.api.letsencrypt.org/directory" 30 | cf_api_token = "" 31 | cf_zone_id = "" 32 | auto_set_caa = true 33 | domain = "*.example.com" 34 | renew_interval = "1h" 35 | renew_before_expiration = "10d" 36 | renew_timeout = "120s" 37 | 38 | [core.wg] 39 | public_key = "" 40 | private_key = "" 41 | listen_port = 51820 42 | ip = "10.0.0.1/24" 43 | reserved_net = ["10.0.0.1/32"] 44 | client_ip_range = "10.0.0.0/25" 45 | config_path = "/etc/wireguard/wg0.conf" 46 | interface = "wg0" 47 | endpoint = "10.0.2.2:51820" 48 | 49 | [core.proxy] 50 | cert_chain = "/etc/rproxy/certs/cert.pem" 51 | cert_key = "/etc/rproxy/certs/key.pem" 52 | base_domain = "app.localhost" 53 | listen_addr = "0.0.0.0" 54 | listen_port = 8443 55 | agent_port = 8090 56 | buffer_size = 8192 57 | # number of hosts to try to connect to 58 | connect_top_n = 3 59 | localhost_enabled = false 60 | app_address_ns_prefix = "_dstack-app-address" 61 | 62 | [core.proxy.timeouts] 63 | # Timeout for establishing a connection to the target app. 64 | connect = "5s" 65 | # TLS-termination handshake timeout or SNI extraction timeout. 66 | handshake = "5s" 67 | 68 | # Timeout for top n hosts selection 69 | cache_top_n = "30s" 70 | 71 | # Enable data transfer timeouts below. This might impact performance. Turn off if 72 | # bad performance is observed. 73 | data_timeout_enabled = true 74 | # Timeout for a connection without any data transfer. 75 | idle = "10m" 76 | # Timeout for writing data to the target app or to the client. 77 | write = "5s" 78 | # Timeout for shutting down a connection. 79 | shutdown = "5s" 80 | # Timeout for total connection duration. 81 | total = "5h" 82 | 83 | [core.recycle] 84 | enabled = true 85 | interval = "5m" 86 | timeout = "10h" 87 | node_timeout = "10m" 88 | 89 | [core.sync] 90 | enabled = false 91 | interval = "30s" 92 | broadcast_interval = "10m" 93 | timeout = "2s" 94 | my_url = "https://localhost:8011" 95 | # The url of the bootnode used to join the network 96 | bootnode = "https://localhost:8011" 97 | -------------------------------------------------------------------------------- /gateway/rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dstack-gateway-rpc" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | prpc.workspace = true 10 | prost.workspace = true 11 | serde = { workspace = true, features = ["derive"] } 12 | serde_json.workspace = true 13 | anyhow.workspace = true 14 | scale = { workspace = true, features = ["derive"] } 15 | 16 | [build-dependencies] 17 | prpc-build.workspace = true 18 | -------------------------------------------------------------------------------- /gateway/rpc/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | prpc_build::configure() 3 | .out_dir("./src/generated") 4 | .mod_prefix("super::") 5 | .build_scale_ext(false) 6 | .disable_package_emission() 7 | .enable_serde_extension() 8 | .disable_service_name_emission() 9 | .compile_dir("./proto") 10 | .expect("failed to compile proto files"); 11 | } 12 | -------------------------------------------------------------------------------- /gateway/rpc/src/generated.rs: -------------------------------------------------------------------------------- 1 | pub use gateway::*; 2 | 3 | #[allow(async_fn_in_trait)] 4 | mod gateway; 5 | -------------------------------------------------------------------------------- /gateway/rpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | pub use generated::*; 4 | 5 | mod generated; 6 | -------------------------------------------------------------------------------- /gateway/src/admin_service.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use dstack_gateway_rpc::{ 3 | admin_server::{AdminRpc, AdminServer}, 4 | RenewCertResponse, 5 | }; 6 | use ra_rpc::{CallContext, RpcCall}; 7 | 8 | use crate::main_service::Proxy; 9 | 10 | pub struct AdminRpcHandler { 11 | state: Proxy, 12 | } 13 | 14 | impl AdminRpc for AdminRpcHandler { 15 | async fn exit(self) -> Result<()> { 16 | self.state.lock().exit(); 17 | } 18 | 19 | async fn renew_cert(self) -> Result { 20 | let bot = self.state.certbot.context("Certbot is not enabled")?; 21 | let renewed = bot.renew(true).await?; 22 | Ok(RenewCertResponse { renewed }) 23 | } 24 | 25 | async fn set_caa(self) -> Result<()> { 26 | let bot = self.state.certbot.context("Certbot is not enabled")?; 27 | bot.set_caa().await?; 28 | Ok(()) 29 | } 30 | } 31 | 32 | impl RpcCall for AdminRpcHandler { 33 | type PrpcService = AdminServer; 34 | 35 | fn construct(context: CallContext<'_, Proxy>) -> Result { 36 | Ok(AdminRpcHandler { 37 | state: context.state.clone(), 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /gateway/src/main_service/auth_client.rs: -------------------------------------------------------------------------------- 1 | use crate::config::AuthConfig; 2 | use anyhow::{Context, Result}; 3 | use ra_tls::attestation::AppInfo; 4 | use reqwest::Client; 5 | 6 | pub(crate) struct AuthClient { 7 | config: AuthConfig, 8 | client: Client, 9 | } 10 | 11 | impl AuthClient { 12 | pub(crate) fn new(config: AuthConfig) -> Self { 13 | Self { 14 | config, 15 | client: reqwest::Client::new(), 16 | } 17 | } 18 | 19 | pub(crate) async fn ensure_app_authorized(&self, app_info: &AppInfo) -> Result<()> { 20 | if !self.config.enabled { 21 | return Ok(()); 22 | } 23 | let req = self.client.post(&self.config.url).json(app_info).send(); 24 | let res = tokio::time::timeout(self.config.timeout, req) 25 | .await 26 | .context("Auth timeout")? 27 | .context("Failed to send request")?; 28 | res.error_for_status().context("Request failed")?; 29 | Ok(()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /gateway/src/main_service/snapshots/dstack_gateway__main_service__tests__config-2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: gateway/src/main_service/tests.rs 3 | assertion_line: 36 4 | expression: info1 5 | --- 6 | InstanceInfo { 7 | id: "test-id-1", 8 | app_id: "app-id-1", 9 | ip: 10.0.0.3, 10 | public_key: "test-pubkey-1", 11 | reg_time: SystemTime { 12 | tv_sec: 0, 13 | tv_nsec: 0, 14 | }, 15 | last_seen: SystemTime { 16 | tv_sec: 0, 17 | tv_nsec: 0, 18 | }, 19 | connections: 0, 20 | } 21 | -------------------------------------------------------------------------------- /gateway/src/main_service/snapshots/dstack_gateway__main_service__tests__config-3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: gateway/src/main_service/tests.rs 3 | assertion_line: 34 4 | expression: wg_config 5 | snapshot_kind: text 6 | --- 7 | [Interface] 8 | PrivateKey = 9 | ListenPort = 51820 10 | 11 | 12 | [Peer] 13 | PublicKey = test-pubkey-0 14 | AllowedIPs = 10.0.0.2/32 15 | PersistentKeepalive = 25 16 | 17 | [Peer] 18 | PublicKey = test-pubkey-1 19 | AllowedIPs = 10.0.0.3/32 20 | PersistentKeepalive = 25 21 | -------------------------------------------------------------------------------- /gateway/src/main_service/snapshots/dstack_gateway__main_service__tests__config.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: gateway/src/main_service/tests.rs 3 | assertion_line: 29 4 | expression: info 5 | --- 6 | InstanceInfo { 7 | id: "test-id-0", 8 | app_id: "app-id-0", 9 | ip: 10.0.0.2, 10 | public_key: "test-pubkey-0", 11 | reg_time: SystemTime { 12 | tv_sec: 0, 13 | tv_nsec: 0, 14 | }, 15 | last_seen: SystemTime { 16 | tv_sec: 0, 17 | tv_nsec: 0, 18 | }, 19 | connections: 0, 20 | } 21 | -------------------------------------------------------------------------------- /gateway/src/main_service/snapshots/dstack_gateway__main_service__tests__empty_config.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: gateway/src/main_service/tests.rs 3 | assertion_line: 14 4 | expression: wg_config 5 | snapshot_kind: text 6 | --- 7 | [Interface] 8 | PrivateKey = 9 | ListenPort = 51820 10 | -------------------------------------------------------------------------------- /gateway/src/main_service/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::config::{load_config_figment, Config}; 3 | 4 | async fn create_test_state() -> Proxy { 5 | let figment = load_config_figment(None); 6 | let config = figment.focus("core").extract::().unwrap(); 7 | Proxy::new(config, None) 8 | .await 9 | .expect("failed to create app state") 10 | } 11 | 12 | #[tokio::test] 13 | async fn test_empty_config() { 14 | let state = create_test_state().await; 15 | let wg_config = state.lock().generate_wg_config().unwrap(); 16 | insta::assert_snapshot!(wg_config); 17 | } 18 | 19 | #[tokio::test] 20 | async fn test_config() { 21 | let state = create_test_state().await; 22 | let mut info = state 23 | .lock() 24 | .new_client_by_id("test-id-0", "app-id-0", "test-pubkey-0") 25 | .unwrap(); 26 | 27 | info.reg_time = SystemTime::UNIX_EPOCH; 28 | info.last_seen = SystemTime::UNIX_EPOCH; 29 | insta::assert_debug_snapshot!(info); 30 | let mut info1 = state 31 | .lock() 32 | .new_client_by_id("test-id-1", "app-id-1", "test-pubkey-1") 33 | .unwrap(); 34 | info1.reg_time = SystemTime::UNIX_EPOCH; 35 | info1.last_seen = SystemTime::UNIX_EPOCH; 36 | insta::assert_debug_snapshot!(info1); 37 | let wg_config = state.lock().generate_wg_config().unwrap(); 38 | insta::assert_snapshot!(wg_config); 39 | } 40 | -------------------------------------------------------------------------------- /gateway/src/proxy/sni.rs: -------------------------------------------------------------------------------- 1 | use parcelona::parser_combinators::{Msg, PErr}; 2 | use parcelona::u8::*; 3 | use tracing::trace; 4 | 5 | pub fn extract_sni(b: &[u8]) -> Option<&[u8]> { 6 | extract_sni_inner(b).ok().map(|r| r.1) 7 | } 8 | 9 | fn extract_sni_inner(b: &[u8]) -> Result<(usize, &[u8]), PErr> { 10 | const HANDSHAKE_TYPE_CLIENT_HELLO: usize = 1; 11 | const EXTENSION_TYPE_SNI: usize = 0; 12 | const NAME_TYPE_HOST_NAME: usize = 0; 13 | 14 | let origin_len = b.len(); 15 | if origin_len < 10 { 16 | return Err(PErr::new(b)); 17 | } 18 | 19 | let b = &b[5..]; 20 | // Handshake message type. 21 | let (b, c) = take_len_be_u8(b)?; 22 | if c != HANDSHAKE_TYPE_CLIENT_HELLO { 23 | let err = PErr::new(b).user_msg_push(Msg::Str("HANDSHAKE_TYPE_CLIENT_HELLO error")); 24 | return Err(err); 25 | } 26 | 27 | // Handshake message length. 28 | let (b, c) = take_len_be_u24(b)?; 29 | trace!("1. message len {:?}", c); 30 | 31 | // ProtocolVersion (2 bytes) & random (32 bytes). 32 | let (b, _) = take_record(b, 34)?; 33 | 34 | // Session ID (u8-length vec), cipher suites (u16-length vec), compression methods (u8-length vec). 35 | let (b, _) = take_record_be_u8(b)?; 36 | let (b, _) = take_record_be_u16(b)?; 37 | let (b, _) = take_record_be_u8(b)?; 38 | 39 | // Extensions length. 40 | let (mut b, mut c) = take_len_be_u16(b)?; 41 | let mut ext_type: usize; 42 | let mut ext_leng: usize; 43 | trace!("3. Extensions length {:?}", c); 44 | loop { 45 | // Extension type & length. 46 | (b, ext_type) = take_len_be_u16(b)?; 47 | (b, ext_leng) = take_len_be_u16(b)?; 48 | 49 | trace!("4. Ext type (0) {:?} len {:?}", ext_type, ext_leng); 50 | if ext_type != EXTENSION_TYPE_SNI { 51 | if ext_leng > 0 { 52 | (b, _) = take_record(b, ext_leng)?; 53 | } 54 | continue; 55 | } 56 | // ServerNameList length. 57 | (b, c) = take_len_be_u16(b)?; 58 | trace!("5. ServerNameListmessag len {:?}", c); 59 | // ServerNameList. 60 | let mut sni: &[u8]; 61 | let mut name_type: usize; 62 | let mut name_leng: usize; 63 | loop { 64 | // NameType & length. 65 | (b, name_type) = take_len_be_u8(b)?; 66 | (b, name_leng) = take_len_be_u16(b)?; 67 | (b, sni) = take_record(b, name_leng)?; 68 | if name_type != NAME_TYPE_HOST_NAME { 69 | continue; 70 | } 71 | let sni_point: usize = origin_len - b.len(); 72 | trace!( 73 | "[sni] {:?} sni {:?}", 74 | sni_point, 75 | String::from_utf8_lossy(sni) 76 | ); 77 | return Ok((sni_point, sni)); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /gateway/src/web_routes.rs: -------------------------------------------------------------------------------- 1 | use crate::main_service::Proxy; 2 | use anyhow::Result; 3 | use rocket::{get, response::content::RawHtml, routes, Route, State}; 4 | 5 | mod route_index; 6 | 7 | #[get("/")] 8 | async fn index(state: &State) -> Result, String> { 9 | route_index::index(state).await.map_err(|e| format!("{e}")) 10 | } 11 | 12 | pub fn routes() -> Vec { 13 | routes![index] 14 | } 15 | -------------------------------------------------------------------------------- /gateway/src/web_routes/route_index.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | main_service::{Proxy, RpcHandler}, 3 | models::Dashboard, 4 | }; 5 | use anyhow::Context; 6 | use dstack_gateway_rpc::gateway_server::GatewayRpc; 7 | use ra_rpc::{CallContext, RpcCall}; 8 | use rinja::Template as _; 9 | use rocket::{response::content::RawHtml as Html, State}; 10 | 11 | pub async fn index(state: &State) -> anyhow::Result> { 12 | let context = CallContext::builder().state(&**state).build(); 13 | let rpc_handler = 14 | RpcHandler::construct(context.clone()).context("Failed to construct RpcHandler")?; 15 | let status = rpc_handler.status().await.context("Failed to get status")?; 16 | let rpc_handler = RpcHandler::construct(context).context("Failed to construct RpcHandler")?; 17 | let acme_info = rpc_handler 18 | .acme_info() 19 | .await 20 | .context("Failed to get ACME info")?; 21 | let model = Dashboard { status, acme_info }; 22 | let html = model.render().context("Failed to render template")?; 23 | Ok(Html(html)) 24 | } 25 | -------------------------------------------------------------------------------- /gateway/templates/rproxy.yaml: -------------------------------------------------------------------------------- 1 | servers: 2 | {%- for p in portmap %} 3 | - type: socket 4 | listen: {{ p.listen_addr }}:{{ p.listen_port }} 5 | handler: 6 | type: lazytls 7 | certificate: {{ cert_chain }} 8 | key: {{ cert_key }} 9 | sni: {% if peers.is_empty() -%} 10 | [] 11 | {% else -%} 12 | {% for peer in peers %} 13 | - hostname: {{ peer.id }}.{{ base_domain }} 14 | certificate: {{ cert_chain }} 15 | key: {{ cert_key }} 16 | handler: 17 | type: tunnel 18 | target: {{ peer.ip }}:{{ p.target_port }} 19 | {% endfor %} 20 | {%- endif %} 21 | {%- endfor %} -------------------------------------------------------------------------------- /gateway/templates/wg.conf: -------------------------------------------------------------------------------- 1 | [Interface] 2 | PrivateKey = {{ private_key }} 3 | ListenPort = {{ listen_port }} 4 | 5 | {% for peer in peers %} 6 | [Peer] 7 | PublicKey = {{ peer.public_key }} 8 | AllowedIPs = {{ peer.ip }}/32 9 | PersistentKeepalive = 25 10 | {% endfor %} -------------------------------------------------------------------------------- /guest-agent/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dstack-guest-agent" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | rocket.workspace = true 10 | tracing.workspace = true 11 | tracing-subscriber.workspace = true 12 | anyhow.workspace = true 13 | serde.workspace = true 14 | fs-err.workspace = true 15 | rcgen.workspace = true 16 | sha2.workspace = true 17 | clap.workspace = true 18 | tokio.workspace = true 19 | hex.workspace = true 20 | serde_json.workspace = true 21 | bollard.workspace = true 22 | chrono.workspace = true 23 | base64.workspace = true 24 | rinja.workspace = true 25 | git-version.workspace = true 26 | ra-rpc = { workspace = true, features = ["client", "rocket"] } 27 | dstack-guest-agent-rpc.workspace = true 28 | ra-tls.workspace = true 29 | tdx-attest.workspace = true 30 | guest-api = { workspace = true, features = ["client"] } 31 | host-api = { workspace = true, features = ["client"] } 32 | sysinfo.workspace = true 33 | default-net.workspace = true 34 | rocket-vsock-listener.workspace = true 35 | sd-notify.workspace = true 36 | reqwest.workspace = true 37 | cmd_lib.workspace = true 38 | figment = { workspace = true, features = ["json", "toml"] } 39 | load_config.workspace = true 40 | k256 = { workspace = true, features = ["ecdsa"] } 41 | dstack-types.workspace = true 42 | sha3.workspace = true 43 | strip-ansi-escapes.workspace = true 44 | cert-client.workspace = true 45 | ring.workspace = true 46 | -------------------------------------------------------------------------------- /guest-agent/dstack.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | workers = 8 3 | max_blocking = 64 4 | ident = "Dstack guest agent" 5 | temp_dir = "/tmp" 6 | keep_alive = 10 7 | log_level = "debug" 8 | 9 | [default.core] 10 | keys_file = "/dstack/.host-shared/.appkeys.json" 11 | compose_file = "/dstack/.host-shared/app-compose.json" 12 | sys_config_file = "/dstack/.host-shared/.sys-config.json" 13 | data_disks = ["/"] 14 | 15 | [default.core.simulator] 16 | enabled = false 17 | quote_file = "quote.hex" 18 | event_log_file = "eventlog.json" 19 | 20 | [internal-v0] 21 | address = "unix:/var/run/tappd.sock" 22 | reuse = true 23 | 24 | [internal] 25 | address = "unix:/var/run/dstack.sock" 26 | reuse = true 27 | 28 | [external] 29 | address = "0.0.0.0" 30 | port = 8090 31 | 32 | [guest-api] 33 | address = "vsock:0xffffffff" 34 | port = 8000 35 | -------------------------------------------------------------------------------- /guest-agent/rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dstack-guest-agent-rpc" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | prpc.workspace = true 10 | prost.workspace = true 11 | serde.workspace = true 12 | serde_json.workspace = true 13 | anyhow.workspace = true 14 | scale.workspace = true 15 | 16 | [build-dependencies] 17 | prpc-build.workspace = true 18 | -------------------------------------------------------------------------------- /guest-agent/rpc/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | prpc_build::configure() 3 | .out_dir("./src/generated") 4 | .mod_prefix("super::") 5 | .build_scale_ext(false) 6 | .disable_package_emission() 7 | .enable_serde_extension() 8 | .disable_service_name_emission() 9 | .compile_dir("./proto") 10 | .expect("failed to compile proto files"); 11 | } 12 | -------------------------------------------------------------------------------- /guest-agent/rpc/src/generated.rs: -------------------------------------------------------------------------------- 1 | pub use dstack_guest::*; 2 | 3 | #[allow(async_fn_in_trait)] 4 | mod dstack_guest; 5 | -------------------------------------------------------------------------------- /guest-agent/rpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | pub use generated::*; 4 | 5 | mod generated; 6 | -------------------------------------------------------------------------------- /guest-agent/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashSet, ops::Deref, path::PathBuf}; 2 | 3 | use dstack_types::AppCompose; 4 | use figment::Figment; 5 | use fs_err as fs; 6 | use load_config::load_config; 7 | use serde::{de::Error, Deserialize}; 8 | 9 | pub const DEFAULT_CONFIG: &str = include_str!("../dstack.toml"); 10 | 11 | pub fn load_config_figment(config_file: Option<&str>) -> Figment { 12 | load_config("dstack", DEFAULT_CONFIG, config_file, true) 13 | } 14 | 15 | #[derive(Debug, Clone, Copy, Deserialize)] 16 | pub struct BindAddr { 17 | pub port: u16, 18 | } 19 | 20 | #[derive(Debug, Clone)] 21 | pub struct AppComposeWrapper { 22 | pub app_compose: AppCompose, 23 | pub raw: String, 24 | } 25 | 26 | impl Deref for AppComposeWrapper { 27 | type Target = AppCompose; 28 | 29 | fn deref(&self) -> &Self::Target { 30 | &self.app_compose 31 | } 32 | } 33 | 34 | #[derive(Debug, Clone, Deserialize)] 35 | pub struct Config { 36 | pub keys_file: String, 37 | #[serde(deserialize_with = "deserialize_app_compose", flatten)] 38 | pub app_compose: AppComposeWrapper, 39 | pub sys_config_file: PathBuf, 40 | #[serde(default)] 41 | pub pccs_url: Option, 42 | pub simulator: Simulator, 43 | // List of disks to be shown in the dashboard 44 | pub data_disks: HashSet, 45 | } 46 | 47 | fn deserialize_app_compose<'de, D>(deserializer: D) -> Result 48 | where 49 | D: serde::Deserializer<'de>, 50 | { 51 | #[derive(Debug, Clone, Deserialize)] 52 | struct Config { 53 | compose_file: String, 54 | } 55 | 56 | let config = Config::deserialize(deserializer)?; 57 | let content = fs::read_to_string(&config.compose_file) 58 | .map_err(|e| D::Error::custom(format!("Failed to read compose file: {e}")))?; 59 | let app_compose = serde_json::from_str(&content) 60 | .map_err(|e| D::Error::custom(format!("Failed to parse compose file: {e}")))?; 61 | Ok(AppComposeWrapper { 62 | app_compose, 63 | raw: content, 64 | }) 65 | } 66 | 67 | #[derive(Debug, Clone, Deserialize)] 68 | pub struct Simulator { 69 | pub enabled: bool, 70 | pub quote_file: String, 71 | pub event_log_file: String, 72 | } 73 | -------------------------------------------------------------------------------- /guest-agent/src/models.rs: -------------------------------------------------------------------------------- 1 | use guest_api::{Container, SystemInfo}; 2 | use rinja::Template; 3 | 4 | mod filters { 5 | use anyhow::Result; 6 | 7 | pub fn cname<'a>(s: &'a Option<&'a String>) -> Result<&'a str, rinja::Error> { 8 | let name = s.map(|s| s.as_str()).unwrap_or_default(); 9 | Ok(name.strip_prefix("/").unwrap_or(name)) 10 | } 11 | 12 | pub fn hsize(s: &u64) -> Result { 13 | // convert bytes to human readable size 14 | let mut size = *s as f64; 15 | let units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; 16 | let mut unit_index = 0; 17 | while size >= 1024.0 && unit_index < units.len() - 1 { 18 | size /= 1024.0; 19 | unit_index += 1; 20 | } 21 | Ok(format!( 22 | "{:.2} {}", 23 | size, 24 | units.get(unit_index).unwrap_or(&"?") 25 | )) 26 | } 27 | 28 | pub fn hex(s: &[u8]) -> Result { 29 | Ok(hex::encode(s)) 30 | } 31 | } 32 | 33 | #[derive(Template)] 34 | #[template(path = "dashboard.html")] 35 | pub struct Dashboard { 36 | pub app_name: String, 37 | pub app_id: Vec, 38 | pub instance_id: Vec, 39 | pub device_id: Vec, 40 | pub key_provider_info: String, 41 | pub tcb_info: String, 42 | pub containers: Vec, 43 | pub system_info: SystemInfo, 44 | pub public_sysinfo: bool, 45 | pub public_logs: bool, 46 | pub public_tcbinfo: bool, 47 | } 48 | 49 | #[derive(Template)] 50 | #[template(path = "metrics.tpl", escape = "none")] 51 | pub struct Metrics { 52 | pub system_info: SystemInfo, 53 | } 54 | -------------------------------------------------------------------------------- /guest-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "guest-api" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | prpc.workspace = true 10 | prost.workspace = true 11 | serde = { workspace = true, features = ["derive"] } 12 | serde_json.workspace = true 13 | anyhow.workspace = true 14 | http-client = { workspace = true, optional = true, features = ["prpc"] } 15 | 16 | [build-dependencies] 17 | prpc-build.workspace = true 18 | 19 | [features] 20 | default = ["client"] 21 | client = ["dep:http-client"] 22 | -------------------------------------------------------------------------------- /guest-api/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | prpc_build::configure() 3 | .out_dir("./src/generated") 4 | .mod_prefix("super::") 5 | .build_scale_ext(false) 6 | .disable_service_name_emission() 7 | .disable_package_emission() 8 | .enable_serde_extension() 9 | .compile_dir("./proto") 10 | .expect("failed to compile proto files"); 11 | } 12 | -------------------------------------------------------------------------------- /guest-api/src/client.rs: -------------------------------------------------------------------------------- 1 | use crate::guest_api_client::GuestApiClient; 2 | use http_client::prpc::PrpcClient; 3 | 4 | pub type DefaultClient = GuestApiClient; 5 | 6 | pub fn new_client(base_url: String) -> DefaultClient { 7 | DefaultClient::new(PrpcClient::new(base_url)) 8 | } 9 | -------------------------------------------------------------------------------- /guest-api/src/generated/mod.rs: -------------------------------------------------------------------------------- 1 | pub use guest_api::*; 2 | 3 | #[allow(async_fn_in_trait)] 4 | mod guest_api; 5 | -------------------------------------------------------------------------------- /guest-api/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | pub use generated::*; 4 | 5 | mod generated; 6 | 7 | #[cfg(feature = "client")] 8 | pub mod client; 9 | -------------------------------------------------------------------------------- /host-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "host-api" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | prpc.workspace = true 10 | prost.workspace = true 11 | serde = { workspace = true, features = ["derive"] } 12 | serde_json.workspace = true 13 | anyhow.workspace = true 14 | http-client = { workspace = true, optional = true, features = ["prpc"] } 15 | 16 | [build-dependencies] 17 | prpc-build.workspace = true 18 | 19 | [features] 20 | default = ["client"] 21 | client = ["dep:http-client"] 22 | -------------------------------------------------------------------------------- /host-api/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | prpc_build::configure() 3 | .out_dir("./src/generated") 4 | .mod_prefix("super::") 5 | .build_scale_ext(false) 6 | .disable_service_name_emission() 7 | .disable_package_emission() 8 | .enable_serde_extension() 9 | .compile_dir("./proto") 10 | .expect("failed to compile proto files"); 11 | } 12 | -------------------------------------------------------------------------------- /host-api/proto/host_api.proto: -------------------------------------------------------------------------------- 1 | 2 | syntax = "proto3"; 3 | 4 | import "google/protobuf/empty.proto"; 5 | 6 | package host_api; 7 | 8 | message HostInfo { 9 | string name = 1; 10 | string version = 2; 11 | } 12 | 13 | message Notification { 14 | string event = 1; 15 | string payload = 2; 16 | } 17 | 18 | message GetSealingKeyRequest { 19 | bytes quote = 1; 20 | } 21 | 22 | message GetSealingKeyResponse { 23 | bytes encrypted_key = 1; 24 | bytes provider_quote = 2; 25 | } 26 | 27 | service HostApi { 28 | rpc Info(google.protobuf.Empty) returns (HostInfo); 29 | rpc Notify(Notification) returns (google.protobuf.Empty); 30 | rpc GetSealingKey(GetSealingKeyRequest) returns (GetSealingKeyResponse); 31 | } 32 | -------------------------------------------------------------------------------- /host-api/src/client.rs: -------------------------------------------------------------------------------- 1 | use crate::host_api_client::HostApiClient; 2 | use http_client::prpc::PrpcClient; 3 | 4 | pub type DefaultClient = HostApiClient; 5 | 6 | pub fn new_client(base_url: String) -> DefaultClient { 7 | DefaultClient::new(PrpcClient::new(base_url)) 8 | } 9 | -------------------------------------------------------------------------------- /host-api/src/generated/mod.rs: -------------------------------------------------------------------------------- 1 | pub use host_api::*; 2 | 3 | #[allow(async_fn_in_trait)] 4 | mod host_api; 5 | -------------------------------------------------------------------------------- /host-api/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | pub use generated::*; 4 | 5 | mod generated; 6 | 7 | #[cfg(feature = "client")] 8 | pub mod client; 9 | -------------------------------------------------------------------------------- /http-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-client" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | http-body-util.workspace = true 11 | hyper.workspace = true 12 | hyper-util.workspace = true 13 | hyperlocal.workspace = true 14 | log.workspace = true 15 | pin-project-lite = "0.2.15" 16 | prpc = { workspace = true, optional = true } 17 | reqwest.workspace = true 18 | serde.workspace = true 19 | tokio.workspace = true 20 | tokio-vsock.workspace = true 21 | tower-service = "0.3.3" 22 | 23 | [dev-dependencies] 24 | tokio = { workspace = true, features = ["full"] } 25 | 26 | [features] 27 | default = ["prpc"] 28 | prpc = ["dep:prpc"] 29 | -------------------------------------------------------------------------------- /http-client/src/prpc.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use prpc::{ 3 | client::{Error, RequestClient}, 4 | serde_json, Message, 5 | }; 6 | use serde::{de::DeserializeOwned, Serialize}; 7 | 8 | pub struct PrpcClient { 9 | base_url: String, 10 | path_append: String, 11 | } 12 | 13 | impl PrpcClient { 14 | pub fn new(base_url: String) -> Self { 15 | Self { 16 | base_url, 17 | path_append: String::new(), 18 | } 19 | } 20 | 21 | pub fn new_unix(socket_path: String, mut path: String) -> Self { 22 | if !path.ends_with('/') { 23 | path.push('/'); 24 | } 25 | Self { 26 | base_url: format!("unix:{socket_path}"), 27 | path_append: path, 28 | } 29 | } 30 | } 31 | 32 | impl RequestClient for PrpcClient { 33 | async fn request(&self, path: &str, body: T) -> Result 34 | where 35 | T: Message + Serialize, 36 | R: Message + DeserializeOwned, 37 | { 38 | let body = serde_json::to_vec(&body).context("Failed to serialize body")?; 39 | let path = format!("{}{path}?json", self.path_append); 40 | let (status, body) = super::http_request("POST", &self.base_url, &path, &body).await?; 41 | if status != 200 { 42 | anyhow::bail!("Invalid status code: {status}, path={path}"); 43 | } 44 | let response = serde_json::from_slice(&body).context("Failed to deserialize response")?; 45 | Ok(response) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /iohash/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iohash" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | blake2.workspace = true 11 | clap = { workspace = true, features = ["derive"] } 12 | fs-err.workspace = true 13 | hex_fmt.workspace = true 14 | sha2.workspace = true 15 | sha3.workspace = true 16 | -------------------------------------------------------------------------------- /key-provider-build/Dockerfile.aesmd: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | # Prevent timezone prompt 4 | ENV DEBIAN_FRONTEND=noninteractive \ 5 | TZ=Etc/UTC \ 6 | TZDATA=Etc/UTC \ 7 | LC_ALL=en_US.UTF-8 \ 8 | LANG=en_US.UTF-8 9 | 10 | # Set timezone 11 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 12 | 13 | # Install AESM service and dependencies 14 | RUN apt-get update && apt-get install -y \ 15 | wget curl gnupg2 \ 16 | && rm -rf /var/lib/apt/lists/* 17 | 18 | # Add Intel SGX repository 19 | RUN echo 'deb [arch=amd64] https://download.01.org/intel-sgx/sgx_repo/ubuntu focal main' > /etc/apt/sources.list.d/intel-sgx.list \ 20 | && wget -qO - https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | apt-key add - 21 | 22 | # Install SGX AESM service and plugins 23 | RUN apt-get update && apt-get install -y \ 24 | sgx-aesm-service \ 25 | libsgx-aesm-launch-plugin \ 26 | libsgx-aesm-quote-ex-plugin \ 27 | libsgx-aesm-ecdsa-plugin \ 28 | libsgx-dcap-quote-verify \ 29 | libsgx-dcap-default-qpl \ 30 | psmisc \ 31 | && rm -rf /var/lib/apt/lists/* 32 | 33 | # Create an entrypoint script for AESM 34 | COPY entrypoint-aesmd.sh /entrypoint.sh 35 | RUN chmod +x /entrypoint.sh 36 | 37 | ENTRYPOINT ["/entrypoint.sh"] -------------------------------------------------------------------------------- /key-provider-build/Dockerfile.key-provider: -------------------------------------------------------------------------------- 1 | FROM gramineproject/gramine:v1.5@sha256:615849089db84477f03cd13209c08eaf6b6a3a68b4df733e097db56781935589 2 | 3 | RUN apt-get update && apt-get install -y \ 4 | git=1:2.25.1-1ubuntu3.14 \ 5 | build-essential=12.8ubuntu1.1 \ 6 | && rm -rf /var/lib/apt/lists/* 7 | 8 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain 1.80 -y 9 | ENV PATH="/root/.cargo/bin:${PATH}" 10 | 11 | # Set environment variables 12 | ENV SGX=1 13 | ENV DEBUG=0 14 | ENV DEV_MODE=0 15 | ENV GRAMINE=gramine-sgx 16 | ENV RUST_LOG=info 17 | 18 | # Clone and checkout the repository 19 | RUN git clone https://github.com/MoeMahhouk/gramine-sealing-key-provider && \ 20 | cd gramine-sealing-key-provider && \ 21 | git checkout 6b4658d3d2158615b6c78879ad61612885372712 22 | 23 | WORKDIR /gramine-sealing-key-provider 24 | COPY Cargo.lock . 25 | 26 | # Build key provider, generate keys, and build manifest 27 | RUN make target/release/gramine-sealing-key-provider && \ 28 | gramine-sgx-gen-private-key && \ 29 | make RUST_LOG=info 30 | 31 | RUN gramine-sgx-sigstruct-view --output-format json gramine-sealing-key-provider.sig 32 | 33 | # Create an entrypoint script for the key provider only 34 | COPY entrypoint-key-provider.sh /entrypoint.sh 35 | RUN chmod +x /entrypoint.sh 36 | 37 | ENTRYPOINT ["/entrypoint.sh"] -------------------------------------------------------------------------------- /key-provider-build/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | x-common: &common-config 2 | restart: always 3 | logging: 4 | driver: "json-file" 5 | options: 6 | max-size: "100m" 7 | max-file: "5" 8 | 9 | services: 10 | aesmd: 11 | <<: *common-config 12 | container_name: aesmd 13 | build: 14 | context: . 15 | dockerfile: Dockerfile.aesmd 16 | privileged: true 17 | devices: 18 | - "/dev/sgx_enclave:/dev/sgx_enclave" 19 | - "/dev/sgx_provision:/dev/sgx_provision" 20 | volumes: 21 | - "./sgx_default_qcnl.conf:/etc/sgx_default_qcnl.conf" 22 | - "aesmd:/var/run/aesmd/" 23 | network_mode: "host" 24 | 25 | gramine-sealing-key-provider: 26 | <<: *common-config 27 | container_name: gramine-sealing-key-provider 28 | build: 29 | context: . 30 | dockerfile: Dockerfile.key-provider 31 | privileged: true 32 | devices: 33 | - "/dev/sgx_enclave:/dev/sgx_enclave" 34 | - "/dev/sgx_provision:/dev/sgx_provision" 35 | depends_on: 36 | - aesmd 37 | volumes: 38 | - "aesmd:/var/run/aesmd/" 39 | ports: 40 | - "127.0.0.1:3443:3443" 41 | 42 | volumes: 43 | aesmd: 44 | -------------------------------------------------------------------------------- /key-provider-build/entrypoint-aesmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Starting AESM service..." 3 | # Make sure the AESM directory exists with proper permissions 4 | mkdir -p /var/run/aesmd 5 | chmod 755 /var/run/aesmd 6 | 7 | # Start the AESM service 8 | export AESM_PATH=/opt/intel/sgx-aesm-service/aesm 9 | export LD_LIBRARY_PATH=/opt/intel/sgx-aesm-service/aesm 10 | /opt/intel/sgx-aesm-service/aesm/aesm_service --no-daemon 11 | -------------------------------------------------------------------------------- /key-provider-build/entrypoint-key-provider.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Waiting for AESM socket to be available..." 3 | AESM_SOCKET="/var/run/aesmd/aesm.socket" 4 | for i in $(seq 1 30); do 5 | if [ -S "$AESM_SOCKET" ]; then 6 | echo "AESM socket is available." 7 | break 8 | fi 9 | echo "Waiting for AESM socket ($i/30)..." 10 | sleep 1 11 | done 12 | 13 | if [ ! -S "$AESM_SOCKET" ]; then 14 | echo "Error: AESM socket is not available after 30 seconds." 15 | exit 1 16 | fi 17 | 18 | echo "Enclave info:" 19 | gramine-sgx-sigstruct-view --output-format json gramine-sealing-key-provider.sig 20 | 21 | echo "Starting Gramine Sealing Key Provider" 22 | make SGX=1 run-provider -------------------------------------------------------------------------------- /key-provider-build/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Starting all SGX services using docker-compose..." 4 | docker compose up --build -d 5 | 6 | echo "==========================" 7 | echo "Services started!" 8 | echo "==========================" 9 | echo "Key provider endpoint: https://localhost:3443" 10 | echo " - Using shared socket with AESM service" 11 | echo " - Socket location: /var/run/aesmd/aesm.socket" 12 | echo 13 | echo "Check logs with:" 14 | echo " docker compose logs -f aesmd" 15 | echo " docker compose logs -f gramine-sealing-key-provider" 16 | echo "==========================" -------------------------------------------------------------------------------- /key-provider-build/sgx_default_qcnl.conf: -------------------------------------------------------------------------------- 1 | { 2 | "pccs_url": "https://127.0.0.1:8081/sgx/certification/v4/", 3 | "use_secure_cert": false, 4 | "retry_times": 6, 5 | "retry_delay": 10, 6 | "pck_cache_expire_hours": 168, 7 | "verify_collateral_cache_expire_hours": 168, 8 | "local_cache_only": false 9 | } -------------------------------------------------------------------------------- /key-provider-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "key-provider-client" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | serde = { workspace = true, features = ["derive"] } 11 | serde_json.workspace = true 12 | tokio = { workspace = true, features = ["net", "io-util"] } 13 | -------------------------------------------------------------------------------- /key-provider-client/src/host.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context, Result}; 2 | use serde::{Deserialize, Serialize}; 3 | use std::net::IpAddr; 4 | use tokio::{ 5 | io::{AsyncReadExt, AsyncWriteExt}, 6 | net::TcpStream, 7 | }; 8 | 9 | #[derive(Serialize, Deserialize)] 10 | struct QuoteRequest<'a> { 11 | quote: &'a [u8], 12 | } 13 | 14 | #[derive(Serialize, Deserialize, Debug)] 15 | pub struct QuoteResponse { 16 | pub encrypted_key: Vec, 17 | pub provider_quote: Vec, 18 | } 19 | 20 | pub async fn get_key(quote: Vec, address: IpAddr, port: u16) -> Result { 21 | if quote.len() > 1024 * 1024 { 22 | bail!("Quote is too long"); 23 | } 24 | let mut tcp_stream = TcpStream::connect((address, port)) 25 | .await 26 | .context("Failed to connect to key provider")?; 27 | let payload = QuoteRequest { quote: "e }; 28 | let serialized = serde_json::to_vec(&payload)?; 29 | let length = serialized.len() as u32; 30 | tcp_stream 31 | .write_all(&length.to_be_bytes()) 32 | .await 33 | .context("Failed to write length")?; 34 | tcp_stream 35 | .write_all(&serialized) 36 | .await 37 | .context("Failed to write payload")?; 38 | 39 | let mut response_length = [0; 4]; 40 | tcp_stream 41 | .read_exact(&mut response_length) 42 | .await 43 | .context("Failed to read response length")?; 44 | let response_length = u32::from_be_bytes(response_length); 45 | let mut response = vec![0; response_length as usize]; 46 | tcp_stream 47 | .read_exact(&mut response) 48 | .await 49 | .context("Failed to read response")?; 50 | let response: QuoteResponse = 51 | serde_json::from_slice(&response).context("Failed to deserialize response")?; 52 | Ok(response) 53 | } 54 | -------------------------------------------------------------------------------- /key-provider-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod host; 2 | -------------------------------------------------------------------------------- /kms/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dstack-kms" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | chrono.workspace = true 11 | clap.workspace = true 12 | fs-err.workspace = true 13 | git-version.workspace = true 14 | hex.workspace = true 15 | hex_fmt.workspace = true 16 | rocket.workspace = true 17 | serde.workspace = true 18 | tracing.workspace = true 19 | tracing-subscriber.workspace = true 20 | x25519-dalek.workspace = true 21 | yasna.workspace = true 22 | 23 | dstack-kms-rpc.workspace = true 24 | ra-rpc = { workspace = true, features = ["client", "rocket"] } 25 | ra-tls.workspace = true 26 | load_config.workspace = true 27 | serde-human-bytes.workspace = true 28 | reqwest = { workspace = true, features = ["json"] } 29 | sha2.workspace = true 30 | sha3.workspace = true 31 | k256.workspace = true 32 | rand.workspace = true 33 | dstack-guest-agent-rpc.workspace = true 34 | http-client = { workspace = true, features = ["prpc"] } 35 | scale.workspace = true 36 | x509-parser = { workspace = true, features = ["verify"] } 37 | ring.workspace = true 38 | safe-write.workspace = true 39 | serde_json.workspace = true 40 | dstack-types.workspace = true 41 | tokio = { workspace = true, features = ["process"] } 42 | tempfile.workspace = true 43 | serde-duration.workspace = true 44 | 45 | [features] 46 | default = [] 47 | -------------------------------------------------------------------------------- /kms/auth-eth/.gitignore: -------------------------------------------------------------------------------- 1 | /artifacts 2 | /cache 3 | /dist 4 | -------------------------------------------------------------------------------- /kms/auth-eth/contracts/IAppAuth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IAppAuth { 5 | struct AppBootInfo { 6 | address appId; 7 | bytes32 composeHash; 8 | address instanceId; 9 | bytes32 deviceId; 10 | bytes32 mrAggregated; 11 | bytes32 mrSystem; 12 | bytes32 osImageHash; 13 | string tcbStatus; 14 | string[] advisoryIds; 15 | } 16 | 17 | function isAppAllowed( 18 | AppBootInfo calldata bootInfo 19 | ) external view returns (bool isAllowed, string memory reason); 20 | } 21 | -------------------------------------------------------------------------------- /kms/auth-eth/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | roots: ['/src', '/test'], 5 | testMatch: ['**/*.test.ts'], 6 | moduleFileExtensions: ['ts', 'js', 'json', 'node'], 7 | collectCoverageFrom: [ 8 | 'src/**/*.ts', 9 | '!src/**/*.d.ts' 10 | ], 11 | coverageDirectory: 'coverage', 12 | verbose: true, 13 | setupFilesAfterEnv: ['/test/setup.ts'] 14 | }; 15 | -------------------------------------------------------------------------------- /kms/auth-eth/jest.integration.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | testMatch: ['**/*.integration.test.ts'], 6 | setupFilesAfterEnv: ['/test/setup.ts'], 7 | testTimeout: 30000, // Increase timeout for blockchain operations 8 | }; 9 | -------------------------------------------------------------------------------- /kms/auth-eth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth-eth", 3 | "version": "1.0.0", 4 | "description": "DStack KMS Ethereum Backend", 5 | "main": "dist/main.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "start": "node dist/main.js", 9 | "dev": "ts-node src/main.ts", 10 | "test": "jest", 11 | "test:watch": "jest --watch", 12 | "test:coverage": "jest --coverage", 13 | "test:integration": "jest -c jest.integration.config.js" 14 | }, 15 | "dependencies": { 16 | "@fastify/swagger": "^8.12.0", 17 | "@fastify/swagger-ui": "^2.0.1", 18 | "@openzeppelin/contracts-upgradeable": "^5.2.0", 19 | "dotenv": "^16.3.1", 20 | "ethers": "^6.9.0", 21 | "fastify": "^4.24.3", 22 | "yargs": "^17.7.2" 23 | }, 24 | "devDependencies": { 25 | "@nomicfoundation/hardhat-ethers": "^3.0.8", 26 | "@nomicfoundation/hardhat-toolbox": "^4.0.0", 27 | "@openzeppelin/hardhat-upgrades": "^3.9.0", 28 | "@types/jest": "^29.5.14", 29 | "@types/node": "^20.17.12", 30 | "@types/supertest": "^6.0.2", 31 | "hardhat": "^2.22.17", 32 | "jest": "^29.7.0", 33 | "supertest": "^6.3.3", 34 | "ts-jest": "^29.1.1", 35 | "ts-node": "^10.9.1", 36 | "typescript": "^5.3.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /kms/auth-eth/run-tests.sh: -------------------------------------------------------------------------------- 1 | npm run test 2 | -------------------------------------------------------------------------------- /kms/auth-eth/scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 2 | import * as helpers from "../lib/deployment-helpers"; 3 | 4 | // This function should be called directly by Hardhat tasks 5 | export async function deployContract(hre: HardhatRuntimeEnvironment, contractName: string, initializerArgs: any[] = [], quiet: boolean = false) { 6 | try { 7 | function log(...msgs: any[]) { 8 | if (!quiet) { 9 | console.log(...msgs); 10 | } 11 | } 12 | 13 | log(`Starting ${contractName} deployment process...`); 14 | 15 | if (!quiet) { 16 | // Get network info 17 | await helpers.logNetworkInfo(hre); 18 | } 19 | 20 | log("Getting contract factory..."); 21 | const contractFactory = await hre.ethers.getContractFactory(contractName); 22 | 23 | if (!quiet) { 24 | // Estimate gas for deployment 25 | await helpers.estimateDeploymentCost( 26 | hre, 27 | contractName, 28 | initializerArgs 29 | ); 30 | 31 | // Prompt for confirmation 32 | if (!(await helpers.confirmAction('Do you want to proceed with deployment?'))) { 33 | log('Deployment cancelled'); 34 | return; 35 | } 36 | } 37 | 38 | // Deploy using proxy pattern 39 | log("Deploying proxy..."); 40 | const contract = await hre.upgrades.deployProxy(contractFactory, 41 | initializerArgs, 42 | { kind: 'uups' } 43 | ); 44 | log("Waiting for deployment..."); 45 | await contract.waitForDeployment(); 46 | 47 | const address = await contract.getAddress(); 48 | log(`${contractName} Proxy deployed to:`, address); 49 | 50 | // Verify deployment 51 | await helpers.verifyDeployment(hre, address, quiet); 52 | 53 | const tx = await contract.deploymentTransaction(); 54 | log("Deployment completed successfully"); 55 | log("Transaction hash:", tx?.hash); 56 | 57 | return contract; 58 | } catch (error) { 59 | console.error("Error during deployment:", error); 60 | throw error; 61 | } 62 | } 63 | 64 | // For backward compatibility when running the script directly 65 | async function main() { 66 | const hre = require("hardhat"); 67 | const deployer = await helpers.getSigner(hre); 68 | const address = await deployer.getAddress(); 69 | console.log("Deploying with account:", address); 70 | console.log("Account balance:", await helpers.accountBalance(hre.ethers, address)); 71 | await deployContract(hre, "KmsAuth", [address]); 72 | } 73 | 74 | // Only execute if directly run 75 | if (require.main === module) { 76 | main().catch((error) => { 77 | console.error(error); 78 | process.exitCode = 1; 79 | }); 80 | } -------------------------------------------------------------------------------- /kms/auth-eth/scripts/upgrade.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 2 | import * as helpers from "../lib/deployment-helpers"; 3 | 4 | // This function can be called directly by Hardhat tasks 5 | export async function upgradeContract( 6 | hre: HardhatRuntimeEnvironment, 7 | contractName: string, 8 | proxyAddress?: string, 9 | dryRun: boolean = false 10 | ) { 11 | try { 12 | if (!proxyAddress) { 13 | throw new Error("Proxy address is required but was not provided"); 14 | } 15 | 16 | console.log(`Preparing to upgrade ${contractName} at ${proxyAddress}...`); 17 | console.log(`Mode: ${dryRun ? "Dry Run (simulation only)" : "Live Upgrade"}`); 18 | 19 | // Get network info to confirm we're on the right network 20 | await helpers.logNetworkInfo(hre); 21 | 22 | // Prepare the upgrade 23 | const { 24 | newImplementationAddress, 25 | upgradeTx 26 | } = await helpers.prepareContractUpgrade(hre, proxyAddress, contractName, "uups"); 27 | 28 | if (dryRun) { 29 | console.log("Upgrade transaction data:", upgradeTx); 30 | return { 31 | proxyAddress, 32 | newImplementationAddress, 33 | upgradeTx 34 | }; 35 | } else { 36 | // Estimate the gas cost 37 | await helpers.estimateUpgradeCost(hre, proxyAddress, upgradeTx); 38 | 39 | // Confirm the upgrade 40 | const confirmed = await helpers.confirmAction(`Are you sure you want to upgrade ${contractName}?`); 41 | if (!confirmed) { 42 | console.log("Upgrade cancelled"); 43 | return; 44 | } 45 | 46 | console.log("Executing upgrade..."); 47 | // Execute the upgrade 48 | const upgraded = await helpers.executeContractUpgrade( 49 | hre, 50 | proxyAddress, 51 | contractName, 52 | "uups" 53 | ); 54 | 55 | return upgraded; 56 | } 57 | } catch (error) { 58 | console.error("Error during upgrade:", error); 59 | throw error; 60 | } 61 | } 62 | 63 | // For backward compatibility when running the script directly 64 | async function main() { 65 | const hre = require("hardhat"); 66 | try { 67 | const proxyAddress = process.env.PROXY_ADDRESS; 68 | const dryRun = process.env.DRY_RUN === "true"; 69 | const contractName = process.env.CONTRACT_NAME || "KmsAuth"; 70 | await upgradeContract(hre, contractName, proxyAddress, dryRun); 71 | } catch (error) { 72 | console.error(error); 73 | process.exitCode = 1; 74 | } 75 | } 76 | 77 | // Only execute if directly run 78 | if (require.main === module) { 79 | main().catch((error) => { 80 | console.error(error); 81 | process.exitCode = 1; 82 | }); 83 | } -------------------------------------------------------------------------------- /kms/auth-eth/scripts/verify.ts: -------------------------------------------------------------------------------- 1 | import { run } from "hardhat"; 2 | 3 | async function main() { 4 | const PROXY_ADDRESS = "0xda1d4bc372FE139d63b85f6160D2F849fFed9c10"; 5 | 6 | try { 7 | // Verify the proxy contract 8 | console.log("\nVerifying proxy contract..."); 9 | await run("verify:verify", { 10 | address: PROXY_ADDRESS, 11 | constructorArguments: [], 12 | }); 13 | 14 | console.log("\nVerification completed successfully!"); 15 | } catch (error) { 16 | console.error("Error during verification:", error); 17 | process.exitCode = 1; 18 | } 19 | } 20 | 21 | main().catch((error) => { 22 | console.error(error); 23 | process.exitCode = 1; 24 | }); -------------------------------------------------------------------------------- /kms/auth-eth/src/ethereum.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import { BootInfo, BootResponse } from './types'; 3 | import { KmsAuth__factory } from '../typechain-types/factories/contracts/KmsAuth__factory'; 4 | import { KmsAuth } from '../typechain-types/contracts/KmsAuth'; 5 | import { HardhatEthersProvider } from '@nomicfoundation/hardhat-ethers/internal/hardhat-ethers-provider'; 6 | 7 | export class EthereumBackend { 8 | private provider: ethers.JsonRpcProvider | HardhatEthersProvider; 9 | private kmsAuth: KmsAuth; 10 | 11 | constructor(provider: ethers.JsonRpcProvider | HardhatEthersProvider, kmsAuthAddr: string) { 12 | this.provider = provider; 13 | this.kmsAuth = KmsAuth__factory.connect( 14 | ethers.getAddress(kmsAuthAddr), 15 | this.provider 16 | ); 17 | } 18 | 19 | private decodeHex(hex: string, sz: number = 32): string { 20 | // Remove '0x' prefix if present 21 | hex = hex.startsWith('0x') ? hex.slice(2) : hex; 22 | 23 | // Pad hex string to 64 characters (32 bytes) 24 | hex = hex.padStart(sz * 2, '0'); 25 | 26 | // Add '0x' prefix back 27 | return '0x' + hex; 28 | } 29 | 30 | async checkBoot(bootInfo: BootInfo, isKms: boolean): Promise { 31 | // Create boot info struct for contract call 32 | const bootInfoStruct = { 33 | appId: this.decodeHex(bootInfo.appId, 20), 34 | instanceId: this.decodeHex(bootInfo.instanceId, 20), 35 | composeHash: this.decodeHex(bootInfo.composeHash, 32), 36 | deviceId: this.decodeHex(bootInfo.deviceId, 32), 37 | mrSystem: this.decodeHex(bootInfo.mrSystem, 32), 38 | mrAggregated: this.decodeHex(bootInfo.mrAggregated, 32), 39 | osImageHash: this.decodeHex(bootInfo.osImageHash, 32), 40 | tcbStatus: bootInfo.tcbStatus, 41 | advisoryIds: bootInfo.advisoryIds 42 | }; 43 | let response; 44 | if (isKms) { 45 | response = await this.kmsAuth.isKmsAllowed(bootInfoStruct); 46 | } else { 47 | response = await this.kmsAuth.isAppAllowed(bootInfoStruct); 48 | } 49 | const [isAllowed, reason] = response; 50 | const gatewayAppId = await this.kmsAuth.gatewayAppId(); 51 | return { 52 | isAllowed, 53 | reason, 54 | gatewayAppId, 55 | } 56 | } 57 | 58 | async getGatewayAppId(): Promise { 59 | return await this.kmsAuth.gatewayAppId(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /kms/auth-eth/src/main.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv'; 2 | import { build } from './server'; 3 | 4 | // Load environment variables 5 | config(); 6 | 7 | async function main() { 8 | try { 9 | const port = process.env.PORT ? parseInt(process.env.PORT) : 8000; 10 | const host = process.env.HOST || '127.0.0.1'; 11 | 12 | const server = await build(); 13 | await server.listen({ port, host }); 14 | console.log(`Server listening on ${host}:${port}`); 15 | 16 | // Handle graceful shutdown 17 | const signals = ['SIGINT', 'SIGTERM']; 18 | for (const signal of signals) { 19 | process.on(signal, async () => { 20 | try { 21 | await server.close(); 22 | process.exit(0); 23 | } catch (err) { 24 | console.error('Error during shutdown:', err); 25 | process.exit(1); 26 | } 27 | }); 28 | } 29 | } catch (err) { 30 | console.error('Error starting server:', err); 31 | process.exit(1); 32 | } 33 | } 34 | 35 | main(); 36 | -------------------------------------------------------------------------------- /kms/auth-eth/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface BootInfo { 2 | tcbStatus: string; 3 | advisoryIds: string[]; 4 | mrAggregated: string; 5 | mrSystem: string; 6 | osImageHash: string; 7 | appId: string; 8 | composeHash: string; 9 | instanceId: string; 10 | deviceId: string; 11 | } 12 | 13 | export interface BootResponse { 14 | isAllowed: boolean; 15 | gatewayAppId: string; 16 | reason: string; 17 | } 18 | 19 | // Removed KMS_CONTRACT_ABI and APP_CONTRACT_ABI since we're using typechain types now 20 | -------------------------------------------------------------------------------- /kms/auth-eth/test/setup.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; 2 | import hre from "hardhat"; 3 | import { ethers } from "hardhat"; 4 | import { KmsAuth } from "../typechain-types/contracts/KmsAuth"; 5 | import { AppAuth } from "../typechain-types/contracts/AppAuth"; 6 | import { deployContract } from "../scripts/deploy"; 7 | import { BootInfo } from "src/types"; 8 | 9 | declare global { 10 | var testContracts: { 11 | kmsAuth: KmsAuth; 12 | appAuth: AppAuth; 13 | appId: string; 14 | owner: SignerWithAddress; 15 | }; 16 | } 17 | 18 | beforeAll(async () => { 19 | 20 | // Get signers 21 | const [owner] = await ethers.getSigners(); 22 | 23 | // Deploy contracts 24 | const kmsAuth = await deployContract(hre, "KmsAuth", [owner.address], true) as KmsAuth; 25 | 26 | // Initialize the contract with an app and KMS info 27 | const appId = await kmsAuth.nextAppId(); 28 | 29 | const appAuth = await deployContract(hre, "AppAuth", [owner.address, appId, false, true], true) as AppAuth; 30 | 31 | await kmsAuth.registerApp(await appAuth.getAddress()); 32 | 33 | // Set up KMS info with the generated app ID 34 | await kmsAuth.setKmsInfo({ 35 | quote: ethers.encodeBytes32String("1234"), 36 | caPubkey: ethers.encodeBytes32String("test-ca-pubkey"), 37 | k256Pubkey: ethers.encodeBytes32String("test-k256-pubkey"), 38 | eventlog: ethers.encodeBytes32String("test-eventlog") 39 | }); 40 | 41 | const mockBootInfo: BootInfo = { 42 | appId, 43 | instanceId: ethers.encodeBytes32String("test-instance-id"), 44 | composeHash: ethers.encodeBytes32String("test-compose-hash"), 45 | deviceId: ethers.encodeBytes32String("test-device-id"), 46 | mrSystem: ethers.encodeBytes32String("test-mr-system"), 47 | mrAggregated: ethers.encodeBytes32String("test-mr-aggregated"), 48 | osImageHash: ethers.encodeBytes32String("test-os-image-hash"), 49 | tcbStatus: "UpToDate", 50 | advisoryIds: [] 51 | }; 52 | // Register some test enclaves and images 53 | await kmsAuth.addKmsAggregatedMr(ethers.encodeBytes32String("11")); 54 | await kmsAuth.addOsImageHash(ethers.encodeBytes32String("22")); 55 | await appAuth.addComposeHash(ethers.encodeBytes32String("33")); 56 | 57 | // Set up global test contracts 58 | global.testContracts = { 59 | kmsAuth, 60 | appAuth, 61 | appId, 62 | owner 63 | }; 64 | }); 65 | -------------------------------------------------------------------------------- /kms/auth-eth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "lib": ["es2020"], 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "outDir": "./dist", 11 | "rootDir": ".", 12 | "resolveJsonModule": true, 13 | "types": ["node", "jest", "hardhat"], 14 | "baseUrl": "." 15 | }, 16 | "include": ["src/**/*", "test/**/*", "hardhat.config.ts", "typechain-types/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts-upgradeable/access/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export type { OwnableUpgradeable } from "./OwnableUpgradeable"; 5 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts-upgradeable/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import type * as access from "./access"; 5 | export type { access }; 6 | import type * as proxy from "./proxy"; 7 | export type { proxy }; 8 | import type * as utils from "./utils"; 9 | export type { utils }; 10 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts-upgradeable/proxy/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import type * as utils from "./utils"; 5 | export type { utils }; 6 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts-upgradeable/proxy/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export type { Initializable } from "./Initializable"; 5 | export type { UUPSUpgradeable } from "./UUPSUpgradeable"; 6 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts-upgradeable/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export type { ContextUpgradeable } from "./ContextUpgradeable"; 5 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import type * as interfaces from "./interfaces"; 5 | export type { interfaces }; 6 | import type * as proxy from "./proxy"; 7 | export type { proxy }; 8 | import type * as utils from "./utils"; 9 | export type { utils }; 10 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts/interfaces/draft-IERC1822.sol/IERC1822Proxiable.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import type { 5 | BaseContract, 6 | BytesLike, 7 | FunctionFragment, 8 | Result, 9 | Interface, 10 | ContractRunner, 11 | ContractMethod, 12 | Listener, 13 | } from "ethers"; 14 | import type { 15 | TypedContractEvent, 16 | TypedDeferredTopicFilter, 17 | TypedEventLog, 18 | TypedListener, 19 | TypedContractMethod, 20 | } from "../../../../common"; 21 | 22 | export interface IERC1822ProxiableInterface extends Interface { 23 | getFunction(nameOrSignature: "proxiableUUID"): FunctionFragment; 24 | 25 | encodeFunctionData( 26 | functionFragment: "proxiableUUID", 27 | values?: undefined 28 | ): string; 29 | 30 | decodeFunctionResult( 31 | functionFragment: "proxiableUUID", 32 | data: BytesLike 33 | ): Result; 34 | } 35 | 36 | export interface IERC1822Proxiable extends BaseContract { 37 | connect(runner?: ContractRunner | null): IERC1822Proxiable; 38 | waitForDeployment(): Promise; 39 | 40 | interface: IERC1822ProxiableInterface; 41 | 42 | queryFilter( 43 | event: TCEvent, 44 | fromBlockOrBlockhash?: string | number | undefined, 45 | toBlock?: string | number | undefined 46 | ): Promise>>; 47 | queryFilter( 48 | filter: TypedDeferredTopicFilter, 49 | fromBlockOrBlockhash?: string | number | undefined, 50 | toBlock?: string | number | undefined 51 | ): Promise>>; 52 | 53 | on( 54 | event: TCEvent, 55 | listener: TypedListener 56 | ): Promise; 57 | on( 58 | filter: TypedDeferredTopicFilter, 59 | listener: TypedListener 60 | ): Promise; 61 | 62 | once( 63 | event: TCEvent, 64 | listener: TypedListener 65 | ): Promise; 66 | once( 67 | filter: TypedDeferredTopicFilter, 68 | listener: TypedListener 69 | ): Promise; 70 | 71 | listeners( 72 | event: TCEvent 73 | ): Promise>>; 74 | listeners(eventName?: string): Promise>; 75 | removeAllListeners( 76 | event?: TCEvent 77 | ): Promise; 78 | 79 | proxiableUUID: TypedContractMethod<[], [string], "view">; 80 | 81 | getFunction( 82 | key: string | FunctionFragment 83 | ): T; 84 | 85 | getFunction( 86 | nameOrSignature: "proxiableUUID" 87 | ): TypedContractMethod<[], [string], "view">; 88 | 89 | filters: {}; 90 | } 91 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts/interfaces/draft-IERC1822.sol/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export type { IERC1822Proxiable } from "./IERC1822Proxiable"; 5 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import type * as draftIerc1822Sol from "./draft-IERC1822.sol"; 5 | export type { draftIerc1822Sol }; 6 | export type { IERC1967 } from "./IERC1967"; 7 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import type { 5 | BaseContract, 6 | FunctionFragment, 7 | Interface, 8 | ContractRunner, 9 | ContractMethod, 10 | Listener, 11 | } from "ethers"; 12 | import type { 13 | TypedContractEvent, 14 | TypedDeferredTopicFilter, 15 | TypedEventLog, 16 | TypedListener, 17 | } from "../../../../common"; 18 | 19 | export interface ERC1967UtilsInterface extends Interface {} 20 | 21 | export interface ERC1967Utils extends BaseContract { 22 | connect(runner?: ContractRunner | null): ERC1967Utils; 23 | waitForDeployment(): Promise; 24 | 25 | interface: ERC1967UtilsInterface; 26 | 27 | queryFilter( 28 | event: TCEvent, 29 | fromBlockOrBlockhash?: string | number | undefined, 30 | toBlock?: string | number | undefined 31 | ): Promise>>; 32 | queryFilter( 33 | filter: TypedDeferredTopicFilter, 34 | fromBlockOrBlockhash?: string | number | undefined, 35 | toBlock?: string | number | undefined 36 | ): Promise>>; 37 | 38 | on( 39 | event: TCEvent, 40 | listener: TypedListener 41 | ): Promise; 42 | on( 43 | filter: TypedDeferredTopicFilter, 44 | listener: TypedListener 45 | ): Promise; 46 | 47 | once( 48 | event: TCEvent, 49 | listener: TypedListener 50 | ): Promise; 51 | once( 52 | filter: TypedDeferredTopicFilter, 53 | listener: TypedListener 54 | ): Promise; 55 | 56 | listeners( 57 | event: TCEvent 58 | ): Promise>>; 59 | listeners(eventName?: string): Promise>; 60 | removeAllListeners( 61 | event?: TCEvent 62 | ): Promise; 63 | 64 | getFunction( 65 | key: string | FunctionFragment 66 | ): T; 67 | 68 | filters: {}; 69 | } 70 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts/proxy/ERC1967/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export type { ERC1967Utils } from "./ERC1967Utils"; 5 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts/proxy/beacon/IBeacon.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import type { 5 | BaseContract, 6 | BytesLike, 7 | FunctionFragment, 8 | Result, 9 | Interface, 10 | ContractRunner, 11 | ContractMethod, 12 | Listener, 13 | } from "ethers"; 14 | import type { 15 | TypedContractEvent, 16 | TypedDeferredTopicFilter, 17 | TypedEventLog, 18 | TypedListener, 19 | TypedContractMethod, 20 | } from "../../../../common"; 21 | 22 | export interface IBeaconInterface extends Interface { 23 | getFunction(nameOrSignature: "implementation"): FunctionFragment; 24 | 25 | encodeFunctionData( 26 | functionFragment: "implementation", 27 | values?: undefined 28 | ): string; 29 | 30 | decodeFunctionResult( 31 | functionFragment: "implementation", 32 | data: BytesLike 33 | ): Result; 34 | } 35 | 36 | export interface IBeacon extends BaseContract { 37 | connect(runner?: ContractRunner | null): IBeacon; 38 | waitForDeployment(): Promise; 39 | 40 | interface: IBeaconInterface; 41 | 42 | queryFilter( 43 | event: TCEvent, 44 | fromBlockOrBlockhash?: string | number | undefined, 45 | toBlock?: string | number | undefined 46 | ): Promise>>; 47 | queryFilter( 48 | filter: TypedDeferredTopicFilter, 49 | fromBlockOrBlockhash?: string | number | undefined, 50 | toBlock?: string | number | undefined 51 | ): Promise>>; 52 | 53 | on( 54 | event: TCEvent, 55 | listener: TypedListener 56 | ): Promise; 57 | on( 58 | filter: TypedDeferredTopicFilter, 59 | listener: TypedListener 60 | ): Promise; 61 | 62 | once( 63 | event: TCEvent, 64 | listener: TypedListener 65 | ): Promise; 66 | once( 67 | filter: TypedDeferredTopicFilter, 68 | listener: TypedListener 69 | ): Promise; 70 | 71 | listeners( 72 | event: TCEvent 73 | ): Promise>>; 74 | listeners(eventName?: string): Promise>; 75 | removeAllListeners( 76 | event?: TCEvent 77 | ): Promise; 78 | 79 | implementation: TypedContractMethod<[], [string], "view">; 80 | 81 | getFunction( 82 | key: string | FunctionFragment 83 | ): T; 84 | 85 | getFunction( 86 | nameOrSignature: "implementation" 87 | ): TypedContractMethod<[], [string], "view">; 88 | 89 | filters: {}; 90 | } 91 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts/proxy/beacon/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export type { IBeacon } from "./IBeacon"; 5 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts/proxy/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import type * as erc1967 from "./ERC1967"; 5 | export type { erc1967 }; 6 | import type * as beacon from "./beacon"; 7 | export type { beacon }; 8 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts/utils/Address.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import type { 5 | BaseContract, 6 | FunctionFragment, 7 | Interface, 8 | ContractRunner, 9 | ContractMethod, 10 | Listener, 11 | } from "ethers"; 12 | import type { 13 | TypedContractEvent, 14 | TypedDeferredTopicFilter, 15 | TypedEventLog, 16 | TypedListener, 17 | } from "../../../common"; 18 | 19 | export interface AddressInterface extends Interface {} 20 | 21 | export interface Address extends BaseContract { 22 | connect(runner?: ContractRunner | null): Address; 23 | waitForDeployment(): Promise; 24 | 25 | interface: AddressInterface; 26 | 27 | queryFilter( 28 | event: TCEvent, 29 | fromBlockOrBlockhash?: string | number | undefined, 30 | toBlock?: string | number | undefined 31 | ): Promise>>; 32 | queryFilter( 33 | filter: TypedDeferredTopicFilter, 34 | fromBlockOrBlockhash?: string | number | undefined, 35 | toBlock?: string | number | undefined 36 | ): Promise>>; 37 | 38 | on( 39 | event: TCEvent, 40 | listener: TypedListener 41 | ): Promise; 42 | on( 43 | filter: TypedDeferredTopicFilter, 44 | listener: TypedListener 45 | ): Promise; 46 | 47 | once( 48 | event: TCEvent, 49 | listener: TypedListener 50 | ): Promise; 51 | once( 52 | filter: TypedDeferredTopicFilter, 53 | listener: TypedListener 54 | ): Promise; 55 | 56 | listeners( 57 | event: TCEvent 58 | ): Promise>>; 59 | listeners(eventName?: string): Promise>; 60 | removeAllListeners( 61 | event?: TCEvent 62 | ): Promise; 63 | 64 | getFunction( 65 | key: string | FunctionFragment 66 | ): T; 67 | 68 | filters: {}; 69 | } 70 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts/utils/Errors.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import type { 5 | BaseContract, 6 | FunctionFragment, 7 | Interface, 8 | ContractRunner, 9 | ContractMethod, 10 | Listener, 11 | } from "ethers"; 12 | import type { 13 | TypedContractEvent, 14 | TypedDeferredTopicFilter, 15 | TypedEventLog, 16 | TypedListener, 17 | } from "../../../common"; 18 | 19 | export interface ErrorsInterface extends Interface {} 20 | 21 | export interface Errors extends BaseContract { 22 | connect(runner?: ContractRunner | null): Errors; 23 | waitForDeployment(): Promise; 24 | 25 | interface: ErrorsInterface; 26 | 27 | queryFilter( 28 | event: TCEvent, 29 | fromBlockOrBlockhash?: string | number | undefined, 30 | toBlock?: string | number | undefined 31 | ): Promise>>; 32 | queryFilter( 33 | filter: TypedDeferredTopicFilter, 34 | fromBlockOrBlockhash?: string | number | undefined, 35 | toBlock?: string | number | undefined 36 | ): Promise>>; 37 | 38 | on( 39 | event: TCEvent, 40 | listener: TypedListener 41 | ): Promise; 42 | on( 43 | filter: TypedDeferredTopicFilter, 44 | listener: TypedListener 45 | ): Promise; 46 | 47 | once( 48 | event: TCEvent, 49 | listener: TypedListener 50 | ): Promise; 51 | once( 52 | filter: TypedDeferredTopicFilter, 53 | listener: TypedListener 54 | ): Promise; 55 | 56 | listeners( 57 | event: TCEvent 58 | ): Promise>>; 59 | listeners(eventName?: string): Promise>; 60 | removeAllListeners( 61 | event?: TCEvent 62 | ): Promise; 63 | 64 | getFunction( 65 | key: string | FunctionFragment 66 | ): T; 67 | 68 | filters: {}; 69 | } 70 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/contracts/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export type { Address } from "./Address"; 5 | export type { Errors } from "./Errors"; 6 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/@openzeppelin/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import type * as contracts from "./contracts"; 5 | export type { contracts }; 6 | import type * as contractsUpgradeable from "./contracts-upgradeable"; 7 | export type { contractsUpgradeable }; 8 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/contracts/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export type { AppAuth } from "./AppAuth"; 5 | export type { IAppAuth } from "./IAppAuth"; 6 | export type { KmsAuth } from "./KmsAuth"; 7 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts-upgradeable/access/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export { OwnableUpgradeable__factory } from "./OwnableUpgradeable__factory"; 5 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts-upgradeable/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export * as access from "./access"; 5 | export * as proxy from "./proxy"; 6 | export * as utils from "./utils"; 7 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts-upgradeable/proxy/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export * as utils from "./utils"; 5 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable__factory.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | 5 | import { Contract, Interface, type ContractRunner } from "ethers"; 6 | import type { 7 | Initializable, 8 | InitializableInterface, 9 | } from "../../../../../@openzeppelin/contracts-upgradeable/proxy/utils/Initializable"; 10 | 11 | const _abi = [ 12 | { 13 | inputs: [], 14 | name: "InvalidInitialization", 15 | type: "error", 16 | }, 17 | { 18 | inputs: [], 19 | name: "NotInitializing", 20 | type: "error", 21 | }, 22 | { 23 | anonymous: false, 24 | inputs: [ 25 | { 26 | indexed: false, 27 | internalType: "uint64", 28 | name: "version", 29 | type: "uint64", 30 | }, 31 | ], 32 | name: "Initialized", 33 | type: "event", 34 | }, 35 | ] as const; 36 | 37 | export class Initializable__factory { 38 | static readonly abi = _abi; 39 | static createInterface(): InitializableInterface { 40 | return new Interface(_abi) as InitializableInterface; 41 | } 42 | static connect( 43 | address: string, 44 | runner?: ContractRunner | null 45 | ): Initializable { 46 | return new Contract(address, _abi, runner) as unknown as Initializable; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts-upgradeable/proxy/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export { Initializable__factory } from "./Initializable__factory"; 5 | export { UUPSUpgradeable__factory } from "./UUPSUpgradeable__factory"; 6 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable__factory.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | 5 | import { Contract, Interface, type ContractRunner } from "ethers"; 6 | import type { 7 | ContextUpgradeable, 8 | ContextUpgradeableInterface, 9 | } from "../../../../@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable"; 10 | 11 | const _abi = [ 12 | { 13 | inputs: [], 14 | name: "InvalidInitialization", 15 | type: "error", 16 | }, 17 | { 18 | inputs: [], 19 | name: "NotInitializing", 20 | type: "error", 21 | }, 22 | { 23 | anonymous: false, 24 | inputs: [ 25 | { 26 | indexed: false, 27 | internalType: "uint64", 28 | name: "version", 29 | type: "uint64", 30 | }, 31 | ], 32 | name: "Initialized", 33 | type: "event", 34 | }, 35 | ] as const; 36 | 37 | export class ContextUpgradeable__factory { 38 | static readonly abi = _abi; 39 | static createInterface(): ContextUpgradeableInterface { 40 | return new Interface(_abi) as ContextUpgradeableInterface; 41 | } 42 | static connect( 43 | address: string, 44 | runner?: ContractRunner | null 45 | ): ContextUpgradeable { 46 | return new Contract(address, _abi, runner) as unknown as ContextUpgradeable; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts-upgradeable/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export { ContextUpgradeable__factory } from "./ContextUpgradeable__factory"; 5 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export * as interfaces from "./interfaces"; 5 | export * as proxy from "./proxy"; 6 | export * as utils from "./utils"; 7 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts/interfaces/IERC1967__factory.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | 5 | import { Contract, Interface, type ContractRunner } from "ethers"; 6 | import type { 7 | IERC1967, 8 | IERC1967Interface, 9 | } from "../../../../@openzeppelin/contracts/interfaces/IERC1967"; 10 | 11 | const _abi = [ 12 | { 13 | anonymous: false, 14 | inputs: [ 15 | { 16 | indexed: false, 17 | internalType: "address", 18 | name: "previousAdmin", 19 | type: "address", 20 | }, 21 | { 22 | indexed: false, 23 | internalType: "address", 24 | name: "newAdmin", 25 | type: "address", 26 | }, 27 | ], 28 | name: "AdminChanged", 29 | type: "event", 30 | }, 31 | { 32 | anonymous: false, 33 | inputs: [ 34 | { 35 | indexed: true, 36 | internalType: "address", 37 | name: "beacon", 38 | type: "address", 39 | }, 40 | ], 41 | name: "BeaconUpgraded", 42 | type: "event", 43 | }, 44 | { 45 | anonymous: false, 46 | inputs: [ 47 | { 48 | indexed: true, 49 | internalType: "address", 50 | name: "implementation", 51 | type: "address", 52 | }, 53 | ], 54 | name: "Upgraded", 55 | type: "event", 56 | }, 57 | ] as const; 58 | 59 | export class IERC1967__factory { 60 | static readonly abi = _abi; 61 | static createInterface(): IERC1967Interface { 62 | return new Interface(_abi) as IERC1967Interface; 63 | } 64 | static connect(address: string, runner?: ContractRunner | null): IERC1967 { 65 | return new Contract(address, _abi, runner) as unknown as IERC1967; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts/interfaces/draft-IERC1822.sol/IERC1822Proxiable__factory.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | 5 | import { Contract, Interface, type ContractRunner } from "ethers"; 6 | import type { 7 | IERC1822Proxiable, 8 | IERC1822ProxiableInterface, 9 | } from "../../../../../@openzeppelin/contracts/interfaces/draft-IERC1822.sol/IERC1822Proxiable"; 10 | 11 | const _abi = [ 12 | { 13 | inputs: [], 14 | name: "proxiableUUID", 15 | outputs: [ 16 | { 17 | internalType: "bytes32", 18 | name: "", 19 | type: "bytes32", 20 | }, 21 | ], 22 | stateMutability: "view", 23 | type: "function", 24 | }, 25 | ] as const; 26 | 27 | export class IERC1822Proxiable__factory { 28 | static readonly abi = _abi; 29 | static createInterface(): IERC1822ProxiableInterface { 30 | return new Interface(_abi) as IERC1822ProxiableInterface; 31 | } 32 | static connect( 33 | address: string, 34 | runner?: ContractRunner | null 35 | ): IERC1822Proxiable { 36 | return new Contract(address, _abi, runner) as unknown as IERC1822Proxiable; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts/interfaces/draft-IERC1822.sol/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export { IERC1822Proxiable__factory } from "./IERC1822Proxiable__factory"; 5 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export * as draftIerc1822Sol from "./draft-IERC1822.sol"; 5 | export { IERC1967__factory } from "./IERC1967__factory"; 6 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts/proxy/ERC1967/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export { ERC1967Utils__factory } from "./ERC1967Utils__factory"; 5 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts/proxy/beacon/IBeacon__factory.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | 5 | import { Contract, Interface, type ContractRunner } from "ethers"; 6 | import type { 7 | IBeacon, 8 | IBeaconInterface, 9 | } from "../../../../../@openzeppelin/contracts/proxy/beacon/IBeacon"; 10 | 11 | const _abi = [ 12 | { 13 | inputs: [], 14 | name: "implementation", 15 | outputs: [ 16 | { 17 | internalType: "address", 18 | name: "", 19 | type: "address", 20 | }, 21 | ], 22 | stateMutability: "view", 23 | type: "function", 24 | }, 25 | ] as const; 26 | 27 | export class IBeacon__factory { 28 | static readonly abi = _abi; 29 | static createInterface(): IBeaconInterface { 30 | return new Interface(_abi) as IBeaconInterface; 31 | } 32 | static connect(address: string, runner?: ContractRunner | null): IBeacon { 33 | return new Contract(address, _abi, runner) as unknown as IBeacon; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts/proxy/beacon/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export { IBeacon__factory } from "./IBeacon__factory"; 5 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts/proxy/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export * as erc1967 from "./ERC1967"; 5 | export * as beacon from "./beacon"; 6 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts/utils/Address__factory.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import { 5 | Contract, 6 | ContractFactory, 7 | ContractTransactionResponse, 8 | Interface, 9 | } from "ethers"; 10 | import type { Signer, ContractDeployTransaction, ContractRunner } from "ethers"; 11 | import type { NonPayableOverrides } from "../../../../common"; 12 | import type { 13 | Address, 14 | AddressInterface, 15 | } from "../../../../@openzeppelin/contracts/utils/Address"; 16 | 17 | const _abi = [ 18 | { 19 | inputs: [ 20 | { 21 | internalType: "address", 22 | name: "target", 23 | type: "address", 24 | }, 25 | ], 26 | name: "AddressEmptyCode", 27 | type: "error", 28 | }, 29 | ] as const; 30 | 31 | const _bytecode = 32 | "0x60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea26469706673582212205965a49a4005a86ed954ab6e57bfedc675ff3c6b372d5d68bd315532662a642c64736f6c63430008160033"; 33 | 34 | type AddressConstructorParams = 35 | | [signer?: Signer] 36 | | ConstructorParameters; 37 | 38 | const isSuperArgs = ( 39 | xs: AddressConstructorParams 40 | ): xs is ConstructorParameters => xs.length > 1; 41 | 42 | export class Address__factory extends ContractFactory { 43 | constructor(...args: AddressConstructorParams) { 44 | if (isSuperArgs(args)) { 45 | super(...args); 46 | } else { 47 | super(_abi, _bytecode, args[0]); 48 | } 49 | } 50 | 51 | override getDeployTransaction( 52 | overrides?: NonPayableOverrides & { from?: string } 53 | ): Promise { 54 | return super.getDeployTransaction(overrides || {}); 55 | } 56 | override deploy(overrides?: NonPayableOverrides & { from?: string }) { 57 | return super.deploy(overrides || {}) as Promise< 58 | Address & { 59 | deploymentTransaction(): ContractTransactionResponse; 60 | } 61 | >; 62 | } 63 | override connect(runner: ContractRunner | null): Address__factory { 64 | return super.connect(runner) as Address__factory; 65 | } 66 | 67 | static readonly bytecode = _bytecode; 68 | static readonly abi = _abi; 69 | static createInterface(): AddressInterface { 70 | return new Interface(_abi) as AddressInterface; 71 | } 72 | static connect(address: string, runner?: ContractRunner | null): Address { 73 | return new Contract(address, _abi, runner) as unknown as Address; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/contracts/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export { Address__factory } from "./Address__factory"; 5 | export { Errors__factory } from "./Errors__factory"; 6 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/@openzeppelin/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export * as contracts from "./contracts"; 5 | export * as contractsUpgradeable from "./contracts-upgradeable"; 6 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/contracts/IAppAuth__factory.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | 5 | import { Contract, Interface, type ContractRunner } from "ethers"; 6 | import type { IAppAuth, IAppAuthInterface } from "../../contracts/IAppAuth"; 7 | 8 | const _abi = [ 9 | { 10 | inputs: [ 11 | { 12 | components: [ 13 | { 14 | internalType: "address", 15 | name: "appId", 16 | type: "address", 17 | }, 18 | { 19 | internalType: "bytes32", 20 | name: "composeHash", 21 | type: "bytes32", 22 | }, 23 | { 24 | internalType: "address", 25 | name: "instanceId", 26 | type: "address", 27 | }, 28 | { 29 | internalType: "bytes32", 30 | name: "deviceId", 31 | type: "bytes32", 32 | }, 33 | { 34 | internalType: "bytes32", 35 | name: "mrAggregated", 36 | type: "bytes32", 37 | }, 38 | { 39 | internalType: "bytes32", 40 | name: "mrSystem", 41 | type: "bytes32", 42 | }, 43 | { 44 | internalType: "bytes32", 45 | name: "osImageHash", 46 | type: "bytes32", 47 | }, 48 | { 49 | internalType: "string", 50 | name: "tcbStatus", 51 | type: "string", 52 | }, 53 | { 54 | internalType: "string[]", 55 | name: "advisoryIds", 56 | type: "string[]", 57 | }, 58 | ], 59 | internalType: "struct IAppAuth.AppBootInfo", 60 | name: "bootInfo", 61 | type: "tuple", 62 | }, 63 | ], 64 | name: "isAppAllowed", 65 | outputs: [ 66 | { 67 | internalType: "bool", 68 | name: "isAllowed", 69 | type: "bool", 70 | }, 71 | { 72 | internalType: "string", 73 | name: "reason", 74 | type: "string", 75 | }, 76 | ], 77 | stateMutability: "view", 78 | type: "function", 79 | }, 80 | ] as const; 81 | 82 | export class IAppAuth__factory { 83 | static readonly abi = _abi; 84 | static createInterface(): IAppAuthInterface { 85 | return new Interface(_abi) as IAppAuthInterface; 86 | } 87 | static connect(address: string, runner?: ContractRunner | null): IAppAuth { 88 | return new Contract(address, _abi, runner) as unknown as IAppAuth; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/contracts/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export { AppAuth__factory } from "./AppAuth__factory"; 5 | export { IAppAuth__factory } from "./IAppAuth__factory"; 6 | export { KmsAuth__factory } from "./KmsAuth__factory"; 7 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/factories/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export * as openzeppelin from "./@openzeppelin"; 5 | export * as contracts from "./contracts"; 6 | -------------------------------------------------------------------------------- /kms/auth-eth/typechain-types/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import type * as openzeppelin from "./@openzeppelin"; 5 | export type { openzeppelin }; 6 | import type * as contracts from "./contracts"; 7 | export type { contracts }; 8 | export * as factories from "./factories"; 9 | export type { OwnableUpgradeable } from "./@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable"; 10 | export { OwnableUpgradeable__factory } from "./factories/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable__factory"; 11 | export type { Initializable } from "./@openzeppelin/contracts-upgradeable/proxy/utils/Initializable"; 12 | export { Initializable__factory } from "./factories/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable__factory"; 13 | export type { UUPSUpgradeable } from "./@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable"; 14 | export { UUPSUpgradeable__factory } from "./factories/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable__factory"; 15 | export type { ContextUpgradeable } from "./@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable"; 16 | export { ContextUpgradeable__factory } from "./factories/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable__factory"; 17 | export type { IERC1822Proxiable } from "./@openzeppelin/contracts/interfaces/draft-IERC1822.sol/IERC1822Proxiable"; 18 | export { IERC1822Proxiable__factory } from "./factories/@openzeppelin/contracts/interfaces/draft-IERC1822.sol/IERC1822Proxiable__factory"; 19 | export type { IERC1967 } from "./@openzeppelin/contracts/interfaces/IERC1967"; 20 | export { IERC1967__factory } from "./factories/@openzeppelin/contracts/interfaces/IERC1967__factory"; 21 | export type { IBeacon } from "./@openzeppelin/contracts/proxy/beacon/IBeacon"; 22 | export { IBeacon__factory } from "./factories/@openzeppelin/contracts/proxy/beacon/IBeacon__factory"; 23 | export type { ERC1967Utils } from "./@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils"; 24 | export { ERC1967Utils__factory } from "./factories/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils__factory"; 25 | export type { Address } from "./@openzeppelin/contracts/utils/Address"; 26 | export { Address__factory } from "./factories/@openzeppelin/contracts/utils/Address__factory"; 27 | export type { Errors } from "./@openzeppelin/contracts/utils/Errors"; 28 | export { Errors__factory } from "./factories/@openzeppelin/contracts/utils/Errors__factory"; 29 | export type { AppAuth } from "./contracts/AppAuth"; 30 | export { AppAuth__factory } from "./factories/contracts/AppAuth__factory"; 31 | export type { IAppAuth } from "./contracts/IAppAuth"; 32 | export { IAppAuth__factory } from "./factories/contracts/IAppAuth__factory"; 33 | export type { KmsAuth } from "./contracts/KmsAuth"; 34 | export { KmsAuth__factory } from "./factories/contracts/KmsAuth__factory"; 35 | -------------------------------------------------------------------------------- /kms/dstack-app/.gitignore: -------------------------------------------------------------------------------- 1 | .app-compose.json 2 | .env 3 | -------------------------------------------------------------------------------- /kms/dstack-app/compose-dev.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | auth-api: 3 | build: 4 | context: . 5 | dockerfile_inline: | 6 | FROM node:18-alpine@sha256:06f7bbbcec00dd10c21a3a0962609600159601b5004d84aff142977b449168e9 7 | WORKDIR /app 8 | 9 | RUN apk add --no-cache git 10 | RUN git clone https://github.com/Dstack-TEE/dstack.git && \ 11 | cd dstack && \ 12 | git checkout ${GIT_REV} 13 | WORKDIR /app/dstack/kms/auth-eth 14 | RUN npm install 15 | RUN npx tsc --project tsconfig.json 16 | CMD node dist/src/main.js 17 | environment: 18 | - HOST=0.0.0.0 19 | - PORT=8000 20 | - ETH_RPC_URL=${ETH_RPC_URL} 21 | - KMS_CONTRACT_ADDR=${KMS_CONTRACT_ADDR} 22 | restart: unless-stopped 23 | ports: 24 | - 8001:8000 25 | 26 | kms: 27 | build: 28 | context: . 29 | dockerfile_inline: | 30 | FROM golang:1.22-alpine@sha256:1699c10032ca2582ec89a24a1312d986a3f094aed3d5c1147b19880afe40e052 AS dstack-mr-builder 31 | WORKDIR /app 32 | RUN apk add --no-cache git 33 | RUN git clone https://github.com/kvinwang/dstack-mr.git 34 | WORKDIR /app/dstack-mr 35 | RUN git checkout 5cf6d917e076f3624eab1b6b662f222ece15600f 36 | RUN CGO_ENABLED=0 go build -ldflags="-extldflags -static" -o /usr/local/bin/dstack-mr 37 | 38 | FROM rust:1.86.0@sha256:300ec56abce8cc9448ddea2172747d048ed902a3090e6b57babb2bf19f754081 AS kms-builder 39 | WORKDIR /app 40 | RUN apt-get update && apt-get install -y \ 41 | git \ 42 | build-essential \ 43 | musl-tools \ 44 | libssl-dev \ 45 | protobuf-compiler \ 46 | libprotobuf-dev \ 47 | clang \ 48 | libclang-dev \ 49 | --no-install-recommends \ 50 | && rm -rf /var/lib/apt/lists/* 51 | RUN git clone https://github.com/Dstack-TEE/dstack.git && \ 52 | cd dstack && \ 53 | git checkout ${GIT_REV} 54 | WORKDIR /app/dstack 55 | RUN rustup target add x86_64-unknown-linux-musl 56 | RUN cargo build --release -p dstack-kms --target x86_64-unknown-linux-musl 57 | 58 | FROM alpine:latest 59 | COPY --from=kms-builder /app/dstack/target/x86_64-unknown-linux-musl/release/dstack-kms /usr/local/bin/dstack-kms 60 | COPY --from=kms-builder /app/dstack/kms/dstack-app/entrypoint.sh /entrypoint.sh 61 | COPY --from=dstack-mr-builder /usr/local/bin/dstack-mr /usr/local/bin/dstack-mr 62 | WORKDIR /app/kms 63 | CMD ["/entrypoint.sh"] 64 | volumes: 65 | - kms-volume:/etc/kms 66 | - /var/run/dstack.sock:/var/run/dstack.sock 67 | environment: 68 | - IMAGE_DOWNLOAD_URL=${IMAGE_DOWNLOAD_URL} 69 | ports: 70 | - 8000:8000 71 | depends_on: 72 | - auth-api 73 | restart: unless-stopped 74 | 75 | volumes: 76 | kms-volume: 77 | -------------------------------------------------------------------------------- /kms/dstack-app/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cat < ./kms.toml 5 | [core.image] 6 | verify = true 7 | cache_dir = "./images" 8 | download_url = "${IMAGE_DOWNLOAD_URL}" 9 | download_timeout = "2m" 10 | EOF 11 | 12 | exec dstack-kms -c ./kms.toml 13 | -------------------------------------------------------------------------------- /kms/kms.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | workers = 8 3 | max_blocking = 64 4 | ident = "DStack KMS" 5 | temp_dir = "/tmp" 6 | keep_alive = 10 7 | log_level = "info" 8 | 9 | [rpc] 10 | address = "0.0.0.0" 11 | port = 8000 12 | 13 | [rpc.tls] 14 | key = "/etc/kms/certs/rpc.key" 15 | certs = "/etc/kms/certs/rpc.crt" 16 | 17 | [rpc.tls.mutual] 18 | ca_certs = "/etc/kms/certs/tmp-ca.crt" 19 | mandatory = false 20 | 21 | [core] 22 | cert_dir = "/etc/kms/certs" 23 | subject_postfix = ".dstack" 24 | 25 | [core.image] 26 | verify = true 27 | cache_dir = "/usr/share/dstack/images" 28 | download_url = "http://localhost:8000/{OS_IMAGE_HASH}.tar.gz" 29 | download_timeout = "2m" 30 | 31 | [core.auth_api] 32 | type = "webhook" 33 | 34 | [core.auth_api.webhook] 35 | url = "http://auth-api:8000" 36 | 37 | [core.auth_api.dev] 38 | gateway_app_id = "any" 39 | 40 | [core.onboard] 41 | enabled = true 42 | auto_bootstrap_domain = "" 43 | quote_enabled = true 44 | address = "0.0.0.0" 45 | port = 8000 46 | -------------------------------------------------------------------------------- /kms/rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dstack-kms-rpc" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | prpc.workspace = true 10 | prost.workspace = true 11 | serde.workspace = true 12 | serde_json.workspace = true 13 | anyhow.workspace = true 14 | scale.workspace = true 15 | 16 | [build-dependencies] 17 | prpc-build.workspace = true 18 | fs-err.workspace = true 19 | -------------------------------------------------------------------------------- /kms/rpc/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | prpc_build::configure() 3 | .out_dir("./src/generated") 4 | .mod_prefix("super::") 5 | .build_scale_ext(false) 6 | .disable_package_emission() 7 | .enable_serde_extension() 8 | .disable_service_name_emission() 9 | .compile_dir("./proto") 10 | .expect("failed to compile proto files"); 11 | } 12 | -------------------------------------------------------------------------------- /kms/rpc/src/.gitignore: -------------------------------------------------------------------------------- 1 | /generated 2 | -------------------------------------------------------------------------------- /kms/rpc/src/generated.rs: -------------------------------------------------------------------------------- 1 | pub use kms::*; 2 | 3 | #[allow(async_fn_in_trait)] 4 | mod kms; 5 | -------------------------------------------------------------------------------- /kms/rpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | pub use generated::*; 4 | 5 | mod generated; 6 | -------------------------------------------------------------------------------- /kms/src/crypto.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use k256::ecdsa::SigningKey; 3 | use sha3::{Digest, Keccak256}; 4 | 5 | use ra_tls::kdf; 6 | 7 | pub(crate) fn derive_k256_key( 8 | parent_key: &SigningKey, 9 | app_id: &[u8], 10 | ) -> Result<(SigningKey, Vec)> { 11 | let context_data = [app_id, b"app-key"]; 12 | let derived_key_bytes: [u8; 32] = 13 | kdf::derive_ecdsa_key(&parent_key.to_bytes(), &context_data, 32)? 14 | .try_into() 15 | .ok() 16 | .context("Invalid derived key len")?; 17 | let derived_signing_key = SigningKey::from_bytes(&derived_key_bytes.into())?; 18 | let pubkey = derived_signing_key.verifying_key(); 19 | 20 | let signature = sign_message( 21 | parent_key, 22 | b"dstack-kms-issued", 23 | app_id, 24 | &pubkey.to_sec1_bytes(), 25 | )?; 26 | Ok((derived_signing_key, signature)) 27 | } 28 | 29 | pub(crate) fn sign_message( 30 | key: &SigningKey, 31 | prefix: &[u8], 32 | appid: &[u8], 33 | message: &[u8], 34 | ) -> Result> { 35 | let digest = Keccak256::new_with_prefix([prefix, b":", appid, message].concat()); 36 | let (signature, recid) = key.sign_digest_recoverable(digest)?; 37 | let mut signature_bytes = signature.to_vec(); 38 | signature_bytes.push(recid.to_byte()); 39 | Ok(signature_bytes) 40 | } 41 | -------------------------------------------------------------------------------- /kms/src/ct_log.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use anyhow::{Context, Result}; 4 | use chrono::Utc; 5 | use fs_err as fs; 6 | 7 | #[allow(dead_code)] 8 | pub(crate) fn iter_ct_log_files(log_dir: &Path) -> Result> { 9 | // Certs files at log_dir/YYYYMMDD/xxx.cert 10 | let day_dirs = fs::read_dir(log_dir)?.filter_map(|entry| { 11 | let path = entry.ok()?.path(); 12 | if path.is_dir() { 13 | Some(path) 14 | } else { 15 | None 16 | } 17 | }); 18 | 19 | Ok(day_dirs 20 | .flat_map(|dir| { 21 | let iter = fs::read_dir(dir).ok()?.filter_map(|entry| { 22 | let path = entry.ok()?.path(); 23 | if path.is_file() && path.ends_with(".cert") { 24 | Some(path) 25 | } else { 26 | None 27 | } 28 | }); 29 | Some(iter) 30 | }) 31 | .flatten()) 32 | } 33 | 34 | pub(crate) fn ct_log_write_cert(app_id: &str, cert: &str, log_dir: &str) -> Result<()> { 35 | // filename: %Y%d%m-%H%M%S.%sn.%app_id.cert 36 | let log_dir = Path::new(log_dir); 37 | let now = Utc::now(); 38 | let day = now.format("%Y%d%m").to_string(); 39 | let base_filename = format!("{}-{app_id}", now.format("%Y%d%m-%H%M%S")); 40 | let day_dir = log_dir.join(day); 41 | fs::create_dir_all(&day_dir).context("failed to create ct log dir")?; 42 | let cert_log_path = find_available_filename(&day_dir, &base_filename) 43 | .context("failed to find available filename")?; 44 | fs::write(cert_log_path, cert).context("faile to write ct log cert")?; 45 | Ok(()) 46 | } 47 | 48 | fn binary_search(mut upper: usize, is_ok: impl Fn(usize) -> bool) -> Option { 49 | let mut lower = 0; 50 | if is_ok(0) { 51 | return Some(0); 52 | } 53 | if !is_ok(upper) { 54 | return None; 55 | } 56 | while lower < upper { 57 | let mid = lower + (upper - lower) / 2; 58 | if is_ok(mid) { 59 | upper = mid; 60 | } else { 61 | lower = mid + 1; 62 | } 63 | } 64 | Some(upper) 65 | } 66 | 67 | fn find_available_filename(dir: &Path, base_filename: &str) -> Option { 68 | fn mk_filename(dir: &Path, base_filename: &str, index: usize) -> PathBuf { 69 | dir.join(format!("{base_filename}.{index}.cert")) 70 | } 71 | let available_index = binary_search(4096, |i| { 72 | !std::path::Path::new(&mk_filename(dir, base_filename, i)).exists() 73 | })?; 74 | Some(mk_filename(dir, base_filename, available_index)) 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::*; 80 | 81 | #[test] 82 | fn test_binary_search_simple() { 83 | for i in 0..=4096 { 84 | let result = binary_search(4096, |x| x >= i); 85 | assert_eq!(result, Some(i)); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /kms/src/main_service/upgrade_authority.rs: -------------------------------------------------------------------------------- 1 | use crate::config::AuthApi; 2 | use anyhow::{bail, Result}; 3 | use serde::{Deserialize, Serialize}; 4 | use serde_human_bytes as hex_bytes; 5 | 6 | #[derive(Debug, Serialize, Deserialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub(crate) struct BootInfo { 9 | #[serde(with = "hex_bytes")] 10 | pub mrtd: Vec, 11 | #[serde(with = "hex_bytes")] 12 | pub rtmr0: Vec, 13 | #[serde(with = "hex_bytes")] 14 | pub rtmr1: Vec, 15 | #[serde(with = "hex_bytes")] 16 | pub rtmr2: Vec, 17 | #[serde(with = "hex_bytes")] 18 | pub rtmr3: Vec, 19 | #[serde(with = "hex_bytes")] 20 | pub mr_aggregated: Vec, 21 | #[serde(with = "hex_bytes")] 22 | pub os_image_hash: Vec, 23 | #[serde(with = "hex_bytes")] 24 | pub mr_system: Vec, 25 | #[serde(with = "hex_bytes")] 26 | pub mr_key_provider: Vec, 27 | #[serde(with = "hex_bytes")] 28 | pub app_id: Vec, 29 | #[serde(with = "hex_bytes")] 30 | pub compose_hash: Vec, 31 | #[serde(with = "hex_bytes")] 32 | pub instance_id: Vec, 33 | #[serde(with = "hex_bytes")] 34 | pub device_id: Vec, 35 | #[serde(with = "hex_bytes")] 36 | pub key_provider_info: Vec, 37 | pub event_log: String, 38 | pub tcb_status: String, 39 | pub advisory_ids: Vec, 40 | } 41 | 42 | #[derive(Debug, Serialize, Deserialize)] 43 | #[serde(rename_all = "camelCase")] 44 | pub(crate) struct BootResponse { 45 | pub is_allowed: bool, 46 | pub gateway_app_id: String, 47 | pub reason: String, 48 | } 49 | 50 | impl AuthApi { 51 | pub async fn is_app_allowed(&self, boot_info: &BootInfo, is_kms: bool) -> Result { 52 | match self { 53 | AuthApi::Dev { dev } => Ok(BootResponse { 54 | is_allowed: true, 55 | reason: "".to_string(), 56 | gateway_app_id: dev.gateway_app_id.clone(), 57 | }), 58 | AuthApi::Webhook { webhook } => { 59 | let client = reqwest::Client::new(); 60 | let path = if is_kms { 61 | "bootAuth/kms" 62 | } else { 63 | "bootAuth/app" 64 | }; 65 | let url = url_join(&webhook.url, path); 66 | let response = client.post(&url).json(&boot_info).send().await?; 67 | if !response.status().is_success() { 68 | bail!("Failed to check boot auth: {}", response.text().await?); 69 | } 70 | Ok(response.json().await?) 71 | } 72 | } 73 | } 74 | } 75 | 76 | fn url_join(url: &str, path: &str) -> String { 77 | let mut url = url.to_string(); 78 | if !url.ends_with('/') { 79 | url.push('/'); 80 | } 81 | url.push_str(path); 82 | url 83 | } 84 | -------------------------------------------------------------------------------- /load_config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "load_config" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | figment = { workspace = true, features = ["json", "toml"] } 10 | rocket.workspace = true 11 | tracing.workspace = true 12 | 13 | [dev-dependencies] 14 | tempfile.workspace = true 15 | -------------------------------------------------------------------------------- /lspci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lspci" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | 11 | [dev-dependencies] 12 | insta.workspace = true 13 | -------------------------------------------------------------------------------- /lspci/src/snapshots/lspci__lspci.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: lspci/src/lib.rs 3 | assertion_line: 303 4 | expression: devices 5 | --- 6 | [ 7 | Device { 8 | slot: "bc:02.0", 9 | class: "PCI bridge", 10 | class_id: "0604", 11 | description: "PMC-Sierra Inc.", 12 | vendor_id: "11f8", 13 | product_id: "4128", 14 | control: Control { 15 | io: true, 16 | mem: true, 17 | bus_master: true, 18 | }, 19 | numa_node: Some( 20 | 1, 21 | ), 22 | }, 23 | Device { 24 | slot: "bc:03.0", 25 | class: "PCI bridge", 26 | class_id: "0604", 27 | description: "PMC-Sierra Inc.", 28 | vendor_id: "11f8", 29 | product_id: "4128", 30 | control: Control { 31 | io: true, 32 | mem: true, 33 | bus_master: true, 34 | }, 35 | numa_node: Some( 36 | 1, 37 | ), 38 | }, 39 | Device { 40 | slot: "bd:00.0", 41 | class: "Bridge", 42 | class_id: "0680", 43 | description: "NVIDIA Corporation GH100 [H100 NVSwitch]", 44 | vendor_id: "10de", 45 | product_id: "22a3", 46 | control: Control { 47 | io: false, 48 | mem: false, 49 | bus_master: false, 50 | }, 51 | numa_node: Some( 52 | 1, 53 | ), 54 | }, 55 | Device { 56 | slot: "be:00.0", 57 | class: "Bridge", 58 | class_id: "0680", 59 | description: "NVIDIA Corporation GH100 [H100 NVSwitch]", 60 | vendor_id: "10de", 61 | product_id: "22a3", 62 | control: Control { 63 | io: false, 64 | mem: false, 65 | bus_master: false, 66 | }, 67 | numa_node: Some( 68 | 1, 69 | ), 70 | }, 71 | Device { 72 | slot: "bf:00.0", 73 | class: "Bridge", 74 | class_id: "0680", 75 | description: "NVIDIA Corporation GH100 [H100 NVSwitch]", 76 | vendor_id: "10de", 77 | product_id: "22a3", 78 | control: Control { 79 | io: false, 80 | mem: false, 81 | bus_master: false, 82 | }, 83 | numa_node: Some( 84 | 1, 85 | ), 86 | }, 87 | ] 88 | -------------------------------------------------------------------------------- /mod-tdx-guest/Kconfig: -------------------------------------------------------------------------------- 1 | config TDX_GUEST_DRIVER 2 | tristate "TDX Guest driver" 3 | depends on INTEL_TDX_GUEST 4 | select TSM_REPORTS 5 | help 6 | The driver provides userspace interface to communicate with 7 | the TDX module to request the TDX guest details like attestation 8 | report. 9 | 10 | To compile this driver as module, choose M here. The module will 11 | be called tdx-guest. 12 | -------------------------------------------------------------------------------- /mod-tdx-guest/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | SRC := $(shell pwd) 4 | KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build 5 | INSTALL_MOD_PATH := $(shell pwd)/dist/ 6 | 7 | obj-m += tdx-guest.o 8 | tdx-guest-objs := tdcall.o mod.o 9 | 10 | all: 11 | $(MAKE) -C $(KERNEL_SRC) M=$(SRC) 12 | 13 | modules_install: 14 | $(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules_install 15 | 16 | clean: 17 | $(MAKE) -C $(KERNEL_SRC) M=$(SRC) clean 18 | 19 | install: 20 | make -C $(KDIR) M=$(PWD) modules_install INSTALL_MOD_PATH=$(INSTALL_MOD_PATH) 21 | -------------------------------------------------------------------------------- /mod-tdx-guest/tdx-guest.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* 3 | * Userspace interface for TDX guest driver 4 | * 5 | * Copyright (C) 2022 Intel Corporation 6 | */ 7 | 8 | #ifndef _UAPI_LINUX_TDX_GUEST_H_ 9 | #define _UAPI_LINUX_TDX_GUEST_H_ 10 | 11 | #include 12 | #include 13 | 14 | /* Length of the REPORTDATA used in TDG.MR.REPORT TDCALL */ 15 | #define TDX_REPORTDATA_LEN 64 16 | 17 | /* Length of TDREPORT used in TDG.MR.REPORT TDCALL */ 18 | #define TDX_REPORT_LEN 1024 19 | 20 | /** 21 | * struct tdx_report_req - Request struct for TDX_CMD_GET_REPORT0 IOCTL. 22 | * 23 | * @reportdata: User buffer with REPORTDATA to be included into TDREPORT. 24 | * Typically it can be some nonce provided by attestation 25 | * service, so the generated TDREPORT can be uniquely verified. 26 | * @tdreport: User buffer to store TDREPORT output from TDCALL[TDG.MR.REPORT]. 27 | */ 28 | struct tdx_report_req { 29 | __u8 reportdata[TDX_REPORTDATA_LEN]; 30 | __u8 tdreport[TDX_REPORT_LEN]; 31 | }; 32 | 33 | /* 34 | * TDX_CMD_GET_REPORT0 - Get TDREPORT0 (a.k.a. TDREPORT subtype 0) using 35 | * TDCALL[TDG.MR.REPORT] 36 | * 37 | * Return 0 on success, -EIO on TDCALL execution failure, and 38 | * standard errno on other general error cases. 39 | */ 40 | #define TDX_CMD_GET_REPORT0 _IOWR('T', 1, struct tdx_report_req) 41 | 42 | #define TDX_CMD_EXTEND_RTMR _IOR('T', 3, struct tdx_extend_rtmr_req) 43 | #define TDX_CMD_EXTEND_RTMR2 _IOW('T', 3, struct tdx_extend_rtmr_req) 44 | #define TDX_EXTEND_RTMR_DATA_LEN 48 45 | struct tdx_extend_rtmr_req 46 | { 47 | u8 data[TDX_EXTEND_RTMR_DATA_LEN]; 48 | u8 index; 49 | }; 50 | 51 | #endif /* _UAPI_LINUX_TDX_GUEST_H_ */ 52 | -------------------------------------------------------------------------------- /mod-tdx-guest/tdx.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | #ifndef _ASM_X86_SHARED_TDX_H 3 | #define _ASM_X86_SHARED_TDX_H 4 | 5 | #include 6 | #include 7 | 8 | #define TDX_HYPERCALL_STANDARD 0 9 | 10 | #define TDX_CPUID_LEAF_ID 0x21 11 | #define TDX_IDENT "IntelTDX " 12 | 13 | /* TDX module Call Leaf IDs */ 14 | #define TDG_VP_VMCALL 0 15 | #define TDG_VP_INFO 1 16 | #define TDG_MR_RTMR_EXTEND 2 17 | #define TDG_VP_VEINFO_GET 3 18 | #define TDG_MR_REPORT 4 19 | #define TDG_MEM_PAGE_ACCEPT 6 20 | #define TDG_VM_WR 8 21 | 22 | /* TDCS fields. To be used by TDG.VM.WR and TDG.VM.RD module calls */ 23 | #define TDCS_NOTIFY_ENABLES 0x9100000000000010 24 | 25 | /* TDX hypercall Leaf IDs */ 26 | #define TDVMCALL_MAP_GPA 0x10001 27 | #define TDVMCALL_GET_QUOTE 0x10002 28 | #define TDVMCALL_REPORT_FATAL_ERROR 0x10003 29 | 30 | #define TDVMCALL_STATUS_RETRY 1 31 | 32 | /* 33 | * Bitmasks of exposed registers (with VMM). 34 | */ 35 | #define TDX_RDX BIT(2) 36 | #define TDX_RBX BIT(3) 37 | #define TDX_RSI BIT(6) 38 | #define TDX_RDI BIT(7) 39 | #define TDX_R8 BIT(8) 40 | #define TDX_R9 BIT(9) 41 | #define TDX_R10 BIT(10) 42 | #define TDX_R11 BIT(11) 43 | #define TDX_R12 BIT(12) 44 | #define TDX_R13 BIT(13) 45 | #define TDX_R14 BIT(14) 46 | #define TDX_R15 BIT(15) 47 | 48 | /* 49 | * These registers are clobbered to hold arguments for each 50 | * TDVMCALL. They are safe to expose to the VMM. 51 | * Each bit in this mask represents a register ID. Bit field 52 | * details can be found in TDX GHCI specification, section 53 | * titled "TDCALL [TDG.VP.VMCALL] leaf". 54 | */ 55 | #define TDVMCALL_EXPOSE_REGS_MASK \ 56 | (TDX_RDX | TDX_RBX | TDX_RSI | TDX_RDI | TDX_R8 | TDX_R9 | \ 57 | TDX_R10 | TDX_R11 | TDX_R12 | TDX_R13 | TDX_R14 | TDX_R15) 58 | 59 | /* TDX supported page sizes from the TDX module ABI. */ 60 | #define TDX_PS_4K 0 61 | #define TDX_PS_2M 1 62 | #define TDX_PS_1G 2 63 | #define TDX_PS_NR (TDX_PS_1G + 1) 64 | 65 | #ifndef __ASSEMBLY__ 66 | 67 | #include 68 | 69 | /* 70 | * Used in __tdcall*() to gather the input/output registers' values of the 71 | * TDCALL instruction when requesting services from the TDX module. This is a 72 | * software only structure and not part of the TDX module/VMM ABI 73 | */ 74 | struct tdx_module_args { 75 | /* callee-clobbered */ 76 | u64 rcx; 77 | u64 rdx; 78 | u64 r8; 79 | u64 r9; 80 | /* extra callee-clobbered */ 81 | u64 r10; 82 | u64 r11; 83 | /* callee-saved + rdi/rsi */ 84 | u64 r12; 85 | u64 r13; 86 | u64 r14; 87 | u64 r15; 88 | u64 rbx; 89 | u64 rdi; 90 | u64 rsi; 91 | }; 92 | 93 | /* Used to communicate with the TDX module */ 94 | u64 __tdcall(u64 fn, struct tdx_module_args *args); 95 | u64 __tdcall_ret(u64 fn, struct tdx_module_args *args); 96 | u64 __tdcall_saved_ret(u64 fn, struct tdx_module_args *args); 97 | 98 | #endif /* !__ASSEMBLY__ */ 99 | #endif /* _ASM_X86_SHARED_TDX_H */ 100 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | build/ 4 | -------------------------------------------------------------------------------- /python/ct_monitor/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "ct_monitor" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "requests", 6 | "cryptography", 7 | ] 8 | 9 | [project.scripts] 10 | ct_monitor = "ct_monitor:main" 11 | -------------------------------------------------------------------------------- /ra-rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ra-rpc" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | prpc.workspace = true 11 | rocket = { workspace = true, features = ["mtls"], optional = true } 12 | serde_json.workspace = true 13 | tracing.workspace = true 14 | reqwest = { workspace = true, default-features = false, features = ["rustls-tls", "charset"], optional = true } 15 | 16 | ra-tls.workspace = true 17 | bon.workspace = true 18 | rocket-vsock-listener = { workspace = true, optional = true } 19 | serde.workspace = true 20 | x509-parser.workspace = true 21 | 22 | [features] 23 | default = ["rocket", "client"] 24 | rocket = ["dep:rocket", "dep:rocket-vsock-listener"] 25 | client = ["reqwest"] 26 | -------------------------------------------------------------------------------- /ra-rpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(async_fn_in_trait)] 2 | 3 | use std::{net::SocketAddr, path::PathBuf}; 4 | 5 | use anyhow::Result; 6 | use prpc::{codec::encode_message_to_vec, server::Service as PrpcService}; 7 | use tracing::{error, info}; 8 | 9 | pub use ra_tls::attestation::{Attestation, VerifiedAttestation}; 10 | 11 | #[cfg(feature = "rocket")] 12 | pub mod rocket_helper; 13 | 14 | #[cfg(feature = "client")] 15 | pub mod client; 16 | 17 | #[derive(Debug, Clone, PartialEq, Eq)] 18 | pub enum RemoteEndpoint { 19 | Tcp(SocketAddr), 20 | Quic(SocketAddr), 21 | Unix(PathBuf), 22 | Vsock { cid: u32, port: u32 }, 23 | Other(String), 24 | } 25 | 26 | #[derive(Clone, bon::Builder)] 27 | pub struct CallContext<'a, State> { 28 | pub state: &'a State, 29 | pub attestation: Option, 30 | pub remote_endpoint: Option, 31 | pub remote_app_id: Option>, 32 | } 33 | 34 | pub trait RpcCall: Sized { 35 | type PrpcService: PrpcService + From + Send + 'static; 36 | 37 | fn construct(context: CallContext<'_, State>) -> Result; 38 | 39 | async fn call( 40 | self, 41 | method: String, 42 | payload: Vec, 43 | is_json: bool, 44 | is_query: bool, 45 | ) -> (u16, Vec) { 46 | dispatch_prpc( 47 | method, 48 | payload, 49 | is_json, 50 | is_query, 51 | >::from(self), 52 | ) 53 | .await 54 | } 55 | } 56 | 57 | async fn dispatch_prpc( 58 | path: String, 59 | data: Vec, 60 | json: bool, 61 | query: bool, 62 | server: impl PrpcService + Send + 'static, 63 | ) -> (u16, Vec) { 64 | info!("dispatching request: {path}"); 65 | let result = server.dispatch_request(&path, data, json, query).await; 66 | let (code, data) = match result { 67 | Ok(data) => (200, data), 68 | Err(err) => { 69 | error!("rpc error: {err:?}"); 70 | (400, encode_error(json, format!("{err:?}"))) 71 | } 72 | }; 73 | (code, data) 74 | } 75 | 76 | pub fn encode_error(json: bool, error: impl Into) -> Vec { 77 | if json { 78 | serde_json::to_string_pretty(&serde_json::json!({ "error": error.into() })) 79 | .unwrap_or_else(|_| r#"{"error": "failed to encode the error"}"#.to_string()) 80 | .into_bytes() 81 | } else { 82 | encode_message_to_vec(&::prpc::server::ProtoError::new(error.into())) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /ra-tls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ra-tls" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | bon.workspace = true 11 | dcap-qvl.workspace = true 12 | elliptic-curve.workspace = true 13 | fs-err.workspace = true 14 | hex.workspace = true 15 | hkdf.workspace = true 16 | p256.workspace = true 17 | rcgen = { workspace = true, features = ["x509-parser", "pem"] } 18 | ring.workspace = true 19 | rustls-pki-types.workspace = true 20 | serde.workspace = true 21 | serde_json.workspace = true 22 | sha2.workspace = true 23 | x509-parser.workspace = true 24 | yasna.workspace = true 25 | tracing.workspace = true 26 | sha3.workspace = true 27 | tdx-attest.workspace = true 28 | scale.workspace = true 29 | 30 | cc-eventlog.workspace = true 31 | serde-human-bytes.workspace = true 32 | -------------------------------------------------------------------------------- /ra-tls/assets/tdx_quote: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/ra-tls/assets/tdx_quote -------------------------------------------------------------------------------- /ra-tls/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! RATLS library for Phala 2 | #![deny(missing_docs)] 3 | 4 | pub extern crate dcap_qvl as qvl; 5 | pub extern crate rcgen; 6 | 7 | pub mod attestation; 8 | pub mod cert; 9 | pub mod kdf; 10 | pub mod oids; 11 | pub mod traits; 12 | -------------------------------------------------------------------------------- /ra-tls/src/oids.rs: -------------------------------------------------------------------------------- 1 | //! OIDs used by the RATLS protocol. 2 | 3 | /// OID for the SGX/TDX quote extension. 4 | pub const PHALA_RATLS_QUOTE: &[u64] = &[1, 3, 6, 1, 4, 1, 62397, 1, 1]; 5 | /// OID for the TDX event log extension. 6 | pub const PHALA_RATLS_EVENT_LOG: &[u64] = &[1, 3, 6, 1, 4, 1, 62397, 1, 2]; 7 | /// OID for the TDX app ID extension. 8 | pub const PHALA_RATLS_APP_ID: &[u64] = &[1, 3, 6, 1, 4, 1, 62397, 1, 3]; 9 | /// OID for Special Certificate Usage. 10 | pub const PHALA_RATLS_CERT_USAGE: &[u64] = &[1, 3, 6, 1, 4, 1, 62397, 1, 4]; 11 | -------------------------------------------------------------------------------- /ra-tls/src/traits.rs: -------------------------------------------------------------------------------- 1 | //! Traits for the crate 2 | 3 | use anyhow::{Context, Result}; 4 | 5 | use crate::oids::{PHALA_RATLS_APP_ID, PHALA_RATLS_CERT_USAGE}; 6 | 7 | /// Types that can get custom cert extensions from. 8 | pub trait CertExt { 9 | /// Get a cert extension from the type. 10 | fn get_extension_der(&self, oid: &[u64]) -> Result>>; 11 | /// Get externtion bytes 12 | fn get_extension_bytes(&self, oid: &[u64]) -> Result>> { 13 | let Some(der) = self.get_extension_der(oid)? else { 14 | return Ok(None); 15 | }; 16 | let ext = yasna::parse_der(&der, |reader| reader.read_bytes())?; 17 | Ok(Some(ext)) 18 | } 19 | 20 | /// Get Certificate Special Usage from the type. 21 | fn get_special_usage(&self) -> Result> { 22 | let Some(found) = self 23 | .get_extension_bytes(PHALA_RATLS_CERT_USAGE) 24 | .context("Failed to get extension")? 25 | else { 26 | return Ok(None); 27 | }; 28 | let found = String::from_utf8(found).context("Failed to decode special usage as utf8")?; 29 | Ok(Some(found)) 30 | } 31 | 32 | /// Get the app id from the certificate 33 | fn get_app_id(&self) -> Result>> { 34 | let app_id = self 35 | .get_extension_bytes(PHALA_RATLS_APP_ID) 36 | .context("Failed to get extension")?; 37 | Ok(app_id) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /rocket-vsock-listener/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rocket-vsock-listener" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | derive_more = { workspace = true, features = ["display"] } 11 | pin-project.workspace = true 12 | rocket.workspace = true 13 | serde = { workspace = true, features = ["derive"] } 14 | thiserror.workspace = true 15 | tokio-vsock.workspace = true 16 | 17 | [dev-dependencies] 18 | tokio = { workspace = true, features = ["full"] } 19 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | (cd sdk/simulator && ./build.sh) 5 | 6 | pushd sdk/simulator 7 | ./dstack-simulator & 8 | SIMULATOR_PID=$! 9 | echo "Simulator process (PID: $SIMULATOR_PID) started." 10 | popd 11 | 12 | export DSTACK_SIMULATOR_ENDPOINT=$(realpath sdk/simulator/dstack.sock) 13 | 14 | echo "DSTACK_SIMULATOR_ENDPOINT: $DSTACK_SIMULATOR_ENDPOINT" 15 | 16 | # Run the tests 17 | cargo test 18 | 19 | # Kill the simulator after tests finish 20 | kill $SIMULATOR_PID 21 | echo "Simulator process (PID: $SIMULATOR_PID) terminated." 22 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VMDIR=$1 4 | IMAGE_PATH=./images/$(jq -r '.image' ${VMDIR}/vm-manifest.json) 5 | IMG_METADATA=${IMAGE_PATH}/metadata.json 6 | MEM=$(jq -r '.memory' ${VMDIR}/vm-manifest.json) 7 | VCPUS=$(jq -r '.vcpu' ${VMDIR}/vm-manifest.json) 8 | 9 | VDA=${VMDIR}/hda.img 10 | 11 | PROCESS_NAME=qemu 12 | 13 | INITRD=${IMAGE_PATH}/$(jq -r '.initrd' ${IMG_METADATA}) 14 | KERNEL=${IMAGE_PATH}/$(jq -r '.kernel' ${IMG_METADATA}) 15 | CDROM=${IMAGE_PATH}/$(jq -r '.rootfs' ${IMG_METADATA}) 16 | TDVF_FIRMWARE=${IMAGE_PATH}/$(jq -r '.bios' ${IMG_METADATA}) 17 | CMDLINE=$(jq -r '.cmdline' ${IMG_METADATA}) 18 | CONFIG_DIR=${VMDIR}/shared 19 | TD=${TD:-1} 20 | RO=${RO:-on} 21 | CID=$(( ( RANDOM % 10000 ) + 3 )) 22 | 23 | ARGS="${ARGS} -kernel ${KERNEL}" 24 | ARGS="${ARGS} -initrd ${INITRD}" 25 | 26 | echo INITRD=${INITRD} 27 | echo ARGS=${ARGS} 28 | echo VDA=${VDA} 29 | echo CMDLINE=${CMDLINE} 30 | echo TD=${TD} 31 | 32 | if [ "${TD}" == "1" ]; then 33 | MACHINE_ARGS=",confidential-guest-support=tdx,hpet=off" 34 | PROCESS_NAME=td 35 | TDX_ARGS="-device vhost-vsock-pci,guest-cid=${CID} -object tdx-guest,id=tdx" 36 | fi 37 | BIOS="-bios ${TDVF_FIRMWARE}" 38 | 39 | sleep 2 40 | 41 | qemu-system-x86_64 \ 42 | -accel kvm \ 43 | -m ${MEM}M -smp ${VCPUS} \ 44 | -name ${PROCESS_NAME},process=${PROCESS_NAME},debug-threads=on \ 45 | -cpu host \ 46 | -machine q35,kernel_irqchip=split${MACHINE_ARGS} \ 47 | ${BIOS} \ 48 | ${TDX_ARGS} \ 49 | -nographic \ 50 | -nodefaults \ 51 | -chardev stdio,id=ser0,signal=on -serial chardev:ser0 \ 52 | -device virtio-net-pci,netdev=nic0_td -netdev user,id=nic0_td \ 53 | -drive file=${VDA},if=none,id=virtio-disk0 -device virtio-blk-pci,drive=virtio-disk0 \ 54 | -cdrom ${CDROM} \ 55 | -virtfs local,path=${CONFIG_DIR},mount_tag=host-shared,readonly=${RO},security_model=mapped,id=virtfs0 \ 56 | ${ARGS} \ 57 | -append "${CMDLINE}" 58 | -------------------------------------------------------------------------------- /sdk/README.md: -------------------------------------------------------------------------------- 1 | # SDK 2 | 3 | This SDK propose for simple the interaction with DStack guest agent. You can also download simulator [here](https://github.com/Leechael/dstack-simulator/releases) or using the [Docker based image](https://hub.docker.com/r/phalanetwork/dstack-simulator) to kick start without real TDX hardware. 4 | 5 | Checkout [this guide](https://docs.phala.network/references/hackathon-guides/ethglobal-bangkok) for further information. -------------------------------------------------------------------------------- /sdk/go/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Dstack-TEE/dstack/sdk/go 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.8 6 | 7 | require ( 8 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect 9 | github.com/ethereum/go-ethereum v1.15.7 // indirect 10 | github.com/holiman/uint256 v1.3.2 // indirect 11 | golang.org/x/crypto v0.35.0 // indirect 12 | golang.org/x/sys v0.30.0 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /sdk/go/go.sum: -------------------------------------------------------------------------------- 1 | github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= 2 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= 3 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= 4 | github.com/ethereum/go-ethereum v1.15.7 h1:vm1XXruZVnqtODBgqFaTclzP0xAvCvQIDKyFNUA1JpY= 5 | github.com/ethereum/go-ethereum v1.15.7/go.mod h1:+S9k+jFzlyVTNcYGvqFhzN/SFhI6vA+aOY4T5tLSPL0= 6 | github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= 7 | github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= 8 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= 9 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 10 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 11 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 12 | -------------------------------------------------------------------------------- /sdk/js/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | *.log 4 | -------------------------------------------------------------------------------- /sdk/js/.npmignore: -------------------------------------------------------------------------------- 1 | README.md 2 | bun.lockb 3 | src/__tests__ 4 | -------------------------------------------------------------------------------- /sdk/js/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/sdk/js/bun.lockb -------------------------------------------------------------------------------- /sdk/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@phala/dstack-sdk", 3 | "version": "0.2.0", 4 | "description": "Dstack SDK", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "exports": { 8 | ".": "./dist/index.js", 9 | "./viem": { 10 | "import": "./dist/viem.js", 11 | "require": "./dist/viem.js", 12 | "types": "./dist/viem.d.ts" 13 | }, 14 | "./encrypt-env-vars": { 15 | "import": "./dist/encrypt-env-vars.js", 16 | "require": "./dist/encrypt-env-vars.js", 17 | "types": "./dist/encrypt-env-vars.d.ts" 18 | }, 19 | "./solana": { 20 | "import": "./dist/solana.js", 21 | "require": "./dist/solana.js", 22 | "types": "./dist/solana.d.ts" 23 | } 24 | }, 25 | "engines": { 26 | "node": ">=18.0.0" 27 | }, 28 | "scripts": { 29 | "build": "tsc", 30 | "test": "vitest", 31 | "test:ci": "vitest --run", 32 | "release": "npm run build && npm publish --access public" 33 | }, 34 | "keywords": ["sdk", "Dstack", "Phala"], 35 | "author": "Leechael Yim", 36 | "license": "Apache-2.0", 37 | "devDependencies": { 38 | "@types/node": "latest", 39 | "typescript": "latest", 40 | "vitest": "^2.1.3" 41 | }, 42 | "optionalDependencies": { 43 | "viem": "^2.21.0 <3.0.0", 44 | "@noble/curves": "^1.8.1", 45 | "@solana/web3.js": "^1.98.0" 46 | }, 47 | "peerDependencies": { 48 | "viem": "^2.21.0 <3.0.0", 49 | "@noble/curves": "^1.8.1", 50 | "@solana/web3.js": "^1.98.0" 51 | }, 52 | "peerDependenciesMeta": { 53 | "viem": { 54 | "optional": true 55 | }, 56 | "@noble/curves": { 57 | "optional": true 58 | }, 59 | "@solana/web3.js": { 60 | "optional": true 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /sdk/js/src/__tests__/solana.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from 'vitest' 2 | import { Keypair } from '@solana/web3.js' 3 | 4 | import { DstackClient } from '../index' 5 | import { toKeypair } from '../solana' 6 | 7 | describe('solana support', () => { 8 | it('should able to get keypair from deriveKey', async () => { 9 | const client = new DstackClient() 10 | const result = await client.getKey('/', 'test') 11 | const keypair = toKeypair(result) 12 | expect(keypair).toBeInstanceOf(Keypair) 13 | console.log(keypair.publicKey.toBase58()) 14 | }) 15 | }) -------------------------------------------------------------------------------- /sdk/js/src/__tests__/viem.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from 'vitest' 2 | import { DstackClient } from '../index' 3 | import { toViemAccount } from '../viem' 4 | 5 | describe('viem support', () => { 6 | it('should able to get account from getKey', async () => { 7 | const client = new DstackClient() 8 | const result = await client.getKey('/', 'test') 9 | const account = toViemAccount(result) 10 | 11 | expect(account.source).toBe('privateKey') 12 | expect(typeof account.sign).toBe('function') 13 | expect(typeof account.signMessage).toBe('function') 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /sdk/js/src/encrypt-env-vars.ts: -------------------------------------------------------------------------------- 1 | import { x25519 } from "@noble/curves/ed25519" 2 | import crypto from 'crypto' 3 | 4 | // Convert hex string to Uint8Array 5 | function hexToUint8Array(hex: string) { 6 | hex = hex.startsWith("0x") ? hex.slice(2) : hex; 7 | return new Uint8Array( 8 | hex.match(/.{1,2}/g)?.map((byte: string) => parseInt(byte, 16)) ?? [], 9 | ); 10 | } 11 | 12 | function uint8ArrayToHex(buffer: Uint8Array) { 13 | return Array.from(buffer) 14 | .map((byte: number) => byte.toString(16).padStart(2, "0")) 15 | .join(""); 16 | } 17 | 18 | export interface EnvVar { 19 | key: string 20 | value: string 21 | } 22 | 23 | // Encrypt environment variables 24 | export async function encryptEnvVars(envs: EnvVar[], publicKeyHex: string) { 25 | // Prepare environment data 26 | const envsJson = JSON.stringify({ env: envs }); 27 | 28 | // Generate private key and derive public key 29 | const privateKey = x25519.utils.randomPrivateKey(); 30 | const publicKey = x25519.getPublicKey(privateKey); 31 | 32 | // Generate shared key 33 | const remotePubkey = hexToUint8Array(publicKeyHex); 34 | const shared = x25519.getSharedSecret(privateKey, remotePubkey); 35 | 36 | // Import shared key for AES-GCM 37 | const importedShared = await crypto.subtle.importKey( 38 | "raw", 39 | shared, 40 | { name: "AES-GCM", length: 256 }, 41 | true, 42 | ["encrypt"], 43 | ); 44 | 45 | // Encrypt the data 46 | const iv = crypto.getRandomValues(new Uint8Array(12)); 47 | const encrypted = await crypto.subtle.encrypt( 48 | { name: "AES-GCM", iv }, 49 | importedShared, 50 | new TextEncoder().encode(envsJson), 51 | ); 52 | 53 | // Combine all components 54 | const result = new Uint8Array( 55 | publicKey.length + iv.length + encrypted.byteLength, 56 | ); 57 | 58 | result.set(publicKey); 59 | result.set(iv, publicKey.length); 60 | result.set(new Uint8Array(encrypted), publicKey.length + iv.length); 61 | 62 | return uint8ArrayToHex(result); 63 | } -------------------------------------------------------------------------------- /sdk/js/src/solana.ts: -------------------------------------------------------------------------------- 1 | import { type GetKeyResponse } from './index' 2 | import { Keypair } from '@solana/web3.js' 3 | 4 | export function toKeypair(keyResponse: GetKeyResponse) { 5 | return Keypair.fromSeed(keyResponse.key) 6 | } -------------------------------------------------------------------------------- /sdk/js/src/viem.ts: -------------------------------------------------------------------------------- 1 | import { type GetKeyResponse } from './index' 2 | import { privateKeyToAccount } from 'viem/accounts' 3 | 4 | export function toViemAccount(keyResponse: GetKeyResponse) { 5 | const hex = Array.from(keyResponse.key).map(b => b.toString(16).padStart(2, '0')).join('') 6 | return privateKeyToAccount(`0x${hex}`) 7 | } 8 | -------------------------------------------------------------------------------- /sdk/js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "lib": ["es2018"], 6 | "declaration": true, 7 | "declarationMap": true, 8 | "sourceMap": true, 9 | "outDir": "./dist", 10 | "rootDir": "./src", 11 | "strict": true, 12 | "esModuleInterop": true, 13 | "skipLibCheck": true, 14 | "forceConsistentCasingInFileNames": true 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules", "**/*.test.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /sdk/python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "dstack-sdk" 3 | version = "0.2.0" 4 | description = "DStack SDK for Python" 5 | authors = [ 6 | {name = "Leechael Yim", email = "yanleech@gmail.com"}, 7 | ] 8 | dependencies = [ 9 | "httpx>=0.27.2", 10 | "asyncio>=3.4.3", 11 | "pydantic>=2.9.2", 12 | ] 13 | requires-python = ">=3.10" 14 | readme = "README.md" 15 | license = {text = "Apache-2.0"} 16 | 17 | [project.optional-dependencies] 18 | solana = ["solders"] 19 | ethereum = ["web3"] 20 | sol = ["solders"] 21 | eth = ["web3"] 22 | all = ["solders", "web3"] 23 | 24 | [build-system] 25 | requires = ["pdm-backend"] 26 | build-backend = "pdm.backend" 27 | 28 | [tool.pdm] 29 | distribution = true 30 | 31 | [tool.pdm.publish] 32 | repository = "pypi" 33 | 34 | [tool.pdm.dev-dependencies] 35 | test = [ 36 | "pytest>=8.3.3", 37 | "pytest-asyncio>=0.24.0", 38 | "evidence-api>=0.5.0", 39 | ] 40 | solana = [ 41 | "solders", 42 | ] 43 | ethereum = [ 44 | "web3", 45 | ] -------------------------------------------------------------------------------- /sdk/python/src/dstack_sdk/__init__.py: -------------------------------------------------------------------------------- 1 | from .dstack_client import DstackClient, AsyncDstackClient, GetKeyResponse, GetTlsKeyResponse, GetQuoteResponse 2 | 3 | __all__ = ['DstackClient', 'AsyncDstackClient', 'GetKeyResponse', 'GetTlsKeyResponse', 'GetQuoteResponse'] 4 | -------------------------------------------------------------------------------- /sdk/python/src/dstack_sdk/ethereum.py: -------------------------------------------------------------------------------- 1 | from eth_account import Account 2 | 3 | from .dstack_client import GetKeyResponse 4 | 5 | def to_account(get_key_response: GetKeyResponse) -> Account: 6 | return Account.from_key(get_key_response.decode_key()) 7 | -------------------------------------------------------------------------------- /sdk/python/src/dstack_sdk/solana.py: -------------------------------------------------------------------------------- 1 | from solders.keypair import Keypair 2 | 3 | from .dstack_client import GetKeyResponse 4 | 5 | def to_keypair(get_key_response: GetKeyResponse) -> Keypair: 6 | return Keypair.from_seed(get_key_response.decode_key()) 7 | -------------------------------------------------------------------------------- /sdk/python/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phala-Network/dstack/0dd93563763a3fb2da3b5fb52c953894abcd3ebf/sdk/python/tests/__init__.py -------------------------------------------------------------------------------- /sdk/python/tests/test_ethereum.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from eth_account.signers.local import LocalAccount 3 | 4 | from dstack_sdk import AsyncDstackClient, GetKeyResponse 5 | from dstack_sdk.ethereum import to_account 6 | 7 | @pytest.mark.asyncio 8 | async def test_async_to_keypair(): 9 | client = AsyncDstackClient() 10 | result = await client.get_key('test') 11 | assert isinstance(result, GetKeyResponse) 12 | account = to_account(result) 13 | assert isinstance(account, LocalAccount) -------------------------------------------------------------------------------- /sdk/python/tests/test_solana.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from solders.keypair import Keypair 3 | 4 | from dstack_sdk import AsyncDstackClient, GetKeyResponse 5 | from dstack_sdk.solana import to_keypair 6 | 7 | @pytest.mark.asyncio 8 | async def test_async_to_keypair(): 9 | client = AsyncDstackClient() 10 | result = await client.get_key('test') 11 | assert isinstance(result, GetKeyResponse) 12 | keypair = to_keypair(result) 13 | assert isinstance(keypair, Keypair) -------------------------------------------------------------------------------- /sdk/rust/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ -------------------------------------------------------------------------------- /sdk/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dstack-sdk" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "This crate provides a rust client for communicating with dstack" 7 | authors = ["Encifher "] 8 | 9 | [dependencies] 10 | alloy = { workspace = true, features = ["signers", "signer-local"] } 11 | anyhow.workspace = true 12 | bon.workspace = true 13 | hex.workspace = true 14 | http.workspace = true 15 | http-client-unix-domain-socket = "0.1.1" 16 | reqwest = { workspace = true, features = ["json"] } 17 | serde.workspace = true 18 | serde_json.workspace = true 19 | sha2.workspace = true 20 | 21 | [dev-dependencies] 22 | dcap-qvl.workspace = true 23 | tokio = { workspace = true, features = ["full"] } 24 | -------------------------------------------------------------------------------- /sdk/rust/src/ethereum.rs: -------------------------------------------------------------------------------- 1 | use crate::dstack_client::GetKeyResponse; 2 | use alloy::signers::local::PrivateKeySigner; 3 | 4 | pub fn to_account( 5 | get_key_response: &GetKeyResponse, 6 | ) -> Result> { 7 | let key_bytes = hex::decode(&get_key_response.key)?; 8 | let wallet = PrivateKeySigner::from_slice(&key_bytes)?; 9 | Ok(wallet) 10 | } 11 | -------------------------------------------------------------------------------- /sdk/rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod dstack_client; 2 | pub mod ethereum; 3 | -------------------------------------------------------------------------------- /sdk/rust/tests/test_client.rs: -------------------------------------------------------------------------------- 1 | use dcap_qvl::quote::Quote; 2 | use dstack_sdk::dstack_client::DstackClient as AsyncDstackClient; 3 | 4 | #[tokio::test] 5 | async fn test_async_client_get_key() { 6 | let client = AsyncDstackClient::new(None); 7 | let result = client.get_key(None, None).await.unwrap(); 8 | assert!(!result.key.is_empty()); 9 | assert_eq!(result.decode_key().unwrap().len(), 32); 10 | } 11 | 12 | #[tokio::test] 13 | async fn test_async_client_get_quote() { 14 | let client = AsyncDstackClient::new(None); 15 | let result = client.get_quote("test".into()).await.unwrap(); 16 | assert!(!result.quote.is_empty()); 17 | } 18 | 19 | #[tokio::test] 20 | async fn test_async_client_get_tls_key() { 21 | let client = AsyncDstackClient::new(None); 22 | let key_config = dstack_sdk::dstack_client::TlsKeyConfig::builder().build(); 23 | let result = client.get_tls_key(key_config).await.unwrap(); 24 | assert!(result.key.starts_with("-----BEGIN PRIVATE KEY-----")); 25 | assert!(!result.certificate_chain.is_empty()); 26 | } 27 | 28 | #[tokio::test] 29 | async fn test_tls_key_uniqueness() { 30 | let client = AsyncDstackClient::new(None); 31 | let key_config_1 = dstack_sdk::dstack_client::TlsKeyConfig::builder().build(); 32 | let key_config_2 = dstack_sdk::dstack_client::TlsKeyConfig::builder().build(); 33 | let result1 = client.get_tls_key(key_config_1).await.unwrap(); 34 | let result2 = client.get_tls_key(key_config_2).await.unwrap(); 35 | assert_ne!(result1.key, result2.key); 36 | } 37 | 38 | #[tokio::test] 39 | async fn test_replay_rtmr() { 40 | let client = AsyncDstackClient::new(None); 41 | let result = client.get_quote("test".into()).await.unwrap(); 42 | let rtmrs = result.replay_rtmrs().unwrap(); 43 | let quote = result.decode_quote().unwrap(); 44 | 45 | let tdx_quote = Quote::parse("e).unwrap(); 46 | let quote_report = tdx_quote.report.as_td10().unwrap(); 47 | assert_eq!(rtmrs[&0], hex::encode(quote_report.rt_mr0)); 48 | assert_eq!(rtmrs[&1], hex::encode(quote_report.rt_mr1)); 49 | assert_eq!(rtmrs[&2], hex::encode(quote_report.rt_mr2)); 50 | assert_eq!(rtmrs[&3], hex::encode(quote_report.rt_mr3)); 51 | } 52 | 53 | #[tokio::test] 54 | async fn test_report_data() { 55 | let report_data = "test"; 56 | let client = AsyncDstackClient::new(None); 57 | let result = client.get_quote(report_data.into()).await.unwrap(); 58 | let quote = result.decode_quote().unwrap(); 59 | 60 | let tdx_quote = Quote::parse("e).unwrap(); 61 | let quote_report = tdx_quote.report.as_td10().unwrap(); 62 | let expected = { 63 | let mut padded = report_data.as_bytes().to_vec(); 64 | padded.resize(64, 0); 65 | padded 66 | }; 67 | assert_eq!("e_report.report_data[..], &expected[..]); 68 | } 69 | -------------------------------------------------------------------------------- /sdk/rust/tests/test_eth.rs: -------------------------------------------------------------------------------- 1 | use dstack_sdk::dstack_client::{DstackClient, GetKeyResponse}; 2 | use dstack_sdk::ethereum::to_account; 3 | 4 | #[tokio::test] 5 | async fn test_async_to_keypair() { 6 | let client = DstackClient::new(None); 7 | let result = client 8 | .get_key(Some("test".to_string()), None) 9 | .await 10 | .expect("get_key failed"); 11 | 12 | let _: &GetKeyResponse = &result; 13 | let _wallet = to_account(&result).expect("to_account failed"); 14 | } 15 | -------------------------------------------------------------------------------- /sdk/simulator/.gitignore: -------------------------------------------------------------------------------- 1 | dstack-simulator 2 | dstack-guest-agent 3 | *.lock 4 | -------------------------------------------------------------------------------- /sdk/simulator/app-compose.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "kvin-nb", 4 | "runner": "docker-compose", 5 | "docker_compose_file": "services:\n jupyter:\n image: quay.io/jupyter/base-notebook\n user: root\n environment:\n - GRANT_SUDO=yes\n ports:\n - \"8888:8888\"\n volumes:\n - /:/host/\n - /var/run/tappd.sock:/var/run/tappd.sock\n - /var/run/dstack.sock:/var/run/dstack.sock\n logging:\n driver: journald\n options:\n tag: jupyter-notebook\n", 6 | "docker_config": {}, 7 | "kms_enabled": true, 8 | "tproxy_enabled": true, 9 | "public_logs": true, 10 | "public_sysinfo": true, 11 | "public_tcbinfo": false, 12 | "local_key_provider_enabled": false, 13 | "allowed_envs": [], 14 | "no_instance_id": false 15 | } 16 | -------------------------------------------------------------------------------- /sdk/simulator/appkeys.json: -------------------------------------------------------------------------------- 1 | { 2 | "disk_crypt_key": "1122e1f340c19407adc5ec531ac98d72bcf702bf7858f6fa49b5be79b61e4d5b", 3 | "env_crypt_key": "ca1a3895d9d613287fc14034d0ec60abb5089896e7c8fd7c2f02bd91fa0076aa", 4 | "k256_key": "e0e5d254fb944dcc370a2e5288b336a1e809871545a73ee645368957fefa31f9", 5 | "k256_signature": "2f431c7956869a4fe3e028c5f9518a935e2d01e81a3628f8b1d178fc2fac7b6d2405ace433624e5568e23c4ed291dbaf60dac79b756837c0fe745154ebfdc0a601", 6 | "gateway_app_id": "any", 7 | "ca_cert": "-----BEGIN CERTIFICATE-----\nMIIBmTCCAUCgAwIBAgIUU7801+krCs2OpIdne3t6OWrJ2fMwCgYIKoZIzj0EAwIw\nKTEPMA0GA1UECgwGRHN0YWNrMRYwFAYDVQQDDA1Ec3RhY2sgS01TIENBMB4XDTc1\nMDEwMTAwMDAwMFoXDTM1MDMxNzA5NDQ0MlowKTEPMA0GA1UECgwGRHN0YWNrMRYw\nFAYDVQQDDA1Ec3RhY2sgS01TIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\nGbJFfdm4qmRG2YDxNv/3gS7NbHd0DusOKLENVsDAACiltuWdzqMH1YO9H3B2npwR\nbfK8+xdYqV2GE+feHISCwKNGMEQwDwYDVR0PAQH/BAUDAweAADAdBgNVHQ4EFgQU\nevjJ+VZPvDxHJ2ejjeIaUYMMcEcwEgYDVR0TAQH/BAgwBgEB/wIBATAKBggqhkjO\nPQQDAgNHADBEAiAhQHQNbmyvx9BDBXRjW1eCkPCpFs/2Vt/nvbi+M69FPAIgQ13F\n3pmxicxyFeVW2iOjrbG1cxLdT9Kh+9ICF9zn8kA=\n-----END CERTIFICATE-----\n", 8 | "key_provider": { 9 | "Local": { 10 | "key": "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg1PYCFKYfDmUfv5fk\nstppasf4mPGqnz0fEoLEnGx8CnKhRANCAAQZskV92biqZEbZgPE2//eBLs1sd3QO\n6w4osQ1WwMAAKKW25Z3OowfVg70fcHaenBFt8rz7F1ipXYYT594chILA\n-----END PRIVATE KEY-----\n" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /sdk/simulator/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd $(dirname $0) 3 | cargo build --release -p dstack-guest-agent 4 | cp ../../target/release/dstack-guest-agent . 5 | ln -sf dstack-guest-agent dstack-simulator 6 | 7 | -------------------------------------------------------------------------------- /sdk/simulator/dstack.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | workers = 8 3 | max_blocking = 64 4 | ident = "Dstack Simulator" 5 | temp_dir = "/tmp" 6 | keep_alive = 10 7 | log_level = "debug" 8 | 9 | [default.core] 10 | keys_file = "appkeys.json" 11 | compose_file = "app-compose.json" 12 | sys_config_file = "sys-config.json" 13 | 14 | [default.core.simulator] 15 | enabled = true 16 | quote_file = "quote.hex" 17 | event_log_file = "eventlog.json" 18 | 19 | [internal-v0] 20 | address = "unix:./tappd.sock" 21 | reuse = true 22 | 23 | [internal] 24 | address = "unix:./dstack.sock" 25 | reuse = true 26 | 27 | [external] 28 | address = "unix:./external.sock" 29 | reuse = true 30 | 31 | [guest-api] 32 | address = "unix:./guest.sock" 33 | reuse = true 34 | 35 | -------------------------------------------------------------------------------- /sdk/simulator/sys-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "kms_urls": [ 3 | "https://kms.1022.kvin.wang:12001" 4 | ], 5 | "gateway_urls": [ 6 | "https://tproxy.1022.kvin.wang:12002" 7 | ], 8 | "pccs_url": "", 9 | "docker_registry": "", 10 | "host_api_url": "vsock://2:12000/api", 11 | "vm_config": "{\"os_image_hash\":\"64f0d1545cd510a8dfed7ad609d105b5d41f0cb2afcfdda8867ede00c88add7a\",\"cpu_count\":1,\"memory_size\":2147483648}" 12 | } -------------------------------------------------------------------------------- /serde-duration/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde-duration" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | [dependencies] 11 | serde = { workspace = true, features = ["derive"] } 12 | -------------------------------------------------------------------------------- /serde-duration/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Deserializer, Serializer}; 2 | use std::time::Duration; 3 | 4 | pub fn serialize(duration: &Duration, serializer: S) -> Result 5 | where 6 | S: Serializer, 7 | { 8 | if duration == &Duration::MAX { 9 | return serializer.serialize_str("never"); 10 | } 11 | let (value, unit) = if duration.as_secs() % (24 * 3600) == 0 { 12 | (duration.as_secs() / (24 * 3600), "d") 13 | } else if duration.as_secs() % 3600 == 0 { 14 | (duration.as_secs() / 3600, "h") 15 | } else if duration.as_secs() % 60 == 0 { 16 | (duration.as_secs() / 60, "m") 17 | } else { 18 | (duration.as_secs(), "s") 19 | }; 20 | serializer.serialize_str(&format!("{}{}", value, unit)) 21 | } 22 | 23 | pub fn deserialize<'de, D>(deserializer: D) -> Result 24 | where 25 | D: Deserializer<'de>, 26 | { 27 | let s = String::deserialize(deserializer)?; 28 | if s.is_empty() { 29 | return Err(serde::de::Error::custom("Duration string cannot be empty")); 30 | } 31 | if s == "never" { 32 | return Ok(Duration::MAX); 33 | } 34 | let (value, unit) = s.split_at(s.len() - 1); 35 | let value = value.parse::().map_err(serde::de::Error::custom)?; 36 | 37 | let seconds = match unit { 38 | "s" => value, 39 | "m" => value * 60, 40 | "h" => value * 3600, 41 | "d" => value * 24 * 3600, 42 | _ => { 43 | return Err(serde::de::Error::custom( 44 | "Invalid time unit. Use s, m, h, or d", 45 | )) 46 | } 47 | }; 48 | 49 | Ok(Duration::from_secs(seconds)) 50 | } 51 | -------------------------------------------------------------------------------- /sodiumbox/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sodiumbox" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "Pure Rust implementation of libsodium's sealed box encryption" 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | [dependencies] 11 | x25519-dalek.workspace = true 12 | xsalsa20poly1305.workspace = true 13 | salsa20.workspace = true 14 | rand_core.workspace = true 15 | blake2.workspace = true 16 | -------------------------------------------------------------------------------- /sodiumbox/README.md: -------------------------------------------------------------------------------- 1 | # SodiumBox 2 | 3 | A pure Rust implementation of libsodium's sealed box encryption, compatible with libsodium/sodiumoxide but without any C dependencies. 4 | 5 | ## Overview 6 | 7 | SodiumBox provides a standalone implementation of the sealed box functionality from libsodium (NaCl) using only pure Rust cryptographic libraries. It is designed to be a drop-in replacement for sodiumoxide's sealed box functionality. 8 | 9 | The implementation uses modern, well-maintained Rust cryptographic libraries: 10 | 11 | - `x25519-dalek` for Curve25519 key exchange 12 | - `xsalsa20poly1305` for authenticated encryption 13 | - `blake2` for key derivation 14 | - `salsa20` for the HSalsa20 function 15 | 16 | ## Features 17 | 18 | - Generate X25519 keypairs for sealed box operations 19 | - Seal messages using a recipient's public key 20 | - Open sealed boxes created by libsodium/sodiumoxide 21 | - Pure Rust implementation with no C dependencies 22 | - Comprehensive test vectors based on libsodium's test suite 23 | 24 | ## Usage 25 | 26 | ```rust 27 | use sodiumbox::{generate_keypair, seal, open_sealed_box}; 28 | 29 | // Generate a new keypair 30 | let (public_key, secret_key) = generate_keypair(); 31 | 32 | // Create a message to encrypt 33 | let message = b"This is a secret message"; 34 | 35 | // Seal the message for the recipient 36 | let sealed_box = seal(message, &public_key); 37 | 38 | // Open a sealed box 39 | let result = open_sealed_box(&sealed_box, &public_key, &secret_key); 40 | match result { 41 | Ok(plaintext) => println!("Decrypted message: {:?}", plaintext), 42 | Err(_) => println!("Failed to decrypt"), 43 | } 44 | ``` 45 | 46 | ## License 47 | 48 | This crate is licensed under either of: 49 | 50 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 51 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 52 | 53 | at your option. 54 | -------------------------------------------------------------------------------- /supervisor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "supervisor" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | bon.workspace = true 11 | clap = { workspace = true, features = ["derive", "string"] } 12 | dashmap.workspace = true 13 | fs-err.workspace = true 14 | git-version.workspace = true 15 | libc.workspace = true 16 | load_config.workspace = true 17 | nix.workspace = true 18 | notify.workspace = true 19 | rocket = { workspace = true, features = ["json"] } 20 | serde = { workspace = true, features = ["derive"] } 21 | serde_json.workspace = true 22 | tokio = { workspace = true, features = ["process", "sync", "macros", "rt-multi-thread", "io-util"] } 23 | tracing.workspace = true 24 | tracing-subscriber.workspace = true 25 | -------------------------------------------------------------------------------- /supervisor/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "supervisor-client" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [[bin]] 9 | name = "supervisor-client" 10 | path = "src/main.rs" 11 | required-features = ["cli"] 12 | 13 | [dependencies] 14 | anyhow.workspace = true 15 | clap = { workspace = true, optional = true } 16 | tokio.workspace = true 17 | hyperlocal.workspace = true 18 | hyper.workspace = true 19 | http.workspace = true 20 | serde_json.workspace = true 21 | hyper-util.workspace = true 22 | serde.workspace = true 23 | http-body-util.workspace = true 24 | tracing-subscriber.workspace = true 25 | log.workspace = true 26 | fs-err.workspace = true 27 | futures.workspace = true 28 | 29 | supervisor.workspace = true 30 | http-client.workspace = true 31 | 32 | [features] 33 | cli = ["dep:clap", "tokio/full"] 34 | -------------------------------------------------------------------------------- /supervisor/client/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use supervisor::ProcessConfig; 4 | use supervisor_client::SupervisorClient; 5 | 6 | #[derive(Parser)] 7 | #[command(author, version, about, long_about = None)] 8 | struct Cli { 9 | #[arg(long, default_value = "unix:/var/run/supervisor.sock")] 10 | base_url: String, 11 | 12 | #[command(subcommand)] 13 | command: Commands, 14 | } 15 | 16 | #[derive(Subcommand)] 17 | enum Commands { 18 | Deploy { 19 | #[arg(long)] 20 | id: String, 21 | #[arg(long)] 22 | command: String, 23 | #[arg(long = "arg")] 24 | args: Vec, 25 | }, 26 | Start { 27 | id: String, 28 | }, 29 | Stop { 30 | id: String, 31 | }, 32 | Remove { 33 | id: String, 34 | }, 35 | List, 36 | Info { 37 | id: String, 38 | }, 39 | Ping, 40 | Clear, 41 | Shutdown, 42 | } 43 | 44 | #[tokio::main] 45 | async fn main() -> Result<()> { 46 | { 47 | use tracing_subscriber::{fmt, EnvFilter}; 48 | let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); 49 | fmt().with_env_filter(filter).init(); 50 | } 51 | 52 | let cli = Cli::parse(); 53 | let client = SupervisorClient::new(&cli.base_url); 54 | 55 | match cli.command { 56 | Commands::Deploy { id, command, args } => { 57 | let config = ProcessConfig { 58 | id, 59 | name: String::new(), 60 | command, 61 | args, 62 | env: Default::default(), 63 | cwd: String::new(), 64 | stdout: String::new(), 65 | stderr: String::new(), 66 | pidfile: String::new(), 67 | cid: None, 68 | note: String::new(), 69 | }; 70 | print_json(&client.deploy(config).await?); 71 | } 72 | Commands::Start { id } => { 73 | print_json(&client.start(&id).await?); 74 | } 75 | Commands::Stop { id } => { 76 | print_json(&client.stop(&id).await?); 77 | } 78 | Commands::Remove { id } => { 79 | print_json(&client.remove(&id).await?); 80 | } 81 | Commands::List => { 82 | print_json(&client.list().await?); 83 | } 84 | Commands::Info { id } => { 85 | print_json(&client.info(&id).await?); 86 | } 87 | Commands::Ping => { 88 | print_json(&client.ping().await?); 89 | } 90 | Commands::Clear => { 91 | print_json(&client.clear().await?); 92 | } 93 | Commands::Shutdown => { 94 | print_json(&client.shutdown().await?); 95 | } 96 | } 97 | Ok(()) 98 | } 99 | 100 | fn print_json(value: &T) { 101 | println!("{}", serde_json::to_string(value).unwrap()); 102 | } 103 | -------------------------------------------------------------------------------- /supervisor/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod process; 2 | mod supervisor; 3 | pub mod web_api; 4 | pub use process::{ProcessConfig, ProcessInfo, ProcessState, ProcessStatus}; 5 | pub use web_api::Response; 6 | -------------------------------------------------------------------------------- /supervisor/supervisor.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | workers = 8 3 | max_blocking = 64 4 | ident = "Supervisor" 5 | temp_dir = "/tmp" 6 | keep_alive = 10 7 | log_level = "info" 8 | address = "unix:/var/run/supervisor.sock" 9 | reuse = false 10 | 11 | [default.shutdown] 12 | ctrlc = false 13 | signals = [] 14 | grace = 0 15 | mercy = 0 16 | -------------------------------------------------------------------------------- /tdx-attest-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tdx-attest-sys" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | 10 | [build-dependencies] 11 | bindgen.workspace = true 12 | cc.workspace = true 13 | -------------------------------------------------------------------------------- /tdx-attest-sys/bindings.h: -------------------------------------------------------------------------------- 1 | #include "csrc/tdx_attest.h" 2 | -------------------------------------------------------------------------------- /tdx-attest-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | 4 | fn main() { 5 | println!("cargo:rerun-if-changed=csrc/tdx_attest.c"); 6 | println!("cargo:rerun-if-changed=csrc/qgs_msg_lib.cpp"); 7 | let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 8 | bindgen::Builder::default() 9 | .header("bindings.h") 10 | .default_enum_style(bindgen::EnumVariation::ModuleConsts) 11 | .generate() 12 | .expect("Unable to generate bindings") 13 | .write_to_file(output_path.join("bindings.rs")) 14 | .expect("Couldn't write bindings!"); 15 | cc::Build::new() 16 | .file("csrc/tdx_attest.c") 17 | .file("csrc/qgs_msg_lib.cpp") 18 | .compile("tdx_attest"); 19 | } 20 | -------------------------------------------------------------------------------- /tdx-attest-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | #![allow(non_snake_case)] 3 | #![allow(non_upper_case_globals)] 4 | #![allow(clippy::missing_safety_doc)] 5 | 6 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 7 | -------------------------------------------------------------------------------- /tdx-attest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tdx-attest" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | anyhow.workspace = true 10 | hex.workspace = true 11 | num_enum.workspace = true 12 | scale.workspace = true 13 | serde.workspace = true 14 | serde-human-bytes.workspace = true 15 | cc-eventlog.workspace = true 16 | thiserror.workspace = true 17 | fs-err.workspace = true 18 | serde_json.workspace = true 19 | sha2.workspace = true 20 | 21 | [target.'cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))'.dependencies] 22 | tdx-attest-sys.workspace = true 23 | 24 | [dev-dependencies] 25 | insta.workspace = true 26 | serde_json.workspace = true 27 | -------------------------------------------------------------------------------- /tdx-attest/src/dummy.rs: -------------------------------------------------------------------------------- 1 | use cc_eventlog::TdxEventLog; 2 | use num_enum::FromPrimitive; 3 | use thiserror::Error; 4 | 5 | use crate::{TdxReport, TdxReportData, TdxUuid}; 6 | 7 | type Result = std::result::Result; 8 | 9 | #[repr(u32)] 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, Error)] 11 | pub enum TdxAttestError { 12 | #[error("unexpected")] 13 | Unexpected, 14 | #[error("invalid parameter")] 15 | InvalidParameter, 16 | #[error("out of memory")] 17 | OutOfMemory, 18 | #[error("vsock failure")] 19 | VsockFailure, 20 | #[error("report failure")] 21 | ReportFailure, 22 | #[error("extend failure")] 23 | ExtendFailure, 24 | #[error("not supported")] 25 | NotSupported, 26 | #[error("quote failure")] 27 | QuoteFailure, 28 | #[error("busy")] 29 | Busy, 30 | #[error("device failure")] 31 | DeviceFailure, 32 | #[error("invalid rtmr index")] 33 | InvalidRtmrIndex, 34 | #[error("unsupported att key id")] 35 | UnsupportedAttKeyId, 36 | #[num_enum(catch_all)] 37 | #[error("unknown error ({0})")] 38 | UnknownError(u32), 39 | } 40 | 41 | pub fn extend_rtmr(_index: u32, _event_type: u32, _digest: [u8; 48]) -> Result<()> { 42 | Err(TdxAttestError::NotSupported) 43 | } 44 | pub fn log_rtmr_event(_log: &TdxEventLog) -> Result<()> { 45 | Err(TdxAttestError::NotSupported) 46 | } 47 | pub fn get_report(_report_data: &TdxReportData) -> Result { 48 | Err(TdxAttestError::NotSupported) 49 | } 50 | pub fn get_quote( 51 | _report_data: &TdxReportData, 52 | _att_key_id_list: Option<&[TdxUuid]>, 53 | ) -> Result<(TdxUuid, Vec)> { 54 | let _ = _report_data; 55 | Err(TdxAttestError::NotSupported) 56 | } 57 | pub fn get_supported_att_key_ids() -> Result> { 58 | Err(TdxAttestError::NotSupported) 59 | } 60 | -------------------------------------------------------------------------------- /tdx-attest/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))] 2 | pub use linux::*; 3 | #[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))] 4 | mod linux; 5 | 6 | #[cfg(not(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu")))] 7 | pub use dummy::*; 8 | 9 | #[cfg(not(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu")))] 10 | mod dummy; 11 | 12 | pub use cc_eventlog as eventlog; 13 | 14 | pub type Result = std::result::Result; 15 | 16 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 17 | pub struct TdxUuid(pub [u8; 16]); 18 | 19 | pub type TdxReportData = [u8; 64]; 20 | 21 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 22 | pub struct TdxReport(pub [u8; 1024]); 23 | 24 | pub fn extend_rtmr3(event: &str, payload: &[u8]) -> anyhow::Result<()> { 25 | use anyhow::Context; 26 | // This code is not defined in the TCG specification. 27 | // See https://trustedcomputinggroup.org/wp-content/uploads/PC-ClientSpecific_Platform_Profile_for_TPM_2p0_Systems_v51.pdf 28 | let event_type = 0x08000001; 29 | let index = 3; 30 | let log = eventlog::TdxEventLog::new(index, event_type, event.to_string(), payload.to_vec()); 31 | extend_rtmr(index, event_type, log.digest).context("Failed to extend RTMR")?; 32 | log_rtmr_event(&log).context("Failed to log RTMR event") 33 | } 34 | -------------------------------------------------------------------------------- /test-scripts/get-app-key.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | CERT_FILE=${1} 3 | KEY_FILE=${CERT_FILE%.*}.key 4 | CERT_DIR=../certs 5 | URL=https://localhost:8043/prpc/KMS.GetAppKey 6 | 7 | if [ -z "$CERT_FILE" ]; then 8 | curl -s --cacert ${CERT_DIR}/ca.cert ${URL} 9 | else 10 | curl -vv --cacert ${CERT_DIR}/ca.cert --cert ${CERT_FILE} --key ${KEY_FILE} ${URL} 11 | fi 12 | -------------------------------------------------------------------------------- /test-scripts/inspect-cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | openssl x509 -text -noout -in $1 -------------------------------------------------------------------------------- /vmm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dstack-vmm" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | rocket = { workspace = true, features = ["mtls"] } 10 | rocket-vsock-listener = { workspace = true } 11 | tracing.workspace = true 12 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 13 | anyhow.workspace = true 14 | serde = { workspace = true, features = ["derive"] } 15 | serde_json.workspace = true 16 | shared_child.workspace = true 17 | bon.workspace = true 18 | uuid = { workspace = true, features = ["v4"] } 19 | sha2.workspace = true 20 | hex.workspace = true 21 | fs-err.workspace = true 22 | dirs.workspace = true 23 | which.workspace = true 24 | clap = { workspace = true, features = ["derive", "string"] } 25 | humantime.workspace = true 26 | strip-ansi-escapes.workspace = true 27 | tailf.workspace = true 28 | tokio = { workspace = true, features = ["full"] } 29 | git-version.workspace = true 30 | rocket-apitoken.workspace = true 31 | 32 | supervisor-client.workspace = true 33 | ra-rpc = { workspace = true, features = ["client", "rocket"] } 34 | dstack-vmm-rpc.workspace = true 35 | dstack-kms-rpc.workspace = true 36 | path-absolutize.workspace = true 37 | host-api.workspace = true 38 | safe-write.workspace = true 39 | guest-api = { workspace = true, features = ["client"] } 40 | load_config.workspace = true 41 | key-provider-client.workspace = true 42 | dstack-types.workspace = true 43 | hex_fmt.workspace = true 44 | lspci.workspace = true 45 | 46 | [dev-dependencies] 47 | insta.workspace = true 48 | -------------------------------------------------------------------------------- /vmm/requirements.txt: -------------------------------------------------------------------------------- 1 | annotated-types==0.7.0 2 | cffi==1.17.1 3 | cryptography==44.0.2 4 | cytoolz==1.0.1 5 | eth-hash==0.7.1 6 | eth-keys==0.7.0 7 | eth-typing==5.2.1 8 | eth-utils==5.3.0 9 | pycparser==2.22 10 | pydantic==2.11.3 11 | pydantic_core==2.33.1 12 | safe-pysha3==1.0.4 13 | toolz==1.0.0 14 | typing-inspection==0.4.0 15 | typing_extensions==4.13.2 -------------------------------------------------------------------------------- /vmm/rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dstack-vmm-rpc" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [dependencies] 9 | prpc.workspace = true 10 | prost.workspace = true 11 | serde = { workspace = true, features = ["derive"] } 12 | serde_json.workspace = true 13 | anyhow.workspace = true 14 | scale = { workspace = true, features = ["derive"] } 15 | 16 | [build-dependencies] 17 | prpc-build.workspace = true 18 | -------------------------------------------------------------------------------- /vmm/rpc/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | prpc_build::configure() 3 | .out_dir("./src/generated") 4 | .mod_prefix("super::") 5 | .build_scale_ext(false) 6 | .disable_package_emission() 7 | .enable_serde_extension() 8 | .disable_service_name_emission() 9 | .compile_dir("./proto") 10 | .expect("failed to compile proto files"); 11 | } 12 | -------------------------------------------------------------------------------- /vmm/rpc/src/generated.rs: -------------------------------------------------------------------------------- 1 | pub use vmm::*; 2 | 3 | #[allow(async_fn_in_trait)] 4 | mod vmm; 5 | -------------------------------------------------------------------------------- /vmm/rpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | pub use generated::*; 4 | 5 | mod generated; 6 | -------------------------------------------------------------------------------- /vmm/src/app/id_pool.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | 3 | use anyhow::bail; 4 | 5 | macro_rules! impl_numbers { 6 | ($($t:ty),*) => { 7 | $(impl Number for $t { 8 | fn next(&self) -> Option { 9 | (*self).checked_add(1) 10 | } 11 | })* 12 | }; 13 | } 14 | pub trait Number: Ord + Sized + Clone { 15 | fn next(&self) -> Option; 16 | } 17 | impl_numbers!(u8, u16, u32, u64, u128); 18 | impl_numbers!(i8, i16, i32, i64, i128); 19 | 20 | pub struct IdPool { 21 | start: T, 22 | end: T, 23 | allocated: BTreeSet, 24 | } 25 | 26 | impl IdPool { 27 | pub fn new(start: T, end: T) -> Self { 28 | Self { 29 | start, 30 | end, 31 | allocated: BTreeSet::new(), 32 | } 33 | } 34 | 35 | pub fn occupy(&mut self, id: T) -> anyhow::Result<()> { 36 | if self.allocated.insert(id) { 37 | Ok(()) 38 | } else { 39 | bail!("id already occupied") 40 | } 41 | } 42 | 43 | pub fn allocate(&mut self) -> Option { 44 | let mut id = self.start.clone(); 45 | while let Some(next) = id.next() { 46 | if next >= self.end { 47 | return None; 48 | } 49 | if !self.allocated.contains(&next) { 50 | self.allocated.insert(next.clone()); 51 | return Some(next); 52 | } 53 | id = next; 54 | } 55 | None 56 | } 57 | 58 | pub fn free(&mut self, id: T) { 59 | self.allocated.remove(&id); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /vmm/src/guest_api_service.rs: -------------------------------------------------------------------------------- 1 | use crate::App as AppState; 2 | use anyhow::Result; 3 | use guest_api::{ 4 | proxied_guest_api_server::{ProxiedGuestApiRpc, ProxiedGuestApiServer}, 5 | GuestInfo, Id, ListContainersResponse, NetworkInformation, SystemInfo, 6 | }; 7 | use ra_rpc::{CallContext, RpcCall}; 8 | use std::ops::Deref; 9 | 10 | pub struct GuestApiHandler { 11 | state: AppState, 12 | } 13 | 14 | impl Deref for GuestApiHandler { 15 | type Target = AppState; 16 | 17 | fn deref(&self) -> &Self::Target { 18 | &self.state 19 | } 20 | } 21 | 22 | impl RpcCall for GuestApiHandler { 23 | type PrpcService = ProxiedGuestApiServer; 24 | 25 | fn construct(context: CallContext<'_, AppState>) -> Result { 26 | Ok(Self { 27 | state: context.state.clone(), 28 | }) 29 | } 30 | } 31 | 32 | impl ProxiedGuestApiRpc for GuestApiHandler { 33 | async fn info(self, request: Id) -> Result { 34 | self.guest_agent_client(&request.id)?.info().await 35 | } 36 | 37 | async fn sys_info(self, request: Id) -> Result { 38 | self.guest_agent_client(&request.id)?.sys_info().await 39 | } 40 | 41 | async fn network_info(self, request: Id) -> Result { 42 | self.guest_agent_client(&request.id)?.network_info().await 43 | } 44 | 45 | async fn list_containers(self, request: Id) -> Result { 46 | self.guest_agent_client(&request.id)? 47 | .list_containers() 48 | .await 49 | } 50 | 51 | async fn shutdown(self, request: Id) -> Result<()> { 52 | self.guest_agent_client(&request.id)?.shutdown().await 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /vmm/src/host_api_service.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context, Result}; 2 | use host_api::{ 3 | host_api_server::{HostApiRpc, HostApiServer}, 4 | GetSealingKeyRequest, GetSealingKeyResponse, HostInfo, Notification, 5 | }; 6 | use ra_rpc::{CallContext, RemoteEndpoint, RpcCall}; 7 | use rocket_vsock_listener::VsockEndpoint; 8 | 9 | use crate::app::App; 10 | use key_provider_client::host::get_key; 11 | 12 | pub struct HostApiHandler { 13 | endpoint: VsockEndpoint, 14 | app: App, 15 | } 16 | 17 | impl RpcCall for HostApiHandler { 18 | type PrpcService = HostApiServer; 19 | 20 | fn construct(context: CallContext<'_, App>) -> Result { 21 | let Some(RemoteEndpoint::Vsock { cid, port }) = context.remote_endpoint else { 22 | bail!("invalid remote endpoint: {:?}", context.remote_endpoint); 23 | }; 24 | Ok(Self { 25 | endpoint: VsockEndpoint { cid, port }, 26 | app: context.state.clone(), 27 | }) 28 | } 29 | } 30 | 31 | impl HostApiRpc for HostApiHandler { 32 | async fn info(self) -> Result { 33 | let host_info = HostInfo { 34 | name: "Dstack VMM".to_string(), 35 | version: env!("CARGO_PKG_VERSION").to_string(), 36 | }; 37 | Ok(host_info) 38 | } 39 | 40 | async fn notify(self, request: Notification) -> Result<()> { 41 | self.app 42 | .vm_event_report(self.endpoint.cid, &request.event, request.payload) 43 | } 44 | 45 | async fn get_sealing_key(self, request: GetSealingKeyRequest) -> Result { 46 | let key_provider = &self.app.config.key_provider; 47 | if !key_provider.enabled { 48 | bail!("Key provider is not enabled"); 49 | } 50 | let response = get_key(request.quote, key_provider.address, key_provider.port) 51 | .await 52 | .context("Failed to get sealing key from key provider")?; 53 | 54 | Ok(GetSealingKeyResponse { 55 | encrypted_key: response.encrypted_key, 56 | provider_quote: response.provider_quote, 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /vmm/venv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -f ".venv/bin/activate" ]; then 4 | source .venv/bin/activate 5 | else 6 | python3 -m venv .venv 7 | source .venv/bin/activate 8 | pip install requests eth_keys cryptography "eth-hash[pycryptodome]" 9 | cp src/vmm-cli.py .venv/bin/ 10 | ln -sf vmm-cli.py .venv/bin/vmm 11 | fi 12 | -------------------------------------------------------------------------------- /vmm/vmm.toml: -------------------------------------------------------------------------------- 1 | workers = 8 2 | max_blocking = 64 3 | ident = "Dstack VMM" 4 | temp_dir = "/tmp" 5 | keep_alive = 10 6 | log_level = "debug" 7 | address = "unix:./vmm.sock" 8 | reuse = true 9 | kms_url = "http://127.0.0.1:8081" 10 | 11 | 12 | [networking] 13 | mode = "user" 14 | net = "10.0.2.0/24" 15 | dhcp_start = "10.0.2.10" 16 | restrict = false 17 | 18 | [cvm] 19 | qemu_path = "" 20 | kms_urls = ["http://127.0.0.1:8081"] 21 | gateway_urls = ["http://127.0.0.1:8082"] 22 | # PCCS URL used by guest to verify the quote from local key provider 23 | pccs_url = "" 24 | docker_registry = "" 25 | max_disk_size = 500 26 | cid_start = 1000 27 | cid_pool_size = 1000 28 | max_allocable_vcpu = 20 29 | max_allocable_memory_in_mb = 100_000 # MB 30 | # Enable QMP socket 31 | qmp_socket = false 32 | # The user to run the VM as. If empty, the VM will be run as the current user. 33 | user = "" 34 | 35 | [cvm.port_mapping] 36 | enabled = false 37 | address = "127.0.0.1" 38 | range = [ 39 | { protocol = "tcp", from = 1, to = 20000 }, 40 | ] 41 | 42 | [cvm.auto_restart] 43 | enabled = true 44 | interval = 20 45 | 46 | [cvm.gpu] 47 | enabled = false 48 | # The product IDs of the GPUs to discover 49 | # H200: 10de:2335 50 | listing = ["10de:2335"] 51 | # The PCI addresses of the cards to exclude 52 | exclude = [] 53 | # The PCI addresses of the cards to include 54 | include = [] 55 | 56 | [gateway] 57 | base_domain = "localhost" 58 | port = 8082 59 | agent_port = 8090 60 | 61 | [auth] 62 | enabled = false 63 | tokens = [] 64 | 65 | [supervisor] 66 | exe = "./supervisor" 67 | sock = "./run/supervisor.sock" 68 | pid_file = "./run/supervisor.pid" 69 | log_file = "./run/supervisor.log" 70 | detached = false 71 | auto_start = true 72 | 73 | [host_api] 74 | ident = "Dstack VMM" 75 | address = "vsock:2" 76 | port = 10000 77 | 78 | [key_provider] 79 | enabled = true 80 | address = "127.0.0.1" 81 | port = 3443 82 | --------------------------------------------------------------------------------