# Security Architecture Patterns Defense-in-depth, Zero Trust, and authentication patterns for Go services. ## Defense-in-Depth Layers Multiple security controls ensure that failure of one layer doesn't compromise the system: ``` Layer 1: PERIMETER — Rate limiting, DDoS mitigation, WAF Layer 2: NETWORK — TLS/mTLS, network segmentation Layer 3: APPLICATION — Input validation, auth, authz, secure coding Layer 4: DATA — Encryption at rest/transit, access controls, backups ``` ### Go Implementation by Layer **Layer 1 — Rate Limiting Middleware:** ```go import "golang.org/x/time/rate" // Global rate limiter func RateLimitMiddleware(rps float64, burst int) func(http.Handler) http.Handler { limiter := rate.NewLimiter(rate.Limit(rps), burst) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !limiter.Allow() { http.Error(w, "Too Many Requests", http.StatusTooManyRequests) return } next.ServeHTTP(w, r) }) } } ``` **Per-client rate limiting** prevents a single abuser from exhausting the global limit: ```go type ClientRateLimiter struct { mu sync.Mutex clients map[string]*rate.Limiter rps rate.Limit burst int } func (crl *ClientRateLimiter) GetLimiter(clientIP string) *rate.Limiter { crl.mu.Lock() defer crl.mu.Unlock() if limiter, exists := crl.clients[clientIP]; exists { return limiter } limiter := rate.NewLimiter(crl.rps, crl.burst) crl.clients[clientIP] = limiter return limiter } ``` **Layer 2 — mTLS for Service-to-Service:** ```go func mTLSConfig(caCertFile, clientCertFile, clientKeyFile string) (*tls.Config, error) { caCertPool := x509.NewCertPool() caCert, err := os.ReadFile(caCertFile) if err != nil { return nil, err } caCertPool.AppendCertsFromPEM(caCert) cert, err := tls.LoadX509KeyPair(clientCertFile, clientKeyFile) if err != nil { return nil, err } return &tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: caCertPool, MinVersion: tls.VersionTLS12, }, nil } ``` **Layer 3 — Request Body Size Limiting:** ```go func MaxBodySize(maxBytes int64) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxBytes) next.ServeHTTP(w, r) }) } } ``` **Layer 4 — Encryption at Rest (AES-GCM):** Use `crypto/aes` with GCM mode for authenticated encryption. See [Cryptography Security](./cryptography.md) for full `EncryptAESGCM`/`DecryptAESGCM` implementations, algorithm selection guide, and envelope encryption for key rotation. --- ## Zero Trust Principles | Principle | Implementation | | --- | --- | | Verify explicitly | Authenticate and authorize every request — no implicit trust from network location | | Least privilege | Grant minimum permissions; use short-lived tokens (15min access, 7d refresh) | | Assume breach | Segment services, encrypt all communication, log all access for anomaly detection | ```go // Zero Trust middleware: verify identity + permissions on every request func ZeroTrustMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 1. Verify token claims, err := validateJWT(r.Header.Get("Authorization")) if err != nil { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // 2. Verify permissions for this specific resource if !hasPermission(claims.Subject, r.Method, r.URL.Path) { http.Error(w, "Forbidden", http.StatusForbidden) return } // 3. Audit log logger.Info("access_granted", "user", claims.Subject, "method", r.Method, "path", r.URL.Path, "ip", r.RemoteAddr, ) ctx := context.WithValue(r.Context(), userClaimsKey, claims) next.ServeHTTP(w, r.WithContext(ctx)) }) } ``` --- ## Authentication Pattern Selection | Use Case | Recommended Pattern | Go Implementation | | --- | --- | --- | | Web application | OAuth 2.0 + PKCE with OIDC | `golang.org/x/oauth2` | | API authentication | JWT with short expiry + refresh tokens | `github.com/golang-jwt/jwt/v5` | | Service-to-service | mTLS with certificate rotation | `crypto/tls` with `tls.LoadX509KeyPair` | | CLI/Automation | API keys with IP allowlisting | Custom middleware with `net.ParseIP` | | High security | FIDO2/WebAuthn hardware keys | `github.com/go-webauthn/webauthn` | ### JWT Validation — Complete Example JWT validation must pin the signing algorithm to prevent algorithm confusion attacks (where an attacker switches RS256 to HS256 and signs with the public key): ```go import "github.com/golang-jwt/jwt/v5" func validateJWT(authHeader string) (*jwt.RegisteredClaims, error) { tokenString := strings.TrimPrefix(authHeader, "Bearer ") token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) { // Pin signing algorithm — prevents algorithm confusion if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return publicKey, nil }, jwt.WithIssuer("your-issuer"), jwt.WithAudience("your-audience"), jwt.WithExpirationRequired(), ) if err != nil { return nil, err } claims, ok := token.Claims.(*jwt.RegisteredClaims) if !ok { return nil, errors.New("invalid claims") } return claims, nil } ``` ### Password Hashing — Argon2id Argon2id is the recommended password hashing algorithm (memory-hard, resists GPU attacks). For algorithm comparison (bcrypt, scrypt, PBKDF2), see [Cryptography Security](./cryptography.md). ```go import "golang.org/x/crypto/argon2" type PasswordConfig struct { Time uint32 // iterations Memory uint32 // KB Threads uint8 KeyLen uint32 SaltLen uint32 } // OWASP recommended parameters var DefaultConfig = PasswordConfig{ Time: 3, Memory: 64 * 1024, Threads: 4, KeyLen: 32, SaltLen: 16, } func HashPassword(password string, cfg PasswordConfig) (string, error) { salt := make([]byte, cfg.SaltLen) if _, err := rand.Read(salt); err != nil { return "", err } hash := argon2.IDKey([]byte(password), salt, cfg.Time, cfg.Memory, cfg.Threads, cfg.KeyLen) // Encode salt + hash for storage return fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, cfg.Memory, cfg.Time, cfg.Threads, base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(hash), ), nil } ``` --- ## HTTP Security Headers Set on every response via middleware: ```go func SecurityHeadersMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'") w.Header().Set("X-Frame-Options", "DENY") w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains") w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()") next.ServeHTTP(w, r) }) } ``` | Header | Purpose | Recommended Value | | --- | --- | --- | | Content-Security-Policy | Prevents XSS by restricting resource sources | `default-src 'self'; script-src 'self'` | | X-Frame-Options | Prevents clickjacking via framing | `DENY` | | X-Content-Type-Options | Prevents MIME-type sniffing | `nosniff` | | Strict-Transport-Security | Forces HTTPS, prevents protocol downgrade | `max-age=31536000; includeSubDomains` | | Referrer-Policy | Controls referrer header leakage | `strict-origin-when-cross-origin` | | Permissions-Policy | Restricts browser features (camera, mic, geolocation) | `geolocation=(), microphone=(), camera=()` | --- ## Security Anti-Patterns | Anti-Pattern | Why It Fails | Go Fix | | --- | --- | --- | | Security through obscurity | Hidden admin URLs are discoverable via fuzzing, logs, or source code | Authentication + authorization on all endpoints | | Trusting client headers | `X-Forwarded-For`, `X-Is-Admin` — clients forge any header | Server-side identity verification; trust proxy headers only from known load balancers | | Client-side authorization | JavaScript checks are trivially bypassed by any HTTP client | Server-side `if !user.HasRole("admin")` on every protected handler | | Shared secrets across environments | Staging breach → production compromise | Per-environment secrets via secret manager | | Catching and ignoring crypto errors | `_, _ = encrypt(data)` silently proceeds with unencrypted data | Always check error returns — fail closed, never open | | Rolling your own crypto | Custom encryption hasn't been analyzed by cryptographers | Use `crypto/aes` GCM, `golang.org/x/crypto/argon2` | | Verbose error responses | Stack traces and DB errors reveal internals to attackers | Generic errors to clients (`http.Error(w, "Internal error", 500)`), detailed logs server-side | ```go // Anti-pattern: trusting client-provided identity func badHandler(w http.ResponseWriter, r *http.Request) { if r.Header.Get("X-Is-Admin") == "true" { // attacker sets this header adminPanel(w, r) } } // Correct: server-side identity verification func goodHandler(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(userClaimsKey).(*jwt.RegisteredClaims) if !hasRole(claims.Subject, "admin") { http.Error(w, "Forbidden", http.StatusForbidden) return } adminPanel(w, r) } ```