better/compute
Access

Access

How people reach nodes and instances through tunnels with daemon-verified exec tickets, break-glass SSH certificates and allowlisted commands.

Sooner or later someone needs a shell. A training job hangs on one GPU, a VM does not boot its service, a node reports an XID and an engineer wants to look at dmesg. The usual answer, SSH keys copied onto every machine, leaves standing access that nobody reviews and nobody removes.

Better Compute gives no one standing access. Every interactive session is decided by IAM at the moment it opens, carries a credential that is bound to one machine and expires within minutes, and is checked again while it lasts. The that relays the bytes holds no key that could open a session on its own.

Three ways in

PathTargetDecided asCredentialLifetime
Tunnel, op exec, ssh, tcp or console (the serial console, 798e710)An instancecompute:instances:exec on the instanceAn exec ticket from tunnels.openTicket 60 s; tunnel at most 12 h
Tunnel, op consoleA node (a root shell)compute:nodes:console on the nodeAn exec ticket from tunnels.openTicket 60 s; tunnel at most 12 h
Break-glass SSHA nodecompute:nodes:ssh-root on the node, with an MFA sessionAn OpenSSH user certificate from nodes.issueSshCert15 min by default, 1 h at most
Allowlisted commandsA nodecompute:nodes:exec on the nodeNone: the daemon runs a registered command kindThe kind's timeout
  • Tunnels are the everyday path. The caller gets a short and opens a WebSocket with it; the API relays the stream to the gateway that holds the node's , and the node opens a Tunnel stream back. The browser terminal, an instance's serial console page and a node's console in the console use exactly this. bcd refuses exec into an instance until guests have an agent, so on a real host an instance's serial console is the terminal that works.
  • Break-glass SSH is for platform operators when the agent itself is the problem. It signs the caller's own public key with the compute SSH CA; nothing goes through the gateway.
  • Commands (commands.run) cover the repeatable jobs, such as a diagnostic or a log collection, without any shell at all. Each is registered and audited.

How a tunnel is trusted

Three processes check every exec ticket before a byte flows, and each keeps its own set of ticket ids, so a ticket opens one tunnel, once.

  • The api decides the caller, then signs an ES256 ticket for one node (aud = node:<id>), one tunnel (sid), one operation (op) and at most 60 seconds.
  • The gateway verifies the signature against the issuer's JWKS, the audience, the lifetime and the jti, then asks the node over its session.
  • The daemon verifies the ticket again against the JWKS pinned in its access section and its own jti set, and only then opens the stream.

The last check is the one that matters. PLAN.md's security model treats the gateway as relay-only: a compromised gateway can delay, drop or read frames, but it holds no signing key, so it cannot mint a ticket the daemon would accept. The gateway test suite covers exactly that case: a gateway handed an attacker's JWKS accepts a forged ticket, and a bcd-like test daemon holding the real JWKS refuses it, so no tunnel opens. bcd's own tests refuse replayed, expired, tampered and other-node tickets and a ticket whose sid is not the tunnel's.

Authorization

The actions live on the compute-node and compute-instance resource types (see actions and resources):

ActionResourceUsed by
compute:instances:execcompute-instance/<id>tunnels.open with an instanceId
compute:nodes:consolecompute-node/<id>tunnels.open with a nodeId
compute:nodes:ssh-rootcompute-node/<id>nodes.issueSshCert
compute:nodes:execcompute-node/<id>commands.run

A node must also be visible to the calling tenant through the authorization projection; otherwise the answer is NOT_FOUND, never a hint that the node exists.

PLAN.md §12.6 describes break-glass as an eligible binding: a policy that allows compute:nodes:console and compute:nodes:ssh-root only with MFA and only on platform-trusted nodes, bound so that people activate it when they need it. better-compute iam setup does not print this policy; write it yourself in the policy format:

break-glass (eligible binding)
{
  "name": "break-glass",
  "document": {
    "version": 1,
    "statements": [
      {
        "sid": "BreakGlass",
        "effect": "allow",
        "actions": ["compute:nodes:console", "compute:nodes:ssh-root"],
        "resources": ["compute-node/*"],
        "conditions": {
          "Bool": { "principal.mfa": true },
          "StringEquals": { "resource.trust": "platform" }
        }
      }
    ]
  }
}

The agent-guardrails example policy that iam setup does print denies compute:nodes:ssh-root to agent keys and to delegated sessions, so an AI agent never gets a root shell through a person's delegation.

Re-checks while a tunnel is open

The api re-authorizes the caller every 60 seconds. When the WebSocket upgrade carried a credential (an Authorization header or the IAM cookie), each re-check is an ordinary decision with it. Browsers cannot set headers on a WebSocket, so without one the re-check goes by the identity and session recorded in the ticket: the session must still be listed and unexpired, the identity active, and policies.simulate must allow the same action on the same target. Sessions of agents, delegations and assumed roles cannot be simulated exactly, so they answer false and the tunnel closes at its first re-check. A refusal closes the tunnel; IAM unreachable for 2 minutes closes it too.

Audit and events

  • tunnels.open, nodes.issueSshCert and commands.run are mutations, so each allowed call writes an audit_events row with its admission context: who, which action on which resource, and when. See audit.
  • tunnels.open publishes tunnel.issued (subject instance/<id> or node/<id>) with the tunnel id, node, op, identity and expiry. nodes.issueSshCert publishes node.ssh-cert.issued with the certificate's key id, serial, principals and expiry. Both reach webhooks and watch like any other event.
  • The end of a tunnel is reported to the host's onClose hook with the tunnel id, node, identity and reason (revoked, iam-unreachable, max-duration, caller-closed and so on). No audit row is written for it by default.

What ships today

Interactive access is the v0.3 roadmap item "tunnels with daemon-verified tickets, nodes.issueSshCert, the access section". Both halves are written and tested: the control plane, and the node half in the Go bcd (built, not released yet) and in the TypeScript FakeDaemon.

PieceState
tunnels.open, nodes.issueSshCertImplemented in @better-compute/server, registered when you configure tunnels
Exec tickets, the SSH CA, JWKS rotationImplemented in @better-compute/pki
GET /api/compute/tunnels/<ticket> relay, re-authorizationhandleTunnel, mounted by better-compute serve and by the console's server.mjs
Gateway relay (OpenTunnel, Tunnel stream, internal /v1/tunnels)Implemented in @better-compute/gateway
Browser terminal, instance serial console and node consoleapps/console with @better-compute/console-kit
NodeSpec access section (JWKS, SSH CA keys)Sent to every node
Daemon side: verifying tickets, opening Tunnel streams, running shells, applying access to sshdIn bcd (built and tested, not released): it re-verifies each ticket against the NodeSpec's JWKS and its journaled jti set, runs a node console on a real pseudo-terminal and pipes ssh and tcp to loopback ports (tests/e2e/bcd.test.ts opens a console end to end), and its access converger writes the sshd drop-in, CA, node-bound principals and KRL. exec into an instance waits for bcd's instances runtime. The TypeScript FakeDaemon, with its tunnels option (on in dev --sim), answers OpenTunnel with an echo shell, which the CLI's console and exec tests drive end to end; it still reports a non-empty access section as SectionUnsupported

The generated reference says planned

The API reference lists tunnels.open and nodes.issueSshCert as planned for v0.3, because it is generated from a control plane built without tunnels options. Both methods exist in the code and are served as soon as you pass tunnels to betterCompute.

In this section

Was this page helpful?

Better Compute is created by Sean Filimon

Last updated

On this page