Skip to content

Commit cd79afe

Browse files
authored
fix(llm): refresh ESS assertion credentials (#1143)
Signed-off-by: Mike Camp <mcamp@nvidia.com>
1 parent 2cb92a5 commit cd79afe

9 files changed

Lines changed: 413 additions & 43 deletions

File tree

src/compute-plane-services/nvca/vendor/github.com/NVIDIA/nvcf/src/libraries/go/lib/pkg/icms-translate/translate/function/llm.go

Lines changed: 26 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/compute-plane-services/worker-llm-credentials/README.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
# worker-llm-credentials
22

3-
Sidecar that maintains a fresh NVCF worker credential token on disk for the
4-
LLM inference container. It connects to NVCF over gRPC using the worker token,
5-
runs a background refresher that periodically fetches a new token, and writes
6-
it atomically to the configured token path (owner-only `0600` permissions). The
7-
process runs until its context is cancelled.
3+
Sidecar that maintains fresh NVCF credentials on disk for explicit LLM
4+
functions. It connects to NVCF over gRPC using the worker token and always
5+
refreshes the worker credential used by the LLM router client. When
6+
`ESS_ASSERTION_TOKEN_PATH` is set, it also refreshes the ESS assertion used by
7+
the ESS Agent. The process runs until its context is cancelled.
8+
9+
Each refresher fetches its credential immediately, schedules later refreshes
10+
from the returned expiration, and atomically replaces the configured file. The
11+
shared files use `0644` permissions because the consuming infrastructure
12+
sidecars run as different non-root users. Customer workload containers do not
13+
mount these credential volumes.
14+
15+
## Configuration
16+
17+
- `WORKER_TOKEN_PATH` sets the worker credential file. It defaults to
18+
`/var/run/llm/worker-token`.
19+
- `ESS_ASSERTION_TOKEN_PATH` optionally sets the ESS assertion file. When it is
20+
unset, the sidecar does not request or write an ESS assertion.
821

922
## Build
1023

src/compute-plane-services/worker-llm-credentials/configs/BUILD.bazel

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
load("@rules_go//go:def.bzl", "go_library")
1+
load("@rules_go//go:def.bzl", "go_library", "go_test")
22

33
go_library(
44
name = "configs",
@@ -7,6 +7,12 @@ go_library(
77
visibility = ["//visibility:public"],
88
)
99

10+
go_test(
11+
name = "configs_test",
12+
srcs = ["configs_test.go"],
13+
embed = [":configs"],
14+
)
15+
1016
alias(
1117
name = "go_default_library",
1218
actual = ":configs",

src/compute-plane-services/worker-llm-credentials/configs/configs.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@ limitations under the License.
1818
package configs
1919

2020
type Config struct {
21-
NvcfFqdnGrpc string `mapstructure:"NVCF_FQDN_GRPC"`
22-
NvcfWorkerToken string `mapstructure:"NVCF_WORKER_TOKEN"`
23-
FunctionId string `mapstructure:"FUNCTION_ID"`
24-
FunctionVersionId string `mapstructure:"FUNCTION_VERSION_ID"`
25-
NcaId string `mapstructure:"NCA_ID"`
26-
InstanceId string `mapstructure:"INSTANCE_ID"`
27-
SharedConfigDir string `mapstructure:"SHARED_CONFIG_DIR"`
28-
WorkerTokenPath string `mapstructure:"WORKER_TOKEN_PATH"`
21+
NvcfFqdnGrpc string `mapstructure:"NVCF_FQDN_GRPC"`
22+
NvcfWorkerToken string `mapstructure:"NVCF_WORKER_TOKEN"`
23+
FunctionId string `mapstructure:"FUNCTION_ID"`
24+
FunctionVersionId string `mapstructure:"FUNCTION_VERSION_ID"`
25+
NcaId string `mapstructure:"NCA_ID"`
26+
InstanceId string `mapstructure:"INSTANCE_ID"`
27+
SharedConfigDir string `mapstructure:"SHARED_CONFIG_DIR"`
28+
WorkerTokenPath string `mapstructure:"WORKER_TOKEN_PATH"`
29+
ESSAssertionTokenPath string `mapstructure:"ESS_ASSERTION_TOKEN_PATH"`
2930
}
3031

3132
const DefaultSharedConfigDir = "/config/shared"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
SPDX-License-Identifier: Apache-2.0
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package configs
19+
20+
import (
21+
"reflect"
22+
"testing"
23+
)
24+
25+
func TestConfigSupportsOptionalESSAssertionTokenPath(t *testing.T) {
26+
field, ok := reflect.TypeOf(Config{}).FieldByName("ESSAssertionTokenPath")
27+
if !ok {
28+
t.Fatal("Config must expose an optional ESSAssertionTokenPath field")
29+
}
30+
if got := field.Tag.Get("mapstructure"); got != "ESS_ASSERTION_TOKEN_PATH" {
31+
t.Fatalf("ESSAssertionTokenPath mapstructure tag = %q, want %q", got, "ESS_ASSERTION_TOKEN_PATH")
32+
}
33+
}

src/compute-plane-services/worker-llm-credentials/internal/worker/worker.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ func (w *Worker) Run(ctx context.Context) error {
8080
if err != nil {
8181
return err
8282
}
83+
if w.config.ESSAssertionTokenPath != "" {
84+
w.client.StartAssertionTokenRefresher(ctx, w.config.ESSAssertionTokenPath, true)
85+
}
8386

8487
token.StartTokenRefresher(ctx, "llm worker token", true,
8588
func(ctx context.Context) (token.Token, error) {

src/compute-plane-services/worker-llm-credentials/internal/worker/worker_test.go

Lines changed: 183 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"net"
2424
"os"
2525
"path/filepath"
26+
"sync/atomic"
2627
"testing"
2728
"time"
2829

@@ -39,6 +40,7 @@ const testWorkerToken = "test-worker-token"
3940

4041
type mockNVCFServer struct {
4142
pb.UnimplementedWorkerServer
43+
requestSecretCredentials func(context.Context, *pb.SecretCredentialsRequest) (*pb.SecretCredentialsResponse, error)
4244
}
4345

4446
func (s *mockNVCFServer) ConnectOnce(_ context.Context, _ *pb.WorkerConnect) (*pb.WorkerConnectOnceResponse, error) {
@@ -49,19 +51,65 @@ func (s *mockNVCFServer) ConnectOnce(_ context.Context, _ *pb.WorkerConnect) (*p
4951
}, nil
5052
}
5153

54+
func (s *mockNVCFServer) RequestSecretCredentials(
55+
ctx context.Context,
56+
req *pb.SecretCredentialsRequest,
57+
) (*pb.SecretCredentialsResponse, error) {
58+
if s.requestSecretCredentials == nil {
59+
return s.UnimplementedWorkerServer.RequestSecretCredentials(ctx, req)
60+
}
61+
return s.requestSecretCredentials(ctx, req)
62+
}
63+
5264
func startMockNVCFServer(t *testing.T) string {
65+
return startMockNVCFServerWithImplementation(t, &mockNVCFServer{})
66+
}
67+
68+
func startMockNVCFServerWithImplementation(t *testing.T, implementation *mockNVCFServer) string {
5369
t.Helper()
54-
lis, err := net.Listen("tcp", "127.0.0.1:0")
70+
var listenConfig net.ListenConfig
71+
lis, err := listenConfig.Listen(context.Background(), "tcp", "127.0.0.1:0")
5572
if err != nil {
5673
t.Fatalf("failed to listen: %v", err)
5774
}
5875
srv := grpc.NewServer()
59-
pb.RegisterWorkerServer(srv, &mockNVCFServer{})
60-
go srv.Serve(lis)
61-
t.Cleanup(srv.GracefulStop)
76+
pb.RegisterWorkerServer(srv, implementation)
77+
serveErr := make(chan error, 1)
78+
go func() {
79+
serveErr <- srv.Serve(lis)
80+
}()
81+
t.Cleanup(func() {
82+
srv.GracefulStop()
83+
if err := <-serveErr; err != nil {
84+
t.Errorf("serve mock NVCF server: %v", err)
85+
}
86+
})
6287
return fmt.Sprintf("http://%s", lis.Addr().String())
6388
}
6489

90+
func waitForFileContent(path, want string, timeout time.Duration) bool {
91+
deadline := time.Now().Add(timeout)
92+
for time.Now().Before(deadline) {
93+
content, err := os.ReadFile(path)
94+
if err == nil && string(content) == want {
95+
return true
96+
}
97+
time.Sleep(10 * time.Millisecond)
98+
}
99+
content, err := os.ReadFile(path)
100+
return err == nil && string(content) == want
101+
}
102+
103+
func cancelAndWaitForRun(t *testing.T, cancel context.CancelFunc, runErr <-chan error) {
104+
t.Helper()
105+
cancel()
106+
select {
107+
case <-runErr:
108+
case <-time.After(2 * time.Second):
109+
t.Log("Run did not stop within the cleanup timeout")
110+
}
111+
}
112+
65113
func TestRun_WritesTokenToDisk(t *testing.T) {
66114
addr := startMockNVCFServer(t)
67115
tmpDir := t.TempDir()
@@ -110,3 +158,134 @@ func TestRun_WritesTokenToDisk(t *testing.T) {
110158
t.Fatalf("expected token %q, got %q", testWorkerToken, string(content))
111159
}
112160
}
161+
162+
func TestRun_RefreshesESSAssertionTokenUntilCancelled(t *testing.T) {
163+
tmpDir := t.TempDir()
164+
assertionTokenPath := filepath.Join(tmpDir, "ess", "jwt.token")
165+
if err := os.MkdirAll(filepath.Dir(assertionTokenPath), 0755); err != nil {
166+
t.Fatalf("create assertion token directory: %v", err)
167+
}
168+
if err := os.WriteFile(assertionTokenPath, []byte("worker-init-token"), 0600); err != nil {
169+
t.Fatalf("write initial assertion token: %v", err)
170+
}
171+
172+
var requestCount atomic.Int32
173+
addr := startMockNVCFServerWithImplementation(t, &mockNVCFServer{
174+
requestSecretCredentials: func(
175+
_ context.Context,
176+
_ *pb.SecretCredentialsRequest,
177+
) (*pb.SecretCredentialsResponse, error) {
178+
call := requestCount.Add(1)
179+
tokenValue := "refreshed-assertion-1"
180+
expiration := time.Now().Add(300 * time.Millisecond)
181+
if call > 1 {
182+
tokenValue = "refreshed-assertion-2"
183+
expiration = time.Now().Add(time.Hour)
184+
}
185+
return &pb.SecretCredentialsResponse{
186+
SecretCredentialsToken: tokenValue,
187+
Expiration: timestamppb.New(expiration),
188+
}, nil
189+
},
190+
})
191+
192+
cfg := configs.Config{
193+
NvcfFqdnGrpc: addr,
194+
NvcfWorkerToken: "initial-token",
195+
FunctionId: "test-function-id",
196+
FunctionVersionId: "test-function-version-id",
197+
NcaId: "test-nca-id",
198+
InstanceId: "test-instance-id",
199+
SharedConfigDir: tmpDir,
200+
WorkerTokenPath: filepath.Join(tmpDir, "worker-token"),
201+
ESSAssertionTokenPath: assertionTokenPath,
202+
}
203+
204+
w, err := New(cfg)
205+
if err != nil {
206+
t.Fatalf("New: %v", err)
207+
}
208+
209+
ctx, cancel := context.WithCancel(context.Background())
210+
runErr := lo.Async(func() error { return w.Run(ctx) })
211+
212+
if !waitForFileContent(assertionTokenPath, "refreshed-assertion-1", 5*time.Second) {
213+
cancelAndWaitForRun(t, cancel, runErr)
214+
t.Fatal("existing assertion token was not replaced by the immediate refresh")
215+
}
216+
info, err := os.Stat(assertionTokenPath)
217+
if err != nil {
218+
cancelAndWaitForRun(t, cancel, runErr)
219+
t.Fatalf("stat refreshed assertion token: %v", err)
220+
}
221+
if got := info.Mode().Perm(); got != 0644 {
222+
cancelAndWaitForRun(t, cancel, runErr)
223+
t.Fatalf("assertion token mode = %o, want 0644", got)
224+
}
225+
if !waitForFileContent(assertionTokenPath, "refreshed-assertion-2", 5*time.Second) {
226+
cancelAndWaitForRun(t, cancel, runErr)
227+
t.Fatal("assertion token was not rotated on the next refresh cycle")
228+
}
229+
230+
cancel()
231+
select {
232+
case err := <-runErr:
233+
if err != nil {
234+
t.Fatalf("Run after cancellation: %v", err)
235+
}
236+
case <-time.After(2 * time.Second):
237+
t.Fatal("Run did not stop cleanly after cancellation")
238+
}
239+
}
240+
241+
func TestRun_WithoutESSAssertionTokenPathSkipsSecretCredentialRefresh(t *testing.T) {
242+
var requestCount atomic.Int32
243+
addr := startMockNVCFServerWithImplementation(t, &mockNVCFServer{
244+
requestSecretCredentials: func(
245+
_ context.Context,
246+
_ *pb.SecretCredentialsRequest,
247+
) (*pb.SecretCredentialsResponse, error) {
248+
requestCount.Add(1)
249+
return &pb.SecretCredentialsResponse{
250+
SecretCredentialsToken: "unexpected-token",
251+
Expiration: timestamppb.New(time.Now().Add(time.Hour)),
252+
}, nil
253+
},
254+
})
255+
256+
tmpDir := t.TempDir()
257+
assertionTokenPath := filepath.Join(tmpDir, "ess", "jwt.token")
258+
workerTokenPath := filepath.Join(tmpDir, "worker-token")
259+
cfg := configs.Config{
260+
NvcfFqdnGrpc: addr,
261+
NvcfWorkerToken: "initial-token",
262+
FunctionId: "test-function-id",
263+
FunctionVersionId: "test-function-version-id",
264+
NcaId: "test-nca-id",
265+
InstanceId: "test-instance-id",
266+
SharedConfigDir: tmpDir,
267+
WorkerTokenPath: workerTokenPath,
268+
}
269+
270+
w, err := New(cfg)
271+
if err != nil {
272+
t.Fatalf("New: %v", err)
273+
}
274+
ctx, cancel := context.WithCancel(context.Background())
275+
runErr := lo.Async(func() error { return w.Run(ctx) })
276+
if !waitForFileContent(workerTokenPath, testWorkerToken, 5*time.Second) {
277+
cancelAndWaitForRun(t, cancel, runErr)
278+
t.Fatal("worker token was not written")
279+
}
280+
cancel()
281+
if err := <-runErr; err != nil {
282+
t.Fatalf("Run: %v", err)
283+
}
284+
285+
if got := requestCount.Load(); got != 0 {
286+
t.Fatalf("RequestSecretCredentials calls = %d, want 0", got)
287+
}
288+
if _, err := os.Stat(assertionTokenPath); !os.IsNotExist(err) {
289+
t.Fatalf("assertion token file should not exist without ESS_ASSERTION_TOKEN_PATH, stat error: %v", err)
290+
}
291+
}

0 commit comments

Comments
 (0)