Skip to content

The Docker socket proxy is reachable from every app container — measure how bad, then narrow it #430

Description

@onel

Size

M

Area

backend

Depends on

Topology fix is #187. This issue is the parts #187 does not cover: measuring how bad the exposure is today, narrowing the allowlist, and a wrong code comment.

Summary

Verify this before you build any of it. This was written by reading the tree from outside an os working copy, so it may be stale or may miss context the code carries. Step 1 of "Do" is to re-check every claim below against the current tree, on a real box, and decide whether the issue still makes sense. If it does not, say so and close it — that is a good outcome, not a failed task.

#187 records that app main_service containers join malmo-ingress, the network carrying Caddy's unauthenticated admin API, and prescribes the fix (apps off malmo-ingress; Caddy connects outward into each per-app network).

malmo-docker-proxy is on that same network — host-agent launches it there in EnsureTransport / proxyRunSpec (internal/hostagent/brainlaunch/brainlaunch.go). So the same app containers can also reach docker-proxy:2375, which has no authentication. #187's fix would close this as a side effect, but its body never mentions the proxy, so nobody weighing #187's priority is weighing this.

Three things follow, and only the first is about the network.

1. The severity is unknown and needs measuring. tecnativa/docker-socket-proxy is an HAProxy that allows or denies by URL prefix and method only — it never reads request bodies. The allowlist (proxyAllowlist()) is:

POST=1  PING=1  VERSION=1  INFO=1  CONTAINERS=1  IMAGES=1  NETWORKS=1  VOLUMES=1

So POST /containers/create is permitted by path, and Privileged: true / Binds: ["/:/host"] live in the body. Whether that reaches host root turns on whether the pinned v0.4.2 gates /containers/<id>/start behind a separate ALLOW_START flag, which is not in the allowlist.

  • If start is denied: the exposure is read access to every container, image, network and volume on the box, from any app. Cross-instance information disclosure. Bad, bounded.
  • If start is allowed: it is a container escape to host root from any store app, and THREAT_MODEL.md B2's first row ("Container escape to host root — mitigated") is false as written. That would also change how urgent Caddy connects outward to per-app networks; apps off malmo-ingress (admin-port isolation) #187 is.

Nobody should guess this. It is one curl from inside an app container.

2. The allowlist may be wider than the brain needs. VOLUMES, IMAGES and NETWORKS are all granted. Worth confirming each is really used.

3. A code comment describes a control that does not exist. Above proxyAllowlist(): "EXEC and host-bind mounts stay denied (the proxy defaults them off)". The EXEC half is right. The host-bind-mount half looks wrong, because the proxy never parses bodies, so there is no such default. CONTROL_PLANE.md line ~126 carries the same claim ("Dangerous endpoints (EXEC, arbitrary host mounts on POST /containers/create) are denied"). If that reading holds, both will reassure the next reader about a control that is not there.

Spec / source of truth

docs/specs/CONTROL_PLANE.md # Locked: Docker socket exposure. docs/specs/THREAT_MODEL.md B2 is where the result lands either way — as a closed threat or as a stated residual. Read #187 first; it owns the network change.

Do

  1. Settle the severity question first, on a real box, before touching code. From a shell inside an app container:
    curl -s http://docker-proxy:2375/version
    curl -s -X POST -H 'Content-Type: application/json' \
      -d '{"Image":"alpine","Cmd":["true"]}' http://docker-proxy:2375/containers/create
    
    then try POST /containers/<id>/start on whatever the second returns. Record the exact responses on this issue. That result is the most useful thing this task produces, and it should be posted even if nothing else here gets done. Clean up any container you create.
  2. Confirm the reachability premise — that app main services really do share a network with the proxy on the current tree.
  3. Feed the answer back into Caddy connects outward to per-app networks; apps off malmo-ingress (admin-port isolation) #187. If step 1 shows a host-root path, Caddy connects outward to per-app networks; apps off malmo-ingress (admin-port isolation) #187 stops being "admin-port isolation" and becomes the fix for a live escape; say so there and re-triage. Its blocked label also looks stale — it depends on M1b — Brain brings up the control-plane stack (Caddy + malmo-ui + socket-proxy) #165, which is closed.
  4. Narrow the allowlist to what the brain actually calls. Check the managed-DB one-shot path (internal/lifecycle/services.go) before dropping anything, since that is the heaviest Docker user.
  5. Fix the comment and the spec line if step 1 confirms they are wrong.
  6. Update THREAT_MODEL.md B2 with what you found.
  7. Add a regression test asserting an app container cannot reach docker-proxy:2375 — separate from Caddy connects outward to per-app networks; apps off malmo-ingress (admin-port isolation) #187's assertion about malmo-caddy:2019, so that removing one exposure cannot silently leave the other.

Touch

internal/hostagent/brainlaunch/brainlaunch.go, docs/specs/CONTROL_PLANE.md, docs/specs/THREAT_MODEL.md, test assertions alongside #187's.

Done when

The step-1 result is written on this issue, whatever it is. The allowlist matches what the brain calls. The comment and the spec line describe a control that exists. A test asserts an app container cannot reach docker-proxy:2375, and the brain still installs apps and provisions a managed database. If the investigation shows the premise is wrong, "done" is a comment explaining what is actually true, and the issue closed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions