mirror of
https://github.com/hhftechnology/middleware-manager.git
synced 2026-04-28 03:29:42 +00:00
510 lines
No EOL
16 KiB
Go
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
|
|
}
|
|
} |