Skip to content

Commit 729e43f

Browse files
committed
Add one-command mock services demo
1 parent 7f0f45c commit 729e43f

9 files changed

Lines changed: 410 additions & 45 deletions

File tree

.dockerignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/.git
2+
/.cursor
3+
/target
4+
/certs
5+
/logs.txt
6+
/*.log
7+
/*.jsonl

README.md

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,35 @@ sudo apt-get install libtss2-dev swtpm tpm2-tools pkg-config
2020
## Quick start
2121

2222
```bash
23-
./demo/generate-server-certs.sh # server CA + gateway cert under certs/
24-
cp config.example.toml config.toml # edit to taste
25-
docker compose -f docker-compose.postgres.yml up -d
26-
export AGENT_GATEWAY_DATABASE_URL=postgres://agent_gateway_admin:agent_gateway_dev@localhost:5432/agent_gateway
27-
psql "$AGENT_GATEWAY_DATABASE_URL" -f migrations/0001_signed_authorization_registry.sql
23+
./demo/setup.sh
2824
```
2925

30-
`demo/generate-server-certs.sh` only creates **server** TLS material (`server-ca.pem`, `server.pem`, ...). The local demo enrolls with `./demo/demo-agent.sh`, which starts a local `swtpm`, creates a persistent P-256 signing key in that simulated TPM, and prepares `machine-client.pem` as a certificate carrier for that public key and identity extension. The gateway does not trust a client CA bundle; it authorizes the exact subject public key recorded in signed Postgres permission rows.
26+
The setup script generates demo TLS material, creates `config.toml` when needed,
27+
starts Postgres and static HTTPS mock services with Podman Compose or Docker Compose, applies the
28+
database migration, builds and starts the gateway, enrolls a demo principal,
29+
grants access to `docstore` and `messaging`, creates a demo agent handle, and
30+
verifies that Claude Code can fetch `https://docstore/health` through the
31+
gateway.
32+
33+
`demo/generate-server-certs.sh` creates gateway TLS material (`server-ca.pem`,
34+
`server.pem`, ...) and mock HTTPS service TLS material (`mock-ca.pem`,
35+
`mock-services.pem`, ...). The local demo enrolls with `./demo/demo-agent.sh`,
36+
which starts a local `swtpm`, creates a persistent P-256 signing key in that
37+
simulated TPM, and prepares `machine-client.pem` as a certificate carrier for
38+
that public key and identity extension. The gateway does not trust a client CA
39+
bundle; it authorizes the exact subject public key recorded in signed Postgres
40+
permission rows.
3141

32-
Typical first-time flow:
42+
`demo/setup.sh` keeps its tpm2-pkcs11 state under the demo state directory by
43+
default. Override `AGENT_GATEWAY_DEMO_TPM2_PKCS11_STORE` only when you
44+
intentionally want the demo principal to use another store.
3345

34-
1. `./demo/generate-server-certs.sh` and `cp config.example.toml config.toml`.
35-
2. Enroll a trusted principal signing key and grant its destination delegation scope.
36-
3. The principal creates an agent handle; the script prepares the subject certificate, signs permission rows for its exact SPKI DER, and starts the sidecar.
37-
4. Start the gateway (`cargo run -- --config config.toml`) before sending prompts through the sidecar.
46+
After setup, prompt the demo agent:
47+
48+
```bash
49+
./demo/demo-agent.sh prompt agent-alpha \
50+
--prompt "Access https://docstore/documents using curl"
51+
```
3852

3953
On later runs, start the gateway first and use `./demo/demo-agent.sh prompt`. `./demo/connect.sh` prepares `machine-client.pem` for the current simulated TPM key and identity extension whenever it prepares or starts the sidecar. `--regenerate-certs` creates a fresh simulated TPM state; any permissions for the old subject key will no longer match.
4054

@@ -110,30 +124,26 @@ Register a principal signing key from the TPM owner machine with:
110124

111125
The script creates or reuses a non-exportable TPM-backed P-256 key through `tpm2_ptool` and PKCS#11, stores only the public key in `principal_signing_keys`, and uses the friendly `key_id` (`org-alice`, `org-bob`, etc.) for the registry row. Run it on the machine that owns the TPM, with `AGENT_GATEWAY_DATABASE_URL` or `DATABASE_URL` pointing at Postgres.
112126

113-
For the demo, use three windows:
127+
For a manual demo without `./demo/setup.sh`, use three windows:
114128

115129
```bash
116130
# Principal shell: enroll the principal TPM public key.
117131
./registry-cli/register-principal-key.sh org-alice
118132

119133
# Admin shell: grant destination delegation authority to that principal.
120-
./registry-cli/grant-principal-scope.sh org-alice api.anthropic.com example.com
134+
./registry-cli/grant-principal-scope.sh org-alice docstore messaging api.anthropic.com
121135

122136
# Principal shell: create a local agent handle with initial signed permissions.
123137
AGENT_HANDLE="$(./demo/demo-agent.sh create \
124138
--identity agent-alpha \
125-
--grant api.anthropic.com)"
139+
--grant docstore \
140+
--grant messaging)"
126141

127142
# Principal shell: send the first prompt through that agent.
128-
./demo/demo-agent.sh prompt "$AGENT_HANDLE" --prompt "test prompt"
129-
130-
# Principal shell: grant another destination, then continue the same Claude session.
131-
./demo/demo-agent.sh grant "$AGENT_HANDLE" --grant example.com
132-
./demo/demo-agent.sh prompt "$AGENT_HANDLE" --prompt "now try the second destination"
143+
./demo/demo-agent.sh prompt "$AGENT_HANDLE" \
144+
--prompt "Access https://docstore/documents with curl."
133145
```
134146

135-
The dashboard runs separately and observes Postgres plus OpenTelemetry. `demo-agent.sh` keeps gateway connection details out of the principal-facing command; set `AGENT_GATEWAY_DEMO_GATEWAY` and `AGENT_GATEWAY_DEMO_GATEWAY_CA` only when overriding the local defaults. `AGENT_GATEWAY_DEMO_GATEWAY_CA` is the CA for the gateway's server certificate, not a client trust root. The first prompt uses `claude -p`; later prompts for the same handle use `claude -c -p` from the handle's working directory.
136-
137147
## Authorization Registry
138148

139149
The gateway authorizes a CONNECT only when all of these checks pass:

demo/connect.sh

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,16 +206,19 @@ run_prompt() {
206206
PROXY_URL="$(<"$proxy_file")"
207207
WORK_DIR="${WORK_DIR:-$STATE_DIR/work}"
208208
mkdir -p "$WORK_DIR"
209+
CLAUDE_CURL_PERMISSIONS=(
210+
--allowedTools "Bash(curl *)"
211+
)
209212

210213
if [[ -f "$STATE_DIR/claude_started" ]]; then
211214
(
212215
cd "$WORK_DIR"
213-
HTTP_PROXY="$PROXY_URL" HTTPS_PROXY="$PROXY_URL" claude -c -p "$PROMPT"
216+
HTTP_PROXY="$PROXY_URL" HTTPS_PROXY="$PROXY_URL" CURL_CA_BUNDLE="${CURL_CA_BUNDLE:-}" SSL_CERT_FILE="${SSL_CERT_FILE:-}" claude "${CLAUDE_CURL_PERMISSIONS[@]}" -c -p "$PROMPT"
214217
)
215218
else
216219
(
217220
cd "$WORK_DIR"
218-
HTTP_PROXY="$PROXY_URL" HTTPS_PROXY="$PROXY_URL" claude -p "$PROMPT"
221+
HTTP_PROXY="$PROXY_URL" HTTPS_PROXY="$PROXY_URL" CURL_CA_BUNDLE="${CURL_CA_BUNDLE:-}" SSL_CERT_FILE="${SSL_CERT_FILE:-}" claude "${CLAUDE_CURL_PERMISSIONS[@]}" -p "$PROMPT"
219222
)
220223
: > "$STATE_DIR/claude_started"
221224
fi

demo/demo-agent.sh

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ STATE_ROOT="${AGENT_GATEWAY_DEMO_STATE_DIR:-${XDG_STATE_HOME:-$HOME/.local/state
77
DEFAULT_VALID_DAYS="${AGENT_GATEWAY_PERMISSION_VALID_DAYS:-30}"
88
DEFAULT_GATEWAY="${AGENT_GATEWAY_DEMO_GATEWAY:-127.0.0.1:8443}"
99
DEFAULT_GATEWAY_CA="${AGENT_GATEWAY_DEMO_GATEWAY_CA:-$REPO_ROOT/certs/server-ca.pem}"
10+
DEFAULT_MOCK_CA="${AGENT_GATEWAY_DEMO_MOCK_CA:-$REPO_ROOT/certs/mock-ca.pem}"
11+
DEFAULT_SIDECAR_BIN="${AGENT_GATEWAY_DEMO_SIDECAR_BIN:-}"
1012
DEFAULT_LISTEN_HOST="${AGENT_GATEWAY_DEMO_LISTEN_HOST:-127.0.0.1}"
1113
DEFAULT_LISTEN_PORT="${AGENT_GATEWAY_DEMO_LISTEN_PORT:-3128}"
1214
DEFAULT_SWTPM_PORT="${AGENT_GATEWAY_DEMO_SWTPM_PORT:-2321}"
@@ -25,6 +27,8 @@ Usage:
2527
Environment:
2628
AGENT_GATEWAY_DEMO_GATEWAY defaults to 127.0.0.1:8443.
2729
AGENT_GATEWAY_DEMO_GATEWAY_CA defaults to certs/server-ca.pem.
30+
AGENT_GATEWAY_DEMO_MOCK_CA defaults to certs/mock-ca.pem for Claude HTTPS requests.
31+
AGENT_GATEWAY_DEMO_SIDECAR_BIN can pin the sidecar binary used by start-sidecar.
2832
AGENT_GATEWAY_DEMO_STATE_DIR overrides the local agent handle directory.
2933
AGENT_GATEWAY_PERMISSION_VALID_DAYS defaults to 30.
3034
TPM2_PKCS11_STORE defaults to $HOME/.tpm2_pkcs11.
@@ -150,17 +154,24 @@ sidecar_running() {
150154

151155
ensure_sidecar() {
152156
local dir="$1"
157+
local args=(
158+
start-sidecar
159+
--state-dir "$dir"
160+
--gateway "$GATEWAY"
161+
--gateway-ca "$GATEWAY_CA"
162+
--extension-value "$IDENTITY"
163+
--listen "$LISTEN"
164+
--swtpm-port "$SWTPM_PORT"
165+
)
153166
if sidecar_running "$dir"; then
154167
return
155168
fi
156169

157-
"$SCRIPT_DIR/connect.sh" start-sidecar \
158-
--state-dir "$dir" \
159-
--gateway "$GATEWAY" \
160-
--gateway-ca "$GATEWAY_CA" \
161-
--extension-value "$IDENTITY" \
162-
--listen "$LISTEN" \
163-
--swtpm-port "$SWTPM_PORT" >&2
170+
if [[ -n "$DEFAULT_SIDECAR_BIN" ]]; then
171+
args+=(--sidecar-bin "$DEFAULT_SIDECAR_BIN")
172+
fi
173+
174+
"$SCRIPT_DIR/connect.sh" "${args[@]}" >&2
164175
}
165176

166177
prepare_subject_certificate() {
@@ -261,6 +272,15 @@ cmd_prompt() {
261272

262273
[[ -n "$PROMPT" ]] || { echo "error: prompt requires --prompt" >&2; exit 2; }
263274
ensure_sidecar "$STATE_DIR_CURRENT"
275+
[[ -f "$DEFAULT_MOCK_CA" ]] || {
276+
echo "error: mock service CA file not found: $DEFAULT_MOCK_CA" >&2
277+
echo "hint: run ./demo/generate-server-certs.sh or ./demo/setup.sh first" >&2
278+
exit 1
279+
}
280+
export NODE_EXTRA_CA_CERTS="$DEFAULT_MOCK_CA"
281+
export CURL_CA_BUNDLE="$DEFAULT_MOCK_CA"
282+
export SSL_CERT_FILE="$DEFAULT_MOCK_CA"
283+
export CLAUDE_CODE_PROXY_RESOLVES_HOSTS="${CLAUDE_CODE_PROXY_RESOLVES_HOSTS:-1}"
264284
"$SCRIPT_DIR/connect.sh" prompt \
265285
--state-dir "$STATE_DIR_CURRENT" \
266286
--work-dir "$STATE_DIR_CURRENT/work" \

demo/gateway.Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM rust:1-bookworm AS builder
2+
3+
WORKDIR /src
4+
COPY . .
5+
RUN cargo build --release --bin agent_gateway
6+
7+
FROM debian:bookworm-slim
8+
9+
COPY --from=builder /src/target/release/agent_gateway /usr/local/bin/agent_gateway
10+
WORKDIR /workspaces/agent_gateway
11+
12+
ENTRYPOINT ["agent_gateway"]

demo/generate-server-certs.sh

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,56 @@
11
#!/usr/bin/env bash
2-
# Demo gateway server TLS only: certs/server-ca*.pem and certs/server*.pem.
2+
# Demo TLS material: gateway server certs plus mock HTTPS service certs.
33

44
set -euo pipefail
55

66
DIR="certs"
77

88
mkdir -p "$DIR"
99

10-
echo "==> Generating server CA"
11-
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
12-
-keyout "$DIR/server-ca-key.pem" -out "$DIR/server-ca.pem" \
13-
-days 365 -nodes -subj "/CN=agent-gateway server CA" 2>/dev/null
14-
15-
echo "==> Generating gateway server cert (localhost / 127.0.0.1)"
16-
openssl req -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
17-
-keyout "$DIR/server-key.pem" -out "$DIR/server.csr" \
18-
-nodes -subj "/CN=localhost" 2>/dev/null
19-
openssl x509 -req -in "$DIR/server.csr" \
20-
-CA "$DIR/server-ca.pem" -CAkey "$DIR/server-ca-key.pem" -CAcreateserial \
21-
-out "$DIR/server.pem" -days 365 \
22-
-extfile <(printf 'subjectAltName=DNS:localhost,IP:127.0.0.1') 2>/dev/null
23-
rm -f "$DIR/server.csr" "$DIR/server-ca.srl"
10+
have_files() {
11+
local file
12+
for file in "$@"; do
13+
[[ -f "$file" ]] || return 1
14+
done
15+
}
16+
17+
if have_files "$DIR/server-ca-key.pem" "$DIR/server-ca.pem" "$DIR/server-key.pem" "$DIR/server.pem"; then
18+
echo "==> Using existing gateway server certs"
19+
else
20+
echo "==> Generating server CA"
21+
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
22+
-keyout "$DIR/server-ca-key.pem" -out "$DIR/server-ca.pem" \
23+
-days 365 -nodes -subj "/CN=agent-gateway server CA" 2>/dev/null
24+
25+
echo "==> Generating gateway server cert (localhost / 127.0.0.1)"
26+
openssl req -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
27+
-keyout "$DIR/server-key.pem" -out "$DIR/server.csr" \
28+
-nodes -subj "/CN=localhost" 2>/dev/null
29+
openssl x509 -req -in "$DIR/server.csr" \
30+
-CA "$DIR/server-ca.pem" -CAkey "$DIR/server-ca-key.pem" -CAcreateserial \
31+
-out "$DIR/server.pem" -days 365 \
32+
-extfile <(printf 'subjectAltName=DNS:localhost,IP:127.0.0.1') 2>/dev/null
33+
rm -f "$DIR/server.csr" "$DIR/server-ca.srl"
34+
fi
35+
36+
if have_files "$DIR/mock-ca-key.pem" "$DIR/mock-ca.pem" "$DIR/mock-services-key.pem" "$DIR/mock-services.pem"; then
37+
echo "==> Using existing mock service certs"
38+
else
39+
echo "==> Generating mock service CA"
40+
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
41+
-keyout "$DIR/mock-ca-key.pem" -out "$DIR/mock-ca.pem" \
42+
-days 365 -nodes -subj "/CN=agent-gateway mock service CA" 2>/dev/null
43+
44+
echo "==> Generating mock service cert (docstore / messaging)"
45+
openssl req -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
46+
-keyout "$DIR/mock-services-key.pem" -out "$DIR/mock-services.csr" \
47+
-nodes -subj "/CN=agent-gateway mock services" 2>/dev/null
48+
openssl x509 -req -in "$DIR/mock-services.csr" \
49+
-CA "$DIR/mock-ca.pem" -CAkey "$DIR/mock-ca-key.pem" -CAcreateserial \
50+
-out "$DIR/mock-services.pem" -days 365 \
51+
-extfile <(printf 'subjectAltName=DNS:docstore,DNS:messaging\nextendedKeyUsage=serverAuth') 2>/dev/null
52+
rm -f "$DIR/mock-services.csr" "$DIR/mock-ca.srl"
53+
fi
2454

2555
echo ""
2656
echo "Generated in $DIR/:"

demo/mock/Caddyfile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
auto_https off
3+
}
4+
5+
docstore:443 {
6+
tls /certs/mock-services.pem /certs/mock-services-key.pem
7+
header Content-Type application/json
8+
9+
respond /health `{"status":"ok","service":"docstore"}` 200
10+
11+
respond /documents `{"documents":[{"id":"handbook","title":"Employee Handbook","summary":"Demo employees can find travel, device, and security policies here."},{"id":"incident-runbook","title":"Incident Runbook","summary":"Page the on-call lead, create a channel, and post status updates every 30 minutes."}]}` 200
12+
13+
respond `{"error":"not_found","service":"docstore"}` 404
14+
}
15+
16+
messaging:443 {
17+
tls /certs/mock-services.pem /certs/mock-services-key.pem
18+
header Content-Type application/json
19+
20+
respond /health `{"status":"ok","service":"messaging"}` 200
21+
22+
respond /messages `{"messages":[{"from":"alice","channel":"demo-ops","text":"Quarterly planning starts at 2pm."},{"from":"bob","channel":"demo-ops","text":"Please review the incident runbook before the tabletop."}]}` 200
23+
24+
respond `{"error":"not_found","service":"messaging"}` 404
25+
}

0 commit comments

Comments
 (0)