carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1// The sidecar pause: an operator allowlist in front of the two doors that
2// bring a native sidecar into being.
3//
4// Why it exists: a native sidecar runs on the flat tier
5// (activator.ExecBackend.StartWorkerStaged) as the box's carlos uid with no
6// mount namespace, so on its box it can read and write every nested
7// tenant's /data/instances/<iid>. The fix is nested sidecars (amadan
8// discussion #370). Until that lands, an operator lists the accounts that
9// may still enable or create one (CARLOS_SIDECAR_ACCOUNTS) and every other
10// account is refused.
11//
12// The doors, and why there are only two. A sidecar runs only from a record
13// at apps/<sqid>/<app>/sidecars/<name>.json, which the box's sidecar
14// reconcile pass (or, under host claim, the root claim path) turns into a
15// Kind "sidecar" route, and it takes its argv and ceilings from
16// apps/<sqid>/<app>/sidecars.json. The console writes the record in exactly
17// one place (handleAPISidecarCreate, both the CreateSidecarRecord and the
18// host-claim beginClaimCreate arms) and the manifest in exactly one place
19// (handleAPISidecarEnable). Both call refuseSidecarsPaused. Ship and promote
20// publish releases and channel pointers, never a sidecar manifest; restore
21// clears an app's trash marker and brings back only sidecars that already
22// existed; carlos run needs a nested instance. Store mode (`carlos sidecars
23// enable` against the bucket) is the operator's own bucket credential and
24// is not a console door.
25//
26// What it leaves alone, on purpose: list, get, wake and delete. Existing
27// sidecars keep running, and an account taken off the list must still be
28// able to retire its own.
29package console
30
31import (
32 "log"
33 "net/http"
34 "slices"
35)
36
37// sidecarsPausedMsg is the one sentence a refused account reads.
38// copy: approved by Paul 2026-10-24 (copy review).
39const sidecarsPausedMsg = "sidecars are paused for this account. existing sidecars keep running, and you can still delete them"
40
41// sidecarsPausedCode is the refusal's machine-readable code, beside the
42// sentence the way plan caps carry "cap".
43const sidecarsPausedCode = "sidecars-paused"
44
45// sidecarsPaused reports whether acct is refused new sidecars: the
46// allowlist is set and does not name the account's sqid.
47func (s *Server) sidecarsPaused(acct *Account) bool {
48 return len(s.cfg.SidecarAccounts) > 0 && !slices.Contains(s.cfg.SidecarAccounts, acct.Sqid)
49}
50
51// refuseSidecarsPaused answers 403 sidecars-paused and reports true when
52// acct may not enable or create a sidecar. Call it AFTER the door's
53// authentication, membership and validation checks, so a non-member still
54// gets the byte-identical 404 and the gate never confirms that an account
55// exists, and BEFORE any bucket write. Request-path refusals are not
56// audited in this console (the plan cap's is not either), so it logs.
57func (s *Server) refuseSidecarsPaused(w http.ResponseWriter, acct *Account, app, verb string) bool {
58 if !s.sidecarsPaused(acct) {
59 return false
60 }
61 log.Printf("console: api sidecars %s %s/%s: refused, account not in CARLOS_SIDECAR_ACCOUNTS", verb, acct.Sqid, app)
62 writeJSON(w, http.StatusForbidden, map[string]any{"error": sidecarsPausedMsg, "code": sidecarsPausedCode})
63 return true
64}
65