# API Interception Guide: Observing Aether ↔ Incus Communication How to capture, analyze, and understand the REST API calls between Aether (SDN controller) and the Incus cluster at the wire level. ## Architecture ``` Aether (192.168.102.160) │ Client cert: CN=root@oc-server │ Subscribes to: /1.0/events (lifecycle stream) │ ├──→ Incus API (https://192.168.102.140-142:8443) │ All instance/network/storage operations │ └──→ Aether's own REST API (/api/*) Auth, clusters, ACLs only JWT-authenticated, separate from Incus CLI (dev-vm-beelink) │ Client cert: CN=maarten@dev-vm-beelink │ └──→ Incus API (same endpoints) Cluster nodes (oc-node-01/02/03) │ Client certs: CN=root@oc-node-* │ └──→ Inter-node API (internal cluster operations) ``` ## Capture Methods ### Primary: Incus Event Stream The `incus monitor` command subscribes to the Incus event websocket and captures every API call, operation lifecycle change, and state transition. ```bash # Quick capture (60 seconds) incusos/helpers/incus-mitm --capture 60 # Long capture with custom output incusos/helpers/incus-mitm --capture 300 --output /tmp/my-capture.json # Live streaming (Ctrl+C to stop) incusos/helpers/incus-mitm --live # Filter for Aether only incusos/helpers/incus-mitm --live --filter aether ``` **What gets captured:** - Every REST API call (method, path, caller certificate) - Operation lifecycle (Pending → Running → Success/Failure) - Instance lifecycle events (created, started, stopped, deleted, etc.) - Cluster-internal operations (node-to-node forwarding) **What doesn't get captured:** - Request/response bodies (only the path and method) - Aether-internal operations (JWT API calls don't go through Incus) - Timing/latency data (only timestamps) ### Secondary: Aether Go Log Aether has a live log viewable at `/logs/live` in the web UI (sidebar → "View Live GO Log"). This shows Aether's perspective: what it decides to do and what errors it encounters. Access via Playwright (session-authenticated route): ```bash source env NODE_PATH=~/node_modules node incusos/helpers/aether-browser screenshot /logs/live ``` ### Tertiary: mitmproxy (TLS Interception) For capturing full request/response bodies, a mitmproxy reverse proxy can be deployed. This is complex due to mutual TLS and is only needed if the event stream doesn't provide enough detail. **Not implemented** — the event stream proved sufficient for understanding the API protocol. ## Analyzing Captures ```bash # Full analysis incusos/helpers/incus-mitm --analyze /tmp/incus-events.json # Show only Aether's calls incusos/helpers/incus-mitm --analyze /tmp/incus-events.json --filter aether # Identify callers by certificate incusos/helpers/incus-mitm --callers /tmp/incus-events.json ``` ## Certificate Fingerprints Each caller is identified by their TLS client certificate fingerprint. The event stream logs certificate matching, allowing us to identify who makes each API call. | Fingerprint Prefix | Subject | Role | |---|---|---| | `bb2dc9eac3d3...` | CN=maarten@dev-vm-beelink | CLI client | | `6cfd2a1949a7...` | CN=root@oc-server | Aether SDN controller | | `a68291ca3683...` | CN=root@oc-node-02 | Cluster inter-node | Aether's cert (`root@oc-server`) is the key one — it identifies all operations that Aether initiates against the Incus cluster. ## Captured API Patterns ### Instance Stop (CLI-initiated) ``` CLI → PUT /1.0/instances//state (body: {"action":"stop"}) CLI → GET /1.0/operations/ (poll for completion) Incus → operation: Stopping instance [Pending → Running → Success] Incus → lifecycle: instance-shutdown ``` ### Instance Start (CLI-initiated) ``` CLI → PUT /1.0/instances//state (body: {"action":"start"}) CLI → GET /1.0/operations/ Incus → operation: Starting instance [Pending → Running → Success] Incus → lifecycle: instance-started ``` ### Snapshot Create ``` CLI → POST /1.0/instances//snapshots (body: {"name":"snap-name"}) CLI → GET /1.0/operations/ Incus → operation: Snapshotting instance [Pending → Running → Success] Incus → lifecycle: instance-snapshot-created Aether → GET /1.0 (reacts to lifecycle event) Aether → GET /1.0/instances/?project=default&recursion=1 ``` ### Snapshot Delete ``` CLI → DELETE /1.0/instances//snapshots/ CLI → GET /1.0/operations/ Incus → operation: Deleting snapshot [Pending → Running → Success] Incus → lifecycle: instance-snapshot-deleted Aether → GET /1.0 (reacts to lifecycle event) Aether → GET /1.0/instances/?project=default&recursion=1 ``` ### Exec Command ``` CLI → POST /1.0/instances//exec (body: {"command":["hostname"],...}) Node → POST https://:8443/1.0/instances//exec (forwarded) CLI → GET /1.0/operations//websocket?secret= CLI → GET /1.0/operations//websocket?secret= CLI → GET /1.0/operations//websocket?secret= CLI → GET /1.0/operations//websocket?secret= CLI → GET /1.0/operations/ (poll completion) Incus → operation: Executing command [Pending → Running → Success] Incus → lifecycle: instance-exec ``` Note: exec uses 4 websocket connections (stdin, stdout, stderr, control). The operation is forwarded to the target node where the instance runs. ### Network List ``` CLI → GET /1.0/networks?filter=&recursion=1 ``` ### Instance Info ``` CLI → GET /1.0/instances/?recursion=1 ``` ## Key Findings ### 1. Aether Subscribes to Lifecycle Events Aether maintains a persistent connection to the Incus event stream. When any lifecycle event occurs (instance created, snapshot deleted, etc.), Aether immediately queries the affected resource. This is how Aether keeps its UI in sync with the cluster state. Pattern: `lifecycle event → Aether GET /1.0 → Aether GET /1.0/instances/` ### 2. Aether Uses Client Certificate Auth Aether authenticates to Incus using a TLS client certificate (`CN=root@oc-server`), not API tokens. This certificate is trusted by all cluster members. The certificate is stored in the Aether VM (likely at `/root/.config/incus/client.crt`). ### 3. Operations Are Forwarded When an operation targets an instance on a different node, the receiving node forwards the request internally. This is visible in the event stream as a `POST https://:8443/...` call from an "internal" caller. ### 4. Every CLI Command Starts with GET /1.0 The Incus CLI always fetches `/1.0` first (server info/capabilities) before making any other call. It also opens an `/1.0/events` websocket for real-time operation status updates. ### 5. Aether REST API Is Separate Aether's own API (`/api/auth/token`, `/api/clusters`, `/api/clusters/*/rules`) is completely separate from the Incus API. These calls go to Aether's HTTPS server (port 8443) and use JWT authentication. They are NOT visible in the Incus event stream. Current Aether API coverage: - `POST /api/auth/token` — JWT authentication - `GET /api/clusters` — list managed clusters - `CRUD /api/clusters/{id}/rules` — per-cluster ACL rules - `CRUD /api/global/rules` — global ACL rules All other Aether features (deploys, HAProxy, blueprints, instance management) are UI-only and use session-authenticated routes with CSRF protection. These features call the Incus API directly via Aether's server-side code, visible in the event stream as calls from the `root@oc-server` certificate. ## Reproducing the Capture ```bash # 1. Start capture in one terminal incusos/helpers/incus-mitm --live # 2. In another terminal, trigger operations: incus stop oc-node-01: incus start oc-node-01: incus snapshot create oc-node-01: test-snap incus snapshot delete oc-node-01: test-snap # 3. Or trigger via Aether UI: # - Deploy a container # - Create a blueprint # - Manage HAProxy # (All will appear in the event stream as Aether API calls) ```