middleware-manager/models/middleware_types.go
2026-01-21 19:27:23 +05:30

510 lines
No EOL
16 KiB
Go

package models
import (
"strings"
)
// MiddlewareProcessor interface for type-specific processing
type MiddlewareProcessor interface {
Process(config map[string]interface{}) map[string]interface{}
}
type BufferingProcessor struct{}
// Process implements special handling for buffering middleware
func (p *BufferingProcessor) Process(config map[string]interface{}) map[string]interface{} {
// Convert byte size fields from float64 to int
byteFields := []string{
"maxRequestBodyBytes",
"memRequestBodyBytes",
"maxResponseBodyBytes",
"memResponseBodyBytes",
}
for _, field := range byteFields {
if val, ok := config[field].(float64); ok {
// Convert to int if it's a whole number
if val == float64(int64(val)) {
config[field] = int64(val)
} else {
config[field] = val
}
}
}
// Process other buffering configuration values with general processor
return preserveTraefikValues(config).(map[string]interface{})
}
// Registry of middleware processors
var middlewareProcessors = map[string]MiddlewareProcessor{
"headers": &HeadersProcessor{},
"basicAuth": &AuthProcessor{},
"forwardAuth": &AuthProcessor{},
"digestAuth": &AuthProcessor{},
"redirectRegex": &PathProcessor{},
"redirectScheme": &PathProcessor{},
"replacePath": &PathProcessor{},
"replacePathRegex": &PathProcessor{},
"stripPrefix": &PathProcessor{},
"stripPrefixRegex": &PathProcessor{},
"chain": &ChainProcessor{},
"plugin": &PluginProcessor{},
"rateLimit": &RateLimitProcessor{},
"inFlightReq": &RateLimitProcessor{},
"ipAllowList": &IPFilterProcessor{},
"buffering": &BufferingProcessor{}, // ADD THIS LINE
// Add more middleware types as needed
}
// GetProcessor returns the appropriate processor for a middleware type
func GetProcessor(middlewareType string) MiddlewareProcessor {
if processor, exists := middlewareProcessors[middlewareType]; exists {
return processor
}
return &DefaultProcessor{} // Fallback processor
}
// ProcessMiddlewareConfig processes a middleware configuration based on its type
func ProcessMiddlewareConfig(middlewareType string, config map[string]interface{}) map[string]interface{} {
processor := GetProcessor(middlewareType)
return processor.Process(config)
}
// DefaultProcessor is the fallback processor for middleware types without a specific processor
type DefaultProcessor struct{}
// Process handles general middleware configuration processing
func (p *DefaultProcessor) Process(config map[string]interface{}) map[string]interface{} {
return preserveTraefikValues(config).(map[string]interface{})
}
// HeadersProcessor handles headers middleware specific processing
type HeadersProcessor struct{}
// Process implements special handling for headers middleware
func (p *HeadersProcessor) Process(config map[string]interface{}) map[string]interface{} {
// Special handling for response headers which might contain empty strings
if customResponseHeaders, ok := config["customResponseHeaders"].(map[string]interface{}); ok {
for key, value := range customResponseHeaders {
// Ensure empty strings are preserved exactly
if strValue, ok := value.(string); ok {
customResponseHeaders[key] = strValue
}
}
}
// Special handling for request headers which might contain empty strings
if customRequestHeaders, ok := config["customRequestHeaders"].(map[string]interface{}); ok {
for key, value := range customRequestHeaders {
// Ensure empty strings are preserved exactly
if strValue, ok := value.(string); ok {
customRequestHeaders[key] = strValue
}
}
}
// Process header fields that are often quoted strings
specialStringFields := []string{
"customFrameOptionsValue", "contentSecurityPolicy",
"referrerPolicy", "permissionsPolicy",
}
for _, field := range specialStringFields {
if value, ok := config[field].(string); ok {
// Preserve string exactly, especially if it contains quotes
config[field] = value
}
}
// Process other header configuration values with general processor
return preserveTraefikValues(config).(map[string]interface{})
}
// AuthProcessor handles authentication middlewares specific processing
type AuthProcessor struct{}
// Process implements special handling for authentication middlewares
func (p *AuthProcessor) Process(config map[string]interface{}) map[string]interface{} {
// ForwardAuth middleware special handling
if address, ok := config["address"].(string); ok {
// Preserve address URL exactly
config["address"] = address
}
// Process trust settings
if trustForward, ok := config["trustForwardHeader"].(bool); ok {
config["trustForwardHeader"] = trustForward
}
// Process headers array
if headers, ok := config["authResponseHeaders"].([]interface{}); ok {
for i, header := range headers {
if headerStr, ok := header.(string); ok {
headers[i] = headerStr
}
}
}
// BasicAuth/DigestAuth middleware special handling
// Preserve exact format of users array
if users, ok := config["users"].([]interface{}); ok {
for i, user := range users {
if userStr, ok := user.(string); ok {
users[i] = userStr
}
}
}
// Process other auth configuration values with general processor
return preserveTraefikValues(config).(map[string]interface{})
}
// PathProcessor handles path manipulation middlewares specific processing
type PathProcessor struct{}
// Process implements special handling for path manipulation middlewares
func (p *PathProcessor) Process(config map[string]interface{}) map[string]interface{} {
// Special handling for regex patterns - these need exact preservation
if regex, ok := config["regex"].(string); ok {
// Preserve regex pattern exactly
config["regex"] = regex
} else if regexList, ok := config["regex"].([]interface{}); ok {
// Handle regex arrays (like in stripPrefixRegex)
for i, r := range regexList {
if regexStr, ok := r.(string); ok {
regexList[i] = regexStr
}
}
}
// Special handling for replacement patterns
if replacement, ok := config["replacement"].(string); ok {
// Preserve replacement pattern exactly
config["replacement"] = replacement
}
// Special handling for path values
if path, ok := config["path"].(string); ok {
// Preserve path exactly
config["path"] = path
}
// Special handling for prefixes arrays
if prefixes, ok := config["prefixes"].([]interface{}); ok {
for i, prefix := range prefixes {
if prefixStr, ok := prefix.(string); ok {
prefixes[i] = prefixStr
}
}
}
// Special handling for scheme
if scheme, ok := config["scheme"].(string); ok {
// Preserve scheme exactly
config["scheme"] = scheme
}
// Process boolean options
if forceSlash, ok := config["forceSlash"].(bool); ok {
config["forceSlash"] = forceSlash
}
if permanent, ok := config["permanent"].(bool); ok {
config["permanent"] = permanent
}
// Process other path manipulation configuration values with general processor
return preserveTraefikValues(config).(map[string]interface{})
}
// ChainProcessor handles chain middleware specific processing
type ChainProcessor struct{}
// Process implements special handling for chain middleware
func (p *ChainProcessor) Process(config map[string]interface{}) map[string]interface{} {
// Process middlewares array
if middlewares, ok := config["middlewares"].([]interface{}); ok {
for i, middleware := range middlewares {
if middlewareStr, ok := middleware.(string); ok {
// If this is not already a fully qualified middleware reference
if !strings.Contains(middlewareStr, "@") {
// Assume it's from our file provider
middlewares[i] = middlewareStr
}
}
}
}
// Process other chain configuration values with general processor
return preserveTraefikValues(config).(map[string]interface{})
}
// PluginProcessor handles plugin middleware specific processing
type PluginProcessor struct{}
// Process implements special handling for plugin middleware
func (p *PluginProcessor) Process(config map[string]interface{}) map[string]interface{} {
// Process plugins (including CrowdSec)
for _, pluginCfg := range config {
if pluginConfig, ok := pluginCfg.(map[string]interface{}); ok {
// Process special fields in plugin configurations
// Process API keys and secrets - must be preserved exactly
keyFields := []string{
"crowdsecLapiKey", "apiKey", "token", "secret", "password",
"key", "accessKey", "secretKey", "captchaSiteKey", "captchaSecretKey",
}
for _, field := range keyFields {
if val, exists := pluginConfig[field]; exists {
if strVal, ok := val.(string); ok {
// Ensure keys are preserved exactly as-is
pluginConfig[field] = strVal
}
}
}
// Process boolean options
boolFields := []string{
"enabled", "failureBlock", "unreachableBlock", "insecureVerify",
"allowLocalRequests", "logLocalRequests", "logAllowedRequests",
"logApiRequests", "silentStartUp", "forceMonthlyUpdate",
"allowUnknownCountries", "blackListMode", "addCountryHeader",
}
for _, field := range boolFields {
for configKey, val := range pluginConfig {
if strings.Contains(configKey, field) {
if boolVal, ok := val.(bool); ok {
pluginConfig[configKey] = boolVal
}
}
}
}
// Process arrays
arrayFields := []string{
"forwardedHeadersTrustedIPs", "clientTrustedIPs", "countries",
}
for _, field := range arrayFields {
for configKey, val := range pluginConfig {
if strings.Contains(configKey, field) {
if arrayVal, ok := val.([]interface{}); ok {
for i, item := range arrayVal {
if strItem, ok := item.(string); ok {
arrayVal[i] = strItem
}
}
}
}
}
}
// Process remaining fields with general processor
preserveTraefikValues(pluginConfig)
}
}
// Process the entire config with general processor
return preserveTraefikValues(config).(map[string]interface{})
}
// RateLimitProcessor handles rate limiting middlewares specific processing
type RateLimitProcessor struct{}
// Process implements special handling for rate limiting middlewares
func (p *RateLimitProcessor) Process(config map[string]interface{}) map[string]interface{} {
// Process numeric values
if average, ok := config["average"].(float64); ok {
// Convert to int if it's a whole number
if average == float64(int(average)) {
config["average"] = int(average)
} else {
config["average"] = average
}
}
if burst, ok := config["burst"].(float64); ok {
// Convert to int if it's a whole number
if burst == float64(int(burst)) {
config["burst"] = int(burst)
} else {
config["burst"] = burst
}
}
if amount, ok := config["amount"].(float64); ok {
// Convert to int if it's a whole number
if amount == float64(int(amount)) {
config["amount"] = int(amount)
} else {
config["amount"] = amount
}
}
// Process sourceCriterion for inFlightReq
if sourceCriterion, ok := config["sourceCriterion"].(map[string]interface{}); ok {
// Process IP strategy
if ipStrategy, ok := sourceCriterion["ipStrategy"].(map[string]interface{}); ok {
// Process depth
if depth, ok := ipStrategy["depth"].(float64); ok {
ipStrategy["depth"] = int(depth)
}
// Process excluded IPs
if excludedIPs, ok := ipStrategy["excludedIPs"].([]interface{}); ok {
for i, ip := range excludedIPs {
if ipStr, ok := ip.(string); ok {
excludedIPs[i] = ipStr
}
}
}
}
// Process requestHost boolean
if requestHost, ok := sourceCriterion["requestHost"].(bool); ok {
sourceCriterion["requestHost"] = requestHost
}
}
// Process other rate limiting configuration values with general processor
return preserveTraefikValues(config).(map[string]interface{})
}
// IPFilterProcessor handles IP filtering middlewares specific processing
type IPFilterProcessor struct{}
// Process implements special handling for IP filtering middlewares
func (p *IPFilterProcessor) Process(config map[string]interface{}) map[string]interface{} {
// Process sourceRange IPs
if sourceRange, ok := config["sourceRange"].([]interface{}); ok {
for i, range_ := range sourceRange {
if rangeStr, ok := range_.(string); ok {
// Preserve IP CIDR notation exactly
sourceRange[i] = rangeStr
}
}
}
// Process other IP filtering configuration values with general processor
return preserveTraefikValues(config).(map[string]interface{})
}
// preserveTraefikValues ensures all values in Traefik configurations are properly handled
// This handles special cases in different middleware types and ensures precise value preservation
func preserveTraefikValues(data interface{}) interface{} {
if data == nil {
return nil
}
switch v := data.(type) {
case map[string]interface{}:
// Process each key-value pair in the map
for key, val := range v {
// Process values based on key names that might need special handling
switch {
// URL or path related fields
case key == "path" || key == "url" || key == "address" || strings.HasSuffix(key, "Path"):
// Ensure path strings keep their exact format
if strVal, ok := val.(string); ok && strVal != "" {
// Keep exact string formatting
v[key] = strVal
} else {
v[key] = preserveTraefikValues(val)
}
// Regex and replacement patterns
case key == "regex" || key == "replacement" || strings.HasSuffix(key, "Regex"):
// Ensure regex patterns are preserved exactly
if strVal, ok := val.(string); ok && strVal != "" {
// Keep exact string formatting with special characters
v[key] = strVal
} else {
v[key] = preserveTraefikValues(val)
}
// API keys and security tokens
case key == "key" || key == "token" || key == "secret" ||
strings.Contains(key, "Key") || strings.Contains(key, "Token") ||
strings.Contains(key, "Secret") || strings.Contains(key, "Password"):
// Ensure API keys and tokens are preserved exactly
if strVal, ok := val.(string); ok {
// Always preserve keys/tokens exactly as-is, even if empty
v[key] = strVal
} else {
v[key] = preserveTraefikValues(val)
}
// Empty header values (common in security headers middleware)
case key == "Server" || key == "X-Powered-By" || strings.HasPrefix(key, "X-"):
// Empty string values are often used to remove headers
if strVal, ok := val.(string); ok {
// Preserve empty strings exactly
v[key] = strVal
} else {
v[key] = preserveTraefikValues(val)
}
// IP addresses and networks
case key == "ip" || key == "clientIP" || strings.Contains(key, "IP") ||
key == "sourceRange" || key == "excludedIPs":
// IP addresses often need exact formatting
v[key] = preserveTraefikValues(val)
// Boolean flags that control behavior
case strings.HasPrefix(key, "is") || strings.HasPrefix(key, "has") ||
strings.HasPrefix(key, "enable") || strings.HasSuffix(key, "enabled") ||
strings.HasSuffix(key, "Enabled") || key == "permanent" || key == "forceSlash":
// Ensure boolean values are preserved as actual booleans
if boolVal, ok := val.(bool); ok {
v[key] = boolVal
} else if strVal, ok := val.(string); ok {
// Convert string "true"/"false" to actual boolean if needed
if strVal == "true" {
v[key] = true
} else if strVal == "false" {
v[key] = false
} else {
v[key] = strVal // Keep as is if not a boolean string
}
} else {
v[key] = preserveTraefikValues(val)
}
// Integer values like timeouts, ports, limits
case key == "amount" || key == "burst" || key == "port" ||
strings.HasSuffix(key, "Seconds") || strings.HasSuffix(key, "Limit") ||
strings.HasSuffix(key, "Timeout") || strings.HasSuffix(key, "Size") ||
key == "depth" || key == "priority" || key == "statusCode" ||
key == "attempts" || key == "responseCode":
// Handle float64 to int conversion for whole numbers, common in JSON unmarshaling
if f, ok := val.(float64); ok && f == float64(int(f)) {
v[key] = int(f)
} else {
v[key] = preserveTraefikValues(val)
}
// Default handling for other keys
default:
v[key] = preserveTraefikValues(val)
}
}
return v
case []interface{}:
// Process each element in the array
for i, item := range v {
v[i] = preserveTraefikValues(item)
}
return v
case string, int, float64, bool:
// Preserve primitive types as they are
return v
default:
// For any other type, return as is
return v
}
}