Major refactoring to hexagonal (ports & adapters) architecture: - Add service layer (apikey_service, project_service) for business logic - Add webhook system with dispatcher and delivery tracking - Add command queue with priority-based processing - Add rate limiting with sliding window algorithm - Add audit logging for command execution - Add OpenTelemetry integration (traces, metrics, spans) - Add circuit breaker for fault tolerance - Add cached repository wrapper for performance - Add comprehensive validation package - Add Kubernetes client integration for pod management - Add database migrations (allowed_ips, audit_log, rate_limiting, queue, webhooks) - Add network policy and PodDisruptionBudget for k8s - Remove legacy executor and projects/registry packages - Untrack secrets.yaml (now managed via envault) - Add coverage.out to .gitignore - Add e2e test infrastructure with docker-compose - Add comprehensive documentation (API, architecture, operations, plans) - Add golangci-lint config and pre-commit hook Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
7.3 KiB
7.3 KiB
Security Architecture
rdev implements defense in depth with multiple security layers.
Authentication
API Keys
All API requests (except health checks) require authentication:
┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐
│ Client │────▶│ Auth │────▶│ Auth │────▶│ Handler │
│ │ │ Middleware │ │ Service │ │ │
└────────────┘ └────────────┘ └────────────┘ └────────────┘
│ │
│ ▼
│ ┌────────────┐
│ │ Postgres │
│ │ (keys) │
│ └────────────┘
▼
Check IP Allowlist
Key Format
rdev_<random_32_chars>
Keys are stored as SHA-256 hashes, never in plaintext.
Authentication Flow
- Extract key from
X-API-Keyheader orAuthorization: Bearerheader - Hash the key with SHA-256
- Look up hash in database
- Verify key is not revoked or expired
- Check IP allowlist (if configured)
- Add key to request context
Scopes
| Scope | Description |
|---|---|
projects:read |
List and view projects |
projects:execute |
Execute commands |
keys:read |
List API keys |
keys:write |
Create/revoke keys |
admin |
Full access |
IP Allowlisting
API keys can be restricted to specific IP addresses or CIDR ranges:
type APIKey struct {
// ...
AllowedIPs []string // CIDR notation: ["192.168.1.0/24", "10.0.0.0/8"]
}
func (k *APIKey) IsIPAllowed(clientIP string) bool {
if len(k.AllowedIPs) == 0 {
return true // No restriction
}
for _, cidr := range k.AllowedIPs {
_, network, _ := net.ParseCIDR(cidr)
if network.Contains(net.ParseIP(clientIP)) {
return true
}
}
return false
}
Command Sanitization
All commands are sanitized before execution to prevent:
Shell Injection Protection
// internal/sanitize/command.go
func ShellCommand(cmd string) error {
// Block command chaining
dangerous := []string{";", "&&", "||", "|", "`", "$(", "${"}
for _, d := range dangerous {
if strings.Contains(cmd, d) {
return fmt.Errorf("command chaining not allowed")
}
}
// Block redirects
if strings.ContainsAny(cmd, "<>") {
return fmt.Errorf("redirects not allowed")
}
// Block destructive commands
if isDestructiveRm(cmd) {
return fmt.Errorf("destructive rm not allowed")
}
return nil
}
Blocked Patterns
| Category | Examples |
|---|---|
| Command chaining | `; && |
| Redirects | > >> < << |
| Destructive | rm -rf /, dd if= |
| Escape sequences | Null bytes, control chars |
Git Command Restrictions
func GitArgs(args []string) error {
if len(args) == 0 {
return errors.New("no git subcommand")
}
blocked := map[string]bool{
"config": true, // Could change credentials
"remote": true, // Could add malicious remotes
}
if blocked[args[0]] {
return fmt.Errorf("git %s not allowed", args[0])
}
// Block force push
if args[0] == "push" {
for _, arg := range args {
if arg == "-f" || arg == "--force" {
return errors.New("force push not allowed")
}
}
}
return nil
}
Claude Prompt Sanitization
func ClaudePrompt(prompt string) error {
// Check for null bytes
if strings.ContainsRune(prompt, 0) {
return errors.New("null bytes not allowed")
}
// Check for control characters
for _, r := range prompt {
if r < 32 && r != '\n' && r != '\r' && r != '\t' {
return errors.New("control characters not allowed")
}
}
return nil
}
Rate Limiting
Request Rate Limiting
Token bucket algorithm limits requests per API key:
type RateLimiter struct {
rate rate.Limit // Requests per second
burst int // Maximum burst size
limiters sync.Map // Per-key limiters
}
func (l *RateLimiter) Allow(key string) bool {
limiter := l.getLimiter(key)
return limiter.Allow()
}
Concurrent Command Limiting
Limits active commands per project:
type CommandLimiter struct {
maxConcurrent int
active map[string]int
mu sync.Mutex
}
func (l *CommandLimiter) TryAcquire(projectID string) bool {
l.mu.Lock()
defer l.mu.Unlock()
if l.active[projectID] >= l.maxConcurrent {
return false
}
l.active[projectID]++
return true
}
Rate Limit Headers
Responses include rate limit information:
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1642089600
Network Security
Kubernetes Network Policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: rdev-api-policy
spec:
podSelector:
matchLabels:
app: rdev-api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: databases
ports:
- protocol: TCP
port: 5432
Pod Security
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
RBAC
Service Account Permissions
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: rdev-api-role
rules:
# Read pods for project discovery
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
# Execute commands in pods
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
# Read ConfigMaps for project config
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
Security Checklist
Development
- All inputs sanitized before use
- No secrets in code or logs
- SQL injection protection (parameterized queries)
- No command injection vectors
Deployment
- TLS termination at ingress
- Network policies applied
- Pod security context configured
- RBAC minimized to required permissions
Operations
- API keys rotated regularly
- Audit logs enabled
- Rate limits configured appropriately
- IP allowlists for sensitive keys
Incident Response
Key Compromise
- Revoke the compromised key immediately
- Review audit logs for unauthorized access
- Issue new key to affected user
- Investigate source of compromise
Rate Limit Abuse
- Identify abusing key from metrics
- Temporarily lower key's rate limit
- Contact key owner
- Consider IP-based blocking if severe