You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(distributed): enforce registration token for worker file transfer (#10183)
The worker HTTP file-transfer server is authenticated by the registration
token via checkBearerToken, which fails open on an empty token: every
/v1/files, /v1/files-list and /v1/backend-logs request is then served
unauthenticated, granting read/write to the worker's models/staging/data
directories. The fail-open was also silent (the only auth log sat on the
unreachable reject branch), and the worker process never runs
DistributedConfig.Validate(), so the existing frontend warning did not
cover the component that exposes the server.
Mirror the NatsRequireAuth pattern: keep anonymous as the default but make
it loud and opt-in enforceable.
- Log a prominent warning when the file-transfer server starts tokenless.
- Add LOCALAI_REGISTRATION_REQUIRE_AUTH: DistributedConfig.Validate() errors
on an empty token (frontend) and the worker refuses to start (fail-fast,
before registration), so production can fail closed. Also satisfies the
F-003 suggestion to fail Validate() on distributed + empty token.
- Add LOCALAI_DISTRIBUTED_REQUIRE_AUTH umbrella switch implying both
RegistrationRequireAuth and NatsRequireAuth — one production knob locking
down the registration/file-transfer layer and the NATS bus together; the
granular flags remain available as single-layer overrides. Wired into the
frontend, supervisor worker, and agent worker (vLLM worker has neither a
NATS connection nor a file-transfer server, so it is left untouched).
- Document in distributed-mode.md (warning callout + flag tables).
Assisted-by: Claude:claude-opus-4-8 [Claude Code]
Signed-off-by: Richard Palethorpe <io@richiejp.com>
RegistrationTokenstring`env:"LOCALAI_REGISTRATION_TOKEN" help:"Token that backend nodes must provide to register (empty = no auth required)" group:"distributed"`
157
+
RegistrationRequireAuthbool`env:"LOCALAI_REGISTRATION_REQUIRE_AUTH" default:"false" help:"Fail startup when distributed mode is enabled but LOCALAI_REGISTRATION_TOKEN is empty (node endpoints and worker file-transfer server would otherwise be unauthenticated)" group:"distributed"`
158
+
DistributedRequireAuthbool`env:"LOCALAI_DISTRIBUTED_REQUIRE_AUTH" default:"false" help:"Umbrella switch: require BOTH NATS JWT credentials and a registration token when distributed mode is enabled (implies --nats-require-auth and --registration-require-auth)" group:"distributed"`
157
159
AutoApproveNodesbool`env:"LOCALAI_AUTO_APPROVE_NODES" default:"false" help:"Auto-approve new worker nodes (skip admin approval)" group:"distributed"`
158
160
DistributedPrefixCachebool`env:"LOCALAI_DISTRIBUTED_PREFIX_CACHE" default:"true" help:"Enable prefix-cache-aware routing in distributed mode (default true). When false, routing falls back to round-robin." group:"distributed"`
159
161
DistributedPrefixCacheTTLstring`env:"LOCALAI_DISTRIBUTED_PREFIX_CACHE_TTL" help:"Idle-timeout for prefix-cache index entries; also drives the background eviction cadence (every TTL/2). Default 5m." group:"distributed"`
returnfmt.Errorf("storage-access-key and storage-secret-key must both be set or both empty")
90
102
}
91
-
// Warn about missing registration token (not an error)
103
+
// The registration token guards both the node HTTP register/heartbeat
104
+
// endpoints and the worker file-transfer server (which fails open on an
105
+
// empty token). Enforce it when registration auth is required (the granular
106
+
// flag or the umbrella); otherwise warn.
92
107
ifc.RegistrationToken=="" {
93
-
xlog.Warn("distributed mode running without registration token — node endpoints are unprotected")
108
+
ifc.RegistrationAuthRequired() {
109
+
returnfmt.Errorf("registration auth is required (LOCALAI_REGISTRATION_REQUIRE_AUTH or LOCALAI_DISTRIBUTED_REQUIRE_AUTH) but LOCALAI_REGISTRATION_TOKEN is empty")
110
+
}
111
+
xlog.Warn("distributed mode running without registration token — node endpoints and the worker file-transfer server are unprotected; set LOCALAI_REGISTRATION_TOKEN, or LOCALAI_DISTRIBUTED_REQUIRE_AUTH=true to fail closed")
94
112
}
95
113
iferr:=c.NatsAuthConfig().Validate(); err!=nil {
96
114
returnerr
@@ -170,6 +188,30 @@ var EnableNatsRequireAuth = func(o *ApplicationConfig) {
170
188
o.Distributed.NatsRequireAuth=true
171
189
}
172
190
191
+
// EnableRegistrationRequireAuth makes an empty registration token a hard error
192
+
// in distributed mode (see DistributedConfig.RegistrationRequireAuth).
returnnil, fmt.Errorf("creating staging dir %s: %w", stagingDir, err)
62
62
}
63
63
64
+
// An empty token makes checkBearerToken fail open: every /v1/files,
65
+
// /v1/files-list and /v1/backend-logs request is served unauthenticated,
66
+
// granting read/write to the staging/models/data directories to anyone who
67
+
// can reach this port. Surface that loudly — the worker process does not
68
+
// run DistributedConfig.Validate(), so this is the only signal an operator
69
+
// gets. Set LOCALAI_REGISTRATION_TOKEN (and LOCALAI_REGISTRATION_REQUIRE_AUTH
70
+
// to fail closed) to protect it.
71
+
iftoken=="" {
72
+
xlog.Warn("HTTP file transfer server starting WITHOUT a registration token — read/write to models/staging/data is unauthenticated for anyone who can reach this port; set LOCALAI_REGISTRATION_TOKEN")
Copy file name to clipboardExpand all lines: core/services/worker/config.go
+20-6Lines changed: 20 additions & 6 deletions
Original file line number
Diff line number
Diff line change
@@ -44,12 +44,14 @@ type Config struct {
44
44
AdvertiseHTTPAddrstring`env:"LOCALAI_ADVERTISE_HTTP_ADDR" help:"HTTP address the frontend uses to reach this node for file transfer" group:"server" hidden:""`
45
45
46
46
// Registration (required)
47
-
AdvertiseAddrstring`env:"LOCALAI_ADVERTISE_ADDR" help:"Address the frontend uses to reach this node (defaults to hostname:port from Addr)" group:"registration" hidden:""`
48
-
RegisterTostring`env:"LOCALAI_REGISTER_TO" required:"" help:"Frontend URL for registration" group:"registration"`
49
-
NodeNamestring`env:"LOCALAI_NODE_NAME" help:"Node name for registration (defaults to hostname)" group:"registration"`
50
-
RegistrationTokenstring`env:"LOCALAI_REGISTRATION_TOKEN" help:"Token for authenticating with the frontend" group:"registration"`
51
-
HeartbeatIntervalstring`env:"LOCALAI_HEARTBEAT_INTERVAL" default:"10s" help:"Interval between heartbeats" group:"registration"`
52
-
NodeLabelsstring`env:"LOCALAI_NODE_LABELS" help:"Comma-separated key=value labels for this node (e.g. tier=fast,gpu=a100)" group:"registration"`
47
+
AdvertiseAddrstring`env:"LOCALAI_ADVERTISE_ADDR" help:"Address the frontend uses to reach this node (defaults to hostname:port from Addr)" group:"registration" hidden:""`
48
+
RegisterTostring`env:"LOCALAI_REGISTER_TO" required:"" help:"Frontend URL for registration" group:"registration"`
49
+
NodeNamestring`env:"LOCALAI_NODE_NAME" help:"Node name for registration (defaults to hostname)" group:"registration"`
50
+
RegistrationTokenstring`env:"LOCALAI_REGISTRATION_TOKEN" help:"Token for authenticating with the frontend" group:"registration"`
51
+
RegistrationRequireAuthbool`env:"LOCALAI_REGISTRATION_REQUIRE_AUTH" default:"false" help:"Refuse to start the HTTP file-transfer server when no registration token is set (otherwise it fails open and serves read/write to models/staging/data unauthenticated)" group:"registration"`
52
+
DistributedRequireAuthbool`env:"LOCALAI_DISTRIBUTED_REQUIRE_AUTH" default:"false" help:"Umbrella switch implying both --nats-require-auth and --registration-require-auth" group:"distributed"`
53
+
HeartbeatIntervalstring`env:"LOCALAI_HEARTBEAT_INTERVAL" default:"10s" help:"Interval between heartbeats" group:"registration"`
54
+
NodeLabelsstring`env:"LOCALAI_NODE_LABELS" help:"Comma-separated key=value labels for this node (e.g. tier=fast,gpu=a100)" group:"registration"`
53
55
// MaxReplicasPerModel caps how many replicas of any one model can run on
54
56
// this worker concurrently. Default 1 = historical single-replica
55
57
// behavior. Set higher when a node has enough VRAM to host multiple
0 commit comments