- Add LLMAuth struct with support for Bearer, API Key, and Custom auth types - Implement applyAuth() to inject auth headers into LLM requests - Add X-Tenant-ID header for multi-tenant isolation - Add X-OAuth-Scopes header for OAuth2 scope enforcement - Add UpdateAuth() for runtime token refresh (long-running workflows) - Update LLMRouterConfig with Auth and TenantID fields - Document 4 authentication patterns (Bearer, API Key, Custom, Router config) - Add security best practices: token vault integration, tenant isolation, scopes - Add audit headers for compliance & logging - Create multi-tenant router factory pattern Auth types supported: - Bearer: JWT/OAuth2 tokens (most secure for federated access) - API Key: Static keys (X-API-Key header) - Custom: Any custom header-based scheme - None: No authentication Customers can now pass per-tenant JWT tokens with customized scopes and isolated LLM API access per tenant/customer.
This commit is contained in:
@@ -526,7 +526,7 @@ workflows/
|
||||
# LLM Configuration
|
||||
LLM_ENDPOINT="https://api.riotpiao.com/v1/chat/completions"
|
||||
LLM_MODEL="reasoning"
|
||||
LLM_API_KEY="your-api-key"
|
||||
LOCAL_LLM_BASE_URL="https://api.riotpiao.com" # Override for local dev
|
||||
|
||||
# Temporal Configuration
|
||||
TEMPORAL_HOST_URL="localhost:7233"
|
||||
@@ -539,6 +539,122 @@ MEMORY_SERVICE_URL="http://poimen-memory.poimen.svc.cluster.local:8080"
|
||||
LOG_LEVEL="info"
|
||||
```
|
||||
|
||||
### Authentication & Federation
|
||||
|
||||
Poimen supports multiple authentication methods to federate LLM access across customers and tenants:
|
||||
|
||||
#### 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
|
||||
}
|
||||
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",
|
||||
}
|
||||
client := routing.NewLLMClientWithAuth(auth)
|
||||
```
|
||||
|
||||
#### 3. Custom Header
|
||||
```go
|
||||
auth := &routing.LLMAuth{
|
||||
Type: routing.AuthTypeCustom,
|
||||
HeaderName: "X-Custom-Auth",
|
||||
HeaderValue: "custom-token-value",
|
||||
TenantID: "customer-789",
|
||||
}
|
||||
client := routing.NewLLMClientWithAuth(auth)
|
||||
```
|
||||
|
||||
#### 4. Router Configuration with Auth
|
||||
```go
|
||||
config := &routing.LLMRouterConfig{
|
||||
Provider: openaiProvider,
|
||||
KnowledgeBase: kb,
|
||||
Auth: &routing.LLMAuth{
|
||||
Type: routing.AuthTypeBearer,
|
||||
Token: jwtToken,
|
||||
TenantID: customerID, // Tenant isolation in multi-tenant deployments
|
||||
},
|
||||
TenantID: customerID, // Additional tenant tracking
|
||||
}
|
||||
router := routing.NewLLMRouter(config)
|
||||
```
|
||||
|
||||
#### Multi-Tenant Federated Access
|
||||
|
||||
For multi-tenant deployments:
|
||||
|
||||
```go
|
||||
// Per-customer isolated routers
|
||||
func CreateCustomerRouter(customerID, jwtToken string) (*routing.LLMRouter, error) {
|
||||
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
|
||||
}
|
||||
|
||||
config := &routing.LLMRouterConfig{
|
||||
Provider: &routing.LLMClient{},
|
||||
KnowledgeBase: globalKB,
|
||||
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 <jwtToken>
|
||||
// X-Tenant-ID: <customerID>
|
||||
// X-OAuth-Scopes: llm:read
|
||||
```
|
||||
|
||||
#### Token Refresh & Rotation
|
||||
|
||||
For long-running workflows, update auth at runtime:
|
||||
|
||||
```go
|
||||
client := routing.NewLLMClientWithAuth(oldAuth)
|
||||
|
||||
// Token expires, refresh it
|
||||
newAuth := &routing.LLMAuth{
|
||||
Type: routing.AuthTypeBearer,
|
||||
Token: refreshedToken, // New token from OAuth2 provider
|
||||
TenantID: customerID,
|
||||
}
|
||||
client.UpdateAuth(newAuth)
|
||||
|
||||
// Subsequent requests use new token
|
||||
result, err := client.Chat(ctx, systemPrompt, userMsg)
|
||||
```
|
||||
|
||||
#### Headers Sent to LLM API
|
||||
|
||||
| Header | Set When | Value | Purpose |
|
||||
|--------|----------|-------|---------|
|
||||
| `Authorization` | Bearer auth | `Bearer {token}` | OAuth2/JWT 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)
|
||||
```go
|
||||
config := &routing.LLMRouterConfig{
|
||||
@@ -664,6 +780,97 @@ kubectl apply -f k8s/worker-deployment.yaml
|
||||
|
||||
---
|
||||
|
||||
## Security & Multi-Tenancy
|
||||
|
||||
### Token Management
|
||||
|
||||
**Never hardcode tokens!** Use secure secret management:
|
||||
|
||||
```go
|
||||
// ❌ DON'T DO THIS
|
||||
auth := &routing.LLMAuth{
|
||||
Token: "eyJhbGciOiJIUzI1NiIs...", // Hardcoded!
|
||||
}
|
||||
|
||||
// ✅ DO THIS
|
||||
tokenFromVault, _ := vaultClient.GetSecret("llm-token-" + customerID)
|
||||
auth := &routing.LLMAuth{
|
||||
Type: routing.AuthTypeBearer,
|
||||
Token: tokenFromVault,
|
||||
TenantID: customerID,
|
||||
}
|
||||
```
|
||||
|
||||
**Recommended secret management:**
|
||||
- Kubernetes Secrets (development)
|
||||
- HashiCorp Vault (production)
|
||||
- AWS Secrets Manager / GCP Secret Manager (cloud)
|
||||
- Sealed Secrets / Sealed Policies
|
||||
|
||||
### Tenant Isolation
|
||||
|
||||
The `TenantID` header enforces tenant isolation on the LLM API side:
|
||||
|
||||
```go
|
||||
// Customer A's workflow
|
||||
authA := &routing.LLMAuth{
|
||||
Type: routing.AuthTypeBearer,
|
||||
Token: tokenA,
|
||||
TenantID: "customer-A", // ← Isolates this customer
|
||||
}
|
||||
|
||||
// Customer B's workflow
|
||||
authB := &routing.LLMAuth{
|
||||
Type: routing.AuthTypeBearer,
|
||||
Token: tokenB,
|
||||
TenantID: "customer-B", // ← Isolates this customer
|
||||
}
|
||||
```
|
||||
|
||||
The LLM API server should:
|
||||
- Validate tenant ownership of tokens
|
||||
- Enforce data boundaries per tenant
|
||||
- Log access per tenant ID
|
||||
- Rate-limit per tenant
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
- **[Routing Workflow Spec](./docs/ROUTING_WORKFLOW_SPEC.md)** — Complete spec format reference
|
||||
|
||||
Reference in New Issue
Block a user