Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ io-uring = "0.7.9"
tokio-tungstenite = "0.28"
sha2 = "0.10"
hmac = "0.12"
subtle = "2.6"
hex = "0.4"
semver = "1"
iroh = { version = "=1.0.0-rc.0" }
Expand Down
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ If your server does not support standard KVM, see the [PVM deployment guide](htt
## ⚡ Quick Start (Single Node)

> [!WARNING]
> **AgentENV currently does not support authorization.** Do not expose the AgentENV
> API to the public network. Run it only on a trusted network or behind an
> authorization proxy with appropriate network controls.
> AgentENV authenticates API requests but does not encrypt traffic. Do not send
> the API key over an untrusted plaintext network. Run AgentENV on a trusted
> network or terminate HTTPS at a reverse proxy or load balancer.

**1. Install and start the server**

Expand All @@ -66,7 +66,7 @@ Set up the server:
```bash
curl -fsSL https://raw.githubusercontent.com/kvcache-ai/AgentENV/main/scripts/docker-setup.sh | sudo bash
docker pull ghcr.io/kvcache-ai/aenv-server:latest
docker run -d --privileged -v /dev:/dev -p 8000:8000 ghcr.io/kvcache-ai/aenv-server:latest
docker run -d --name aenv-server --privileged -v /dev:/dev -p 8000:8000 ghcr.io/kvcache-ai/aenv-server:latest
```

The server is accessible at `http://127.0.0.1:8000` by default.
Expand All @@ -83,10 +83,23 @@ curl -fsSL https://raw.githubusercontent.com/kvcache-ai/AgentENV/main/scripts/in

**3. Authenticate**

The server generates an API key on its first startup. Retrieve it for the
installation method used in step 1:

```bash
# Native install
sudo cat /var/lib/aenv/secrets/api-key

# Docker
docker exec aenv-server cat /workspace/env/secrets/api-key
```

Then run `aenv auth` and paste that key:

```bash
aenv auth
# AENV server URL [http://localhost:8000]: http://127.0.0.1:8000
# API key: dummy
# API key: <paste the generated key>
```

**4. Pull a template and run a sandbox**
Expand Down
2 changes: 1 addition & 1 deletion config/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ init_timeout_secs = 60
poll_ms = 3

[sandbox]
# Optional secret used to derive per-sandbox envd access tokens. When unset,
# Optional secret used to derive per-sandbox envd and traffic access tokens. When unset,
# AgentENV creates a node-local seed under $AENV_HOME/secrets. Configure the
# same explicit value on every node when cross-node sandbox recovery is required.
# access_token_hash_seed = "replace-with-a-secret"
Expand Down
21 changes: 3 additions & 18 deletions crates/aenv/src/client/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use super::Client;
use crate::grpc::{RpcError, Transport, ENVD_PORT_STR};
use crate::progress::TransferProgress;

const API_KEY_HEADER: &str = "X-API-Key";
const SANDBOX_ID_HEADER: &str = "x-agentenv-sandbox-id";
const TARGET_PORT_HEADER: &str = "x-agentenv-target-port";
const ACCESS_TOKEN_HEADER: &str = "X-Access-Token";
Expand All @@ -32,17 +31,8 @@ pub struct EnvdFilesClient {
}

impl EnvdFilesClient {
fn new(
base_url: &str,
api_key: &str,
sandbox_id: &str,
envd_access_token: Option<&str>,
) -> Result<Self> {
fn new(base_url: &str, sandbox_id: &str, envd_access_token: Option<&str>) -> Result<Self> {
let mut headers = HeaderMap::new();
headers.insert(
API_KEY_HEADER,
HeaderValue::from_str(api_key).context("invalid API key header value")?,
);
headers.insert(
SANDBOX_ID_HEADER,
HeaderValue::from_str(sandbox_id).context("invalid sandbox ID header value")?,
Expand Down Expand Up @@ -72,7 +62,7 @@ impl EnvdFilesClient {
Ok(Self {
base_url: base_url.trim_end_matches('/').to_string(),
http: client,
transport: Transport::new(base_url, api_key, sandbox_id, envd_access_token)?,
transport: Transport::new(base_url, sandbox_id, envd_access_token)?,
})
}

Expand Down Expand Up @@ -311,12 +301,7 @@ fn format_envd_response_error(status: reqwest::StatusCode, content: &str) -> any
impl Client {
pub fn files(&self, sandbox_id: &str) -> Result<EnvdFilesClient> {
let sandbox = self.get_sandbox(sandbox_id)?;
EnvdFilesClient::new(
&self.base,
&self.api_key,
sandbox_id,
sandbox.envd_access_token.as_deref(),
)
EnvdFilesClient::new(&self.base, sandbox_id, sandbox.envd_access_token.as_deref())
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/aenv/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ impl Client {
sandbox_id: &str,
envd_access_token: Option<&str>,
) -> Result<Transport> {
Transport::new(&self.base, &self.api_key, sandbox_id, envd_access_token)
Transport::new(&self.base, sandbox_id, envd_access_token)
}

fn url(&self, path: &str) -> String {
Expand Down
22 changes: 5 additions & 17 deletions crates/aenv/src/grpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,16 @@ impl std::error::Error for RpcError {}
pub struct Transport {
http: HttpClient,
base_url: String,
api_key: String,
sandbox_id: String,
envd_access_token: Option<String>,
}

impl Transport {
pub fn new(
base_url: &str,
api_key: &str,
sandbox_id: &str,
envd_access_token: Option<&str>,
) -> Result<Self> {
pub fn new(base_url: &str, sandbox_id: &str, envd_access_token: Option<&str>) -> Result<Self> {
let http = Self::http_client(base_url).context("building Connect-RPC HTTP client")?;
Ok(Self {
http,
base_url: base_url.trim_end_matches('/').to_string(),
api_key: api_key.to_string(),
sandbox_id: sandbox_id.to_string(),
envd_access_token: envd_access_token.map(str::to_owned),
})
Expand Down Expand Up @@ -102,7 +95,6 @@ impl Transport {

fn auth(&self, builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
let builder = builder
.header("X-API-Key", &self.api_key)
.header("x-agentenv-sandbox-id", &self.sandbox_id)
.header("x-agentenv-target-port", ENVD_PORT_STR)
.header("Connect-Protocol-Version", "1");
Expand Down Expand Up @@ -508,7 +500,7 @@ mod tests {

#[test]
fn unary_user_is_sent_as_basic_auth() {
let transport = Transport::new("http://127.0.0.1", "api-key", "sandbox-id", None).unwrap();
let transport = Transport::new("http://127.0.0.1", "sandbox-id", None).unwrap();
let request = transport
.unary_request("filesystem.Filesystem", "Stat", Some("app"))
.build()
Expand All @@ -518,6 +510,7 @@ mod tests {
request.headers().get(AUTHORIZATION).unwrap(),
"Basic YXBwOg=="
);
assert!(!request.headers().contains_key("x-api-key"));

let request = transport
.unary_request("filesystem.Filesystem", "Stat", None)
Expand All @@ -528,13 +521,8 @@ mod tests {

#[test]
fn envd_access_token_is_sent_on_connect_requests() {
let transport = Transport::new(
"http://127.0.0.1",
"api-key",
"sandbox-id",
Some("envd-token"),
)
.unwrap();
let transport =
Transport::new("http://127.0.0.1", "sandbox-id", Some("envd-token")).unwrap();

let request = transport
.unary_request("process.Process", "List", None)
Expand Down
10 changes: 8 additions & 2 deletions deploy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ x-agentenv-base: &agentenv-base
init: true
working_dir: /workspace
environment: &agentenv-environment
AENV_API_KEY:
AENV_CONFIG_PATH: /workspace/config/default.toml
AENV_VIRTUALIZATION_MODE: ${AENV_VIRTUALIZATION_MODE:-kvm}
API_ADDR: 0.0.0.0:8000
Expand All @@ -16,8 +17,9 @@ x-agentenv-base: &agentenv-base
- /dev:/dev
- ${CONFIG_PATH:-../config/default.toml}:/workspace/config/default.toml:ro
# Runtime assets are baked into the image by `server --setup-only`; compose
# persists only committed snapshots across container restarts.
# persists committed snapshots and deployment secrets across restarts.
- agentenv-snapshot-store:/workspace/env/snapshot-store
- agentenv-auth:/workspace/env/secrets
devices:
- /dev/kvm:/dev/kvm
privileged: true
Expand Down Expand Up @@ -67,8 +69,11 @@ services:
depends_on:
scheduler:
condition: service_healthy
volumes: *control-plane-config-volume
volumes:
- ./docker/config/default.json:/config/default.json:ro
- agentenv-auth:/run/secrets:ro
environment:
AENV_API_KEY:
GATEWAY_HTTP_LISTEN_ADDR: :8080
GATEWAY_SCHEDULER_ADDR: scheduler:9090
GATEWAY_SANDBOX_PROXY_DOMAINS: ${SANDBOX_PROXY_DOMAINS:-}
Expand Down Expand Up @@ -96,4 +101,5 @@ services:
AENV_NODE_ID: node-b

volumes:
agentenv-auth:
agentenv-snapshot-store:
7 changes: 6 additions & 1 deletion deploy/k8s/base/agentenv-daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ spec:
image: agentenv-runtime:latest
imagePullPolicy: IfNotPresent
env:
- name: AENV_API_KEY
valueFrom:
secretKeyRef:
name: agentenv-auth
key: AENV_API_KEY
- name: AENV_CONFIG_PATH
value: /workspace/config/agentenv.toml
- name: AENV_SANDBOX_ACCESS_TOKEN_HASH_SEED
Expand Down Expand Up @@ -76,7 +81,7 @@ spec:
- |
echo "preStop: waiting for sandboxes to drain..."
while true; do
count=$(curl -sf -H 'X-API-Key: preStop' http://localhost:8000/sandboxes | jq 'length') || count=""
count=$(curl -sf -H "X-API-Key: ${AENV_API_KEY}" http://localhost:8000/sandboxes | jq 'length') || count=""
if [ -z "$count" ]; then
echo "preStop: failed to query sandbox count, retrying..."
sleep 3
Expand Down
5 changes: 5 additions & 0 deletions deploy/k8s/base/gateway-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ spec:
- name: http
containerPort: 8080
env:
- name: AENV_API_KEY
valueFrom:
secretKeyRef:
name: agentenv-auth
key: AENV_API_KEY
- name: GATEWAY_SANDBOX_PROXY_DOMAINS
valueFrom:
configMapKeyRef:
Expand Down
5 changes: 5 additions & 0 deletions deploy/k8s/base/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ configMapGenerator:
literals:
- SANDBOX_PROXY_DOMAINS=

secretGenerator:
- name: agentenv-auth
literals:
- AENV_API_KEY=

images:
- name: agentenv-gateway
newName: agentenv-gateway
Expand Down
Loading
Loading