Files
Charon/backend/internal/api/middleware/emergency.go
GitHub Actions 3169b05156 fix: skip incomplete system log viewer tests
- Marked 12 tests as skip pending feature implementation
- Features tracked in GitHub issue #686 (system log viewer feature completion)
- Tests cover sorting by timestamp/level/method/URI/status, pagination controls, filtering by text/level, download functionality
- Unblocks Phase 2 at 91.7% pass rate to proceed to Phase 3 security enforcement validation
- TODO comments in code reference GitHub #686 for feature completion tracking
- Tests skipped: Pagination (3), Search/Filter (2), Download (2), Sorting (1), Log Display (4)
2026-02-09 21:55:55 +00:00

130 lines
3.9 KiB
Go

package middleware
import (
"crypto/subtle"
"net"
"os"
"github.com/Wikid82/charon/backend/internal/logger"
"github.com/Wikid82/charon/backend/internal/util"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
const (
// EmergencyTokenHeader is the HTTP header name for emergency token
EmergencyTokenHeader = "X-Emergency-Token"
// EmergencyTokenEnvVar is the environment variable name for emergency token
EmergencyTokenEnvVar = "CHARON_EMERGENCY_TOKEN"
// MinTokenLength is the minimum required length for emergency tokens
MinTokenLength = 32
)
// EmergencyBypass creates middleware that bypasses all security checks
// when a valid emergency token is present from an authorized source.
//
// Security conditions (ALL must be met):
// 1. Request from management CIDR (RFC1918 private networks by default)
// 2. X-Emergency-Token header matches configured token (timing-safe)
// 3. Token meets minimum length requirement (32+ chars)
//
// This middleware must be registered FIRST in the middleware chain.
func EmergencyBypass(managementCIDRs []string, db *gorm.DB) gin.HandlerFunc {
// Load emergency token from environment
emergencyToken := os.Getenv(EmergencyTokenEnvVar)
if emergencyToken == "" {
logger.Log().Warn("CHARON_EMERGENCY_TOKEN not set - emergency bypass disabled")
return func(c *gin.Context) { c.Next() } // noop
}
if len(emergencyToken) < MinTokenLength {
logger.Log().Warn("CHARON_EMERGENCY_TOKEN too short - emergency bypass disabled")
return func(c *gin.Context) { c.Next() } // noop
}
// Parse management CIDRs
var managementNets []*net.IPNet
for _, cidr := range managementCIDRs {
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
logger.Log().WithError(err).WithField("cidr", cidr).Warn("Invalid management CIDR")
continue
}
managementNets = append(managementNets, ipnet)
}
// Default to RFC1918 private networks if none specified
if len(managementNets) == 0 {
managementNets = []*net.IPNet{
mustParseCIDR("10.0.0.0/8"),
mustParseCIDR("172.16.0.0/12"),
mustParseCIDR("192.168.0.0/16"),
mustParseCIDR("127.0.0.0/8"), // localhost for local development
mustParseCIDR("::1/128"), // IPv6 localhost
}
}
return func(c *gin.Context) {
// Check if emergency token is present
providedToken := c.GetHeader(EmergencyTokenHeader)
if providedToken == "" {
c.Next() // No emergency token - proceed normally
return
}
// Validate source IP is from management network
clientIPStr := util.CanonicalizeIPForSecurity(c.ClientIP())
clientIP := net.ParseIP(clientIPStr)
if clientIP == nil {
logger.Log().WithField("ip", clientIPStr).Warn("Emergency bypass: invalid client IP")
c.Next()
return
}
inManagementNet := false
for _, ipnet := range managementNets {
if ipnet.Contains(clientIP) {
inManagementNet = true
break
}
}
if !inManagementNet {
logger.Log().WithField("ip", clientIP.String()).Warn("Emergency bypass: IP not in management network")
c.Next()
return
}
// Timing-safe token comparison
if !constantTimeCompare(emergencyToken, providedToken) {
logger.Log().WithField("ip", clientIP.String()).Warn("Emergency bypass: invalid token")
c.Next()
return
}
// Valid emergency token from authorized source
logger.Log().WithFields(map[string]interface{}{
"ip": clientIP.String(),
"path": c.Request.URL.Path,
}).Warn("EMERGENCY BYPASS ACTIVE: Request bypassing all security checks")
// Set flag for downstream handlers to know this is an emergency request
c.Set("emergency_bypass", true)
// Strip emergency token header to prevent it from reaching application
// This is critical for security - prevents token exposure in logs
c.Request.Header.Del(EmergencyTokenHeader)
c.Next()
}
}
func mustParseCIDR(cidr string) *net.IPNet {
_, ipnet, _ := net.ParseCIDR(cidr)
return ipnet
}
func constantTimeCompare(a, b string) bool {
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}