Files

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/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:

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 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 <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.