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:
@@ -546,10 +546,8 @@ Poimen supports multiple authentication methods to federate LLM access across cu
|
|||||||
#### 1. Bearer Token (JWT/OAuth2)
|
#### 1. Bearer Token (JWT/OAuth2)
|
||||||
```go
|
```go
|
||||||
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)
|
||||||
```
|
```
|
||||||
@@ -557,9 +555,8 @@ client := routing.NewLLMClientWithAuth(auth)
|
|||||||
#### 2. API Key
|
#### 2. API Key
|
||||||
```go
|
```go
|
||||||
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)
|
||||||
```
|
```
|
||||||
@@ -581,49 +577,41 @@ config := &routing.LLMRouterConfig{
|
|||||||
Provider: openaiProvider,
|
Provider: openaiProvider,
|
||||||
KnowledgeBase: kb,
|
KnowledgeBase: kb,
|
||||||
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")
|
||||||
Type: routing.AuthTypeBearer,
|
newAuth := &routing.LLMAuth{
|
||||||
Token: tokenB,
|
Type: routing.AuthTypeBearer,
|
||||||
TenantID: "customer-B", // ← Isolates this customer
|
Token: newToken,
|
||||||
}
|
}
|
||||||
|
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
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user