mTLS RPC server providing safe VM-to-host delegation via an adapter-first architecture.
v4 adds: unified /host/op dispatch endpoint, five adapters (ZFS, Systemd, PCT, Git,
Exec/Ansible stub), per-adapter validation, and policy-driven approval flows.
# ZFS list
curl -s --cert client.crt --key client.key -k \
-X POST https://host:18443/host/op \
-H "Content-Type: application/json" \
-d '{"kind":"zfs","args":["list"]}'
# Systemd status
curl -s --cert client.crt --key client.key -k \
-X POST https://host:18443/host/op \
-d '{"kind":"systemd","resource":"nginx.service","args":["status"]}'
# PCT container status
curl -s --cert client.crt --key client.key -k \
-X POST https://host:18443/host/op \
-d '{"kind":"pct","resource":"101","args":["status"]}'
# Git repo status (repo must be in allowed_repos)
curl -s --cert client.crt --key client.key -k \
-X POST https://host:18443/host/op \
-d '{"kind":"git","resource":"/srv/myapp","args":["status"]}'# Git adapter: repo allowlist (empty = allow any absolute path)
[git]
allowed_repos = ["/srv", "/opt", "/home"]
# Exec adapter: disabled by default
[exec]
enabled = false # must be true to activate
allowed_commands = ["/usr/bin/uptime"] # absolute paths only
# ansible_job_queue = "/var/lib/clash/jobs" # for Ansible stubAdapter dispatch respects the same [[rules]] config as v3:
# Systemd — read-only ops: no approval needed
[[rules]]
operation = "systemd-status"
approval_required = false
# PCT — start/stop requires approval
[[rules]]
operation = "pct-start"
approval_required = true
# PCT — destroy always requires admin approval
[[rules]]
operation = "pct-destroy"
approval_required = true
always_ask = true
approval_admin_only = true
# Git — checkout requires approval
[[rules]]
operation = "git-checkout"
approval_required = true- Real mTLS auth middleware — CN extracted from TLS session, ClientIdentity injected
- No HTTP fallback — TLS failure is fatal, no plaintext server (P0-2)
- Caller identity passed to ZFS — All operations use
sudo -u <identity>(P0-3) - Config approval rules enforced —
requires_approval()checked at runtime (P0-4)
- 16-character token entropy — ~80 bits, cryptographically secure (P1-5)
- Token hash logging — Only SHA-256 hashes logged, never plaintext (P1-6)
- Filtered
/pendingendpoint — Returns only caller's pending approvals (P1-7) - Real UID lookup — Uses
nix::unistd::User::from_name()/getpwnam()(P1-8) - CRL support — Certificate revocation list checking in TLS (P1-9)
- Async ZFS commands — Uses
tokio::process::Command(P2-10) - Install script —
install.shfor one-command deployment (P2-11) - Config reload — SIGHUP handler support (P2-12)
- Prometheus metrics —
/metricsendpoint on configurable port (P2-13) - Audit log rotation — Daily rotation with retention (P2-14)
- Clean code — Compiler warnings addressed
- ZeroClaw integration framework — Policy engine trait defined, ready for ZeroClaw connection
- Agent adapter framework — CN → agent identity mapping, ACPX support
- Unified approvals — Signal webhook integration for human confirmation
- Real mTLS auth middleware — CN extracted from TLS session, ClientIdentity injected
- No HTTP fallback — TLS failure is fatal, no plaintext server
- Caller identity passed to operations — All operations use
sudo -u <identity> - Config approval rules enforced — policy checked at runtime
- 16-character token entropy — ~80 bits, cryptographically secure
- Token hash logging — Only SHA-256 hashes logged, never plaintext
- Filtered
/pendingendpoint — Returns only caller's pending approvals - Real UID lookup — Uses
nix::unistd::User::from_name()/getpwnam()
cd /root/projects/calciforge
cargo build --release -p host-agentcd /root/projects/calciforge/crates/host-agent
scp target/release/clash-host-agent root@host.example.invalid:/tmp/
ssh root@host.example.invalid
cd /tmp
./clash-host-agent --help
# Or use the install script:
./install.shcd /root/.openclaw/workspace/infra/ansible
ansible-playbook -i inventories/toy-vm.yml playbooks/host-agent-deploy.yml[server]
bind = "0.0.0.0:18443"
cert = "/etc/clash/certs/server.crt"
key = "/etc/clash/certs/server.key"
client_ca = "/etc/clash/certs/ca.crt"
crl_file = "/etc/clash/certs/ca.crl" # Optional
[audit]
log_path = "/var/log/clash/audit.jsonl"
rotation = "daily" # daily, hourly, never
retention_days = 90
[approval]
ttl_seconds = 300
token_entropy_bits = 80
signal_webhook = "https://signal.example.com/webhook"
allowed_approvers = ["+15555550001"]
# Required for admin endpoints such as /admin/pending and /admin/warn-permissions.
# Leave unset to fail closed until a dedicated admin client certificate exists.
admin_cn_pattern = "admin-*"
[metrics]
enabled = true
bind = "127.0.0.1:19090"
[[agent]]
cn_pattern = "calciforge-agent"
agent_type = "generic"
unix_user = "clash-agent"
autonomy = "supervised"
allowed_operations = ["zfs-list", "zfs-snapshot"]
requires_approval_for = ["zfs-destroy"]
[[rule]]
operation = "zfs-destroy"
approval_required = true
pattern = "tank/.*"curl -k --cert client.pem https://host:18443/healthcurl -k --cert client.pem -X POST \
-H "Content-Type: application/json" \
-d '{"dataset": "tank", "type": "snapshot"}' \
https://host:18443/zfs/listcurl -k --cert client.pem -X POST \
-H "Content-Type: application/json" \
-d '{"dataset": "tank/media", "snapname": "daily-2024-01-15"}' \
https://host:18443/zfs/snapshot# Request approval
curl -k --cert client.pem -X POST \
-H "Content-Type: application/json" \
-d '{"dataset": "tank/media@old", "approval_token": null}' \
https://host:18443/zfs/destroy
# Response: {"pending_approval": true, "approval_id": "...", "message": "Reply CONFIRM <code>"}
# Confirm via API (or Signal webhook)
curl -k --cert client.pem -X POST \
-H "Content-Type: application/json" \
-d '{"approval_id": "...", "token": "<approval-token>"}' \
https://host:18443/approve
# Execute with token
curl -k --cert client.pem -X POST \
-H "Content-Type: application/json" \
-d '{"dataset": "tank/media@old", "approval_token": "<approval-token>"}' \
https://host:18443/zfs/destroycurl http://localhost:19090/metrics# Caller-scoped view: returns only approvals requested by this client cert CN.
curl -k --cert client.pem https://host:18443/pending
# Admin view: returns all pending approvals only when this client cert CN matches
# [approval].admin_cn_pattern. If the pattern is unset, this endpoint is 403.
curl -k --cert admin-client.pem https://host:18443/admin/pending- mTLS is mandatory — No plaintext HTTP fallback
- Client certificates required — Must present valid cert signed by CA
- Identity from CN — Unix user resolved from certificate Common Name
- Operations as user — All ZFS commands run as the authenticated user
- Approval for destruction — Destroy operations require human confirmation
- Admin views fail closed —
/admin/pendingand/admin/warn-permissionsrequireapproval.admin_cn_pattern - Audit everything — All operations logged with hashes (no plaintext tokens)
cargo test -p host-agent# Deploy
ansible-playbook -i inventories/toy-vm.yml playbooks/host-agent-deploy.yml
# Copy client cert locally
scp root@host.example.invalid:/etc/clash/certs/client-bundle.pem ./
# Test health
curl -k --cert client-bundle.pem https://host.example.invalid:18443/health
# Test ZFS operations
curl -k --cert client-bundle.pem -X POST \
-H "Content-Type: application/json" \
-d '{"dataset": "tank"}' \
https://host.example.invalid:18443/zfs/list┌─────────────┐ mTLS ┌────────────────────────────────────────┐
│ Client │ ───────────────▶│ Host-Agent │
│ (cert: CN) │ │ ┌─────────┐ ┌──────────┐ ┌────────┐ │
└─────────────┘ │ │ mTLS │─▶│ Identity │─▶│ Policy │ │
│ │ Layer │ │ Resolver │ │ Engine │ │
│ └─────────┘ └──────────┘ └───┬────┘ │
│ │ │
│ ┌─────────┐ ┌──────────┐ │ │
│ │ ZFS │◀─│ Sudo │◀─────┘ │
│ │ Executor│ │ -u CN │ │
│ └─────────┘ └──────────┘ │
│ │
│ ┌─────────┐ ┌──────────┐ │
│ │ Audit │ │ Signal │ │
│ │ Logger │ │ Webhook │ │
│ └─────────┘ └──────────┘ │
└────────────────────────────────────────┘
journalctl -u clash-host-agent -f
# Check certificate permissions
ls -la /etc/clash/certs/
# Check config syntax
cat /etc/clash/host-agent.toml# Test with verbose curl
curl -v -k --cert client.pem https://host:18443/health
# Check cert is signed by CA
openssl verify -CAfile /etc/clash/certs/ca.crt /etc/clash/certs/client.crt# Check ZFS delegation
zfs allow tank
# Check sudoers
sudo -u clash-agent sudo -u root zfs list tankMIT