Skip to content

openbloxd is reachable only over a Unix socket, so the daemon cannot run on its own host #32

Description

@Lutherwaves

Problem

openbloxd listens on a Unix socket and only a Unix socket — internal/daemon/listener.go calls net.Listen("unix", socketPath) with no other option. That means the daemon and its caller must share a host.

Sharing a host is often the wrong arrangement. gVisor contains escape, not contention: it is explicitly not a hypervisor, and sandboxes compete for CPU, memory bandwidth and disk IO with whatever else runs beside them. When the neighbour is a database or anything else with an availability requirement, the right answer is to move the sandboxes to their own machine — and today that is simply not supported, because the caller can no longer reach the daemon.

That is a real constraint on how openblox can be deployed, not a missing convenience. A deployment either accepts untrusted code contending with its other workloads, or it does not run openbloxd at all.

Why this is not just "add a TCP listener"

The socket is doing more work than transport. Listen(socketPath, group) chmods the socket to the given group, and that group membership is the entire access control list — the filesystem performs the authentication. The daemon itself has no notion of a caller: there is no authentication, no authorization, and no caller identity of any kind (see #28, and SO_PEERCRED is still unimplemented).

Bind the same handler to a TCP port and every one of those properties is gone at once. What is left is an unauthenticated remote sandbox-creation API — which inverts the daemon's entire purpose. openbloxd exists so that a compromised caller gains sandboxes rather than the host; a network listener without authentication hands sandboxes to anyone who can route to the port, and a sandbox is a foothold on the daemon's host.

So the listener is the small half. The authentication model is the issue.

What this needs

  1. A caller credential the daemon verifies. mTLS is the obvious candidate: it authenticates both directions, needs no shared secret at rest on the caller, and a client certificate is a caller identity — the thing Cap concurrent sandboxes per profile #28 noted the daemon does not have. A pre-shared token is simpler and weaker; worth considering only if certificate distribution is judged too costly for the deployments in question.
  2. Identity carried into the request path, so it can later bind to per-caller quotas and audit. Even if nothing consumes it at first, a transport that discards who the caller was will have to be revisited to add it.
  3. Transport-independent policy. Everything that could weaken isolation must stay unreachable from a request regardless of how the request arrived. Nothing about a remote caller may relax that, and it should be a test rather than a convention.
  4. Bind address as explicit configuration, never a default. A daemon that starts listening on a network interface because a key was omitted is the failure this issue is trying to avoid. The Unix socket stays the default and the recommended arrangement where caller and daemon do share a host.
  5. A documented threat model for the remote case, in docs/security.md, stating plainly what the network boundary is assumed to provide and what it is not. "It is on a private network" is a real mitigation and a poor sole control.

Out of scope

  • Per-caller quotas. Identity is a prerequisite for those and this issue should supply it, but the quota logic itself is separable (Cap concurrent sandboxes per profile #28 covers the per-profile bound that needs no identity at all).
  • Multi-daemon fan-out / scheduling across hosts. One caller, one daemon, over a network instead of a socket. Choosing between several daemons is a different problem and should not be smuggled in here.
  • Replacing the Unix socket. It stays, it stays the default, and same-host deployments should be unaffected.

Note on ordering

The per-profile cap in #28 becomes more load-bearing once this exists, not less: a remote caller is by definition less coupled to the host it is filling, so nothing about its own health signals that the daemon's machine is running out of memory.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions