package openai import ( "crypto/sha256" "encoding/hex" "net/http" "net/http/httptest" "strings" "testing" "time" "iop/apps/edge/internal/authprojection" "iop/packages/go/config" iop "iop/proto/gen/iop" ) func sha256Hex(raw string) string { sum := sha256.Sum256([]byte(raw)) return hex.EncodeToString(sum[:]) } func managedProjectionFixture(generation uint64, now time.Time, ttl time.Duration, rawToken string) *iop.PrincipalProjection { projection := &iop.PrincipalProjection{ Generation: generation, IssuedAtUnixNano: now.UnixNano(), ExpiresAtUnixNano: now.Add(ttl).UnixNano(), } if rawToken != "" { projection.Tokens = []*iop.ProjectedPrincipalToken{{ TokenDigestSha256: sha256Hex(rawToken), PrincipalRef: "managed-principal", PrincipalAlias: "managed-alias", TokenRef: "managed-token", TokenRevision: generation, }} } return projection } func setManagedPrincipalProjection(srv *Server, projection authprojection.Reader) { srv.SetCredentialPlaneManaged(true) srv.SetPrincipalProjection(projection) } func TestManagedPrincipalMetadataAndStaticCompatibility(t *testing.T) { clock := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC) cache := authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return clock }) cfg := config.EdgeOpenAIConf{PrincipalTokens: []config.OpenAIPrincipalTokenConf{{ TokenHashSHA256: sha256Hex("static-token"), PrincipalRef: "static-principal", TokenRef: "static-token-ref", }}} legacyServer := NewServer(cfg, &fakeRunService{}, nil) legacyServer.SetPrincipalProjection(cache) var got openAIPrincipal legacyHandlerCalls := 0 legacyHandler := legacyServer.withAuth(func(w http.ResponseWriter, r *http.Request) { legacyHandlerCalls++ got, _ = principalFromContext(r.Context()) w.WriteHeader(http.StatusNoContent) }) staticReq := httptest.NewRequest(http.MethodGet, "/v1/models", nil) staticReq.Header.Set("Authorization", "Bearer static-token") staticW := httptest.NewRecorder() legacyHandler(staticW, staticReq) if staticW.Code != http.StatusNoContent || got.PrincipalRef != "static-principal" || got.Source != principalSourceToken { t.Fatalf("unmanaged static compatibility: status=%d principal=%+v", staticW.Code, got) } if err := cache.Apply(managedProjectionFixture(1, clock, time.Minute, "managed-token-value")); err != nil { t.Fatal(err) } staticAfterProjection := httptest.NewRequest(http.MethodGet, "/v1/models", nil) staticAfterProjection.Header.Set("Authorization", "Bearer static-token") staticAfterProjectionW := httptest.NewRecorder() legacyHandler(staticAfterProjectionW, staticAfterProjection) if staticAfterProjectionW.Code != http.StatusNoContent || got.Source != principalSourceToken { t.Fatalf("disabled mode consumed installed projection: status=%d principal=%+v", staticAfterProjectionW.Code, got) } managedServer := NewServer(cfg, &fakeRunService{}, nil) setManagedPrincipalProjection(managedServer, cache) managedHandlerCalls := 0 managedHandler := managedServer.withAuth(func(w http.ResponseWriter, r *http.Request) { managedHandlerCalls++ got, _ = principalFromContext(r.Context()) w.WriteHeader(http.StatusNoContent) }) managedReq := httptest.NewRequest(http.MethodGet, "/v1/models", nil) managedReq.Header.Set("Authorization", "Bearer managed-token-value") managedW := httptest.NewRecorder() managedHandler(managedW, managedReq) if managedW.Code != http.StatusNoContent { t.Fatalf("managed status=%d body=%s", managedW.Code, managedW.Body.String()) } if got.PrincipalRef != "managed-principal" || got.PrincipalAlias != "managed-alias" || got.TokenRef != "managed-token" || got.Source != principalSourceProjection { t.Fatalf("managed principal metadata: %+v", got) } staticAfterManaged := httptest.NewRequest(http.MethodGet, "/v1/models", nil) staticAfterManaged.Header.Set("Authorization", "Bearer static-token") staticAfterManagedW := httptest.NewRecorder() managedHandler(staticAfterManagedW, staticAfterManaged) if staticAfterManagedW.Code != http.StatusUnauthorized { t.Fatalf("managed mode accepted static source: status=%d", staticAfterManagedW.Code) } if legacyHandlerCalls != 2 || managedHandlerCalls != 1 { t.Fatalf("handler calls: legacy=%d managed=%d, want 2/1", legacyHandlerCalls, managedHandlerCalls) } } func TestManagedOpenAIAuthRejectsBeforeHandler(t *testing.T) { clock := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC) cache := authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return clock }) if err := cache.Apply(managedProjectionFixture(1, clock, time.Minute, "managed-token-value")); err != nil { t.Fatal(err) } srv := NewServer(config.EdgeOpenAIConf{BearerToken: "legacy-token"}, &fakeRunService{}, nil) setManagedPrincipalProjection(srv, cache) handlerCalls := 0 handler := srv.withAuth(func(w http.ResponseWriter, _ *http.Request) { handlerCalls++ w.WriteHeader(http.StatusNoContent) }) assertRejected := func(name, token string) { t.Helper() req := httptest.NewRequest(http.MethodGet, "/v1/models", nil) if token != "" { req.Header.Set("Authorization", "Bearer "+token) } w := httptest.NewRecorder() handler(w, req) if w.Code != http.StatusUnauthorized { t.Fatalf("%s: status=%d body=%s", name, w.Code, w.Body.String()) } } assertRejected("missing", "") assertRejected("unregistered", "unknown-token") assertRejected("legacy source", "legacy-token") if handlerCalls != 0 { t.Fatalf("rejected authentication reached handler %d times", handlerCalls) } if err := cache.Apply(managedProjectionFixture(2, clock, time.Minute, "")); err != nil { t.Fatal(err) } assertRejected("revoked", "managed-token-value") clock = clock.Add(time.Minute) assertRejected("expired", "managed-token-value") if handlerCalls != 0 { t.Fatalf("revoked/expired authentication reached handler %d times", handlerCalls) } } func TestManagedOpenAIAuthFailsClosedBeforeProjectionIsUsable(t *testing.T) { clock := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC) for _, tc := range []struct { name string projection authprojection.Reader }{ {name: "projection unavailable"}, {name: "projection not yet received", projection: authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return clock })}, } { t.Run(tc.name, func(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{ BearerToken: "legacy-token", PrincipalTokens: []config.OpenAIPrincipalTokenConf{{ TokenHashSHA256: sha256Hex("static-token"), PrincipalRef: "static-principal", }}, }, &fakeRunService{}, nil) setManagedPrincipalProjection(srv, tc.projection) handlerCalls := 0 handler := srv.withAuth(func(w http.ResponseWriter, _ *http.Request) { handlerCalls++ w.WriteHeader(http.StatusNoContent) }) for _, token := range []string{"legacy-token", "static-token", "managed-token"} { req := httptest.NewRequest(http.MethodGet, "/v1/models", nil) req.Header.Set("Authorization", "Bearer "+token) w := httptest.NewRecorder() handler(w, req) if w.Code != http.StatusUnauthorized { t.Fatalf("token %q status = %d, want 401", token, w.Code) } } if handlerCalls != 0 { t.Fatalf("unusable managed projection reached handler %d times", handlerCalls) } }) } } func TestRoutesResolvePrincipalTokenMapping(t *testing.T) { const rawToken = "sk-alice-raw-token" cfg := config.EdgeOpenAIConf{ PrincipalTokens: []config.OpenAIPrincipalTokenConf{ {TokenRef: "iop-tok-alice", TokenHashSHA256: sha256Hex(rawToken), PrincipalRef: "user:alice", PrincipalAlias: "alice"}, }, Models: []string{"model-a"}, } srv := NewServer(cfg, &fakeRunService{}, nil) for _, tc := range []struct { name string auth string wantStatus int }{ {name: "missing", wantStatus: http.StatusUnauthorized}, {name: "wrong token", auth: "Bearer wrong-token", wantStatus: http.StatusUnauthorized}, {name: "valid hash-mapped token", auth: "Bearer " + rawToken, wantStatus: http.StatusOK}, } { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/v1/models", nil) if tc.auth != "" { req.Header.Set("Authorization", tc.auth) } w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != tc.wantStatus { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } }) } } func TestRoutesPrincipalTokenMappingFallsBackToLegacyBearer(t *testing.T) { cfg := config.EdgeOpenAIConf{ PrincipalTokens: []config.OpenAIPrincipalTokenConf{ {TokenRef: "iop-tok-alice", TokenHashSHA256: sha256Hex("alice-token"), PrincipalRef: "user:alice"}, }, BearerToken: "legacy-secret", Models: []string{"model-a"}, } srv := NewServer(cfg, &fakeRunService{}, nil) req := httptest.NewRequest(http.MethodGet, "/v1/models", nil) req.Header.Set("Authorization", "Bearer legacy-secret") w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } } func TestResolvePrincipalAllowsMultipleTokensForSamePrincipal(t *testing.T) { cfg := config.EdgeOpenAIConf{ PrincipalTokens: []config.OpenAIPrincipalTokenConf{ {TokenRef: "alice-app-a", TokenHashSHA256: sha256Hex("alice-app-a-token"), PrincipalRef: "user:alice", PrincipalAlias: "alice"}, {TokenRef: "alice-app-b", TokenHashSHA256: sha256Hex("alice-app-b-token"), PrincipalRef: "user:alice", PrincipalAlias: "alice"}, }, } first, ok := resolvePrincipal(cfg, "Bearer alice-app-a-token") if !ok { t.Fatal("expected first app token to authenticate") } if first.PrincipalRef != "user:alice" || first.PrincipalAlias != "alice" || first.TokenRef != "alice-app-a" { t.Fatalf("unexpected first principal: %+v", first) } second, ok := resolvePrincipal(cfg, "Bearer alice-app-b-token") if !ok { t.Fatal("expected second app token to authenticate") } if second.PrincipalRef != "user:alice" || second.PrincipalAlias != "alice" || second.TokenRef != "alice-app-b" { t.Fatalf("unexpected second principal: %+v", second) } } func TestHealthzDoesNotRequireBearerTokenWithPrincipalTokensConfigured(t *testing.T) { cfg := config.EdgeOpenAIConf{ PrincipalTokens: []config.OpenAIPrincipalTokenConf{ {TokenRef: "iop-tok-alice", TokenHashSHA256: sha256Hex("alice-token"), PrincipalRef: "user:alice"}, }, } srv := NewServer(cfg, &fakeRunService{}, nil) req := httptest.NewRequest(http.MethodGet, "/healthz", nil) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } } func TestChatCompletionsAddsPrincipalMetadataFromBearerToken(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"} fake.events <- &iop.RunEvent{Type: "complete"} const rawToken = "sk-alice-raw-token" cfg := config.EdgeOpenAIConf{ Adapter: "ollama", PrincipalTokens: []config.OpenAIPrincipalTokenConf{ {TokenRef: "iop-tok-alice", TokenHashSHA256: sha256Hex(rawToken), PrincipalRef: "user:alice", PrincipalAlias: "alice"}, }, } srv := NewServer(cfg, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"from-request", "messages":[{"role":"user","content":"hi"}], "metadata":{"iop_principal_ref":"spoofed","user":"someone-else"} }`)) req.Header.Set("Authorization", "Bearer "+rawToken) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if fake.req.Metadata["iop_principal_ref"] != "user:alice" { t.Fatalf("iop_principal_ref must come from the authenticated context, not caller metadata: got %q", fake.req.Metadata["iop_principal_ref"]) } if fake.req.Metadata["iop_principal_alias"] != "alice" { t.Fatalf("iop_principal_alias: got %q", fake.req.Metadata["iop_principal_alias"]) } if fake.req.Metadata["iop_token_ref"] != "iop-tok-alice" { t.Fatalf("iop_token_ref: got %q", fake.req.Metadata["iop_token_ref"]) } if fake.req.Metadata["iop_principal_source"] != principalSourceToken { t.Fatalf("iop_principal_source: got %q, want %q", fake.req.Metadata["iop_principal_source"], principalSourceToken) } if fake.req.Metadata["user"] != "someone-else" { t.Fatalf("caller metadata.user should still pass through as opaque metadata (not used as identity): %+v", fake.req.Metadata) } } func TestRoutesDoNotUseMetadataUserForPrincipal(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"} fake.events <- &iop.RunEvent{Type: "complete"} // No principal_tokens and no bearer_token configured: the endpoint is // open (unauthenticated), so no caller identity should be resolved even // though the caller supplies metadata.user. srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"from-request", "messages":[{"role":"user","content":"hi"}], "metadata":{"user":"claimed-identity"} }`)) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if _, ok := fake.req.Metadata["iop_principal_ref"]; ok { t.Fatalf("iop_principal_ref must not be derived from metadata.user: %+v", fake.req.Metadata) } if fake.req.Metadata["iop_principal_source"] != principalSourceUnknown { t.Fatalf("iop_principal_source: got %q, want %q", fake.req.Metadata["iop_principal_source"], principalSourceUnknown) } if fake.req.Metadata["user"] != "claimed-identity" { t.Fatalf("caller metadata.user should still pass through as opaque metadata: %+v", fake.req.Metadata) } } func TestResponsesAddsPrincipalMetadataFromBearerToken(t *testing.T) { fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"} fake.events <- &iop.RunEvent{Type: "complete"} const rawToken = "sk-bob-raw-token" cfg := config.EdgeOpenAIConf{ Adapter: "ollama", PrincipalTokens: []config.OpenAIPrincipalTokenConf{ {TokenRef: "iop-tok-bob", TokenHashSHA256: sha256Hex(rawToken), PrincipalRef: "user:bob"}, }, } srv := NewServer(cfg, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"from-request", "input":"hi" }`)) req.Header.Set("Authorization", "Bearer "+rawToken) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if fake.req.Metadata["iop_principal_ref"] != "user:bob" { t.Fatalf("iop_principal_ref: got %q, want user:bob", fake.req.Metadata["iop_principal_ref"]) } if fake.req.Metadata["iop_token_ref"] != "iop-tok-bob" { t.Fatalf("iop_token_ref: got %q", fake.req.Metadata["iop_token_ref"]) } if fake.req.Metadata["iop_principal_alias"] != "" { t.Fatalf("iop_principal_alias should be empty when unset: got %q", fake.req.Metadata["iop_principal_alias"]) } } func TestProviderTunnelAddsPrincipalMetadataFromBearerToken(t *testing.T) { fake := &providerFakeRunService{ tunnelFrames: staticProviderTunnelFrames(`{"ok":true}`), } catalog := []config.ModelCatalogEntry{{ ID: "ornith:35b", Providers: map[string]string{"prov-vllm": "Ornith-1.0-35B"}, }} const rawToken = "sk-carol-raw-token" cfg := config.EdgeOpenAIConf{ PrincipalTokens: []config.OpenAIPrincipalTokenConf{ {TokenRef: "iop-tok-carol", TokenHashSHA256: sha256Hex(rawToken), PrincipalRef: "user:carol", PrincipalAlias: "carol"}, }, } srv := NewServer(cfg, fake, nil) srv.SetModelCatalog(catalog) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"ornith:35b", "messages":[{"role":"user","content":"hello"}] }`)) req.Header.Set("Authorization", "Bearer "+rawToken) w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } tunnelReqs := fake.tunnelReqsSnapshot() if len(tunnelReqs) != 1 { t.Fatalf("expected 1 tunnel dispatch, got %d", len(tunnelReqs)) } meta := tunnelReqs[0].Metadata if meta["iop_principal_ref"] != "user:carol" { t.Fatalf("iop_principal_ref: got %q, want user:carol", meta["iop_principal_ref"]) } if meta["iop_principal_alias"] != "carol" { t.Fatalf("iop_principal_alias: got %q", meta["iop_principal_alias"]) } if meta["iop_token_ref"] != "iop-tok-carol" { t.Fatalf("iop_token_ref: got %q", meta["iop_token_ref"]) } } func TestChatCompletionsPrincipalMetadataEmptyWhenUnauthenticatedDirectCall(t *testing.T) { // Handlers invoked directly (bypassing withAuth, as most existing tests // do) must not fail when no principal was ever resolved for the context. fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)} fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"} fake.events <- &iop.RunEvent{Type: "complete"} srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"from-request", "messages":[{"role":"user","content":"hi"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if _, ok := fake.req.Metadata["iop_principal_source"]; ok { t.Fatalf("no principal metadata should be added without an authenticated context: %+v", fake.req.Metadata) } }