fetch_ml/cmd/api-server/main.go
Jeremie Fraeys cb142213fa
chore(build): update build system, Dockerfiles, and dependencies
Build and deployment improvements:

Makefile:
- Native library build targets with ASan support
- Cross-platform compilation helpers
- Performance benchmark targets
- Security scan integration

Docker:
- secure-prod.Dockerfile: Hardened production image (non-root, minimal surface)
- simple.Dockerfile: Lightweight development image

Scripts:
- build/: Go and native library build scripts, cross-platform builds
- ci/: checks.sh, test.sh, verify-paths.sh for validation
- benchmarks/: Local performance testing and regression tracking
- dev/: Monitoring setup

Dependencies: Update to latest stable with security patches

Commands:
- api-server/main.go: Server initialization updates
- data_manager/data_sync.go: Data sync with visibility
- errors/main.go: Error handling improvements
- tui/: TUI improvements for group management
2026-03-08 13:03:48 -04:00

141 lines
3.8 KiB
Go

// 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 != "" {
//nolint:gosec // G703: apiKeyFile from environment variable, not user input
info, err := os.Stat(apiKeyFile)
if err != nil {
if !os.IsNotExist(err) {
issues = append(issues, fmt.Sprintf("Cannot stat API key file: %v", err))
}
} else {
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)
}
}
}
}