Files
sbbb/docs/proxy.md

177 lines
6.6 KiB
Markdown
Raw Normal View History

# The Bouncer 💎
Every request to The Super Boujee Business Box ✨ comes through one door — a custom reverse proxy built on Cloudflare's [Pingora](https://github.com/cloudflare/pingora) framework, written in Rust. It's the bouncer at the club: checks IDs, spots trouble, and knows exactly which room to send you to.
## Why Not Nginx/Traefik?
We tried the off-the-rack options. They didn't fit.
- We wanted ML-powered threat detection compiled into the binary
- Static file serving that replaces nginx sidecars entirely
- Hot-reload TLS from K8s Secrets (not file watchers)
- ACME challenge routing built-in
- Auth subrequests for admin endpoints
- All in pure Rust with rustls (no BoringSSL dependency)
- It's a few thousand lines of Rust. That's it.
## How Requests Flow
The three-layer security pipeline — every request walks the velvet rope in order:
```
Request → DDoS Detection → Scanner Detection → Rate Limiting → Cache → Backend
```
### 1. DDoS Detection (per-IP)
The bouncer watches your vibe over time. Fourteen behavioral features extracted over a 60-second sliding window:
- **Features:** request rate, path diversity, error rate, burst patterns, cookie/referer presence, suspicious paths, content-length patterns
- **Two-stage ensemble:**
- Decision tree (fast path, ~2ns inference)
- MLP fallback if uncertain (~85ns inference)
- **Threshold:** 0.6, minimum 10 events before classifying
- Both models fit in L1 cache (~4KB total)
- Currently in **observe-only mode** (logs decisions, doesn't block)
### 2. Scanner Detection (per-request)
Catching the people who show up with a crowbar. Twelve features analyzed: path structure, headers, user-agent, traversal patterns.
**Hard allowlist for legitimate bots:**
- Known host + cookies → allow (legitimate browser)
- Known host + browser UA + accept-language → allow
**Bot verification:**
- Googlebot: DNS + CIDR validation
- Bingbot: DNS + CIDR validation
- containerd: UA only
Same ensemble architecture as DDoS — decision tree fast path, MLP fallback. If you're a scanner, you get a **403 Forbidden**. Goodbye.
### 3. Rate Limiting (per-identity)
Even welcome guests have limits, darling.
**Identity resolution order:** session cookie > Bearer token > client IP
| Tier | Burst | Sustained |
|------|-------|-----------|
| Authenticated | 200 requests | 50 tokens/sec |
| Unauthenticated | 50 requests | 10 tokens/sec |
- **CIDR bypass:** `10.0.0.0/8`, `127.0.0.0/8`, `::1/128`
- Leaky bucket algorithm, 256 shards for low contention
- Response: **429 + Retry-After header**
### 4. Response Caching
In-memory via pingora-cache. Only requests that pass the security pipeline get cached — blocked requests never touch the cache.
- **Key:** `{host}{path}?{query}`
- Respects `Cache-Control`: no-store, private, s-maxage, max-age, stale-while-revalidate
- Per-route: configurable TTL, enable/disable
## Route Table
Every subdomain gets routed by prefix (the part before the first dot). The bouncer knows every room in the building:
| Prefix | Backend | Notes |
|--------|---------|-------|
| `docs` | Collabora (WOPI) | WebSocket |
| `meet` | Meet frontend + `/api/` → backend | WebSocket |
| `drive` | Drive frontend + `/external_api/` → backend | versioning |
| `mail` | Messages frontend (Caddy) | |
| `messages` | Tuwunel (Matrix) | WebSocket |
| `people` | People frontend + `/api/`, `/admin/` → backend | |
| `find` | Find backend | |
| `src` | Gitea HTTP | WebSocket, SSH on port 22 |
| `auth` | Kratos UI + Hydra OAuth2 | `/.well-known` → Hydra |
| `integration` | La Suite navbar | |
| `cal` | Calendars frontend + `/api/`, `/caldav/` → backend | |
| `projects` | Projects | WebSocket |
| `s3` | SeaweedFS filer | |
| `livekit` | LiveKit dashboard | WebSocket |
| `metrics` | Grafana | |
| `systemmetrics` | Prometheus | |
| `systemlogs` | Loki | |
| `systemtracing` | Tempo | |
| `id` | Kratos admin (auth-gated) | subrequest to `/userinfo` |
| `hydra` | Hydra admin (auth-gated) | subrequest to `/userinfo` |
| `search` | OpenSearch (auth-gated) | subrequest to `/userinfo` |
| `vault` | OpenBao (auth-gated) | subrequest to `/userinfo` |
Path sub-routes use longest-prefix-first matching within each host.
## Auth Request Pattern
For admin endpoints (`id`, `hydra`, `search`, `vault`) — the VIP check:
1. Proxy sends `GET` to Hydra `/userinfo` with original `Cookie`/`Authorization` headers
2. If 2xx: forward to backend (you're in, gorgeous)
3. If non-2xx: **403 Forbidden** (not on the list)
## Static File Serving
Replaces nginx sidecars entirely. No more sidecar containers cluttering up your pods.
- **Try-files chain:** exact → `.html``/index.html` → SPA fallback
- Content-Type auto-detection
- Cache headers: `immutable` for hashed assets, `no-cache` for others
- Path traversal protection (`../` rejected)
## TLS
Pure Rust, no C dependencies in the TLS stack.
- **rustls** + **aws-lc-rs** crypto backend
- **K8s Secret watcher:** cert renewal → writes to emptyDir → graceful upgrade (zero downtime)
- **Local:** mkcert wildcard cert
- **Production:** Let's Encrypt via cert-manager (ACME HTTP-01 challenges routed by the proxy itself)
## ML Training Pipeline
The models aren't downloaded — they're compiled into the binary. Weights baked in = zero model file overhead, L1-cache-resident inference.
1. Collect audit logs from production traffic
2. Auto-label with heuristics (request rate, path repetition, error rate thresholds)
3. Merge with external datasets (CSIC 2010, CIC-IDS2017)
4. Train ensemble offline (burn-rs + WGPU GPU acceleration)
5. Export as Rust `const` arrays
6. Recompile binary
7. Deploy + replay logs to validate accuracy
## Metrics
Prometheus endpoint on `:9090/metrics`:
| Metric | Labels |
|--------|--------|
| `sunbeam_requests_total` | method, host, status, backend |
| `sunbeam_request_duration_seconds` | histogram, 1ms10s buckets |
| `sunbeam_ddos_decisions_total` | |
| `sunbeam_scanner_decisions_total` | |
| `sunbeam_rate_limit_decisions_total` | |
| `sunbeam_cache_status_total` | hit/miss |
| `sunbeam_active_connections` | |
## Audit Logs
Every request produces structured JSON:
- `request_id`, `method`, `host`, `path`, `client_ip`, `status`, `duration_ms`, `user_agent`, `backend`
- These logs are the training data — feed them back into the pipeline to retrain models. The bouncer learns from every shift.
## Deployment
- **Namespace:** `ingress`
- Single replica, `Recreate` strategy
- 256Mi memory limit, 100m CPU
- **ConfigMap:** `pingora-config` (config.toml with all routes + detection config)
- **RBAC:** ServiceAccount with read access to Secrets, ConfigMaps, Ingresses
- **ServiceMonitor** for Prometheus scraping
---
**Source:** [proxy repository](../proxy)