diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..c1e813b3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,204 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +ECloud is a TypeScript monorepo providing SDK and CLI tools for deploying and managing applications on EigenCloud TEE (Trusted Execution Environment). The system enables developers to deploy containerized applications with deterministic wallet identities and hardware-level isolation using Intel TDX. + +**Key capabilities:** +- Deploy Docker containers to TEE with auto-generated wallets +- Hardware-isolated execution with cryptographic attestation +- Smart contract-based lifecycle management +- KMS encryption for secure environment variable handling + +## Repository Structure + +This is a pnpm monorepo with two main packages: + +- **`packages/sdk/`** - Core TypeScript SDK (`@layr-labs/ecloud-sdk`) + - Programmatic access to ecloud services + - Exports: main client, compute module, billing module, browser-specific bundle + - Entry points: `src/index.ts`, `src/compute.ts`, `src/billing.ts`, `src/browser.ts` + +- **`packages/cli/`** - Command-line interface (`@layr-labs/ecloud-cli`) + - Built with oclif framework + - Commands auto-discovered from `packages/cli/src/commands/` + - Binary entry: `bin/run.js` + +## Build System + +Both packages use `tsup` for building: + +- **Build Types**: Environment variable `BUILD_TYPE` controls which environments are available + - `dev`: Only `sepolia-dev` environment + - `prod`: `sepolia` and `mainnet-alpha` environments (default) + +- **Build-time Constants**: Injected via tsup's `define` option + - `BUILD_TYPE_BUILD_TIME`: Determines available environments + - `SDK_VERSION_BUILD_TIME` / `CLI_VERSION_BUILD_TIME`: Version from `PACKAGE_VERSION` env or package.json + - `POSTHOG_API_KEY_BUILD_TIME`: Optional telemetry key + +- **Template Loading**: `.tmpl` and `.pem` files loaded as text via tsup loader + +## Common Development Commands + +```bash +# Install dependencies +pnpm install + +# Build all packages (production) +pnpm build + +# Build for development (enables sepolia-dev environment) +pnpm build:dev +# or +BUILD_TYPE=dev pnpm build + +# Run the CLI locally +pnpm ecloud [command] + +# Linting and formatting +pnpm lint +pnpm format # Check formatting +pnpm format:fix # Fix formatting issues + +# Type checking +pnpm typecheck + +# Individual package commands +pnpm -C packages/sdk build +pnpm -C packages/cli build +``` + +## Environment Configuration + +Environments are defined in `packages/sdk/src/client/common/config/environment.ts`: + +- **Sepolia (testnet)**: `sepolia` (prod) or `sepolia-dev` (dev build) +- **Mainnet**: `mainnet-alpha` (prod only) + +Each environment specifies: +- Smart contract addresses (AppController, PermissionController, ERC7702Delegator) +- KMS server URL +- User API server URL +- Default RPC URL +- Chain ID (Sepolia: 11155111, Mainnet: 1) + +**Important**: Environment availability is controlled by build type. Production builds cannot access `sepolia-dev`, and dev builds can only access `sepolia-dev`. + +## Architecture Patterns + +### Client Creation +The SDK uses a modular client architecture: +1. `createECloudClient()` (main entry) creates wallet and public clients using viem +2. Returns modules: `compute` and `billing` +3. Each module is created by factory functions (`createComputeModule`, `createBillingModule`) + +### SDK vs CLI Separation +- **SDK** (`packages/sdk`): Non-interactive, requires all parameters explicitly + - Uses viem's WalletClient and PublicClient for blockchain interactions + - Exports both high-level clients and low-level functions for custom workflows + +- **CLI** (`packages/cli`): Interactive, prompts for missing parameters + - Uses oclif for command structure + - Imports SDK functions and wraps them with interactive prompts + - Authentication handled via OS keyring, environment variables, or flags + +### Deployment Flow +The deployment process is split into phases (see `packages/sdk/src/client/modules/compute/app/deploy.ts`): + +1. **Prepare phase** (`prepareDeploy`): + - Validate inputs (app name, image reference, environment variables) + - Build Docker image if needed (via `prepareRelease`) + - Push to registry + - Encrypt environment variables with KMS public key (RSA-OAEP-256 + AES-256-GCM) + - Create deployment batch data + - Estimate gas costs + +2. **Execute phase** (`executeDeploy`): + - Create EIP-7702 authorization list (delegation to ERC7702Delegator contract) + - Execute batch transaction with deployment data + - Return app ID and transaction hash + +3. **Watch phase** (`watchDeployment`): + - Monitor transaction confirmation + - Poll User API until app reaches "running" state + +This split enables the CLI to show gas estimates and get user confirmation before executing. + +### EIP-7702 Batch Execution +The system uses EIP-7702 for atomic multi-call transactions: +- Developer's EOA delegates code execution to `ERC7702Delegator` contract +- Batch contains multiple contract calls (e.g., deploy + start) +- All operations execute atomically in a single transaction +- See `packages/sdk/src/client/common/contract/eip7702.ts` for implementation + +### KMS Encryption +Environment variables are encrypted before on-chain storage: +- Uses JWE (JSON Web Encryption) with `jose` library +- Key encryption: RSA-OAEP-256 (public key from KMS) +- Content encryption: AES-256-GCM +- Protected headers include `x-eigenx-app-id` for app identification +- Implementation: `packages/sdk/src/client/common/encryption/kms.ts` + +### Template System +The CLI can create starter apps from templates: +- Templates stored in `packages/sdk/src/client/common/templates/` +- Template catalog fetched from remote source +- Categories: TypeScript, Python, Golang, Rust, etc. +- Handlebars used for variable substitution +- Created apps include Dockerfile, .env, and starter code + +## Testing + +Currently no project-specific test files exist. Tests would typically be added as: +- `*.test.ts` files co-located with source files +- Run with `pnpm test` (uses vitest as configured in root package.json) + +## Smart Contract Interactions + +All blockchain interactions use `viem`: +- Wallet client for signing transactions +- Public client for reading state +- Contract calls encoded with `encodeFunctionData` +- ABIs located in `packages/sdk/src/client/common/abis/` + +## Authentication + +Three methods (priority order: flag → environment → keyring): +1. Command flag: `--private-key 0x...` +2. Environment variable: `ECLOUD_PRIVATE_KEY` +3. OS keyring: via `@napi-rs/keyring` package + +## Telemetry + +Optional anonymous usage tracking via PostHog: +- API key injected at build time +- Can be disabled via CLI commands: `ecloud telemetry disable` +- Implementation: `packages/sdk/src/client/common/telemetry/` + +## Important Code Locations + +- **Environment config**: `packages/sdk/src/client/common/config/environment.ts` +- **Contract callers**: `packages/sdk/src/client/common/contract/caller.ts` +- **EIP-7702 handling**: `packages/sdk/src/client/common/contract/eip7702.ts` +- **Docker operations**: `packages/sdk/src/client/common/docker/` +- **KMS encryption**: `packages/sdk/src/client/common/encryption/kms.ts` +- **Deployment logic**: `packages/sdk/src/client/modules/compute/app/deploy.ts` +- **CLI commands**: `packages/cli/src/commands/` (oclif auto-discovery) + +## Development Workflow + +When adding features: +1. Implement core logic in SDK (`packages/sdk/src/`) as non-interactive functions +2. Export from appropriate module or main index +3. Add CLI command (`packages/cli/src/commands/`) that wraps SDK with interactive prompts +4. Build both packages: `pnpm build` +5. Test locally: `pnpm ecloud [your-command]` + +## Git Conventions + +- Main branch: `master` +- Feature branches: descriptive names (e.g., `sm-eigenxKmsClient`) +- Commit with co-authorship when using AI assistance diff --git a/packages/cli/bin/run.cmd b/packages/cli/bin/run.cmd old mode 100644 new mode 100755 diff --git a/packages/cli/src/commands/compute/app/deploy.ts b/packages/cli/src/commands/compute/app/deploy.ts index 9976e65f..ae86689c 100644 --- a/packages/cli/src/commands/compute/app/deploy.ts +++ b/packages/cli/src/commands/compute/app/deploy.ts @@ -135,6 +135,12 @@ export default class AppDeploy extends Command { required: false, env: "ECLOUD_BUILD_CADDYFILE", }), + "use-kms-v2": Flags.boolean({ + required: false, + description: "Use eigenx-kms-client (v2) for environment variable encryption", + default: false, + env: "ECLOUD_USE_KMS_V2", + }), }; async run() { @@ -370,6 +376,7 @@ export default class AppDeploy extends Command { ? "private" : "off"; + const useKmsV2 = flags["use-kms-v2"]; const { prepared, gasEstimate } = isVerifiable ? await compute.app.prepareDeployFromVerifiableBuild({ name: appName, @@ -379,6 +386,7 @@ export default class AppDeploy extends Command { instanceType, logVisibility, resourceUsageMonitoring, + useKmsV2, }) : await compute.app.prepareDeploy({ name: appName, @@ -388,6 +396,7 @@ export default class AppDeploy extends Command { instanceType, logVisibility, resourceUsageMonitoring, + useKmsV2, }); // 9. Show gas estimate and prompt for confirmation on mainnet diff --git a/packages/cli/src/commands/compute/app/upgrade.ts b/packages/cli/src/commands/compute/app/upgrade.ts index d0ed10fd..92be258d 100644 --- a/packages/cli/src/commands/compute/app/upgrade.ts +++ b/packages/cli/src/commands/compute/app/upgrade.ts @@ -113,6 +113,12 @@ export default class AppUpgrade extends Command { required: false, env: "ECLOUD_BUILD_CADDYFILE", }), + "use-kms-v2": Flags.boolean({ + required: false, + description: "Use eigenx-kms-client (v2) for environment variable encryption", + default: false, + env: "ECLOUD_USE_KMS_V2", + }), }; async run() { @@ -337,6 +343,7 @@ export default class AppUpgrade extends Command { ? "private" : "off"; + const useKmsV2 = flags["use-kms-v2"]; const { prepared, gasEstimate } = isVerifiable ? await compute.app.prepareUpgradeFromVerifiableBuild(appID, { imageRef, @@ -345,6 +352,7 @@ export default class AppUpgrade extends Command { instanceType, logVisibility, resourceUsageMonitoring, + useKmsV2, }) : await compute.app.prepareUpgrade(appID, { dockerfile: dockerfilePath, @@ -353,6 +361,7 @@ export default class AppUpgrade extends Command { instanceType, logVisibility, resourceUsageMonitoring, + useKmsV2, }); // 10. Show gas estimate and prompt for confirmation on mainnet diff --git a/packages/sdk/src/client/common/config/environment.ts b/packages/sdk/src/client/common/config/environment.ts index a56c361a..4aaf3894 100644 --- a/packages/sdk/src/client/common/config/environment.ts +++ b/packages/sdk/src/client/common/config/environment.ts @@ -44,6 +44,8 @@ const ENVIRONMENTS: Record> = { kmsServerURL: "http://10.128.0.57:8080", userApiServerURL: "https://userapi-compute-sepolia-dev.eigencloud.xyz", defaultRPCURL: "https://ethereum-sepolia-rpc.publicnode.com", + avsAddress: "0x47c9806e7DC4e6fE9a0a2399831F32d06DaE5730", + operatorSetId: 0, }, sepolia: { name: "sepolia", @@ -54,6 +56,8 @@ const ENVIRONMENTS: Record> = { kmsServerURL: "http://10.128.15.203:8080", userApiServerURL: "https://userapi-compute-sepolia-prod.eigencloud.xyz", defaultRPCURL: "https://ethereum-sepolia-rpc.publicnode.com", + avsAddress: "", + operatorSetId: 0, }, "mainnet-alpha": { name: "mainnet-alpha", @@ -64,6 +68,8 @@ const ENVIRONMENTS: Record> = { kmsServerURL: "http://10.128.0.2:8080", userApiServerURL: "https://userapi-compute.eigencloud.xyz", defaultRPCURL: "https://ethereum-rpc.publicnode.com", + avsAddress: "", + operatorSetId: 0, }, }; diff --git a/packages/sdk/src/client/common/constants.ts b/packages/sdk/src/client/common/constants.ts index b4528512..42b913ca 100644 --- a/packages/sdk/src/client/common/constants.ts +++ b/packages/sdk/src/client/common/constants.ts @@ -11,6 +11,7 @@ export const REGISTRY_PROPAGATION_WAIT_SECONDS = 3; export const LAYERED_DOCKERFILE_NAME = "Dockerfile.eigencompute"; export const ENV_SOURCE_SCRIPT_NAME = "compute-source-env.sh"; export const KMS_CLIENT_BINARY_NAME = "kms-client"; +export const EIGENX_KMS_CLIENT_BINARY_NAME = "eigenx-kms-client"; export const KMS_ENCRYPTION_KEY_NAME = "kms-encryption-public-key.pem"; export const KMS_SIGNING_KEY_NAME = "kms-signing-public-key.pem"; export const TLS_KEYGEN_BINARY_NAME = "tls-keygen"; diff --git a/packages/sdk/src/client/common/docker/layer.ts b/packages/sdk/src/client/common/docker/layer.ts index 61b395b9..af9370f3 100644 --- a/packages/sdk/src/client/common/docker/layer.ts +++ b/packages/sdk/src/client/common/docker/layer.ts @@ -30,6 +30,7 @@ import { CADDYFILE_NAME, LAYERED_BUILD_DIR_PREFIX, DOCKER_PLATFORM, + EIGENX_KMS_CLIENT_BINARY_NAME, } from "../constants"; import { getDirname } from "../utils/dirname"; @@ -95,6 +96,8 @@ export interface BuildAndPushLayeredImageOptions { resourceUsageAllow: string; envFilePath?: string; environmentConfig: EnvironmentConfig; + useKmsV2?: boolean; + appId?: string; } export interface LayerRemoteImageIfNeededOptions { @@ -103,6 +106,8 @@ export interface LayerRemoteImageIfNeededOptions { resourceUsageAllow: string; envFilePath?: string; environmentConfig: EnvironmentConfig; + useKmsV2?: boolean; + appId?: string; } /** @@ -119,6 +124,8 @@ export async function buildAndPushLayeredImage( resourceUsageAllow, envFilePath, environmentConfig, + useKmsV2, + appId, } = options; // 1. Build base image from user's Dockerfile @@ -140,6 +147,8 @@ export async function buildAndPushLayeredImage( resourceUsageAllow, envFilePath, environmentConfig, + useKmsV2, + appId, }, logger, ); @@ -152,7 +161,7 @@ export async function layerRemoteImageIfNeeded( options: LayerRemoteImageIfNeededOptions, logger: Logger, ): Promise { - const { imageRef, logRedirect, resourceUsageAllow, envFilePath, environmentConfig } = options; + const { imageRef, logRedirect, resourceUsageAllow, envFilePath, environmentConfig, useKmsV2, appId } = options; const docker = new Docker(); @@ -181,6 +190,8 @@ export async function layerRemoteImageIfNeeded( resourceUsageAllow, envFilePath, environmentConfig, + useKmsV2, + appId, }, logger, ); @@ -200,6 +211,8 @@ async function layerLocalImage( resourceUsageAllow: string; envFilePath?: string; environmentConfig: EnvironmentConfig; + useKmsV2?: boolean; + appId?: string; }, logger: Logger, ): Promise { @@ -211,6 +224,8 @@ async function layerLocalImage( resourceUsageAllow, envFilePath, environmentConfig, + useKmsV2, + appId, } = options; // 1. Extract original command and user from source image @@ -243,6 +258,12 @@ async function layerLocalImage( const scriptContent = processScriptTemplate({ kmsServerURL: environmentConfig.kmsServerURL, userAPIURL: environmentConfig.userApiServerURL, + useKmsV2, + ethRpcUrl: environmentConfig.defaultRPCURL, + avsAddress: environmentConfig.avsAddress, + operatorSetId: environmentConfig.operatorSetId, + appControllerAddress: environmentConfig.appControllerAddress, + appId, }); // 4. Setup build directory @@ -316,6 +337,18 @@ async function setupLayeredBuildDirectory( fs.copyFileSync(kmsClientSource, kmsClientPath); fs.chmodSync(kmsClientPath, 0o755); + // Copy new eigenx-kms-client binary + const eigenxKmsClientPath = path.join(tempDir, EIGENX_KMS_CLIENT_BINARY_NAME); + const eigenxKmsClientSource = findBinary("eigenx-kms-client-linux-amd64"); + if (!fs.existsSync(eigenxKmsClientSource)) { + throw new Error( + `eigenx-kms-client binary not found. Expected at: ${eigenxKmsClientSource}. ` + + "Make sure binaries are in packages/sdk/tools/ directory.", + ); + } + fs.copyFileSync(eigenxKmsClientSource, eigenxKmsClientPath); + fs.chmodSync(eigenxKmsClientPath, 0o755); + // Include TLS components if requested if (includeTLS) { // Copy tls-keygen binary diff --git a/packages/sdk/src/client/common/encryption/eigenxKms.ts b/packages/sdk/src/client/common/encryption/eigenxKms.ts new file mode 100644 index 00000000..f2dcdc6f --- /dev/null +++ b/packages/sdk/src/client/common/encryption/eigenxKms.ts @@ -0,0 +1,68 @@ +/** + * Encryption using eigenx-kms-client binary (v2 KMS) + * + * Uses Identity-Based Encryption (IBE) with keys fetched dynamically + * from an on-chain operator set. + */ + +import { execFileSync } from "child_process"; + +/** + * Encrypt data using the eigenx-kms-client binary. + * + * The binary must be available on the developer's PATH. + * + * @param ethRpcUrl - Ethereum RPC URL for on-chain key lookups + * @param avsAddress - AVS contract address + * @param operatorSetId - Operator set ID + * @param appId - Application ID + * @param data - JSON string of data to encrypt + * @returns hex-encoded ciphertext string + */ +export function encryptWithEigenxKmsClient( + ethRpcUrl: string, + avsAddress: string, + operatorSetId: number, + appId: string, + data: string, +): string { + if (!avsAddress) { + throw new Error( + "avsAddress is not configured for this environment. " + + "Cannot use --use-kms-v2 without a valid avsAddress in the environment config.", + ); + } + + try { + const result = execFileSync( + "eigenx-kms-client", + [ + "encrypt", + "--eth-rpc-url", + ethRpcUrl, + "--avs-address", + avsAddress, + "--operator-set-id", + String(operatorSetId), + "--app-id", + appId, + "--data", + data, + ], + { + encoding: "utf-8", + timeout: 60_000, + }, + ); + + return result.trim(); + } catch (error: any) { + if (error.code === "ENOENT") { + throw new Error( + "eigenx-kms-client binary not found on PATH. " + + "Please install eigenx-kms-client to use --use-kms-v2.", + ); + } + throw new Error(`eigenx-kms-client encrypt failed: ${error.message}`); + } +} diff --git a/packages/sdk/src/client/common/release/prebuilt.ts b/packages/sdk/src/client/common/release/prebuilt.ts index ca91bf89..b77001e5 100644 --- a/packages/sdk/src/client/common/release/prebuilt.ts +++ b/packages/sdk/src/client/common/release/prebuilt.ts @@ -1,5 +1,6 @@ import { parseAndValidateEnvFile } from "../env/parser"; import { encryptRSAOAEPAndAES256GCM, getAppProtectedHeaders } from "../encryption/kms"; +import { encryptWithEigenxKmsClient } from "../encryption/eigenxKms"; import { getKMSKeysForEnvironment } from "../utils/keys"; import type { EnvironmentConfig, Logger, Release } from "../types"; @@ -10,6 +11,7 @@ export interface CreateReleaseFromImageDigestOptions { instanceType: string; environmentConfig: EnvironmentConfig; appId: string; + useKmsV2?: boolean; } /** @@ -21,7 +23,7 @@ export async function createReleaseFromImageDigest( options: CreateReleaseFromImageDigestOptions, logger: Logger, ): Promise { - const { imageRef, imageDigest, envFilePath, instanceType, environmentConfig, appId } = options; + const { imageRef, imageDigest, envFilePath, instanceType, environmentConfig, appId, useKmsV2 } = options; if (!/^sha256:[0-9a-f]{64}$/i.test(imageDigest)) { throw new Error(`imageDigest must be in format sha256:<64 hex>, got: ${imageDigest}`); @@ -46,17 +48,30 @@ export async function createReleaseFromImageDigest( // Encrypt private environment variables logger.info("Encrypting environment variables..."); - const { encryptionKey } = getKMSKeysForEnvironment( - environmentConfig.name, - environmentConfig.build, - ); - const protectedHeaders = getAppProtectedHeaders(appId); - const privateEnvBytes = Buffer.from(JSON.stringify(privateEnv)); - const encryptedEnvStr = await encryptRSAOAEPAndAES256GCM( - encryptionKey, - privateEnvBytes, - protectedHeaders, - ); + let encryptedEnvStr: string; + + if (useKmsV2) { + logger.info("Using eigenx-kms-client (v2) for encryption..."); + encryptedEnvStr = encryptWithEigenxKmsClient( + environmentConfig.defaultRPCURL, + environmentConfig.avsAddress || "", + environmentConfig.operatorSetId || 0, + appId, + JSON.stringify(privateEnv), + ); + } else { + const { encryptionKey } = getKMSKeysForEnvironment( + environmentConfig.name, + environmentConfig.build, + ); + const protectedHeaders = getAppProtectedHeaders(appId); + const privateEnvBytes = Buffer.from(JSON.stringify(privateEnv)); + encryptedEnvStr = await encryptRSAOAEPAndAES256GCM( + encryptionKey, + privateEnvBytes, + protectedHeaders, + ); + } // Convert digest to bytes32 const digestHex = imageDigest.split(":")[1]!; diff --git a/packages/sdk/src/client/common/release/prepare.ts b/packages/sdk/src/client/common/release/prepare.ts index 59fbc70e..68462b8b 100644 --- a/packages/sdk/src/client/common/release/prepare.ts +++ b/packages/sdk/src/client/common/release/prepare.ts @@ -8,7 +8,8 @@ import { buildAndPushLayeredImage } from "../docker/layer"; import { layerRemoteImageIfNeeded } from "../docker/layer"; import { getImageDigestAndName } from "../registry/digest"; -import { encryptRSAOAEPAndAES256GCM, getAppProtectedHeaders } from "../encryption/kms"; // getAppProtectedHeaders +import { encryptRSAOAEPAndAES256GCM, getAppProtectedHeaders } from "../encryption/kms"; +import { encryptWithEigenxKmsClient } from "../encryption/eigenxKms"; import { getKMSKeysForEnvironment } from "../utils/keys"; import { REGISTRY_PROPAGATION_WAIT_SECONDS } from "../constants"; @@ -25,6 +26,7 @@ export interface PrepareReleaseOptions { instanceType: string; environmentConfig: EnvironmentConfig; appId: AppId; + useKmsV2?: boolean; } export interface PrepareReleaseResult { @@ -47,6 +49,7 @@ export async function prepareRelease( resourceUsageAllow, instanceType, environmentConfig, + useKmsV2, } = options; let finalImageRef = imageRef; @@ -63,6 +66,8 @@ export async function prepareRelease( resourceUsageAllow, envFilePath, environmentConfig, + useKmsV2, + appId: options.appId, }, logger, ); @@ -80,6 +85,8 @@ export async function prepareRelease( resourceUsageAllow, envFilePath, environmentConfig, + useKmsV2, + appId: options.appId, }, logger, ); @@ -152,17 +159,30 @@ export async function prepareRelease( // 5. Encrypt private environment variables logger.info("Encrypting environment variables..."); - const { encryptionKey } = getKMSKeysForEnvironment( - environmentConfig.name, - environmentConfig.build, - ); - const protectedHeaders = getAppProtectedHeaders(options.appId); - const privateEnvBytes = Buffer.from(JSON.stringify(privateEnv)); - const encryptedEnvStr = await encryptRSAOAEPAndAES256GCM( - encryptionKey, - privateEnvBytes, - protectedHeaders, - ); + let encryptedEnvStr: string; + + if (useKmsV2) { + logger.info("Using eigenx-kms-client (v2) for encryption..."); + encryptedEnvStr = encryptWithEigenxKmsClient( + environmentConfig.defaultRPCURL, + environmentConfig.avsAddress || "", + environmentConfig.operatorSetId || 0, + options.appId, + JSON.stringify(privateEnv), + ); + } else { + const { encryptionKey } = getKMSKeysForEnvironment( + environmentConfig.name, + environmentConfig.build, + ); + const protectedHeaders = getAppProtectedHeaders(options.appId); + const privateEnvBytes = Buffer.from(JSON.stringify(privateEnv)); + encryptedEnvStr = await encryptRSAOAEPAndAES256GCM( + encryptionKey, + privateEnvBytes, + protectedHeaders, + ); + } // 6. Create release struct const release: Release = { diff --git a/packages/sdk/src/client/common/templates/Dockerfile.layered.tmpl b/packages/sdk/src/client/common/templates/Dockerfile.layered.tmpl index 14e1ab63..bec6b5b3 100644 --- a/packages/sdk/src/client/common/templates/Dockerfile.layered.tmpl +++ b/packages/sdk/src/client/common/templates/Dockerfile.layered.tmpl @@ -13,6 +13,7 @@ USER root # Copy core TEE components COPY compute-source-env.sh /usr/local/bin/ COPY kms-client /usr/local/bin/ +COPY eigenx-kms-client /usr/local/bin/ COPY kms-signing-public-key.pem /usr/local/bin/ {{#if includeTLS}} @@ -27,7 +28,8 @@ COPY Caddyfile /etc/caddy/ {{#if originalUser}} # Make binaries executable (755 for executables, 644 for keys) RUN chmod 755 /usr/local/bin/compute-source-env.sh \ - && chmod 755 /usr/local/bin/kms-client{{#if includeTLS}} \ + && chmod 755 /usr/local/bin/kms-client \ + && chmod 755 /usr/local/bin/eigenx-kms-client{{#if includeTLS}} \ && chmod 755 /usr/local/bin/tls-keygen \ && chmod 755 /usr/local/bin/caddy{{/if}} \ && chmod 644 /usr/local/bin/kms-signing-public-key.pem @@ -37,7 +39,8 @@ ENV __ECLOUD_ORIGINAL_USER={{originalUser}} {{else}} # Make binaries executable (preserve existing permissions, just add execute) RUN chmod +x /usr/local/bin/compute-source-env.sh \ - && chmod +x /usr/local/bin/kms-client{{#if includeTLS}} \ + && chmod +x /usr/local/bin/kms-client \ + && chmod +x /usr/local/bin/eigenx-kms-client{{#if includeTLS}} \ && chmod +x /usr/local/bin/tls-keygen{{/if}} {{/if}} diff --git a/packages/sdk/src/client/common/templates/compute-source-env.sh.tmpl b/packages/sdk/src/client/common/templates/compute-source-env.sh.tmpl index 45b881d5..11cfda10 100644 --- a/packages/sdk/src/client/common/templates/compute-source-env.sh.tmpl +++ b/packages/sdk/src/client/common/templates/compute-source-env.sh.tmpl @@ -3,11 +3,23 @@ echo "compute-source-env.sh: Running setup script..." # Fetch and source environment variables from KMS echo "Fetching secrets from KMS..." +{{#if useKmsV2}} +if /usr/local/bin/eigenx-kms-client decrypt \ + --eth-rpc-url "{{ethRpcUrl}}" \ + --avs-address "{{avsAddress}}" \ + --operator-set-id "{{operatorSetId}}" \ + --app-id "{{appId}}" \ + --kms-signing-key-file /usr/local/bin/kms-signing-public-key.pem \ + --userapi-url "{{userAPIURL}}" \ + --app-controller-address "{{appControllerAddress}}" \ + --output /tmp/.env; then +{{else}} if /usr/local/bin/kms-client \ --kms-server-url "{{kmsServerURL}}" \ --kms-signing-key-file /usr/local/bin/kms-signing-public-key.pem \ --userapi-url "{{userAPIURL}}" \ --output /tmp/.env; then +{{/if}} echo "compute-source-env.sh: Successfully fetched environment variables from KMS" set -a && . /tmp/.env && set +a rm -f /tmp/.env diff --git a/packages/sdk/src/client/common/templates/scriptTemplate.ts b/packages/sdk/src/client/common/templates/scriptTemplate.ts index 0b562176..9e5ab3f5 100644 --- a/packages/sdk/src/client/common/templates/scriptTemplate.ts +++ b/packages/sdk/src/client/common/templates/scriptTemplate.ts @@ -4,6 +4,12 @@ import scriptTemplate from "./compute-source-env.sh.tmpl"; export interface ScriptTemplateData { kmsServerURL: string; userAPIURL: string; + useKmsV2?: boolean; + ethRpcUrl?: string; + avsAddress?: string; + operatorSetId?: number; + appControllerAddress?: string; + appId?: string; } /** diff --git a/packages/sdk/src/client/common/types/index.ts b/packages/sdk/src/client/common/types/index.ts index c290140b..aee718ee 100644 --- a/packages/sdk/src/client/common/types/index.ts +++ b/packages/sdk/src/client/common/types/index.ts @@ -56,6 +56,8 @@ export interface PrepareDeployOpts { logVisibility: logVisibility; /** Resource usage monitoring setting - optional */ resourceUsageMonitoring?: "enable" | "disable"; + /** Use eigenx-kms-client (v2) for environment variable encryption */ + useKmsV2?: boolean; } /** Options for prepareUpgrade */ @@ -72,6 +74,8 @@ export interface PrepareUpgradeOpts { logVisibility: logVisibility; /** Resource usage monitoring setting - optional */ resourceUsageMonitoring?: "enable" | "disable"; + /** Use eigenx-kms-client (v2) for environment variable encryption */ + useKmsV2?: boolean; } /** Options for prepareDeployFromVerifiableBuild */ @@ -90,6 +94,8 @@ export interface PrepareDeployFromVerifiableBuildOpts { logVisibility: logVisibility; /** Resource usage monitoring setting - optional */ resourceUsageMonitoring?: "enable" | "disable"; + /** Use eigenx-kms-client (v2) for environment variable encryption */ + useKmsV2?: boolean; } /** Options for prepareUpgradeFromVerifiableBuild */ @@ -106,6 +112,8 @@ export interface PrepareUpgradeFromVerifiableBuildOpts { logVisibility: logVisibility; /** Resource usage monitoring setting - optional */ resourceUsageMonitoring?: "enable" | "disable"; + /** Use eigenx-kms-client (v2) for environment variable encryption */ + useKmsV2?: boolean; } /** Gas options for execute functions */ @@ -232,6 +240,8 @@ export interface EnvironmentConfig { kmsServerURL: string; userApiServerURL: string; defaultRPCURL: string; + avsAddress?: string; + operatorSetId?: number; } export interface Release { diff --git a/packages/sdk/src/client/modules/compute/app/deploy.ts b/packages/sdk/src/client/modules/compute/app/deploy.ts index 82edaf32..7b2cc933 100644 --- a/packages/sdk/src/client/modules/compute/app/deploy.ts +++ b/packages/sdk/src/client/modules/compute/app/deploy.ts @@ -74,6 +74,8 @@ export interface SDKDeployOptions { gas?: GasEstimate; /** Skip telemetry (used when called from CLI) - optional */ skipTelemetry?: boolean; + /** Use eigenx-kms-client (v2) for environment variable encryption */ + useKmsV2?: boolean; } /** @@ -128,6 +130,8 @@ export interface VerifiableBuildOptions { resourceUsageMonitoring?: ResourceUsageMonitoring; /** Skip telemetry (used when called from CLI) - optional */ skipTelemetry?: boolean; + /** Use eigenx-kms-client (v2) for environment variable encryption */ + useKmsV2?: boolean; } /** @@ -218,6 +222,7 @@ export async function prepareDeployFromVerifiableBuild( instanceType: options.instanceType, environmentConfig: preflightCtx.environmentConfig, appId: appIDToBeDeployed, + useKmsV2: options.useKmsV2, }, logger, ); @@ -413,6 +418,7 @@ export async function deploy( instanceType, environmentConfig: preflightCtx.environmentConfig, appId: appIDToBeDeployed, + useKmsV2: options.useKmsV2, }, logger, ); @@ -587,6 +593,7 @@ export async function prepareDeploy( instanceType, environmentConfig: preflightCtx.environmentConfig, appId: appIDToBeDeployed, + useKmsV2: options.useKmsV2, }, logger, ); diff --git a/packages/sdk/src/client/modules/compute/app/index.ts b/packages/sdk/src/client/modules/compute/app/index.ts index 6d5643ab..e59795a7 100644 --- a/packages/sdk/src/client/modules/compute/app/index.ts +++ b/packages/sdk/src/client/modules/compute/app/index.ts @@ -248,6 +248,7 @@ export function createAppModule(ctx: AppModuleConfig): AppModule { logVisibility: opts.logVisibility, resourceUsageMonitoring: opts.resourceUsageMonitoring, skipTelemetry, + useKmsV2: opts.useKmsV2, }, logger, ); @@ -267,6 +268,7 @@ export function createAppModule(ctx: AppModuleConfig): AppModule { logVisibility: opts.logVisibility, resourceUsageMonitoring: opts.resourceUsageMonitoring, skipTelemetry, + useKmsV2: opts.useKmsV2, }, logger, ); @@ -318,6 +320,7 @@ export function createAppModule(ctx: AppModuleConfig): AppModule { logVisibility: opts.logVisibility, resourceUsageMonitoring: opts.resourceUsageMonitoring, skipTelemetry, + useKmsV2: opts.useKmsV2, }, logger, ); @@ -337,6 +340,7 @@ export function createAppModule(ctx: AppModuleConfig): AppModule { logVisibility: opts.logVisibility, resourceUsageMonitoring: opts.resourceUsageMonitoring, skipTelemetry, + useKmsV2: opts.useKmsV2, }, logger, ); diff --git a/packages/sdk/src/client/modules/compute/app/upgrade.ts b/packages/sdk/src/client/modules/compute/app/upgrade.ts index 4c857e9d..ff53b863 100644 --- a/packages/sdk/src/client/modules/compute/app/upgrade.ts +++ b/packages/sdk/src/client/modules/compute/app/upgrade.ts @@ -70,6 +70,8 @@ export interface SDKUpgradeOptions { gas?: GasEstimate; /** Skip telemetry (used when called from CLI) - optional */ skipTelemetry?: boolean; + /** Use eigenx-kms-client (v2) for environment variable encryption */ + useKmsV2?: boolean; } export interface UpgradeResult { @@ -163,6 +165,7 @@ export async function prepareUpgradeFromVerifiableBuild( instanceType: options.instanceType, environmentConfig: preflightCtx.environmentConfig, appId: appID as string, + useKmsV2: options.useKmsV2, }, logger, ); @@ -340,6 +343,7 @@ export async function upgrade( instanceType, environmentConfig: preflightCtx.environmentConfig, appId: appID, + useKmsV2: options.useKmsV2, }, logger, ); @@ -450,6 +454,7 @@ export async function prepareUpgrade( instanceType, environmentConfig: preflightCtx.environmentConfig, appId: appID, + useKmsV2: options.useKmsV2, }, logger, ); diff --git a/packages/sdk/tools/eigenx-kms-client-linux-amd64 b/packages/sdk/tools/eigenx-kms-client-linux-amd64 new file mode 100755 index 00000000..ddd763bd Binary files /dev/null and b/packages/sdk/tools/eigenx-kms-client-linux-amd64 differ