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.
This commit is contained in:
Test
2026-09-04 10:56:47 -07:00
parent 66c17e821f
commit ebf95506cd
5 changed files with 51 additions and 119 deletions
+40 -91
View File
@@ -548,8 +548,6 @@ Poimen supports multiple authentication methods to federate LLM access across cu
auth := &routing.LLMAuth{ auth := &routing.LLMAuth{
Type: routing.AuthTypeBearer, Type: routing.AuthTypeBearer,
Token: "eyJhbGciOiJIUzI1NiIs...", // JWT token from your auth provider Token: "eyJhbGciOiJIUzI1NiIs...", // JWT token from your auth provider
TenantID: "customer-123", // Optional: tenant isolation
Scopes: "llm:read llm:write", // Optional: OAuth2 scopes
} }
client := routing.NewLLMClientWithAuth(auth) client := routing.NewLLMClientWithAuth(auth)
``` ```
@@ -559,7 +557,6 @@ client := routing.NewLLMClientWithAuth(auth)
auth := &routing.LLMAuth{ auth := &routing.LLMAuth{
Type: routing.AuthTypeAPIKey, Type: routing.AuthTypeAPIKey,
APIKey: "sk-xxx-yyy-zzz", // API key from provider APIKey: "sk-xxx-yyy-zzz", // API key from provider
TenantID: "customer-456",
} }
client := routing.NewLLMClientWithAuth(auth) client := routing.NewLLMClientWithAuth(auth)
``` ```
@@ -570,7 +567,6 @@ auth := &routing.LLMAuth{
Type: routing.AuthTypeCustom, Type: routing.AuthTypeCustom,
HeaderName: "X-Custom-Auth", HeaderName: "X-Custom-Auth",
HeaderValue: "custom-token-value", HeaderValue: "custom-token-value",
TenantID: "customer-789",
} }
client := routing.NewLLMClientWithAuth(auth) client := routing.NewLLMClientWithAuth(auth)
``` ```
@@ -583,47 +579,39 @@ config := &routing.LLMRouterConfig{
Auth: &routing.LLMAuth{ Auth: &routing.LLMAuth{
Type: routing.AuthTypeBearer, Type: routing.AuthTypeBearer,
Token: jwtToken, Token: jwtToken,
TenantID: customerID, // Tenant isolation in multi-tenant deployments
}, },
TenantID: customerID, // Additional tenant tracking
} }
router := routing.NewLLMRouter(config) router := routing.NewLLMRouter(config)
``` ```
#### Multi-Tenant Federated Access #### Per-Deployment Auth
For multi-tenant deployments: Each Poimen deployment gets its own LLM token:
```go ```go
// Per-customer isolated routers // In K8s secret/vault
func CreateCustomerRouter(customerID, jwtToken string) (*routing.LLMRouter, error) { 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{ auth := &routing.LLMAuth{
Type: routing.AuthTypeBearer, Type: routing.AuthTypeBearer,
Token: jwtToken, Token: token,
TenantID: customerID, // Passed to LLM API in X-Tenant-ID header
Scopes: "llm:read", // Restrict scopes per customer
} }
config := &routing.LLMRouterConfig{ config := &routing.LLMRouterConfig{
Provider: &routing.LLMClient{}, Provider: &routing.LLMClient{},
KnowledgeBase: globalKB, KnowledgeBase: kb,
Auth: auth, Auth: auth,
TenantID: customerID,
} }
return routing.NewLLMRouter(config) 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 <jwtToken>
// X-Tenant-ID: <customerID>
// X-OAuth-Scopes: llm:read
``` ```
#### Token Refresh & Rotation #### Token Refresh & Rotation
@@ -649,10 +637,8 @@ result, err := client.Chat(ctx, systemPrompt, userMsg)
| Header | Set When | Value | Purpose | | 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-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 | | Custom header | Custom auth | `{headerValue}` | Custom authentication scheme |
### LLMRouter Configuration (Programmatic) ### 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 ```go
// ❌ DON'T DO THIS // ❌ DON'T DO THIS
auth := &routing.LLMAuth{ auth := &routing.LLMAuth{
Type: routing.AuthTypeBearer,
Token: "eyJhbGciOiJIUzI1NiIs...", // Hardcoded! Token: "eyJhbGciOiJIUzI1NiIs...", // Hardcoded!
} }
// ✅ DO THIS // ✅ DO THIS
tokenFromVault, _ := vaultClient.GetSecret("llm-token-" + customerID) token := os.Getenv("LLM_AUTH_TOKEN")
auth := &routing.LLMAuth{ auth := &routing.LLMAuth{
Type: routing.AuthTypeBearer, Type: routing.AuthTypeBearer,
Token: tokenFromVault, Token: token,
TenantID: customerID,
} }
``` ```
**Recommended secret management:** **Recommended secret management:**
- Kubernetes Secrets (development) - **Kubernetes Secrets** (development) — stored in etcd
- HashiCorp Vault (production) - **HashiCorp Vault** (production) — centralized secret management
- AWS Secrets Manager / GCP Secret Manager (cloud) - **AWS Secrets Manager** (cloud) — managed service
- Sealed Secrets / Sealed Policies - **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 ```go
// Customer A's workflow client := routing.NewLLMClientWithAuth(auth)
authA := &routing.LLMAuth{
Type: routing.AuthTypeBearer,
Token: tokenA,
TenantID: "customer-A", // ← Isolates this customer
}
// Customer B's workflow // Later: token expires
authB := &routing.LLMAuth{ newToken := os.Getenv("LLM_AUTH_TOKEN_REFRESHED")
newAuth := &routing.LLMAuth{
Type: routing.AuthTypeBearer, Type: routing.AuthTypeBearer,
Token: tokenB, Token: newToken,
TenantID: "customer-B", // ← Isolates this customer
} }
client.UpdateAuth(newAuth)
``` ```
The LLM API server should: ### Authorization: LLM API Side
- Validate tenant ownership of tokens
- Enforce data boundaries per tenant
- Log access per tenant ID
- Rate-limit per tenant
### Scope-Based Access Control The LLM provider (api.riotpiao.com) should:
- Validate JWT signature & expiration
Use OAuth2 scopes to limit capabilities: - Enforce API rate limits per token
- Log all requests with token identity
```go - Support token revocation / blacklisting
// 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
```
--- ---
-16
View File
@@ -53,12 +53,6 @@ type LLMAuth struct {
// HeaderValue is the custom header value for Custom auth // HeaderValue is the custom header value for Custom auth
HeaderValue string `json:"headerValue,omitempty"` 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 // 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) 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 return nil
} }
-1
View File
@@ -69,7 +69,6 @@ type LLMRouterConfig struct {
Validators []WorkflowValidator Validators []WorkflowValidator
ParamBinder ParameterBinder ParamBinder ParameterBinder
Auth *LLMAuth // Authentication config for LLM API Auth *LLMAuth // Authentication config for LLM API
TenantID string // Tenant ID for multi-tenant isolation
} }
// NewLLMRouter creates a new LLM router with custom config // NewLLMRouter creates a new LLM router with custom config
+1 -1
View File
@@ -9,6 +9,6 @@ metadata:
app.kubernetes.io/name: poimen app.kubernetes.io/name: poimen
app.kubernetes.io/component: orchestrator app.kubernetes.io/component: orchestrator
data: data:
GIT_COMMIT: "30644a8e" # Updated automatically by CI/CD GIT_COMMIT: "6eccc86e" # Updated automatically by CI/CD
GIT_BRANCH: "main" GIT_BRANCH: "main"
DEPLOYMENT_DATE: "2026-09-04" DEPLOYMENT_DATE: "2026-09-04"
+1 -1
View File
@@ -13,7 +13,7 @@ spec:
labels: labels:
app: poimen-worker app: poimen-worker
annotations: 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" deployment-date: "2026-09-04"
spec: spec:
containers: containers: