From ebf95506cd0cdd4418f2d0e02dfb83cecd3845d5 Mon Sep 17 00:00:00 2001 From: Test Date: Fri, 4 Sep 2026 10:56:32 -0700 Subject: [PATCH] refactor: simplify auth - remove undefined TenantID concept - Remove TenantID field from LLMAuth (JWT claims handle tenant info) - Remove Scopes field (not part of Poimen's design) - Simplify to 3 core auth types: Bearer, API Key, Custom - Update LLMRouterConfig to only include Auth field - Simplify README examples to per-deployment pattern - Focus on secure token management vs multi-tenant isolation - Clarify token rotation pattern for long-running workflows - Update security section with practical vault integration examples TenantID was introduced without proper context. In Poimen: - JWT token itself contains tenant/customer info in claims - Each deployment gets its own LLM_AUTH_TOKEN from vault - LLM API provider (riotpiao.com) validates token at their end - No need for separate tenant header in Poimen layer Simpler, clearer, more maintainable. --- README.md | 149 +++++++++++---------------------- internal/routing/llm_client.go | 16 ---- internal/routing/llm_router.go | 1 - k8s/git-commit.yaml | 2 +- k8s/worker-deployment.yaml | 2 +- 5 files changed, 51 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index bdc4422..de93506 100644 --- a/README.md +++ b/README.md @@ -546,10 +546,8 @@ Poimen supports multiple authentication methods to federate LLM access across cu #### 1. Bearer Token (JWT/OAuth2) ```go auth := &routing.LLMAuth{ - Type: routing.AuthTypeBearer, - Token: "eyJhbGciOiJIUzI1NiIs...", // JWT token from your auth provider - TenantID: "customer-123", // Optional: tenant isolation - Scopes: "llm:read llm:write", // Optional: OAuth2 scopes + Type: routing.AuthTypeBearer, + Token: "eyJhbGciOiJIUzI1NiIs...", // JWT token from your auth provider } client := routing.NewLLMClientWithAuth(auth) ``` @@ -557,9 +555,8 @@ client := routing.NewLLMClientWithAuth(auth) #### 2. API Key ```go auth := &routing.LLMAuth{ - Type: routing.AuthTypeAPIKey, - APIKey: "sk-xxx-yyy-zzz", // API key from provider - TenantID: "customer-456", + Type: routing.AuthTypeAPIKey, + APIKey: "sk-xxx-yyy-zzz", // API key from provider } client := routing.NewLLMClientWithAuth(auth) ``` @@ -570,7 +567,6 @@ auth := &routing.LLMAuth{ Type: routing.AuthTypeCustom, HeaderName: "X-Custom-Auth", HeaderValue: "custom-token-value", - TenantID: "customer-789", } client := routing.NewLLMClientWithAuth(auth) ``` @@ -581,49 +577,41 @@ config := &routing.LLMRouterConfig{ Provider: openaiProvider, KnowledgeBase: kb, Auth: &routing.LLMAuth{ - Type: routing.AuthTypeBearer, - Token: jwtToken, - TenantID: customerID, // Tenant isolation in multi-tenant deployments + Type: routing.AuthTypeBearer, + Token: jwtToken, }, - TenantID: customerID, // Additional tenant tracking } router := routing.NewLLMRouter(config) ``` -#### Multi-Tenant Federated Access +#### Per-Deployment Auth -For multi-tenant deployments: +Each Poimen deployment gets its own LLM token: ```go -// Per-customer isolated routers -func CreateCustomerRouter(customerID, jwtToken string) (*routing.LLMRouter, error) { +// In K8s secret/vault +LLM_AUTH_TOKEN="eyJhbGciOiJIUzI1NiIs..." + +// In code +func InitializeRouter() (*routing.LLMRouter, error) { + token := os.Getenv("LLM_AUTH_TOKEN") + if token == "" { + return nil, fmt.Errorf("LLM_AUTH_TOKEN not set") + } + auth := &routing.LLMAuth{ - Type: routing.AuthTypeBearer, - Token: jwtToken, - TenantID: customerID, // Passed to LLM API in X-Tenant-ID header - Scopes: "llm:read", // Restrict scopes per customer + Type: routing.AuthTypeBearer, + Token: token, } config := &routing.LLMRouterConfig{ Provider: &routing.LLMClient{}, - KnowledgeBase: globalKB, + KnowledgeBase: kb, Auth: auth, - TenantID: customerID, } return routing.NewLLMRouter(config) } - -// Usage in activity -input := &routing.LLMRouterInput{ - Message: "Analyze code", - MemoryContext: memCtx, -} -output, err := router.Route(ctx, input) -// Headers sent to LLM API: -// Authorization: Bearer -// X-Tenant-ID: -// X-OAuth-Scopes: llm:read ``` #### Token Refresh & Rotation @@ -649,10 +637,8 @@ result, err := client.Chat(ctx, systemPrompt, userMsg) | Header | Set When | Value | Purpose | |--------|----------|-------|---------| -| `Authorization` | Bearer auth | `Bearer {token}` | OAuth2/JWT authentication | +| `Authorization` | Bearer auth | `Bearer {token}` | JWT/OAuth2 authentication | | `X-API-Key` | API Key auth | `{api-key}` | API key authentication | -| `X-Tenant-ID` | Any auth type | `{tenantID}` | Tenant/customer isolation | -| `X-OAuth-Scopes` | Bearer with scopes | Space-separated scopes | OAuth2 scope enforcement | | Custom header | Custom auth | `{headerValue}` | Custom authentication scheme | ### LLMRouter Configuration (Programmatic) @@ -780,94 +766,57 @@ kubectl apply -f k8s/worker-deployment.yaml --- -## Security & Multi-Tenancy +## Security & Token Management -### Token Management +### Never Hardcode Tokens -**Never hardcode tokens!** Use secure secret management: +**Use secure secret management:** ```go // ❌ DON'T DO THIS auth := &routing.LLMAuth{ + Type: routing.AuthTypeBearer, Token: "eyJhbGciOiJIUzI1NiIs...", // Hardcoded! } // ✅ DO THIS -tokenFromVault, _ := vaultClient.GetSecret("llm-token-" + customerID) +token := os.Getenv("LLM_AUTH_TOKEN") auth := &routing.LLMAuth{ - Type: routing.AuthTypeBearer, - Token: tokenFromVault, - TenantID: customerID, + Type: routing.AuthTypeBearer, + Token: token, } ``` **Recommended secret management:** -- Kubernetes Secrets (development) -- HashiCorp Vault (production) -- AWS Secrets Manager / GCP Secret Manager (cloud) -- Sealed Secrets / Sealed Policies +- **Kubernetes Secrets** (development) — stored in etcd +- **HashiCorp Vault** (production) — centralized secret management +- **AWS Secrets Manager** (cloud) — managed service +- **GCP Secret Manager** (cloud) — managed service +- **Sealed Secrets** or **Sealed Policies** — encrypted in git -### Tenant Isolation +### Token Rotation & Refresh -The `TenantID` header enforces tenant isolation on the LLM API side: +For long-running Temporal workflows, refresh tokens before they expire: ```go -// Customer A's workflow -authA := &routing.LLMAuth{ - Type: routing.AuthTypeBearer, - Token: tokenA, - TenantID: "customer-A", // ← Isolates this customer -} +client := routing.NewLLMClientWithAuth(auth) -// Customer B's workflow -authB := &routing.LLMAuth{ - Type: routing.AuthTypeBearer, - Token: tokenB, - TenantID: "customer-B", // ← Isolates this customer +// Later: token expires +newToken := os.Getenv("LLM_AUTH_TOKEN_REFRESHED") +newAuth := &routing.LLMAuth{ + Type: routing.AuthTypeBearer, + Token: newToken, } +client.UpdateAuth(newAuth) ``` -The LLM API server should: -- Validate tenant ownership of tokens -- Enforce data boundaries per tenant -- Log access per tenant ID -- Rate-limit per tenant +### Authorization: LLM API Side -### Scope-Based Access Control - -Use OAuth2 scopes to limit capabilities: - -```go -// Analytics-only customer -analytics := &routing.LLMAuth{ - Type: routing.AuthTypeBearer, - Token: token, - Scopes: "llm:read", // Read-only -} - -// Full-access customer -admin := &routing.LLMAuth{ - Type: routing.AuthTypeBearer, - Token: token, - Scopes: "llm:read llm:write llm:admin", // Full access -} -``` - -The LLM API server should validate scopes before executing requests. - -### Audit & Compliance - -All requests include identifying headers for audit trails: - -``` -POST /v1/chat/completions -Authorization: Bearer eyJhbGc... -X-Tenant-ID: customer-A -X-OAuth-Scopes: llm:read - -# Server logs: -# timestamp=2024-01-15T10:30:00Z tenant=customer-A scope=llm:read status=200 tokens=1500 -``` +The LLM provider (api.riotpiao.com) should: +- Validate JWT signature & expiration +- Enforce API rate limits per token +- Log all requests with token identity +- Support token revocation / blacklisting --- diff --git a/internal/routing/llm_client.go b/internal/routing/llm_client.go index a204d9e..4b55399 100644 --- a/internal/routing/llm_client.go +++ b/internal/routing/llm_client.go @@ -53,12 +53,6 @@ type LLMAuth struct { // HeaderValue is the custom header value for Custom auth HeaderValue string `json:"headerValue,omitempty"` - - // TenantID is the tenant/customer ID for multi-tenant federated access - TenantID string `json:"tenantId,omitempty"` - - // Scopes are the OAuth2 scopes (space-separated) - Scopes string `json:"scopes,omitempty"` } // LLMClient is a simple LLM client for routing @@ -216,16 +210,6 @@ func (c *LLMClient) applyAuth(req *http.Request) error { req.Header.Set(c.auth.HeaderName, c.auth.HeaderValue) } - // Add tenant ID if specified (for multi-tenant federated access) - if c.auth.TenantID != "" { - req.Header.Set("X-Tenant-ID", c.auth.TenantID) - } - - // Add scopes if specified (for OAuth2 flows) - if c.auth.Scopes != "" { - req.Header.Set("X-OAuth-Scopes", c.auth.Scopes) - } - return nil } diff --git a/internal/routing/llm_router.go b/internal/routing/llm_router.go index c00cfd3..1e127d8 100644 --- a/internal/routing/llm_router.go +++ b/internal/routing/llm_router.go @@ -69,7 +69,6 @@ type LLMRouterConfig struct { Validators []WorkflowValidator ParamBinder ParameterBinder Auth *LLMAuth // Authentication config for LLM API - TenantID string // Tenant ID for multi-tenant isolation } // NewLLMRouter creates a new LLM router with custom config diff --git a/k8s/git-commit.yaml b/k8s/git-commit.yaml index 1b48fa8..066ae05 100644 --- a/k8s/git-commit.yaml +++ b/k8s/git-commit.yaml @@ -9,6 +9,6 @@ metadata: app.kubernetes.io/name: poimen app.kubernetes.io/component: orchestrator data: - GIT_COMMIT: "30644a8e" # Updated automatically by CI/CD + GIT_COMMIT: "6eccc86e" # Updated automatically by CI/CD GIT_BRANCH: "main" DEPLOYMENT_DATE: "2026-09-04" diff --git a/k8s/worker-deployment.yaml b/k8s/worker-deployment.yaml index d76eef4..f3d8bc7 100644 --- a/k8s/worker-deployment.yaml +++ b/k8s/worker-deployment.yaml @@ -13,7 +13,7 @@ spec: labels: app: poimen-worker annotations: - git-commit: "30644a8e" # ✅ Updated on each push, triggers rolling restart + git-commit: "6eccc86e" # ✅ Updated on each push, triggers rolling restart deployment-date: "2026-09-04" spec: containers: