-
Notifications
You must be signed in to change notification settings - Fork 76
Send instance name as x-api-key to the OTLP relay #316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 11 commits
5be369e
bfda873
e132c09
ff3f848
23546c0
8f83406
ec5597d
7468e28
df9da0d
a6d280d
3c9a8e5
1d5acf1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "sync" | ||
| "time" | ||
|
|
||
| "github.com/kernel/kernel-images/server/lib/forkidentity" | ||
| ) | ||
|
|
||
| // instanceJWTCacheTTL bounds how often the provider re-reads the applied | ||
| // fork-identity payload from disk. Export requests are frequent; the JWT changes | ||
| // at most once per fork, so a few seconds of staleness is harmless and avoids a | ||
| // filesystem read on every request. | ||
| const instanceJWTCacheTTL = 3 * time.Second | ||
|
|
||
| // instanceJWTProvider resolves the OTLP relay bearer credential per request. | ||
| // | ||
| // In fork-identity-wait mode the platform delivers a fresh KERNEL_INSTANCE_JWT | ||
| // in the applied identity payload after kernel-images-api has already started, | ||
| // and the process is not restarted, so the boot-time env JWT is stale (empty on | ||
| // an unbound fork). This provider reads the JWT from the applied payload instead, | ||
| // falling back to the boot env otherwise. Outside fork mode it returns the boot | ||
| // JWT unchanged. | ||
| type instanceJWTProvider struct { | ||
| bootJWT string | ||
| forkEnabled bool | ||
|
|
||
| mu sync.Mutex | ||
| cached string | ||
| cachedAt time.Time | ||
| } | ||
|
|
||
| func newInstanceJWTProvider(bootJWT string) *instanceJWTProvider { | ||
| enabled, _ := forkidentity.WaitEnabled() | ||
| return &instanceJWTProvider{bootJWT: bootJWT, forkEnabled: enabled} | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| // Token returns the current bearer credential, or "" when none is available yet | ||
| // (the caller then sends no Authorization header rather than an empty bearer). | ||
| func (p *instanceJWTProvider) Token() string { | ||
| if !p.forkEnabled { | ||
| return p.bootJWT | ||
| } | ||
| p.mu.Lock() | ||
| defer p.mu.Unlock() | ||
| if !p.cachedAt.IsZero() && time.Since(p.cachedAt) < instanceJWTCacheTTL { | ||
| return p.cached | ||
| } | ||
| p.cached = p.resolve() | ||
| p.cachedAt = time.Now() | ||
| return p.cached | ||
|
cursor[bot] marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| // resolve reads the applied fork-identity payload for the JWT. It only reads once | ||
| // the applied marker is present, to avoid a torn read while the wrapper is | ||
| // mid-apply, and falls back to the boot JWT on any miss. | ||
| func (p *instanceJWTProvider) resolve() string { | ||
| applied, err := forkidentity.ReadAppliedMarker() | ||
| if err != nil || applied == "" { | ||
| return p.bootJWT | ||
| } | ||
| payload, err := forkidentity.ReadPayload() | ||
| if err != nil { | ||
| return p.bootJWT | ||
| } | ||
| if jwt := payload.Get("kernel_instance_jwt"); jwt != "" { | ||
| return jwt | ||
| } | ||
| return p.bootJWT | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "path/filepath" | ||
| "testing" | ||
|
|
||
| "github.com/kernel/kernel-images/server/lib/forkidentity" | ||
| ) | ||
|
|
||
| // withForkIdentityFiles points the forkidentity package vars at temp paths for | ||
| // the duration of a test and restores them after. | ||
| func withForkIdentityFiles(t *testing.T) { | ||
| t.Helper() | ||
| dir := t.TempDir() | ||
| payload, applied := forkidentity.PayloadFile, forkidentity.AppliedFile | ||
| forkidentity.PayloadFile = filepath.Join(dir, "fork-identity.json") | ||
| forkidentity.AppliedFile = filepath.Join(dir, "fork-identity-applied") | ||
| t.Cleanup(func() { | ||
| forkidentity.PayloadFile = payload | ||
| forkidentity.AppliedFile = applied | ||
| }) | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicate fork-identity test helperLow Severity
Reviewed by Cursor Bugbot for commit 3c9a8e5. Configure here. |
||
|
|
||
| func writeAppliedPayload(t *testing.T, jwt string) { | ||
| t.Helper() | ||
| p := forkidentity.Payload{ | ||
| "instance_name": "browser-fork-1", | ||
| "session_intel_url": "https://intel.example", | ||
| "kernel_instance_jwt": jwt, | ||
| } | ||
| if err := forkidentity.WritePayload(p); err != nil { | ||
| t.Fatalf("write payload: %v", err) | ||
| } | ||
| if err := forkidentity.WriteAppliedMarker(p.InstanceName()); err != nil { | ||
| t.Fatalf("write applied marker: %v", err) | ||
| } | ||
| } | ||
|
|
||
| func TestInstanceJWTProvider(t *testing.T) { | ||
| t.Run("non-fork returns boot jwt", func(t *testing.T) { | ||
| withForkIdentityFiles(t) | ||
| t.Setenv(forkidentity.WaitEnv, "") | ||
| // Even with an applied payload present, non-fork mode ignores it. | ||
| writeAppliedPayload(t, "payload-jwt") | ||
| p := newInstanceJWTProvider("boot-jwt") | ||
| if got := p.Token(); got != "boot-jwt" { | ||
| t.Errorf("Token() = %q, want boot-jwt", got) | ||
| } | ||
| }) | ||
|
|
||
| t.Run("fork before apply falls back to boot jwt", func(t *testing.T) { | ||
| withForkIdentityFiles(t) | ||
| t.Setenv(forkidentity.WaitEnv, "true") | ||
| // No payload/applied marker written yet: mid-apply, must not read a torn value. | ||
| p := newInstanceJWTProvider("boot-jwt") | ||
| if got := p.Token(); got != "boot-jwt" { | ||
| t.Errorf("Token() = %q, want boot-jwt", got) | ||
| } | ||
| }) | ||
|
|
||
| t.Run("fork after apply returns fresh payload jwt", func(t *testing.T) { | ||
| withForkIdentityFiles(t) | ||
| t.Setenv(forkidentity.WaitEnv, "true") | ||
| writeAppliedPayload(t, "fresh-jwt") | ||
| // Boot jwt is empty, mirroring an unbound fork; the fresh credential must | ||
| // come from the applied payload. | ||
| p := newInstanceJWTProvider("") | ||
| if got := p.Token(); got != "fresh-jwt" { | ||
| t.Errorf("Token() = %q, want fresh-jwt", got) | ||
| } | ||
| }) | ||
| } | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Auth header mismatches relay contract
High Severity
The OTLP exporter sends an
Authorization: Bearertoken, but the relay expects anx-api-keyheader with the instance name. This authentication mismatch causes OTLP exports to fail, preventing session telemetry from landing.Reviewed by Cursor Bugbot for commit a6d280d. Configure here.