fetch_ml/internal/auth/flags.go
Jeremie Fraeys 412d7b82e9
security: implement comprehensive secrets protection
Critical fixes:
- Add SanitizeConnectionString() in storage/db_connect.go to remove passwords
- Add SecureEnvVar() in api/factory.go to clear env vars after reading (JWT_SECRET)
- Clear DB password from config after connection

Logging improvements:
- Enhance logging/sanitize.go with patterns for:
  - PostgreSQL connection strings
  - Generic connection string passwords
  - HTTP Authorization headers
  - Private keys

CLI security:
- Add --security-audit flag to api-server for security checks:
  - Config file permissions
  - Exposed environment variables
  - Running as root
  - API key file permissions
- Add warning when --api-key flag used (process list exposure)

Files changed:
- internal/storage/db_connect.go
- internal/api/factory.go
- internal/logging/sanitize.go
- internal/auth/flags.go
- cmd/api-server/main.go
2026-02-18 16:18:09 -05:00

126 lines
3.8 KiB
Go

package auth
import (
"flag"
"fmt"
"log"
"os"
"strings"
"github.com/jfraeys/fetch_ml/internal/fileutil"
)
// Flags holds authentication-related command line flags
type Flags struct {
APIKey string
APIKeyFile string
ConfigFile string
EnableAuth bool
ShowHelp bool
}
// ParseAuthFlags parses authentication command line flags
func ParseAuthFlags() *Flags {
flags := &Flags{}
flag.StringVar(&flags.APIKey, "api-key", "", "API key for authentication")
flag.StringVar(&flags.APIKeyFile, "api-key-file", "", "Path to file containing API key")
flag.StringVar(&flags.ConfigFile, "config", "", "Configuration file path")
flag.BoolVar(&flags.EnableAuth, "enable-auth", false, "Enable authentication")
flag.BoolVar(&flags.ShowHelp, "auth-help", false, "Show authentication help")
// Custom help flag that doesn't exit
flag.Usage = func() {}
flag.Parse()
return flags
}
// GetAPIKeyFromSources gets API key from multiple sources in priority order
func GetAPIKeyFromSources(flags *Flags) string {
// 1. Command line flag (highest priority)
if flags.APIKey != "" {
// Warn about process list exposure
log.Println("WARNING: --api-key exposes credential in process list. Use --api-key-file for better security.")
return flags.APIKey
}
// 2. Explicit file flag
if flags.APIKeyFile != "" {
contents, readErr := os.ReadFile(flags.APIKeyFile)
if readErr == nil {
return strings.TrimSpace(string(contents))
}
log.Printf("Warning: Could not read API key file %s: %v", flags.APIKeyFile, readErr)
}
// 3. Environment variable
if envKey := os.Getenv("FETCH_ML_API_KEY"); envKey != "" {
return envKey
}
// 4. File-based key (for automated scripts)
if fileKey := os.Getenv("FETCH_ML_API_KEY_FILE"); fileKey != "" {
content, err := fileutil.SecureFileRead(fileKey)
if err == nil {
return strings.TrimSpace(string(content))
}
log.Printf("Warning: Could not read API key file %s: %v", fileKey, err)
}
return ""
}
// ValidateFlags validates parsed authentication flags
func ValidateFlags(flags *Flags) error {
if flags.ShowHelp {
PrintAuthHelp()
os.Exit(0)
}
if flags.APIKeyFile != "" {
if _, err := os.Stat(flags.APIKeyFile); err != nil {
return fmt.Errorf("api key file not found: %s", flags.APIKeyFile)
}
if err := CheckConfigFilePermissions(flags.APIKeyFile); err != nil {
log.Printf("Warning: %v", err)
}
}
// If config file is specified, check if it exists
if flags.ConfigFile != "" {
if _, err := os.Stat(flags.ConfigFile); err != nil {
return fmt.Errorf("config file not found: %s", flags.ConfigFile)
}
// Check file permissions
if err := CheckConfigFilePermissions(flags.ConfigFile); err != nil {
log.Printf("Warning: %v", err)
}
}
return nil
}
// PrintAuthHelp prints authentication-specific help
func PrintAuthHelp() {
fmt.Println("Authentication Options:")
fmt.Println(" --api-key <key> API key for authentication")
fmt.Println(" --api-key-file <path> Read API key from file")
fmt.Println(" --config <file> Configuration file path")
fmt.Println(" --enable-auth Enable authentication (if disabled)")
fmt.Println(" --auth-help Show this help")
fmt.Println()
fmt.Println("Environment Variables:")
fmt.Println(" FETCH_ML_API_KEY API key for authentication")
fmt.Println(" FETCH_ML_API_KEY_FILE File containing API key")
fmt.Println(" FETCH_ML_ENV Environment (development/production)")
fmt.Println(" FETCH_ML_ALLOW_INSECURE_AUTH Allow insecure auth (dev only)")
fmt.Println()
fmt.Println("Security Notes:")
fmt.Println(" - API keys in command line may be visible in process lists")
fmt.Println(" - Environment variables are preferred for automated scripts")
fmt.Println(" - File-based keys should have restricted permissions (600)")
fmt.Println(" - Authentication is mandatory in production environments")
}