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
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
|
|
} |