Made-with: Cursor
11 KiB
Flowfish - Security Design
🔒 Overview
Flowfish platform security is designed according to Defense in Depth (layered defense) principles.
🎯 Security Principles
- Least Privilege: Minimum necessary privileges
- Defense in Depth: Multi-layered security
- Zero Trust: Verify every request
- Encryption: Data encryption (at-rest & in-transit)
- Audit: Log all operations
- Isolation: Multi-tenant isolation
🔐 Authentication
1. JWT Token Authentication
Token Structure:
{
"header": {
"alg": "HS256",
"typ": "JWT"
},
"payload": {
"user_id": 123,
"username": "admin",
"roles": ["Super Admin"],
"exp": 1704067200,
"iat": 1704063600
}
}
Token Lifecycle:
- Access Token: 1 hour (short-lived)
- Refresh Token: 7 days (long-lived)
- Storage: HttpOnly cookie (XSS protection)
- Transmission: Authorization header
Token Generation:
import jwt
from datetime import datetime, timedelta
def create_access_token(user_id, username, roles):
payload = {
'user_id': user_id,
'username': username,
'roles': roles,
'exp': datetime.utcnow() + timedelta(hours=1),
'iat': datetime.utcnow()
}
return jwt.encode(payload, SECRET_KEY, algorithm='HS256')
Token Validation:
- Signature verification
- Expiration check
- Blacklist check (Redis)
- Role/permission check
2. OAuth 2.0 / SSO
Supported Providers:
- Google Workspace
- Microsoft Azure AD / Entra ID
- Okta
- Keycloak
OAuth Flow (Authorization Code):
User → Login Button → Flowfish Frontend
→ Redirect to OAuth Provider
→ User authenticates
→ Provider redirects with auth code
→ Flowfish exchanges code for token
→ Create/update user in database
→ Issue JWT token
→ Redirect to dashboard
Security Controls:
- State parameter (CSRF protection)
- PKCE (Proof Key for Code Exchange)
- Token validation
- User account linking
3. Kubernetes Service Account
In-Cluster Authentication:
apiVersion: v1
kind: ServiceAccount
metadata:
name: flowfish-backend
namespace: flowfish
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: flowfish-reader
rules:
- apiGroups: [""]
resources: ["pods", "services", "namespaces"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: flowfish-reader-binding
subjects:
- kind: ServiceAccount
name: flowfish-backend
namespace: flowfish
roleRef:
kind: ClusterRole
name: flowfish-reader
apiGroup: rbac.authorization.k8s.io
👥 Authorization
RBAC (Role-Based Access Control)
Role Hierarchy:
Super Admin (ALL permissions)
└─ Platform Admin (Management + Analysis)
└─ Security Analyst (Security + Read)
└─ Developer (Read-only)
Permission Model:
Permission = Resource + Action
Example: clusters.view, analyses.execute, users.delete
Permission Matrix:
| Resource | Super Admin | Platform Admin | Security Analyst | Developer |
|---|---|---|---|---|
| clusters.* | ✅ | ✅ | ❌ | ❌ |
| analyses.* | ✅ | ✅ | ❌ | ❌ |
| dependencies.view | ✅ | ✅ | ✅ | ✅ |
| anomalies.* | ✅ | ✅ | ✅ | ❌ |
| users.* | ✅ | ❌ | ❌ | ❌ |
| audit.view | ✅ | ✅ | ✅ | ❌ |
Middleware Implementation:
async def require_permission(permission: str):
def decorator(func):
@wraps(func)
async def wrapper(request: Request, *args, **kwargs):
user = request.state.user
if not has_permission(user, permission):
raise HTTPException(403, "Insufficient permissions")
return await func(request, *args, **kwargs)
return wrapper
return decorator
@app.get("/api/v1/clusters")
@require_permission("clusters.view")
async def get_clusters():
...
Multi-Tenant Isolation
Tenant Separation:
- Cluster-level: Users can only access assigned clusters
- Namespace-level: Namespace-based access control
- Data-level: SQL WHERE clauses filter by user
Row-Level Security (PostgreSQL):
-- Enable RLS
ALTER TABLE workloads ENABLE ROW LEVEL SECURITY;
-- Policy: Users can only see workloads from their clusters
CREATE POLICY workload_isolation ON workloads
FOR SELECT
USING (cluster_id IN (
SELECT cluster_id FROM user_cluster_access
WHERE user_id = current_user_id()
));
🔒 Data Encryption
1. At-Rest Encryption
Database Encryption:
PostgreSQL:
# Transparent Data Encryption (TDE)
# Using encrypted volumes or PostgreSQL 15+ TDE
pgcrypto extension for column-level encryption
Encryption Example:
-- Encrypt sensitive fields
CREATE TABLE oauth_providers (
id SERIAL PRIMARY KEY,
client_secret_encrypted TEXT NOT NULL,
...
);
-- Encrypt on insert
INSERT INTO oauth_providers (client_secret_encrypted)
VALUES (pgp_sym_encrypt('secret_value', 'encryption_key'));
-- Decrypt on select
SELECT pgp_sym_decrypt(client_secret_encrypted::bytea, 'encryption_key')
FROM oauth_providers;
ClickHouse:
- Disk encryption via OS (LUKS)
- Column-level encryption (optional)
Neo4j:
- Volume encryption via Kubernetes PVC
Redis:
- Volume encryption
- No built-in encryption
Key Management:
- Development: Environment variables
- Production: Kubernetes Secrets + External Secrets Operator
- Enterprise: HashiCorp Vault, AWS KMS, Azure Key Vault
2. In-Transit Encryption
TLS/SSL Everywhere:
Frontend ↔ User:
HTTPS (TLS 1.3)
- Certificate: Let's Encrypt or corporate CA
- Strong ciphers only
- HSTS header enabled
Frontend ↔ Backend:
HTTPS (TLS 1.2+)
- Internal service mesh (optional: mTLS via Istio)
- Certificate rotation
Backend ↔ Databases:
PostgreSQL:
# Connection string with SSL
DATABASE_URL = "postgresql://user:pass@host:5432/db?sslmode=require"
ClickHouse:
<clickhouse>
<https_port>8443</https_port>
<openSSL>
<server>
<certificateFile>/etc/clickhouse-server/server.crt</certificateFile>
<privateKeyFile>/etc/clickhouse-server/server.key</privateKeyFile>
</server>
</openSSL>
</clickhouse>
Neo4j:
# SSL enabled connection
ssl:
enable: true
cert_path: /path/to/cert.pem
key_path: /path/to/key.pem
🛡️ Application Security
1. Input Validation
Backend Validation (Pydantic):
from pydantic import BaseModel, validator, constr
class ClusterCreate(BaseModel):
name: constr(min_length=3, max_length=255)
api_url: HttpUrl
@validator('name')
def validate_name(cls, v):
if not re.match(r'^[a-z0-9-]+$', v):
raise ValueError('Name must be lowercase alphanumeric with hyphens')
return v
SQL Injection Prevention:
# ✅ Good: Parameterized query
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# ❌ Bad: String concatenation
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
2. XSS Prevention
React:
- Default XSS protection (JSX escaping)
- DangerouslySetInnerHTML avoided
Content Security Policy:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' wss:;
frame-ancestors 'none';
3. CSRF Protection
SameSite Cookies:
response.set_cookie(
key="refresh_token",
value=token,
httponly=True,
secure=True,
samesite="strict"
)
CSRF Token:
# Generate token
csrf_token = secrets.token_urlsafe(32)
session['csrf_token'] = csrf_token
# Validate token
if request.form['csrf_token'] != session['csrf_token']:
raise HTTPException(403, "CSRF token mismatch")
4. Rate Limiting
API Rate Limiting:
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.get("/api/v1/clusters")
@limiter.limit("100/hour")
async def get_clusters():
...
Redis-based Rate Limiting:
def check_rate_limit(user_id, limit=100, window=3600):
key = f"rate_limit:{user_id}"
current = redis.incr(key)
if current == 1:
redis.expire(key, window)
if current > limit:
raise HTTPException(429, "Rate limit exceeded")
🔐 Kubernetes Security
1. Network Policies
Default Deny:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: flowfish
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Allow Backend → Database:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-to-postgres
namespace: flowfish
spec:
podSelector:
matchLabels:
app: postgresql
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 5432
2. Pod Security
Pod Security Standards:
apiVersion: v1
kind: Pod
metadata:
name: backend
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: backend
image: flowfish/backend:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
memory: "4Gi"
cpu: "2000m"
requests:
memory: "1Gi"
cpu: "500m"
3. Secrets Management
Kubernetes Secrets:
apiVersion: v1
kind: Secret
metadata:
name: database-credentials
namespace: flowfish
type: Opaque
stringData:
username: flowfish_user
password: <strong-password>
External Secrets Operator:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: flowfish
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
auth:
kubernetes:
mountPath: "kubernetes"
role: "flowfish"
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-secret
namespace: flowfish
spec:
refreshInterval: 15m
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: database-credentials
data:
- secretKey: password
remoteRef:
key: database/credentials
property: password
📝 Audit Logging
Comprehensive Logging
Log Everything:
- User login/logout
- API requests (method, path, user, IP)
- Database changes (CREATE, UPDATE, DELETE)
- Permission changes
- Configuration changes
- Anomaly detections
- Import/export operations
Log Format (JSON):
{
"timestamp": "2024-01-15T10:30:45.123Z",
"level": "INFO",
"user_id": 123,
"username": "admin",
"action": "create_cluster",
"resource_type": "cluster",
"resource_id": "cluster-prod-01",
"ip_address": "10.0.1.50",
"user_agent": "Mozilla/5.0...",
"request_id": "abc-def-123",
"success": true,
"details": {...}
}
<|tool▁calls▁begin|><|tool▁call▁begin|> StrReplace