Skip to content

docs: define the socket trust model; drop per-caller framing - #50

Merged
abienkowski merged 1 commit into
mainfrom
docs/trust-model-39
Oct 7, 2026
Merged

abienkowski merged 1 commit into
mainfrom
docs/trust-model-39

Conversation

@abienkowski

Copy link
Copy Markdown
Collaborator

Description

Closes #39.

#39 points out that the README promises "per-service policies", but the proxy never learns who is calling. It picks a policy from the request's Image, so any caller of a socket can use any policy behind it. This PR settles the question as a documented decision, with no code changes: the listening socket is the trust boundary.

Why not authenticate callers (option 2 in #39)

The deployment this proxy is built for gives external developers access to a host through one shared account. Peer credentials (SO_PEERCRED) and socket-group permissions both identify a Unix account, and every developer shares the same one. No proxy feature can tell apart people whom the host's own access model does not tell apart. Option 2 would also have left a second gap like #46 in TypeScript, because Node cannot read peer credentials without a native addon.

What the docs now say

  • One trust domain, many services: many policies behind one socket is a normal layout. The callers' privileges are the union of the policies. Image-based selection is how a request is routed to a template, not an access check.
  • Several trust domains on one host: run one instance per domain, each with its own socket, group and --config-dir. The kernel enforces the separation. This works today with no code, because Listening socket: dockerd parity, Unix path only (drop fd:// and --listen-socket-mode, add single-instance lock) #45 gave every instance its own group-owned socket and lock.

Changes

Rejected alternatives

  • Peer-credential authentication. Under a shared account it identifies no one, and TypeScript would have a gap.
  • Several sockets in one process. Running one instance per domain gives the same kernel-enforced separation with no new config schema.
  • A startup warning when several policies are loaded. That is a legitimate setup, so the warning would be noise.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Implementation(s) changed

  • Go
  • Rust
  • TypeScript
  • Quint specification (README only; no model change)
  • CI / infrastructure

Testing

Documentation only. make lint-all passes.

The systemd template unit was run under real systemd (Debian 12 container, systemd as PID 1, a Go binary built from main), with two domains, teama and teamb:

$ systemctl is-active docker-socket-policy@teama docker-socket-policy@teamb
active
active
/run/docker-socket-policy-teama:
srw-rw----  1 docker-socket-policy dsp-teama  0 docker-socket-policy.sock
-rw-------  1 docker-socket-policy dsp-teama  0 docker-socket-policy.sock.lock
/run/docker-socket-policy-teamb:
srw-rw----  1 docker-socket-policy dsp-teamb  0 docker-socket-policy.sock
-rw-------  1 docker-socket-policy dsp-teamb  0 docker-socket-policy.sock.lock

A connect test was run as each domain's shared account. The 502 comes from the proxy answering /_ping; the test container had no Docker daemon behind it.

shareda  -> teama  connected -> HTTP/1.0 502 Bad Gateway
shareda  -> teamb  refused  -> Permission denied
sharedb  -> teama  refused  -> Permission denied
sharedb  -> teamb  connected -> HTTP/1.0 502 Bad Gateway

This confirms two things. systemd expands %i in Group= and RuntimeDirectory=, which its man page does not state for those settings. And the kernel refuses cross-domain connections, as the README says.

  • Unit tests pass (make test-all): not applicable, no code changes
  • Integration tests pass (make test-integration): not applicable
  • Quint verification passes (make verify): not applicable, model unchanged
  • New tests added for the change

Checklist

  • I have read CONTRIBUTING.md
  • My code follows the project's coding style
  • I have updated documentation as needed

@abienkowski abienkowski added the Type: Documentation Added to issues or PRs that relate to the project wiki, or documentation. label Oct 4, 2026
@abienkowski abienkowski self-assigned this Oct 7, 2026
The proxy selects a policy by the request's image and never identifies the
caller. Document that as the supported model: the listening socket is the
trust boundary, every caller of a socket can use every policy behind it, and
separate trust domains run as separate instances with their own socket group
(systemd template unit, verified under real systemd). Record in spec/README.md
that the Quint model has no caller by design.
@abienkowski
abienkowski merged commit 8865134 into main Oct 7, 2026
6 checks passed
@abienkowski
abienkowski deleted the docs/trust-model-39 branch October 7, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Documentation Added to issues or PRs that relate to the project wiki, or documentation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Policy is selected from the request body, not the caller: "per-service" enforcement has no caller identity

1 participant