# Restricted Docker agent ## Security objective The agent reduces the chance that an application bug becomes arbitrary Docker control. Because Docker socket access is effectively host-level privilege, the agent is small, independently testable and deny-by-default. ## Private API foundation The same-host V1 agent listens on the private control network only. The current foundation exposes authenticated health, capacity and typed lifecycle routes: - `GET /v1/health` verifies that the configured Docker Unix socket answers its bounded `_ping` request without exposing daemon details; - `POST /v1/check-disk` accepts at most 16 existing absolute paths and returns capacity only after symlink-aware allowed-root validation; - `GET /v1/instances` returns only entries from the authenticated agent-local registry; - `POST /v1/check-ports` reports only whether requested bindings are available; - `POST /v1/instances` creates a validated and registered container; - `GET /v1/instances/{id}` and `/stats` inspect only a bound registration; - typed `start`, `stop`, `restart` and container `DELETE` routes operate only on that registered identity. Every route, including health, requires request authentication. The agent never exposes its internal Docker HTTP client as a proxy. Lifecycle requests are idempotent where meaningful and re-inspect the container identity and binding labels before mutation. ## Allowed V1 operations - `CreateInstance(plan)` - `StartInstance(instance_id)` - `StopInstance(instance_id, timeout)` - `RestartInstance(instance_id, timeout)` - `ReplaceInstance(plan, expected_revision)` for configuration/update recreation - `DeleteContainer(instance_id)` without deleting host data - `InspectInstance(instance_id)` - `GetInstanceStats(instance_id)` - `CheckPorts(bindings)` without disclosing unrelated container details - `CheckDisk(paths)` - `ListRegisteredInstances()` only There is no generic Docker request, arbitrary command, arbitrary `exec`, arbitrary container ID, image-build endpoint, host filesystem browser or list-all-containers endpoint. ## Registration binding Each managed container has labels such as: ```text io.dogama.managed=true io.dogama.instance-id= io.dogama.template-id= io.dogama.template-version= io.dogama.plan-digest= ``` Labels alone are insufficient. The agent keeps a durable registry of instance ID, expected container identity and plan digest, authenticated by an agent-local key or MAC. An operation succeeds only when request, registry and inspected labels agree. ## Deployment-plan validation Before create or replace, the agent verifies: - instance and template identifiers have valid syntax; - image reference matches the canonical approved plan and preferably a resolved digest; - command, entrypoint, capabilities, devices and security options exactly match allowed template fields; - no privileged mode, host PID/IPC/network namespace, device mount or Docker socket mount; - every bind source resolves below an allowed root after symlink-aware canonicalization; - every container destination is declared by the template; - port protocols and container ports match the template and host ports do not conflict; - resource limits are present and within administrator limits; - labels use the reserved namespace and cannot be overridden; - custom labels are bounded, may not use either `dogama.*` or the internal `io.dogama.*` namespace, and are merged before immutable technical labels; - the optional Docker `User` is either an already validated numeric `UID:GID` value or omitted so the image `USER` applies; - only approved DoGaMa networks are attached. The canonical plan digest alone is not treated as approval. The agent embeds and validates the same catalog, then independently compares every privileged field to the pinned immutable template snapshot before checking paths or pulling an image. A caller cannot make a substituted image or mount valid merely by recomputing a digest. V1 templates do not expose arbitrary Docker security options. The agent applies a secure fixed baseline: no-new-privileges where compatible, dropped capabilities by default, bounded PIDs and a non-host network mode. ## Authentication and replay defense Requests use a shared secret read from a Docker secret file. Sign method, path, body digest, timestamp and nonce. Reject clock-skewed or reused nonces. Use constant-time comparison, small body limits and short timeouts. Rotate the token through an explicit maintenance workflow. The wire format is `DoGaMa-HMAC-SHA256 ` in the `Authorization` header, with `X-DoGaMa-Timestamp` in RFC 3339 and a random base64url `X-DoGaMa-Nonce`. The HMAC-SHA-256 input is the following exact newline-separated canonical value: ```text DOGAMA-HMAC-V1 ``` Queries are rejected. The accepted clock skew is 30 seconds, a nonce is valid once only, and request bodies are limited to 64 KiB before dispatch. The agent returns stable JSON problem codes and never includes secrets, socket paths or raw Docker errors. The agent listens only on the internal control network and publishes no host port. Authentication remains mandatory even on that network. ## Failure semantics - Validate the full plan before pulling or mutating anything. - Return typed, non-sensitive errors. - Create with a deterministic name only after registration intent is persisted. - On partial create, remove the incomplete container but never delete bind-mounted data. - On replace failure, retain or recreate the prior container plan when safe; otherwise leave the instance stopped and report intervention required. - Agent startup reconciles its registry with Docker but never adopts unknown containers automatically. ## Testing focus Negative integration tests must cover forged labels, unknown IDs, path traversal, symlink escape, reserved-label overrides, host networking, privileged flags, extra mounts, image substitution, conflicting ports, replayed requests and attempts to target unrelated containers.