6.0 KiB
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/healthverifies that the configured Docker Unix socket answers its bounded_pingrequest without exposing daemon details;POST /v1/check-diskaccepts at most 16 existing absolute paths and returns capacity only after symlink-aware allowed-root validation;GET /v1/instancesreturns only entries from the authenticated agent-local registry;POST /v1/check-portsreports only whether requested bindings are available;POST /v1/instancescreates a validated and registered container;GET /v1/instances/{id}and/statsinspect only a bound registration;- typed
start,stop,restartand containerDELETEroutes 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 recreationDeleteContainer(instance_id)without deleting host dataInspectInstance(instance_id)GetInstanceStats(instance_id)CheckPorts(bindings)without disclosing unrelated container detailsCheckDisk(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:
io.dogama.managed=true
io.dogama.instance-id=<opaque-id>
io.dogama.template-id=<template-id>
io.dogama.template-version=<version>
io.dogama.plan-digest=<sha256-of-canonical-plan>
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 internalio.dogama.*namespace, and are merged before immutable technical labels; - the optional Docker
Useris either an already validated numericUID:GIDvalue or omitted so the imageUSERapplies; - 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 <base64url-signature> 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:
DOGAMA-HMAC-V1
<HTTP method>
<escaped URL path>
<timestamp header>
<nonce header>
<lowercase SHA-256 hex digest of the body>
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.