// Package main implements the fetch_ml API server package main import ( "flag" "fmt" "log" "os" "github.com/jfraeys/fetch_ml/internal/api" ) // Build variables injected at build time var ( BuildHash = "unknown" BuildTime = "unknown" ) func main() { configFile := flag.String("config", "configs/api/dev.yaml", "Configuration file path") apiKey := flag.String("api-key", "", "API key for authentication") showVersion := flag.Bool("version", false, "Show version and build info") verifyBuild := flag.Bool("verify", false, "Verify build integrity") securityAudit := flag.Bool("security-audit", false, "Run security audit and exit") flag.Parse() // Handle version display if *showVersion { fmt.Printf("fetch_ml API Server\n") fmt.Printf(" Build Hash: %s\n", BuildHash) fmt.Printf(" Build Time: %s\n", BuildTime) os.Exit(0) } // Handle build verification (placeholder - always true for now) if *verifyBuild { fmt.Printf("Build verification: OK\n") fmt.Printf(" Build Hash: %s\n", BuildHash) os.Exit(0) } // Handle security audit if *securityAudit { runSecurityAudit(*configFile) os.Exit(0) } // Create and start server server, err := api.NewServer(*configFile) if err != nil { log.Fatalf("Failed to create server: %v", err) } if err := server.Start(); err != nil { log.Fatalf("Failed to start server: %v", err) } // Wait for shutdown server.WaitForShutdown() // Reserved for future authentication enhancements _ = apiKey } // runSecurityAudit performs security checks and reports issues func runSecurityAudit(configFile string) { fmt.Println("=== Security Audit ===") issues := []string{} warnings := []string{} // Check 1: Config file permissions if info, err := os.Stat(configFile); err == nil { mode := info.Mode().Perm() if mode&0077 != 0 { issues = append(issues, fmt.Sprintf("Config file %s is world/group readable (permissions: %04o)", configFile, mode)) } else { fmt.Printf("Config file permissions: %04o (secure)\n", mode) } } else { warnings = append(warnings, fmt.Sprintf("Could not check config file: %v", err)) } // Check 2: Environment variable exposure sensitiveVars := []string{"JWT_SECRET", "FETCH_ML_API_KEY", "DATABASE_PASSWORD", "REDIS_PASSWORD"} exposedVars := []string{} for _, v := range sensitiveVars { if os.Getenv(v) != "" { exposedVars = append(exposedVars, v) } } if len(exposedVars) > 0 { warnings = append(warnings, fmt.Sprintf("Sensitive environment variables exposed: %v (will be cleared on startup)", exposedVars)) } else { fmt.Println("No sensitive environment variables exposed") } // Check 3: Running as root if os.Getuid() == 0 { issues = append(issues, "Running as root (UID 0) - should run as non-root user") } else { fmt.Printf("Running as non-root user (UID: %d)\n", os.Getuid()) } // Check 4: API key file permissions apiKeyFile := os.Getenv("FETCH_ML_API_KEY_FILE") if apiKeyFile != "" { if info, err := os.Stat(apiKeyFile); err == nil { mode := info.Mode().Perm() if mode&0077 != 0 { issues = append(issues, fmt.Sprintf("API key file %s is world/group readable (permissions: %04o)", apiKeyFile, mode)) } else { fmt.Printf("API key file permissions: %04o (secure)\n", mode) } } } // Report results fmt.Println() if len(issues) == 0 && len(warnings) == 0 { fmt.Println("All security checks passed") } else { if len(issues) > 0 { fmt.Printf("✗ Found %d security issue(s):\n", len(issues)) for _, issue := range issues { fmt.Printf(" - %s\n", issue) } } if len(warnings) > 0 { fmt.Printf("⚠ Found %d warning(s):\n", len(warnings)) for _, warning := range warnings { fmt.Printf(" - %s\n", warning) } } } }