feat(sandbox): TLS + service CA + mTLS agent enrollment

BN
2d ago
+3734-623

Stacked on #619. One auth model for every deployment.

  • Always TLS. sandbox serve and the engine's listener serve TLS only; the loopback/no-token exemption is gone. The in-process InProcess path is unchanged.
  • Service CA. On first start the service creates <state>/sandbox-ca/{ca.pem,ca.key} (0600) and issues its server certificate with SANs from PRELOOP_SANDBOX_TLS_SANS (default hostname, localhost, 127.0.0.1, ::1); PRELOOP_SANDBOX_TLS_CERT / _KEY override it. GET /sandbox/v1/ca.pem is public for pin verification.
  • Callers keep one bearer token (tenancy deferred), now always required: PRELOOP_SANDBOX_TOKEN, or a generated <state>/sandbox-client-token. The CLI finds the token (--token/env/file) and CA (--ca/PRELOOP_SANDBOX_CA/local) automatically.
  • Join tokens. preloop sandbox token create [--ttl 1h] → plj1.<id>.<secret>.<ca_sha256>; only a hash is stored (join_tokens, SQLite + Postgres migrations); single use, expiring.
  • Enrollment. preloop sandbox agent --join https://… --token plj1… checks the server CA against the token's pin, then Providers.Enroll signs a 24 h client certificate from the agent's CSR and binds it to the provider name. The identity lives in <home>/state/sandbox-identity/ (0600) and is reused on later runs.
  • mTLS on Providers. Every Providers RPC except Enroll (and the agent blob endpoints) requires an enrolled client certificate chaining to the service CA; identity is the leaf fingerprint. Register only succeeds for the certificate's own name. PRELOOP_SANDBOX_PROVIDER_TOKEN is removed.
  • Renewal / revocation. Agents renew after 2/3 of the lifetime (Providers.RenewCertificate, old fingerprint valid until expiry) and hot-swap their identity. preloop sandbox provider rm <name> revokes.

Tests (tests/mtls.rs + units): enrollment, single-use and expired tokens, wrong CA pin (both sides), name mismatch, no client cert, renewal, revocation, caller token required; conformance's remote-agent test now runs over TLS with enrollment.
Lockfile: first-party edges only (hostname, rand, rcgen, time on preloop-sandbox; all already locked).


Summary by cubic

Applies one credential model to every Sandbox Service deployment: TLS transport, a caller bearer token, and mTLS provider identities obtained through join-token enrollment. Removes the loopback/no-token exemption and the shared PRELOOP_SANDBOX_PROVIDER_TOKEN.

TLS and caller auth

  • sandbox serve and the engine listener serve TLS only; the in-process InProcess path is unchanged.
  • The service creates a CA and server certificate under <state>/sandbox-ca/; only ca.key is mode 0600, ca.pem is public, and PRELOOP_SANDBOX_TLS_CERT/_KEY and _SANS override the server cert.
  • Callers always need a bearer token (PRELOOP_SANDBOX_TOKEN or a generated <state>/sandbox-client-token); the CLI discovers the token and CA automatically.

Provider enrollment and mTLS

  • preloop sandbox token create [--ttl] mints single-use, expiring plj1.<id>.<secret>.<ca_sha256> tokens; the service stores only a hash.
  • preloop sandbox agent --join verifies the server CA against the token's pin, then Enroll signs a 24h client certificate, bound to the provider name and persisted under <home>/state/sandbox-identity/.
  • Every Providers RPC except Enroll requires an enrolled client certificate; Register only succeeds for the certificate's own name, so existing providers must be re-enrolled.
  • Agents renew at 2/3 of the lifetime and hot-swap identity; preloop sandbox provider rm <name> revokes.
  • Certificates carry Subject and Authority Key Identifiers, basicConstraints, and matching EKUs so OpenSSL 3 verifiers accept the chain; an agent without --join reuses its stored service URL, and a re-keyed CA surfaces a re-enroll error with 30s backoff instead of a handshake retry loop.
  • Provider host operations (probes, boots, transfers, host-tool sweeps) run under shared deadline bounds, so a wedged smolvm child or host tool fails instead of hanging.

Summary by CodeRabbit

  • New Features
    • Sandbox services now use HTTPS, with configurable certificate authorities and secure provider enrollment using single-use join tokens.
    • Providers authenticate with certificates that can be renewed; agents can reuse their saved identity when reconnecting.
    • Added commands to create join tokens and list or remove providers.
  • Improvements
    • Rejected TLS handshakes and RPC registrations are logged. Authentication and permission failures trigger a longer retry delay, with guidance to re-enroll when needed.