feat: Production readiness improvements for WHOOSH council formation
Major security, observability, and configuration improvements:
## Security Hardening
- Implemented configurable CORS (no more wildcards)
- Added comprehensive auth middleware for admin endpoints
- Enhanced webhook HMAC validation
- Added input validation and rate limiting
- Security headers and CSP policies
## Configuration Management
- Made N8N webhook URL configurable (WHOOSH_N8N_BASE_URL)
- Replaced all hardcoded endpoints with environment variables
- Added feature flags for LLM vs heuristic composition
- Gitea fetch hardening with EAGER_FILTER and FULL_RESCAN options
## API Completeness
- Implemented GetCouncilComposition function
- Added GET /api/v1/councils/{id} endpoint
- Council artifacts API (POST/GET /api/v1/councils/{id}/artifacts)
- /admin/health/details endpoint with component status
- Database lookup for repository URLs (no hardcoded fallbacks)
## Observability & Performance
- Added OpenTelemetry distributed tracing with goal/pulse correlation
- Performance optimization database indexes
- Comprehensive health monitoring
- Enhanced logging and error handling
## Infrastructure
- Production-ready P2P discovery (replaces mock implementation)
- Removed unused Redis configuration
- Enhanced Docker Swarm integration
- Added migration files for performance indexes
## Code Quality
- Comprehensive input validation
- Graceful error handling and failsafe fallbacks
- Backwards compatibility maintained
- Following security best practices
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,21 +9,25 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig `envconfig:"server"`
|
||||
Database DatabaseConfig `envconfig:"database"`
|
||||
Redis RedisConfig `envconfig:"redis"`
|
||||
GITEA GITEAConfig `envconfig:"gitea"`
|
||||
Auth AuthConfig `envconfig:"auth"`
|
||||
Logging LoggingConfig `envconfig:"logging"`
|
||||
BACKBEAT BackbeatConfig `envconfig:"backbeat"`
|
||||
Docker DockerConfig `envconfig:"docker"`
|
||||
Server ServerConfig `envconfig:"server"`
|
||||
Database DatabaseConfig `envconfig:"database"`
|
||||
GITEA GITEAConfig `envconfig:"gitea"`
|
||||
Auth AuthConfig `envconfig:"auth"`
|
||||
Logging LoggingConfig `envconfig:"logging"`
|
||||
BACKBEAT BackbeatConfig `envconfig:"backbeat"`
|
||||
Docker DockerConfig `envconfig:"docker"`
|
||||
N8N N8NConfig `envconfig:"n8n"`
|
||||
OpenTelemetry OpenTelemetryConfig `envconfig:"opentelemetry"`
|
||||
Composer ComposerConfig `envconfig:"composer"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
ListenAddr string `envconfig:"LISTEN_ADDR" default:":8080"`
|
||||
ReadTimeout time.Duration `envconfig:"READ_TIMEOUT" default:"30s"`
|
||||
WriteTimeout time.Duration `envconfig:"WRITE_TIMEOUT" default:"30s"`
|
||||
ShutdownTimeout time.Duration `envconfig:"SHUTDOWN_TIMEOUT" default:"30s"`
|
||||
ListenAddr string `envconfig:"LISTEN_ADDR" default:":8080"`
|
||||
ReadTimeout time.Duration `envconfig:"READ_TIMEOUT" default:"30s"`
|
||||
WriteTimeout time.Duration `envconfig:"WRITE_TIMEOUT" default:"30s"`
|
||||
ShutdownTimeout time.Duration `envconfig:"SHUTDOWN_TIMEOUT" default:"30s"`
|
||||
AllowedOrigins []string `envconfig:"ALLOWED_ORIGINS" default:"http://localhost:3000,http://localhost:8080"`
|
||||
AllowedOriginsFile string `envconfig:"ALLOWED_ORIGINS_FILE"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
@@ -40,14 +44,6 @@ type DatabaseConfig struct {
|
||||
MaxIdleConns int `envconfig:"DB_MAX_IDLE_CONNS" default:"5"`
|
||||
}
|
||||
|
||||
type RedisConfig struct {
|
||||
Enabled bool `envconfig:"ENABLED" default:"false"`
|
||||
Host string `envconfig:"HOST" default:"localhost"`
|
||||
Port int `envconfig:"PORT" default:"6379"`
|
||||
Password string `envconfig:"PASSWORD"`
|
||||
PasswordFile string `envconfig:"PASSWORD_FILE"`
|
||||
Database int `envconfig:"DATABASE" default:"0"`
|
||||
}
|
||||
|
||||
type GITEAConfig struct {
|
||||
BaseURL string `envconfig:"BASE_URL" required:"true"`
|
||||
@@ -56,6 +52,13 @@ type GITEAConfig struct {
|
||||
WebhookPath string `envconfig:"WEBHOOK_PATH" default:"/webhooks/gitea"`
|
||||
WebhookToken string `envconfig:"WEBHOOK_TOKEN"`
|
||||
WebhookTokenFile string `envconfig:"WEBHOOK_TOKEN_FILE"`
|
||||
|
||||
// Fetch hardening options
|
||||
EagerFilter bool `envconfig:"EAGER_FILTER" default:"true"` // Pre-filter by labels at API level
|
||||
FullRescan bool `envconfig:"FULL_RESCAN" default:"false"` // Ignore since parameter for full rescan
|
||||
DebugURLs bool `envconfig:"DEBUG_URLS" default:"false"` // Log exact URLs being used
|
||||
MaxRetries int `envconfig:"MAX_RETRIES" default:"3"` // Maximum retry attempts
|
||||
RetryDelay time.Duration `envconfig:"RETRY_DELAY" default:"2s"` // Delay between retries
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
@@ -83,6 +86,45 @@ type DockerConfig struct {
|
||||
Host string `envconfig:"HOST" default:"unix:///var/run/docker.sock"`
|
||||
}
|
||||
|
||||
type N8NConfig struct {
|
||||
BaseURL string `envconfig:"BASE_URL" default:"https://n8n.home.deepblack.cloud"`
|
||||
}
|
||||
|
||||
type OpenTelemetryConfig struct {
|
||||
Enabled bool `envconfig:"ENABLED" default:"true"`
|
||||
ServiceName string `envconfig:"SERVICE_NAME" default:"whoosh"`
|
||||
ServiceVersion string `envconfig:"SERVICE_VERSION" default:"1.0.0"`
|
||||
Environment string `envconfig:"ENVIRONMENT" default:"production"`
|
||||
JaegerEndpoint string `envconfig:"JAEGER_ENDPOINT" default:"http://localhost:14268/api/traces"`
|
||||
SampleRate float64 `envconfig:"SAMPLE_RATE" default:"1.0"`
|
||||
}
|
||||
|
||||
type ComposerConfig struct {
|
||||
// Feature flags for experimental features
|
||||
EnableLLMClassification bool `envconfig:"ENABLE_LLM_CLASSIFICATION" default:"false"`
|
||||
EnableLLMSkillAnalysis bool `envconfig:"ENABLE_LLM_SKILL_ANALYSIS" default:"false"`
|
||||
EnableLLMTeamMatching bool `envconfig:"ENABLE_LLM_TEAM_MATCHING" default:"false"`
|
||||
|
||||
// Analysis features
|
||||
EnableComplexityAnalysis bool `envconfig:"ENABLE_COMPLEXITY_ANALYSIS" default:"true"`
|
||||
EnableRiskAssessment bool `envconfig:"ENABLE_RISK_ASSESSMENT" default:"true"`
|
||||
EnableAlternativeOptions bool `envconfig:"ENABLE_ALTERNATIVE_OPTIONS" default:"false"`
|
||||
|
||||
// Debug and monitoring
|
||||
EnableAnalysisLogging bool `envconfig:"ENABLE_ANALYSIS_LOGGING" default:"true"`
|
||||
EnablePerformanceMetrics bool `envconfig:"ENABLE_PERFORMANCE_METRICS" default:"true"`
|
||||
EnableFailsafeFallback bool `envconfig:"ENABLE_FAILSAFE_FALLBACK" default:"true"`
|
||||
|
||||
// LLM model configuration
|
||||
ClassificationModel string `envconfig:"CLASSIFICATION_MODEL" default:"llama3.1:8b"`
|
||||
SkillAnalysisModel string `envconfig:"SKILL_ANALYSIS_MODEL" default:"llama3.1:8b"`
|
||||
MatchingModel string `envconfig:"MATCHING_MODEL" default:"llama3.1:8b"`
|
||||
|
||||
// Performance settings
|
||||
AnalysisTimeoutSecs int `envconfig:"ANALYSIS_TIMEOUT_SECS" default:"60"`
|
||||
SkillMatchThreshold float64 `envconfig:"SKILL_MATCH_THRESHOLD" default:"0.6"`
|
||||
}
|
||||
|
||||
func readSecretFile(filePath string) (string, error) {
|
||||
if filePath == "" {
|
||||
return "", nil
|
||||
@@ -106,14 +148,6 @@ func (c *Config) loadSecrets() error {
|
||||
c.Database.Password = password
|
||||
}
|
||||
|
||||
// Load Redis password from file if specified
|
||||
if c.Redis.PasswordFile != "" {
|
||||
password, err := readSecretFile(c.Redis.PasswordFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Redis.Password = password
|
||||
}
|
||||
|
||||
// Load GITEA token from file if specified
|
||||
if c.GITEA.TokenFile != "" {
|
||||
@@ -155,6 +189,19 @@ func (c *Config) loadSecrets() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Load allowed origins from file if specified
|
||||
if c.Server.AllowedOriginsFile != "" {
|
||||
origins, err := readSecretFile(c.Server.AllowedOriginsFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Server.AllowedOrigins = strings.Split(origins, ",")
|
||||
// Trim whitespace from each origin
|
||||
for i, origin := range c.Server.AllowedOrigins {
|
||||
c.Server.AllowedOrigins[i] = strings.TrimSpace(origin)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user