Enforcement Methods¶
How the 10 commandments are enforced across the homelab.
Three Layers of Enforcement¶
Layer 1: Database Constraints (Strongest)
└─ Invalid states impossible (CHECK constraints, FOREIGN KEYS)
Layer 2: API Validation (Strong)
└─ Invalid requests rejected (Pydantic models, business logic)
Layer 3: Git Hooks & Documentation (Preventive)
└─ Bad patterns blocked before commit (pre-commit hooks, CLAUDE.md)
Layer 1: Database Constraints (Impossible to Bypass)¶
Example (CharlieHub TCP Routing):
-- TCP routes MUST have entrypoint and backend
ALTER TABLE domains ADD CONSTRAINT chk_tcp_requires_backend
CHECK (
protocol != 'tcp' OR (
backend_host IS NOT NULL AND
backend_port IS NOT NULL
)
);
-- TCP routes CANNOT use HTTP-only features
ALTER TABLE domains ADD CONSTRAINT chk_tcp_no_http_features
CHECK (
protocol != 'tcp' OR (
cors_enabled = false AND
default_path IS NULL
)
);
Result: Invalid states are structurally impossible
# Even direct SQL will fail
docker exec postgres psql -c "
INSERT INTO domains (domain='test.com', protocol='tcp', cors_enabled=true)
"
# ERROR: new row for relation "domains" violates check constraint
Layer 2: API Validation (Friendly Errors)¶
Example (CharlieHub Domain Manager API):
class DomainBase(BaseModel):
protocol: Literal["http", "tcp"]
tcp_entrypoint: Optional[str]
cors_enabled: bool = False
@field_validator('tcp_entrypoint')
def validate_tcp_entrypoint(cls, v, info):
if info.data.get('protocol') == 'tcp' and not v:
raise ValueError('TCP routes require tcp_entrypoint')
return v
@field_validator('cors_enabled')
def validate_cors(cls, v, info):
if info.data.get('protocol') == 'tcp' and v:
raise ValueError('TCP routes cannot use CORS')
return v
Result: Clear, helpful error messages
curl -X POST /api/domains \
-d '{"protocol": "tcp", "cors_enabled": true}'
# Response: 422 Unprocessable Entity
# "TCP routes cannot use CORS (HTTP-only feature)"
Layer 3: Documentation & Git Hooks¶
CLAUDE.md on each node:
Every node has a CLAUDE.md that either: 1. References this central documentation (px1, px2, px3, px5) 2. Extends with node-specific rules (charliehub)
Example (px1 CLAUDE.md):
# CLAUDE.md - px1 Homelab Standards
See centralized standards:
https://hub2:8000/standards/
All px1 configurations follow these rules.
Optional: Git Hooks (prevents bad commits)
#!/bin/bash
# .git/hooks/pre-commit
# Check for direct SQL edits to config tables
if git diff --cached | grep -i "INSERT INTO domains" | grep -v "-- migration"; then
echo "❌ ERROR: Direct SQL inserts detected"
echo "Use the API instead: curl -X POST /api/domains"
exit 1
fi
# Check for manual YAML routes
if git diff --cached --name-only | grep "config/core.*route"; then
echo "❌ ERROR: Manual route file detected"
echo "Use the API to generate routes automatically"
exit 1
fi
# Check for Docker labels (if provider is disabled)
if git diff --cached | grep "traefik.http.routers"; then
echo "⚠️ WARNING: Docker labels detected"
echo "The Docker provider is disabled on this system"
exit 1
fi
exit 0
Per-Node Enforcement¶
charliehub¶
- ✅ Database constraints (TCP routing)
- ✅ API validation (Pydantic models)
- ✅ Pre-commit hook (git-hooks.log)
- ✅ Detailed CLAUDE.md
px1, px2, px3, px5¶
- ✅ Documentation reference (CLAUDE.md → mkdocs)
- ✅ Optional: Git hooks (if applicable)
- ⏳ Future: Database-specific constraints (when APIs are built)
What Happens When You Try to Bypass¶
Scenario 1: Edit Generated Routes.yml¶
$ vim /traefik/config/generated/routes.yml
# Make some manual edits
$ git add .
$ git commit -m "manual route"
# On next generation, your edits are LOST
# routes.yml is regenerated from source (database)
Prevention: Document says "read-only artifact", pre-commit hook warns
Scenario 2: Insert Route Directly to Database¶
$ docker exec postgres psql \
-c "INSERT INTO domains (protocol='tcp', cors_enabled=true)"
# ERROR: new row for relation "domains" violates check constraint "chk_tcp_no_http_features"
Prevention: Database constraint makes it impossible
Scenario 3: Add Docker Label for Routing¶
services:
my-service:
labels:
traefik.http.routers.service.rule: "Host(`test.com`)"
# This does nothing - Docker provider is disabled
# Route doesn't appear in Traefik
# User wastes time wondering why it doesn't work
Prevention: Documentation says not to do this, pre-commit hook warns
Scenario 4: Create Manual Route YAML¶
$ sudo vim /traefik/config/core/emergency-route.yml
# Adds route manually, promises to remove it later
# 6 months later: nobody remembers it's there
Prevention: Pre-commit hook blocks, documentation says not to, CLAUDE.md reminds developer
Summary: Layered Defense¶
| Bypass Method | Layer 1 | Layer 2 | Layer 3 | Result |
|---|---|---|---|---|
| Edit output file | ✗ Regenerated | ✗ Ignored | ⚠️ Warned | Change lost |
| Direct SQL (invalid) | 🔒 Blocked | - | - | Impossible |
| Docker labels | ✗ Provider off | ✗ Rejected | ⚠️ Warned | No effect |
| Manual YAML | ✗ Generated | - | ⚠️ Blocked | Can't commit |
| Direct SQL (valid) | ✓ Allowed | ✗ Audit miss | ⚠️ Warned | Audit gap |
Conclusion: Multiple layers catch different bypass attempts. Constraints make invalid states impossible. API validation catches mistakes. Documentation prevents confusion.
How to Add New Constraints¶
If you discover a pattern that should be impossible:
- Identify the rule: "TCP routes should not have X"
- Add to database:
ALTER TABLE ... ADD CONSTRAINT chk_... - Update API validation: Add to Pydantic model
- Test: Try to violate it, verify it fails
- Document: Add to CLAUDE.md and standards
- Commit: Git commit with explanation
Example (CharlieHub):
ALTER TABLE domains ADD CONSTRAINT chk_tcp_requires_entrypoint
CHECK (protocol != 'tcp' OR tcp_entrypoint IS NOT NULL);
This makes the rule: - Impossible to bypass (Layer 1) - Clear to API users (Layer 2) - Documented for future developers (Layer 3)