 131868bdca
			
		
	
	131868bdca
	
	
	
		
			
			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>
		
	
		
			
				
	
	
		
			252 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			252 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package config
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"net/url"
 | |
| 	"os"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| type Config struct {
 | |
| 	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"`
 | |
| 	AllowedOrigins     []string      `envconfig:"ALLOWED_ORIGINS" default:"http://localhost:3000,http://localhost:8080"`
 | |
| 	AllowedOriginsFile string        `envconfig:"ALLOWED_ORIGINS_FILE"`
 | |
| }
 | |
| 
 | |
| type DatabaseConfig struct {
 | |
| 	Host         string `envconfig:"DB_HOST" default:"localhost"`
 | |
| 	Port         int    `envconfig:"DB_PORT" default:"5432"`
 | |
| 	Database     string `envconfig:"DB_NAME" default:"whoosh"`
 | |
| 	Username     string `envconfig:"DB_USER" default:"whoosh"`
 | |
| 	Password     string `envconfig:"DB_PASSWORD"`
 | |
| 	PasswordFile string `envconfig:"DB_PASSWORD_FILE"`
 | |
| 	SSLMode      string `envconfig:"DB_SSL_MODE" default:"disable"`
 | |
| 	URL          string `envconfig:"DB_URL"`
 | |
| 	AutoMigrate  bool   `envconfig:"DB_AUTO_MIGRATE" default:"false"`
 | |
| 	MaxOpenConns int    `envconfig:"DB_MAX_OPEN_CONNS" default:"25"`
 | |
| 	MaxIdleConns int    `envconfig:"DB_MAX_IDLE_CONNS" default:"5"`
 | |
| }
 | |
| 
 | |
| 
 | |
| type GITEAConfig struct {
 | |
| 	BaseURL          string `envconfig:"BASE_URL" required:"true"`
 | |
| 	Token            string `envconfig:"TOKEN"`
 | |
| 	TokenFile        string `envconfig:"TOKEN_FILE"`
 | |
| 	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 {
 | |
| 	JWTSecret         string        `envconfig:"JWT_SECRET"`
 | |
| 	JWTSecretFile     string        `envconfig:"JWT_SECRET_FILE"`
 | |
| 	JWTExpiry         time.Duration `envconfig:"JWT_EXPIRY" default:"24h"`
 | |
| 	ServiceTokens     []string      `envconfig:"SERVICE_TOKENS"`
 | |
| 	ServiceTokensFile string        `envconfig:"SERVICE_TOKENS_FILE"`
 | |
| }
 | |
| 
 | |
| type LoggingConfig struct {
 | |
| 	Level       string `envconfig:"LEVEL" default:"info"`
 | |
| 	Environment string `envconfig:"ENVIRONMENT" default:"production"`
 | |
| }
 | |
| 
 | |
| type BackbeatConfig struct {
 | |
| 	Enabled     bool   `envconfig:"ENABLED" default:"true"`
 | |
| 	ClusterID   string `envconfig:"CLUSTER_ID" default:"chorus-production"`
 | |
| 	AgentID     string `envconfig:"AGENT_ID" default:"whoosh"`
 | |
| 	NATSUrl     string `envconfig:"NATS_URL" default:"nats://backbeat-nats:4222"`
 | |
| }
 | |
| 
 | |
| type DockerConfig struct {
 | |
| 	Enabled bool   `envconfig:"ENABLED" default:"true"`
 | |
| 	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
 | |
| 	}
 | |
| 	
 | |
| 	content, err := os.ReadFile(filePath)
 | |
| 	if err != nil {
 | |
| 		return "", fmt.Errorf("failed to read secret file %s: %w", filePath, err)
 | |
| 	}
 | |
| 	
 | |
| 	return strings.TrimSpace(string(content)), nil
 | |
| }
 | |
| 
 | |
| func (c *Config) loadSecrets() error {
 | |
| 	// Load database password from file if specified
 | |
| 	if c.Database.PasswordFile != "" {
 | |
| 		password, err := readSecretFile(c.Database.PasswordFile)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		c.Database.Password = password
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	// Load GITEA token from file if specified
 | |
| 	if c.GITEA.TokenFile != "" {
 | |
| 		token, err := readSecretFile(c.GITEA.TokenFile)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		c.GITEA.Token = token
 | |
| 	}
 | |
| 
 | |
| 	// Load GITEA webhook token from file if specified
 | |
| 	if c.GITEA.WebhookTokenFile != "" {
 | |
| 		token, err := readSecretFile(c.GITEA.WebhookTokenFile)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		c.GITEA.WebhookToken = token
 | |
| 	}
 | |
| 
 | |
| 	// Load JWT secret from file if specified
 | |
| 	if c.Auth.JWTSecretFile != "" {
 | |
| 		secret, err := readSecretFile(c.Auth.JWTSecretFile)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		c.Auth.JWTSecret = secret
 | |
| 	}
 | |
| 
 | |
| 	// Load service tokens from file if specified
 | |
| 	if c.Auth.ServiceTokensFile != "" {
 | |
| 		tokens, err := readSecretFile(c.Auth.ServiceTokensFile)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		c.Auth.ServiceTokens = strings.Split(tokens, ",")
 | |
| 		// Trim whitespace from each token
 | |
| 		for i, token := range c.Auth.ServiceTokens {
 | |
| 			c.Auth.ServiceTokens[i] = strings.TrimSpace(token)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// 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
 | |
| }
 | |
| 
 | |
| func (c *Config) Validate() error {
 | |
| 	// Load secrets from files first
 | |
| 	if err := c.loadSecrets(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// Validate required database password
 | |
| 	if c.Database.Password == "" {
 | |
| 		return fmt.Errorf("database password is required (set WHOOSH_DATABASE_DB_PASSWORD or WHOOSH_DATABASE_DB_PASSWORD_FILE)")
 | |
| 	}
 | |
| 
 | |
| 	// Build database URL if not provided
 | |
| 	if c.Database.URL == "" {
 | |
| 		c.Database.URL = fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s",
 | |
| 			url.QueryEscape(c.Database.Username),
 | |
| 			url.QueryEscape(c.Database.Password),
 | |
| 			c.Database.Host,
 | |
| 			c.Database.Port,
 | |
| 			url.QueryEscape(c.Database.Database),
 | |
| 			c.Database.SSLMode,
 | |
| 		)
 | |
| 	}
 | |
| 
 | |
| 	if c.GITEA.BaseURL == "" {
 | |
| 		return fmt.Errorf("GITEA base URL is required")
 | |
| 	}
 | |
| 
 | |
| 	if c.GITEA.Token == "" {
 | |
| 		return fmt.Errorf("GITEA token is required (set WHOOSH_GITEA_TOKEN or WHOOSH_GITEA_TOKEN_FILE)")
 | |
| 	}
 | |
| 
 | |
| 	if c.GITEA.WebhookToken == "" {
 | |
| 		return fmt.Errorf("GITEA webhook token is required (set WHOOSH_GITEA_WEBHOOK_TOKEN or WHOOSH_GITEA_WEBHOOK_TOKEN_FILE)")
 | |
| 	}
 | |
| 
 | |
| 	if c.Auth.JWTSecret == "" {
 | |
| 		return fmt.Errorf("JWT secret is required (set WHOOSH_AUTH_JWT_SECRET or WHOOSH_AUTH_JWT_SECRET_FILE)")
 | |
| 	}
 | |
| 
 | |
| 	if len(c.Auth.ServiceTokens) == 0 {
 | |
| 		return fmt.Errorf("at least one service token is required (set WHOOSH_AUTH_SERVICE_TOKENS or WHOOSH_AUTH_SERVICE_TOKENS_FILE)")
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| } |