You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

315 lines
10 KiB
Go

package config
import (
"log"
"os"
"strconv"
"strings"
"time"
)
type Config struct {
Server ServerConfig
Auth AuthConfig
RateLimit RateLimitConfig
CORS CORSConfig
Services ServicesConfig
Security SecurityConfig
Logging LoggingConfig
Metrics MetricsConfig
}
type ServerConfig struct {
Port string
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
}
type AuthConfig struct {
ZitadelURL string
JWTSigningKey string
TokenExpiration time.Duration
RefreshThreshold time.Duration
RequiredScopes []string
AdminRoles []string
}
type RateLimitConfig struct {
Enabled bool
RequestsPerMinute int
BurstSize int
WindowSize time.Duration
KeyGenerator string
Storage StorageConfig
}
type CORSConfig struct {
AllowedOrigins string
AllowedMethods string
AllowedHeaders string
AllowCredentials bool
MaxAge int
}
type ServicesConfig struct {
GameService ServiceConfig
QuestionService ServiceConfig
UserService ServiceConfig
LeaderboardService ServiceConfig
SessionService ServiceConfig
AdminService ServiceConfig
HealthCheckInterval time.Duration
CircuitBreaker CircuitBreakerConfig
}
type ServiceConfig struct {
URL string
Timeout time.Duration
RetryAttempts int
RetryDelay time.Duration
HealthEndpoint string
Priority int
LoadBalancing LoadBalancingConfig
}
type LoadBalancingConfig struct {
Strategy string
Instances []string
HealthCheck HealthCheckConfig
}
type HealthCheckConfig struct {
Enabled bool
Interval time.Duration
Timeout time.Duration
Path string
}
type CircuitBreakerConfig struct {
Enabled bool
Threshold int
Timeout time.Duration
MaxRequests int
RecoveryTimeout time.Duration
}
type SecurityConfig struct {
IPWhitelist []string
IPBlacklist []string
RequestSizeLimit int64
EnableCSRFProtection bool
CSRFTokenHeader string
EnableXSSProtection bool
ContentTypeValidation bool
AllowedContentTypes []string
SecurityHeaders map[string]string
}
type StorageConfig struct {
Type string
RedisURL string
Options map[string]interface{}
}
type LoggingConfig struct {
Level string
Format string
Output string
AccessLog bool
ErrorLog bool
RequestID bool
Structured bool
}
type MetricsConfig struct {
Enabled bool
Endpoint string
Namespace string
Subsystem string
CollectGo bool
CollectProcess bool
}
func Load() *Config {
return &Config{
Server: ServerConfig{
Port: getEnv("GATEWAY_PORT", ":8086"),
ReadTimeout: getDurationEnv("GATEWAY_READ_TIMEOUT", 30*time.Second),
WriteTimeout: getDurationEnv("GATEWAY_WRITE_TIMEOUT", 30*time.Second),
IdleTimeout: getDurationEnv("GATEWAY_IDLE_TIMEOUT", 120*time.Second),
},
Auth: AuthConfig{
ZitadelURL: getEnv("ZITADEL_URL", "http://localhost:8080"),
JWTSigningKey: getEnv("JWT_SIGNING_KEY", ""),
TokenExpiration: getDurationEnv("TOKEN_EXPIRATION", 24*time.Hour),
RefreshThreshold: getDurationEnv("REFRESH_THRESHOLD", 5*time.Minute),
RequiredScopes: getSliceEnv("REQUIRED_SCOPES", []string{"openid", "profile", "email"}),
AdminRoles: getSliceEnv("ADMIN_ROLES", []string{"admin", "super_admin", "moderator"}),
},
RateLimit: RateLimitConfig{
Enabled: getBoolEnv("RATE_LIMIT_ENABLED", true),
RequestsPerMinute: getIntEnv("RATE_LIMIT_RPM", 60),
BurstSize: getIntEnv("RATE_LIMIT_BURST", 10),
WindowSize: getDurationEnv("RATE_LIMIT_WINDOW", time.Minute),
KeyGenerator: getEnv("RATE_LIMIT_KEY_GENERATOR", "ip"),
Storage: StorageConfig{
Type: getEnv("RATE_LIMIT_STORAGE", "memory"),
RedisURL: getEnv("REDIS_URL", "redis://localhost:6379"),
},
},
CORS: CORSConfig{
AllowedOrigins: getEnv("CORS_ALLOWED_ORIGINS", "*"),
AllowedMethods: getEnv("CORS_ALLOWED_METHODS", "GET,POST,PUT,DELETE,OPTIONS,PATCH"),
AllowedHeaders: getEnv("CORS_ALLOWED_HEADERS", "Origin,Content-Type,Accept,Authorization,X-Requested-With"),
AllowCredentials: getBoolEnv("CORS_ALLOW_CREDENTIALS", true),
MaxAge: getIntEnv("CORS_MAX_AGE", 86400),
},
Services: ServicesConfig{
GameService: ServiceConfig{
URL: getEnv("GAME_SERVICE_URL", "http://localhost:8080"),
Timeout: getDurationEnv("GAME_SERVICE_TIMEOUT", 10*time.Second),
RetryAttempts: getIntEnv("GAME_SERVICE_RETRY_ATTEMPTS", 3),
RetryDelay: getDurationEnv("GAME_SERVICE_RETRY_DELAY", 1*time.Second),
HealthEndpoint: "/health",
Priority: 1,
},
QuestionService: ServiceConfig{
URL: getEnv("QUESTION_SERVICE_URL", "http://localhost:8081"),
Timeout: getDurationEnv("QUESTION_SERVICE_TIMEOUT", 10*time.Second),
RetryAttempts: getIntEnv("QUESTION_SERVICE_RETRY_ATTEMPTS", 3),
RetryDelay: getDurationEnv("QUESTION_SERVICE_RETRY_DELAY", 1*time.Second),
HealthEndpoint: "/health",
Priority: 2,
},
UserService: ServiceConfig{
URL: getEnv("USER_SERVICE_URL", "http://localhost:8082"),
Timeout: getDurationEnv("USER_SERVICE_TIMEOUT", 10*time.Second),
RetryAttempts: getIntEnv("USER_SERVICE_RETRY_ATTEMPTS", 3),
RetryDelay: getDurationEnv("USER_SERVICE_RETRY_DELAY", 1*time.Second),
HealthEndpoint: "/health",
Priority: 3,
},
LeaderboardService: ServiceConfig{
URL: getEnv("LEADERBOARD_SERVICE_URL", "http://localhost:8083"),
Timeout: getDurationEnv("LEADERBOARD_SERVICE_TIMEOUT", 10*time.Second),
RetryAttempts: getIntEnv("LEADERBOARD_SERVICE_RETRY_ATTEMPTS", 3),
RetryDelay: getDurationEnv("LEADERBOARD_SERVICE_RETRY_DELAY", 1*time.Second),
HealthEndpoint: "/health",
Priority: 4,
},
SessionService: ServiceConfig{
URL: getEnv("SESSION_SERVICE_URL", "http://localhost:8084"),
Timeout: getDurationEnv("SESSION_SERVICE_TIMEOUT", 10*time.Second),
RetryAttempts: getIntEnv("SESSION_SERVICE_RETRY_ATTEMPTS", 3),
RetryDelay: getDurationEnv("SESSION_SERVICE_RETRY_DELAY", 1*time.Second),
HealthEndpoint: "/health",
Priority: 5,
},
AdminService: ServiceConfig{
URL: getEnv("ADMIN_SERVICE_URL", "http://localhost:8085"),
Timeout: getDurationEnv("ADMIN_SERVICE_TIMEOUT", 10*time.Second),
RetryAttempts: getIntEnv("ADMIN_SERVICE_RETRY_ATTEMPTS", 3),
RetryDelay: getDurationEnv("ADMIN_SERVICE_RETRY_DELAY", 1*time.Second),
HealthEndpoint: "/health",
Priority: 6,
},
HealthCheckInterval: getDurationEnv("HEALTH_CHECK_INTERVAL", 30*time.Second),
CircuitBreaker: CircuitBreakerConfig{
Enabled: getBoolEnv("CIRCUIT_BREAKER_ENABLED", true),
Threshold: getIntEnv("CIRCUIT_BREAKER_THRESHOLD", 5),
Timeout: getDurationEnv("CIRCUIT_BREAKER_TIMEOUT", 60*time.Second),
MaxRequests: getIntEnv("CIRCUIT_BREAKER_MAX_REQUESTS", 3),
RecoveryTimeout: getDurationEnv("CIRCUIT_BREAKER_RECOVERY_TIMEOUT", 30*time.Second),
},
},
Security: SecurityConfig{
IPWhitelist: getSliceEnv("IP_WHITELIST", []string{}),
IPBlacklist: getSliceEnv("IP_BLACKLIST", []string{}),
RequestSizeLimit: getInt64Env("REQUEST_SIZE_LIMIT", 10*1024*1024),
EnableCSRFProtection: getBoolEnv("ENABLE_CSRF_PROTECTION", true),
CSRFTokenHeader: getEnv("CSRF_TOKEN_HEADER", "X-CSRF-Token"),
EnableXSSProtection: getBoolEnv("ENABLE_XSS_PROTECTION", true),
ContentTypeValidation: getBoolEnv("CONTENT_TYPE_VALIDATION", true),
AllowedContentTypes: getSliceEnv("ALLOWED_CONTENT_TYPES", []string{"application/json", "application/x-www-form-urlencoded", "multipart/form-data"}),
SecurityHeaders: map[string]string{
"X-Frame-Options": "DENY",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"Referrer-Policy": "strict-origin-when-cross-origin",
},
},
Logging: LoggingConfig{
Level: getEnv("LOG_LEVEL", "info"),
Format: getEnv("LOG_FORMAT", "json"),
Output: getEnv("LOG_OUTPUT", "stdout"),
AccessLog: getBoolEnv("ACCESS_LOG", true),
ErrorLog: getBoolEnv("ERROR_LOG", true),
RequestID: getBoolEnv("LOG_REQUEST_ID", true),
Structured: getBoolEnv("STRUCTURED_LOGGING", true),
},
Metrics: MetricsConfig{
Enabled: getBoolEnv("METRICS_ENABLED", true),
Endpoint: getEnv("METRICS_ENDPOINT", "/metrics"),
Namespace: getEnv("METRICS_NAMESPACE", "knowfoolery"),
Subsystem: getEnv("METRICS_SUBSYSTEM", "gateway"),
CollectGo: getBoolEnv("METRICS_COLLECT_GO", true),
CollectProcess: getBoolEnv("METRICS_COLLECT_PROCESS", true),
},
}
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getIntEnv(key string, defaultValue int) int {
if value := os.Getenv(key); value != "" {
if parsed, err := strconv.Atoi(value); err == nil {
return parsed
}
log.Printf("Invalid integer value for %s: %s, using default: %d", key, value, defaultValue)
}
return defaultValue
}
func getInt64Env(key string, defaultValue int64) int64 {
if value := os.Getenv(key); value != "" {
if parsed, err := strconv.ParseInt(value, 10, 64); err == nil {
return parsed
}
log.Printf("Invalid int64 value for %s: %s, using default: %d", key, value, defaultValue)
}
return defaultValue
}
func getBoolEnv(key string, defaultValue bool) bool {
if value := os.Getenv(key); value != "" {
if parsed, err := strconv.ParseBool(value); err == nil {
return parsed
}
log.Printf("Invalid boolean value for %s: %s, using default: %t", key, value, defaultValue)
}
return defaultValue
}
func getDurationEnv(key string, defaultValue time.Duration) time.Duration {
if value := os.Getenv(key); value != "" {
if parsed, err := time.ParseDuration(value); err == nil {
return parsed
}
log.Printf("Invalid duration value for %s: %s, using default: %s", key, value, defaultValue)
}
return defaultValue
}
func getSliceEnv(key string, defaultValue []string) []string {
if value := os.Getenv(key); value != "" {
return strings.Split(value, ",")
}
return defaultValue
}