Files
teamai-test/.teamai/skills/common/golang-security/references/architecture.md
T

9.9 KiB

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:

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:

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:

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:

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 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
// 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):

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.

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:

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
// 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)
}