498 lines
15 KiB
Markdown
498 lines
15 KiB
Markdown
|
|
# Secure By Design Patterns
|
|
|
|
## Overview
|
|
|
|
Build security into system foundations. Core principle: Design systems that are **secure by default, not secured after the fact**.
|
|
|
|
**Key insight**: Preventing security issues through architecture is cheaper and more effective than detecting and responding to them.
|
|
|
|
## When to Use
|
|
|
|
Load this skill when:
|
|
- Designing new systems (greenfield)
|
|
- Refactoring existing architecture
|
|
- Making fundamental architecture decisions
|
|
- Evaluating architecture proposals
|
|
|
|
**Symptoms you need this**:
|
|
- "How do we make this system secure?"
|
|
- Designing authentication, secrets management, data access
|
|
- Architecting microservices, data pipelines, distributed systems
|
|
- Choosing deployment/configuration strategies
|
|
|
|
**Don't use for**:
|
|
- Threat modeling specific attacks (use `ordis/security-architect/threat-modeling`)
|
|
- Implementing security controls (use `ordis/security-architect/security-controls-design`)
|
|
- Reviewing existing designs (use `ordis/security-architect/security-architecture-review`)
|
|
|
|
## Core Patterns
|
|
|
|
### Pattern 1: Zero-Trust Architecture
|
|
|
|
**Principle**: Never trust, always verify. No implicit trust based on network location.
|
|
|
|
#### Three Pillars
|
|
|
|
1. **Verify Explicitly**
|
|
- Authenticate every request (no "internal network = trusted")
|
|
- Authorize based on identity + context (device, location, time, risk)
|
|
- Use strong authentication (mTLS, signed tokens, not IP allowlists alone)
|
|
|
|
2. **Least Privilege Access**
|
|
- Grant minimum necessary permissions
|
|
- Time-limited access (credentials expire, tokens rotate)
|
|
- Resource-level authorization (not just service-level)
|
|
|
|
3. **Assume Breach**
|
|
- Design for "when compromised", not "if compromised"
|
|
- Minimize blast radius (segmentation, isolation)
|
|
- Monitor everything (detect lateral movement)
|
|
|
|
#### Example: Microservices Communication
|
|
|
|
❌ **Not Zero-Trust**:
|
|
```
|
|
Service A → Service B (same network, no auth)
|
|
# Assumes: Internal network = trusted
|
|
# Risk: Compromised service A can access all of service B
|
|
```
|
|
|
|
✅ **Zero-Trust**:
|
|
```
|
|
Service A → Service B (mTLS + JWT + authz check)
|
|
# Every request authenticated + authorized
|
|
# Service B validates: Is this service A? Does it have permission for THIS resource?
|
|
|
|
Implementation:
|
|
- Service mesh (Istio/Linkerd) enforces mTLS
|
|
- Service B validates JWT from service A
|
|
- RBAC policy: service A can only access /resource/{own_resources}
|
|
- Audit log: Record all access attempts
|
|
```
|
|
|
|
**Result**: If service A compromised, attacker cannot impersonate other services or access resources outside A's scope.
|
|
|
|
|
|
### Pattern 2: Immutable Infrastructure
|
|
|
|
**Principle**: No runtime modifications. Replace rather than update.
|
|
|
|
#### Core Concepts
|
|
|
|
1. **Immutable Artifacts**
|
|
- Container images, VM images, binaries
|
|
- Never modified after creation
|
|
- Versioned and signed
|
|
|
|
2. **Deployment Replaces Instances**
|
|
- Updates = deploy new version, terminate old
|
|
- No SSH into servers to patch
|
|
- No runtime configuration changes
|
|
|
|
3. **Configuration as Code**
|
|
- All config in version control
|
|
- Deployments are reproducible
|
|
- Rollback = redeploy previous version
|
|
|
|
#### Benefits
|
|
|
|
- **Security**: No drift from known-good state
|
|
- **Auditability**: All changes in version control
|
|
- **Rollback**: Redeploy previous image
|
|
- **Consistency**: Dev/staging/prod identical
|
|
|
|
#### Example: Application Updates
|
|
|
|
❌ **Mutable (Insecure)**:
|
|
```bash
|
|
# SSH into production server
|
|
ssh prod-server
|
|
|
|
# Update code
|
|
git pull origin main
|
|
|
|
# Restart service
|
|
systemctl restart app
|
|
|
|
# Problem: No audit trail, no rollback, config drift
|
|
```
|
|
|
|
✅ **Immutable (Secure)**:
|
|
```bash
|
|
# Build new image locally
|
|
docker build -t app:v2.1.0 .
|
|
|
|
# Push to registry (signed)
|
|
docker push registry/app:v2.1.0
|
|
|
|
# Deploy new version (Kubernetes)
|
|
kubectl set image deployment/app app=registry/app:v2.1.0
|
|
|
|
# Kubernetes: Creates new pods, terminates old pods
|
|
# Rollback if needed:
|
|
kubectl rollout undo deployment/app
|
|
|
|
# Result: Full audit trail, instant rollback, no drift
|
|
```
|
|
|
|
**Configuration Management**:
|
|
```yaml
|
|
# Configuration in Git, not edited on servers
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: app-config
|
|
data:
|
|
API_TIMEOUT: "30"
|
|
RATE_LIMIT: "1000"
|
|
|
|
# Changes = commit to Git → CI/CD applies → new deployment
|
|
```
|
|
|
|
|
|
### Pattern 3: Security Boundaries
|
|
|
|
**Principle**: Explicit trust zones with validation at every boundary crossing.
|
|
|
|
#### Boundary Identification
|
|
|
|
Trust boundaries are points where data/requests cross from lower-trust to higher-trust zones:
|
|
|
|
```
|
|
Internet (UNTRUSTED)
|
|
↓ BOUNDARY 1
|
|
API Gateway (TRUSTED)
|
|
↓ BOUNDARY 2
|
|
Application Services (TRUSTED)
|
|
↓ BOUNDARY 3
|
|
Database (HIGHLY TRUSTED)
|
|
```
|
|
|
|
#### Validation at Boundaries
|
|
|
|
At EACH boundary:
|
|
1. **Authenticate**: Verify identity
|
|
2. **Authorize**: Check permissions
|
|
3. **Validate**: Check input format/constraints
|
|
4. **Sanitize**: Remove dangerous content
|
|
5. **Log**: Record crossing
|
|
|
|
#### Example: Data Pipeline
|
|
|
|
```
|
|
External API (UNTRUSTED)
|
|
↓ BOUNDARY: API Client
|
|
Validate: JSON schema, required fields
|
|
Authenticate: API key
|
|
Rate limit: 1000 req/hour
|
|
|
|
Message Queue (SEMI-TRUSTED)
|
|
↓ BOUNDARY: Consumer
|
|
Validate: Message structure, idempotency key
|
|
Authorize: Consumer has permission for this message type
|
|
|
|
Processing Service (TRUSTED)
|
|
↓ BOUNDARY: Database Writer
|
|
Validate: Data types, constraints
|
|
Authorize: Service can write to this table
|
|
|
|
Database (HIGHLY TRUSTED)
|
|
```
|
|
|
|
**Key insight**: Never trust data just because it came from "internal" service. Validate at every boundary.
|
|
|
|
#### Minimizing Boundary Surface Area
|
|
|
|
❌ **Large Surface Area (Insecure)**:
|
|
```
|
|
# Database accepts connections from all services
|
|
firewall: allow 0.0.0.0/0 → database:5432
|
|
|
|
# Problem: 50 services can connect, huge attack surface
|
|
```
|
|
|
|
✅ **Small Surface Area (Secure)**:
|
|
```
|
|
# Only specific services connect to database
|
|
firewall:
|
|
allow backend-api → database:5432
|
|
allow analytics → database:5432
|
|
deny all others
|
|
|
|
# Further: Use service mesh with identity-based policies
|
|
```
|
|
|
|
|
|
### Pattern 4: Trusted Computing Base (TCB) Minimization
|
|
|
|
**Principle**: Small security-critical core, everything else untrusted.
|
|
|
|
#### What is TCB?
|
|
|
|
TCB = Components you MUST trust for security. If TCB is compromised, security fails.
|
|
|
|
**Goal**: Minimize TCB size (less code = fewer vulnerabilities).
|
|
|
|
#### Pattern: Small Critical Core
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ Untrusted Zone (Applications) │
|
|
│ - Web servers │
|
|
│ - Application logic │
|
|
│ - User-facing services │
|
|
└────────────┬────────────────────────┘
|
|
│ API calls (validated)
|
|
┌────────────▼────────────────────────┐
|
|
│ TRUSTED COMPUTING BASE (TCB) │
|
|
│ - Authentication service (small!) │
|
|
│ - Secrets vault (minimal code) │
|
|
│ - Audit logger (append-only) │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
#### Example: Secrets Management
|
|
|
|
❌ **Large TCB (Risky)**:
|
|
```
|
|
# Every service has secrets management logic
|
|
# TCB = All 50+ services (huge attack surface)
|
|
|
|
each_service:
|
|
- Fetches secrets from vault
|
|
- Decrypts secrets
|
|
- Manages rotation
|
|
- Handles caching
|
|
|
|
# Problem: Bug in ANY service compromises secrets
|
|
```
|
|
|
|
✅ **Small TCB (Secure)**:
|
|
```
|
|
# Secrets Vault = TCB (small, auditable, formally verified)
|
|
# Applications = Untrusted (use vault API)
|
|
|
|
Vault (TCB):
|
|
- 10,000 lines of code
|
|
- Formally verified
|
|
- Hardware-backed encryption (HSM)
|
|
- Minimal attack surface (no network egress)
|
|
|
|
Applications (Untrusted):
|
|
- Call vault API for secrets
|
|
- Vault enforces all access control
|
|
- Apps cannot access secrets they're not authorized for
|
|
|
|
# Result: Compromise application ≠ compromise vault
|
|
```
|
|
|
|
#### TCB Characteristics
|
|
|
|
1. **Small**: Minimize code size
|
|
2. **Auditable**: Can be formally verified
|
|
3. **Isolated**: Runs in separate environment (sandbox, separate machine)
|
|
4. **Minimal privileges**: TCB has no unnecessary access
|
|
5. **Heavily monitored**: All TCB access logged
|
|
|
|
|
|
### Pattern 5: Fail-Fast Security
|
|
|
|
**Principle**: Validate security properties at construction time. Refuse to operate if misconfigured.
|
|
|
|
#### Construction-Time vs Runtime
|
|
|
|
**Construction time**: When system/component is created (startup, initialization)
|
|
**Runtime**: When system is processing requests
|
|
|
|
**Fail-fast**: Validate security at construction, fail immediately if invalid.
|
|
|
|
#### Example: Security Level Validation
|
|
|
|
❌ **Runtime Validation (Vulnerable)**:
|
|
```python
|
|
# Data pipeline starts, processes data, THEN checks security
|
|
pipeline = Pipeline()
|
|
pipeline.add_source(untrusted_datasource)
|
|
pipeline.add_sink(trusted_datasink)
|
|
pipeline.start() # Starts processing!
|
|
|
|
# Runtime: Check if datasource security level matches sink
|
|
for record in pipeline:
|
|
if record.security_level > sink.max_security_level:
|
|
raise SecurityError("Security mismatch!")
|
|
|
|
# Problem: Exposed data before detecting mismatch
|
|
# Exposure window = time until first mismatched record
|
|
```
|
|
|
|
✅ **Fail-Fast (Secure)**:
|
|
```python
|
|
# Validate security BEFORE processing any data
|
|
pipeline = Pipeline()
|
|
pipeline.add_source(untrusted_datasource)
|
|
pipeline.add_sink(trusted_datasink)
|
|
|
|
# BEFORE start: Validate security properties
|
|
if datasource.security_level > sink.max_security_level:
|
|
raise SecurityError(
|
|
f"Cannot create pipeline: Source {datasource.security_level} "
|
|
f"exceeds sink maximum {sink.max_security_level}"
|
|
)
|
|
|
|
pipeline.start() # Only starts if validation passed
|
|
|
|
# Result: Zero exposure window, fail before processing data
|
|
```
|
|
|
|
#### Startup Validation Checklist
|
|
|
|
Validate at system startup (fail if any check fails):
|
|
- [ ] All required secrets accessible?
|
|
- [ ] TLS certificates valid and not expired?
|
|
- [ ] Database permissions granted?
|
|
- [ ] Security policies loaded?
|
|
- [ ] Encryption keys available?
|
|
|
|
#### Example: Service Startup
|
|
|
|
```python
|
|
class SecureService:
|
|
def __init__(self):
|
|
# Fail-fast validation at construction
|
|
self._validate_security()
|
|
|
|
def _validate_security(self):
|
|
# Check TLS certificate
|
|
if not self.tls_cert_valid():
|
|
raise SecurityError("TLS certificate invalid or expired")
|
|
|
|
# Check encryption keys accessible
|
|
if not self.can_access_keys():
|
|
raise SecurityError("Cannot access encryption keys")
|
|
|
|
# Check database permissions
|
|
if not self.has_required_db_permissions():
|
|
raise SecurityError("Insufficient database permissions")
|
|
|
|
# All checks passed
|
|
logger.info("Security validation passed")
|
|
|
|
def start(self):
|
|
# Only callable after __init__ validation passed
|
|
self.process_requests()
|
|
|
|
# Usage:
|
|
try:
|
|
service = SecureService() # Validates security at construction
|
|
service.start()
|
|
except SecurityError as e:
|
|
logger.error(f"Service failed security validation: {e}")
|
|
sys.exit(1) # Refuse to start with invalid security
|
|
```
|
|
|
|
**Benefits**:
|
|
- **No exposure window**: Catch misconfigurations before processing data
|
|
- **Clear errors**: Fail with specific message ("TLS cert expired")
|
|
- **Operational safety**: Misconfigured systems never reach production
|
|
|
|
|
|
## Pattern Application Framework
|
|
|
|
When designing systems, apply patterns in this order:
|
|
|
|
### 1. Identify Trust Boundaries (Security Boundaries)
|
|
Where does data cross trust zones?
|
|
|
|
### 2. Apply Zero-Trust at Each Boundary
|
|
- Authenticate + authorize every crossing
|
|
- Never trust based on network location
|
|
|
|
### 3. Minimize TCB
|
|
What MUST be trusted? Can it be smaller?
|
|
|
|
### 4. Use Immutable Infrastructure
|
|
Can deployments replace rather than update?
|
|
|
|
### 5. Add Fail-Fast Validation
|
|
Validate security at construction, refuse to start if invalid
|
|
|
|
|
|
## Quick Reference: Pattern Selection
|
|
|
|
| Situation | Pattern | Key Action |
|
|
|-----------|---------|------------|
|
|
| **Designing service-to-service communication** | Zero-Trust | mTLS + JWT + authz on every request |
|
|
| **Deciding deployment strategy** | Immutable Infrastructure | Container images, replace not update |
|
|
| **Architecting multi-tier system** | Security Boundaries | Validate + authenticate at every tier boundary |
|
|
| **Building secrets/auth service** | TCB Minimization | Small core, everything else uses API |
|
|
| **System startup logic** | Fail-Fast Security | Validate security before processing requests |
|
|
|
|
|
|
## Common Mistakes
|
|
|
|
### ❌ Implicit Trust Based on Network
|
|
|
|
**Wrong**: "Services in VPC are trusted, no auth needed"
|
|
|
|
**Right**: Zero-trust - authenticate/authorize every request even within VPC
|
|
|
|
**Why**: Network boundaries are weak. Compromised service = lateral movement without auth.
|
|
|
|
|
|
### ❌ Runtime Security Patches
|
|
|
|
**Wrong**: SSH into production, apply patch, restart
|
|
|
|
**Right**: Build new immutable image, deploy via CI/CD
|
|
|
|
**Why**: Runtime patches create drift, no audit trail, hard to rollback.
|
|
|
|
|
|
### ❌ Large Security-Critical Core
|
|
|
|
**Wrong**: Every service has secrets logic (large TCB)
|
|
|
|
**Right**: Small secrets vault (TCB), services call API (untrusted)
|
|
|
|
**Why**: Smaller TCB = fewer vulnerabilities, easier to audit/verify.
|
|
|
|
|
|
### ❌ Runtime Security Validation
|
|
|
|
**Wrong**: Start processing, check security during execution
|
|
|
|
**Right**: Validate security at construction, refuse to start if invalid
|
|
|
|
**Why**: Runtime checks have exposure windows. Fail-fast = zero exposure.
|
|
|
|
|
|
### ❌ Unclear Trust Boundaries
|
|
|
|
**Wrong**: No explicit boundaries, assume "internal is safe"
|
|
|
|
**Right**: Diagram trust zones, validate at every boundary crossing
|
|
|
|
**Why**: Boundaries are where attacks happen. Explicit validation prevents bypass.
|
|
|
|
|
|
## Cross-References
|
|
|
|
**Use BEFORE this skill**:
|
|
- `ordis/security-architect/threat-modeling` - Identify threats, then apply patterns to address them
|
|
|
|
**Use WITH this skill**:
|
|
- `ordis/security-architect/security-controls-design` - Patterns inform control choices
|
|
|
|
**Use AFTER this skill**:
|
|
- `ordis/security-architect/security-architecture-review` - Review architecture against patterns
|
|
|
|
## Real-World Impact
|
|
|
|
**Systems using secure-by-design patterns:**
|
|
- **Zero-trust + immutable infrastructure**: No successful lateral movement in 2 years despite multiple compromised services (blast radius contained by mTLS + segmentation)
|
|
- **Fail-fast validation**: Prevented VULN-004 class (security level overrides) by refusing to start pipelines with mismatched security levels
|
|
- **TCB minimization**: Secrets vault with 8,000 lines of code (vs 50+ services with embedded secrets logic) - single formal verification point instead of 50 attack surfaces
|
|
|
|
**Key lesson**: **Security built into architecture is more effective and cheaper than security added later.**
|