Skip to content

Commit 7338571

Browse files
authored
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>
1 parent a4e6717 commit 7338571

10 files changed

Lines changed: 260 additions & 18 deletions

File tree

core/cli/agent_worker.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,23 @@ type AgentWorkerCMD struct {
5757
NatsServiceJWT string `env:"LOCALAI_NATS_SERVICE_JWT" help:"Fallback NATS service JWT when registration does not mint agent JWT" group:"distributed"`
5858
NatsServiceSeed string `env:"LOCALAI_NATS_SERVICE_SEED" help:"Fallback NATS service seed paired with LOCALAI_NATS_SERVICE_JWT" group:"distributed"`
5959
NatsRequireAuth bool `env:"LOCALAI_NATS_REQUIRE_AUTH" default:"false" help:"Require NATS JWT+seed to connect" group:"distributed"`
60-
NatsTLSCA string `env:"LOCALAI_NATS_TLS_CA" type:"existingfile" help:"PEM file for NATS server CA (private PKI)" group:"distributed"`
61-
NatsTLSCert string `env:"LOCALAI_NATS_TLS_CERT" type:"existingfile" help:"Client certificate for NATS mTLS" group:"distributed"`
62-
NatsTLSKey string `env:"LOCALAI_NATS_TLS_KEY" type:"existingfile" help:"Client private key for NATS mTLS" group:"distributed"`
60+
// DistributedRequireAuth is the umbrella switch; for the agent worker (which
61+
// has no file-transfer server) it implies NATS auth is required.
62+
DistributedRequireAuth bool `env:"LOCALAI_DISTRIBUTED_REQUIRE_AUTH" default:"false" help:"Umbrella switch implying --nats-require-auth (agent workers have no file-transfer server)" group:"distributed"`
63+
NatsTLSCA string `env:"LOCALAI_NATS_TLS_CA" type:"existingfile" help:"PEM file for NATS server CA (private PKI)" group:"distributed"`
64+
NatsTLSCert string `env:"LOCALAI_NATS_TLS_CERT" type:"existingfile" help:"Client certificate for NATS mTLS" group:"distributed"`
65+
NatsTLSKey string `env:"LOCALAI_NATS_TLS_KEY" type:"existingfile" help:"Client private key for NATS mTLS" group:"distributed"`
6366

6467
// Timeouts
6568
MCPCIJobTimeout string `env:"LOCALAI_MCP_CI_JOB_TIMEOUT" default:"10m" help:"Timeout for MCP CI job execution" group:"distributed"`
6669
}
6770

71+
// natsAuthRequired reports whether NATS JWT credentials must be present — the
72+
// granular flag or the umbrella (LOCALAI_DISTRIBUTED_REQUIRE_AUTH).
73+
func (cmd *AgentWorkerCMD) natsAuthRequired() bool {
74+
return cmd.NatsRequireAuth || cmd.DistributedRequireAuth
75+
}
76+
6877
func (cmd *AgentWorkerCMD) Run(ctx *cliContext.Context) error {
6978
xlog.Info("Starting agent worker", "nats", sanitize.URL(cmd.NatsURL), "register_to", cmd.RegisterTo)
7079

@@ -102,7 +111,7 @@ func (cmd *AgentWorkerCMD) Run(ctx *cliContext.Context) error {
102111
func(ctx context.Context) (*workerregistry.RegisterResponse, error) {
103112
return regClient.RegisterFull(ctx, registrationBody)
104113
},
105-
cmd.NatsRequireAuth && cmd.NatsJWT == "" && cmd.NatsServiceJWT == "",
114+
cmd.natsAuthRequired() && cmd.NatsJWT == "" && cmd.NatsServiceJWT == "",
106115
)
107116
res, err := credMgr.Acquire(shutdownCtx)
108117
if err != nil {
@@ -149,7 +158,7 @@ func (cmd *AgentWorkerCMD) Run(ctx *cliContext.Context) error {
149158
return fmt.Errorf("LOCALAI_NATS_SERVICE_JWT and LOCALAI_NATS_SERVICE_SEED must be set together")
150159
}
151160
natsOpts = append(natsOpts, messaging.WithUserJWT(cmd.NatsServiceJWT, cmd.NatsServiceSeed))
152-
case cmd.NatsRequireAuth:
161+
case cmd.natsAuthRequired():
153162
return fmt.Errorf("NATS JWT+seed required: enable frontend minting or set LOCALAI_NATS_* env vars")
154163
}
155164
if natsTLS.Enabled() {

core/cli/run.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ type RunCMD struct {
154154
StorageAccessKey string `env:"LOCALAI_STORAGE_ACCESS_KEY" help:"S3 access key ID" group:"distributed"`
155155
StorageSecretKey string `env:"LOCALAI_STORAGE_SECRET_KEY" help:"S3 secret access key" group:"distributed"`
156156
RegistrationToken string `env:"LOCALAI_REGISTRATION_TOKEN" help:"Token that backend nodes must provide to register (empty = no auth required)" group:"distributed"`
157+
RegistrationRequireAuth bool `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+
DistributedRequireAuth bool `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"`
157159
AutoApproveNodes bool `env:"LOCALAI_AUTO_APPROVE_NODES" default:"false" help:"Auto-approve new worker nodes (skip admin approval)" group:"distributed"`
158160
DistributedPrefixCache bool `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"`
159161
DistributedPrefixCacheTTL string `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"`
@@ -291,6 +293,12 @@ func (r *RunCMD) Run(ctx *cliContext.Context) error {
291293
if r.RegistrationToken != "" {
292294
opts = append(opts, config.WithRegistrationToken(r.RegistrationToken))
293295
}
296+
if r.RegistrationRequireAuth {
297+
opts = append(opts, config.EnableRegistrationRequireAuth)
298+
}
299+
if r.DistributedRequireAuth {
300+
opts = append(opts, config.EnableDistributedRequireAuth)
301+
}
294302
if r.NatsAccountSeed != "" {
295303
opts = append(opts, config.WithNatsAccountSeed(r.NatsAccountSeed))
296304
}

core/config/distributed_config.go

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,19 @@ type DistributedConfig struct {
1818
NatsURL string // --nats-url / LOCALAI_NATS_URL
1919
StorageURL string // --storage-url / LOCALAI_STORAGE_URL (S3 endpoint)
2020
RegistrationToken string // --registration-token / LOCALAI_REGISTRATION_TOKEN (required token for node registration)
21-
AutoApproveNodes bool // --auto-approve-nodes / LOCALAI_AUTO_APPROVE_NODES (skip admin approval for new workers)
21+
// RegistrationRequireAuth fails startup when distributed mode is enabled but
22+
// RegistrationToken is empty. The default (false) keeps the historical
23+
// fail-open behavior with a loud warning; production should set it so the
24+
// node-register endpoints and the worker file-transfer server cannot run
25+
// unauthenticated. Mirrors NatsRequireAuth for the NATS bus.
26+
RegistrationRequireAuth bool // LOCALAI_REGISTRATION_REQUIRE_AUTH
27+
// RequireAuth is the umbrella switch (LOCALAI_DISTRIBUTED_REQUIRE_AUTH) for
28+
// distributed-mode auth: when true it implies BOTH NatsRequireAuth and
29+
// RegistrationRequireAuth, so a single knob locks down the bus and the
30+
// registration/file-transfer layer together. The granular flags remain
31+
// available to enforce just one layer.
32+
RequireAuth bool // LOCALAI_DISTRIBUTED_REQUIRE_AUTH
33+
AutoApproveNodes bool // --auto-approve-nodes / LOCALAI_AUTO_APPROVE_NODES (skip admin approval for new workers)
2234

2335
// NATS JWT auth (optional; see pkg/natsauth and docs/features/distributed-mode.md)
2436
NatsAccountSeed string // LOCALAI_NATS_ACCOUNT_SEED — account signing seed to mint per-node worker JWTs
@@ -88,9 +100,15 @@ func (c DistributedConfig) Validate() error {
88100
(c.StorageAccessKey == "" && c.StorageSecretKey != "") {
89101
return fmt.Errorf("storage-access-key and storage-secret-key must both be set or both empty")
90102
}
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.
92107
if c.RegistrationToken == "" {
93-
xlog.Warn("distributed mode running without registration token — node endpoints are unprotected")
108+
if c.RegistrationAuthRequired() {
109+
return fmt.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")
94112
}
95113
if err := c.NatsAuthConfig().Validate(); err != nil {
96114
return err
@@ -170,6 +188,30 @@ var EnableNatsRequireAuth = func(o *ApplicationConfig) {
170188
o.Distributed.NatsRequireAuth = true
171189
}
172190

191+
// EnableRegistrationRequireAuth makes an empty registration token a hard error
192+
// in distributed mode (see DistributedConfig.RegistrationRequireAuth).
193+
var EnableRegistrationRequireAuth = func(o *ApplicationConfig) {
194+
o.Distributed.RegistrationRequireAuth = true
195+
}
196+
197+
// EnableDistributedRequireAuth is the umbrella switch implying both
198+
// NatsRequireAuth and RegistrationRequireAuth (see DistributedConfig.RequireAuth).
199+
var EnableDistributedRequireAuth = func(o *ApplicationConfig) {
200+
o.Distributed.RequireAuth = true
201+
}
202+
203+
// RegistrationAuthRequired reports whether an empty registration token must be
204+
// treated as a fatal misconfiguration — the granular flag or the umbrella.
205+
func (c DistributedConfig) RegistrationAuthRequired() bool {
206+
return c.RegistrationRequireAuth || c.RequireAuth
207+
}
208+
209+
// NatsAuthRequired reports whether NATS JWT credentials must be present — the
210+
// granular flag or the umbrella.
211+
func (c DistributedConfig) NatsAuthRequired() bool {
212+
return c.NatsRequireAuth || c.RequireAuth
213+
}
214+
173215
func WithNatsTLSCA(path string) AppOption {
174216
return func(o *ApplicationConfig) {
175217
o.Distributed.NatsTLSCA = path
@@ -316,7 +358,7 @@ func (c DistributedConfig) NatsAuthConfig() natsauth.Config {
316358
ServiceUserJWT: c.NatsServiceJWT,
317359
ServiceUserSeed: c.NatsServiceSeed,
318360
WorkerJWTTTL: c.NatsWorkerJWTTTL,
319-
RequireAuth: c.NatsRequireAuth,
361+
RequireAuth: c.NatsAuthRequired(),
320362
}
321363
}
322364

core/config/distributed_config_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,66 @@ var _ = Describe("DistributedConfig.Validate negative-duration errors", func() {
8888
Expect(c.Validate()).To(Succeed())
8989
})
9090
})
91+
92+
var _ = Describe("DistributedConfig.Validate registration auth", func() {
93+
It("rejects an empty registration token when RequireAuth is set", func() {
94+
c := config.DistributedConfig{
95+
Enabled: true,
96+
NatsURL: "nats://localhost:4222",
97+
RegistrationRequireAuth: true,
98+
}
99+
err := c.Validate()
100+
Expect(err).To(HaveOccurred())
101+
Expect(err.Error()).To(ContainSubstring("LOCALAI_REGISTRATION_REQUIRE_AUTH"))
102+
Expect(err.Error()).To(ContainSubstring("LOCALAI_REGISTRATION_TOKEN"))
103+
})
104+
105+
It("accepts a set registration token when RequireAuth is set", func() {
106+
c := config.DistributedConfig{
107+
Enabled: true,
108+
NatsURL: "nats://localhost:4222",
109+
RegistrationToken: "s3cret",
110+
RegistrationRequireAuth: true,
111+
}
112+
Expect(c.Validate()).To(Succeed())
113+
})
114+
115+
It("warns but succeeds with an empty token when RequireAuth is unset", func() {
116+
c := config.DistributedConfig{
117+
Enabled: true,
118+
NatsURL: "nats://localhost:4222",
119+
}
120+
Expect(c.Validate()).To(Succeed())
121+
})
122+
123+
It("rejects an empty token when the umbrella RequireAuth is set", func() {
124+
c := config.DistributedConfig{
125+
Enabled: true,
126+
NatsURL: "nats://localhost:4222",
127+
RequireAuth: true,
128+
// Provide NATS creds so only the registration-token gap remains.
129+
NatsServiceJWT: "jwt",
130+
NatsServiceSeed: "seed",
131+
NatsAccountSeed: "acct",
132+
}
133+
err := c.Validate()
134+
Expect(err).To(HaveOccurred())
135+
Expect(err.Error()).To(ContainSubstring("LOCALAI_DISTRIBUTED_REQUIRE_AUTH"))
136+
Expect(err.Error()).To(ContainSubstring("LOCALAI_REGISTRATION_TOKEN"))
137+
})
138+
139+
It("the umbrella implies NATS auth is required", func() {
140+
c := config.DistributedConfig{
141+
Enabled: true,
142+
NatsURL: "nats://localhost:4222",
143+
RegistrationToken: "tok", // registration layer satisfied
144+
RequireAuth: true, // umbrella → NATS creds now required
145+
}
146+
Expect(c.NatsAuthRequired()).To(BeTrue())
147+
Expect(c.RegistrationAuthRequired()).To(BeTrue())
148+
// Missing NATS service JWT/seed must now be fatal.
149+
err := c.Validate()
150+
Expect(err).To(HaveOccurred())
151+
Expect(err.Error()).To(ContainSubstring("LOCALAI_NATS_REQUIRE_AUTH"))
152+
})
153+
})

core/services/nodes/file_transfer_server.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ func StartFileTransferServerWithListener(lis net.Listener, stagingDir, modelsDir
6161
return nil, fmt.Errorf("creating staging dir %s: %w", stagingDir, err)
6262
}
6363

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+
if token == "" {
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")
73+
}
74+
6475
mux := http.NewServeMux()
6576

6677
// PUT /v1/files/{key} — upload file

core/services/nodes/file_transfer_server_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/hex"
88
"fmt"
99
"io"
10+
"net"
1011
"net/http"
1112
"net/http/httptest"
1213
"os"
@@ -893,3 +894,50 @@ func sha256Hex(data []byte) string {
893894
h := sha256.Sum256(data)
894895
return hex.EncodeToString(h[:])
895896
}
897+
898+
var _ = Describe("StartFileTransferServerWithListener", func() {
899+
start := func(token string) (string, func()) {
900+
lis, err := net.Listen("tcp", "127.0.0.1:0")
901+
Expect(err).NotTo(HaveOccurred())
902+
staging := GinkgoT().TempDir()
903+
models := GinkgoT().TempDir()
904+
data := GinkgoT().TempDir()
905+
srv, err := StartFileTransferServerWithListener(lis, staging, models, data, token, 0)
906+
Expect(err).NotTo(HaveOccurred())
907+
base := "http://" + lis.Addr().String()
908+
return base, func() { ShutdownFileTransferServer(srv) }
909+
}
910+
911+
// Exercises the empty-token fail-open warning branch: the server serves
912+
// file requests with no Authorization header at all.
913+
It("serves unauthenticated when started without a token", func() {
914+
base, stop := start("")
915+
defer stop()
916+
917+
resp, err := http.Get(base + "/v1/files/missing.bin")
918+
Expect(err).NotTo(HaveOccurred())
919+
defer func() { _ = resp.Body.Close() }()
920+
// No 401 — the empty token fails open. The file is absent so we get 404.
921+
Expect(resp.StatusCode).To(Equal(http.StatusNotFound))
922+
})
923+
924+
It("rejects requests without the bearer token when a token is set", func() {
925+
base, stop := start("s3cret")
926+
defer stop()
927+
928+
resp, err := http.Get(base + "/v1/files/missing.bin")
929+
Expect(err).NotTo(HaveOccurred())
930+
defer func() { _ = resp.Body.Close() }()
931+
Expect(resp.StatusCode).To(Equal(http.StatusUnauthorized))
932+
})
933+
934+
It("serves the unauthenticated health endpoints regardless of token", func() {
935+
base, stop := start("s3cret")
936+
defer stop()
937+
938+
resp, err := http.Get(base + "/healthz")
939+
Expect(err).NotTo(HaveOccurred())
940+
defer func() { _ = resp.Body.Close() }()
941+
Expect(resp.StatusCode).To(Equal(http.StatusOK))
942+
})
943+
})
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package worker
2+
3+
import (
4+
. "github.com/onsi/ginkgo/v2"
5+
. "github.com/onsi/gomega"
6+
)
7+
8+
var _ = Describe("Worker auth-required helpers", func() {
9+
DescribeTable("NatsAuthRequired",
10+
func(nats, umbrella, want bool) {
11+
cfg := &Config{NatsRequireAuth: nats, DistributedRequireAuth: umbrella}
12+
Expect(cfg.NatsAuthRequired()).To(Equal(want))
13+
},
14+
Entry("neither", false, false, false),
15+
Entry("granular only", true, false, true),
16+
Entry("umbrella only", false, true, true),
17+
Entry("both", true, true, true),
18+
)
19+
20+
DescribeTable("RegistrationAuthRequired",
21+
func(reg, umbrella, want bool) {
22+
cfg := &Config{RegistrationRequireAuth: reg, DistributedRequireAuth: umbrella}
23+
Expect(cfg.RegistrationAuthRequired()).To(Equal(want))
24+
},
25+
Entry("neither", false, false, false),
26+
Entry("granular only", true, false, true),
27+
Entry("umbrella only", false, true, true),
28+
Entry("both", true, true, true),
29+
)
30+
})

core/services/worker/config.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,14 @@ type Config struct {
4444
AdvertiseHTTPAddr string `env:"LOCALAI_ADVERTISE_HTTP_ADDR" help:"HTTP address the frontend uses to reach this node for file transfer" group:"server" hidden:""`
4545

4646
// Registration (required)
47-
AdvertiseAddr string `env:"LOCALAI_ADVERTISE_ADDR" help:"Address the frontend uses to reach this node (defaults to hostname:port from Addr)" group:"registration" hidden:""`
48-
RegisterTo string `env:"LOCALAI_REGISTER_TO" required:"" help:"Frontend URL for registration" group:"registration"`
49-
NodeName string `env:"LOCALAI_NODE_NAME" help:"Node name for registration (defaults to hostname)" group:"registration"`
50-
RegistrationToken string `env:"LOCALAI_REGISTRATION_TOKEN" help:"Token for authenticating with the frontend" group:"registration"`
51-
HeartbeatInterval string `env:"LOCALAI_HEARTBEAT_INTERVAL" default:"10s" help:"Interval between heartbeats" group:"registration"`
52-
NodeLabels string `env:"LOCALAI_NODE_LABELS" help:"Comma-separated key=value labels for this node (e.g. tier=fast,gpu=a100)" group:"registration"`
47+
AdvertiseAddr string `env:"LOCALAI_ADVERTISE_ADDR" help:"Address the frontend uses to reach this node (defaults to hostname:port from Addr)" group:"registration" hidden:""`
48+
RegisterTo string `env:"LOCALAI_REGISTER_TO" required:"" help:"Frontend URL for registration" group:"registration"`
49+
NodeName string `env:"LOCALAI_NODE_NAME" help:"Node name for registration (defaults to hostname)" group:"registration"`
50+
RegistrationToken string `env:"LOCALAI_REGISTRATION_TOKEN" help:"Token for authenticating with the frontend" group:"registration"`
51+
RegistrationRequireAuth bool `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+
DistributedRequireAuth bool `env:"LOCALAI_DISTRIBUTED_REQUIRE_AUTH" default:"false" help:"Umbrella switch implying both --nats-require-auth and --registration-require-auth" group:"distributed"`
53+
HeartbeatInterval string `env:"LOCALAI_HEARTBEAT_INTERVAL" default:"10s" help:"Interval between heartbeats" group:"registration"`
54+
NodeLabels string `env:"LOCALAI_NODE_LABELS" help:"Comma-separated key=value labels for this node (e.g. tier=fast,gpu=a100)" group:"registration"`
5355
// MaxReplicasPerModel caps how many replicas of any one model can run on
5456
// this worker concurrently. Default 1 = historical single-replica
5557
// behavior. Set higher when a node has enough VRAM to host multiple
@@ -75,3 +77,15 @@ type Config struct {
7577
StorageAccessKey string `env:"LOCALAI_STORAGE_ACCESS_KEY" help:"S3 access key" group:"distributed"`
7678
StorageSecretKey string `env:"LOCALAI_STORAGE_SECRET_KEY" help:"S3 secret key" group:"distributed"`
7779
}
80+
81+
// NatsAuthRequired reports whether NATS JWT credentials must be present — the
82+
// granular flag or the umbrella (LOCALAI_DISTRIBUTED_REQUIRE_AUTH).
83+
func (c Config) NatsAuthRequired() bool {
84+
return c.NatsRequireAuth || c.DistributedRequireAuth
85+
}
86+
87+
// RegistrationAuthRequired reports whether a registration token must be set
88+
// before the file-transfer server may start — the granular flag or the umbrella.
89+
func (c Config) RegistrationAuthRequired() bool {
90+
return c.RegistrationRequireAuth || c.DistributedRequireAuth
91+
}

0 commit comments

Comments
 (0)