security: harden internal MPC API, enforce TLS-only transport, restrict egress

- Add bearer token auth to internal MPC API (port 9800): /keygen, /keys,
  /backup now require Authorization header. Token from MPC_INTERNAL_API_KEY
  env (KMS-synced) or derived from Ed25519 node identity.
- Replace DualModeListener with TLSOnlyListener: plaintext peer connections
  are rejected. TLS 1.3 with PQ key exchange is mandatory.
- Add dedicated 20 RPM rate limiter on bridge signing and intent signing
  endpoints (separate from global 100 RPM).
- Add body size limit (1MB), read/write timeouts, and 10 RPM rate limiter
  to internal MPC API server.
- Restrict K8s NetworkPolicy egress for MPC nodes to peers, postgres,
  valkey, DNS, and HTTPS only (was unrestricted).
- Wire cluster-api-key in api-deployment for authenticated proxy to
  internal MPC API. Forward as Authorization: Bearer header.
- Remove hardcoded dev JWT_SECRET default from compose.yml (now required).
- Add internal-api-key to KMSSecret CRD and StatefulSet env.
This commit is contained in:
2026-04-07 14:00:47 -07:00
parent 7678f7311c
commit 21da3d64e0
11 changed files with 185 additions and 48 deletions
+6
View File
@@ -32,6 +32,12 @@ func NewRateLimiter(requestsPerMinute int) *RateLimiter {
return rl
}
// Allow returns true if the IP has not exceeded the rate limit.
// Exported for use by other packages (e.g., internal MPC API auth).
func (rl *RateLimiter) Allow(ip string) bool {
return rl.allow(ip)
}
// allow returns true if the IP has not exceeded the rate limit.
func (rl *RateLimiter) allow(ip string) bool {
now := time.Now().Unix()
+3
View File
@@ -187,9 +187,11 @@ func NewServer(database *db.Database, mpcBackend MPCBackend, jwtSecret string, o
r.Get("/pay/{token}", s.handlePublicPay)
// Bridge signing endpoints (API key or JWT auth; API key needs "sign" permission)
// Tighter rate limit for signing: 20 RPM per IP (separate from global 100 RPM).
r.Group(func(r chi.Router) {
r.Use(s.authMiddleware)
r.Use(requirePermission("sign"))
r.Use(RateLimitMiddleware(20))
r.Post("/generate_mpc_sig", s.handleBridgeSign)
r.Post("/complete", s.handleBridgeComplete)
})
@@ -344,6 +346,7 @@ func NewServer(database *db.Database, mpcBackend MPCBackend, jwtSecret string, o
r.Get("/settlements/{id}", s.handleGetSettlement)
r.Group(func(r chi.Router) {
r.Use(requireRole("owner", "admin", "signer", "api"))
r.Use(RateLimitMiddleware(20)) // signing rate limit: 20 RPM per IP
r.Post("/intents", s.handleCreateIntent)
r.Post("/intents/{id}/sign", s.handleSignIntent)
r.Post("/intents/{id}/co-sign", s.handleCoSignIntent)