- Fix YAML tags in auth config struct (json -> yaml) - Update CLI configs to use pre-hashed API keys - Remove double hashing in WebSocket client - Fix port mapping (9102 -> 9103) in CLI commands - Update permission keys to use jobs:read, jobs:create, etc. - Clean up all debug logging from CLI and server - All user roles now authenticate correctly: * Admin: Can queue jobs and see all jobs * Researcher: Can queue jobs and see own jobs * Analyst: Can see status (read-only access) Multi-user authentication is now fully functional.
100 lines
2.5 KiB
Go
100 lines
2.5 KiB
Go
package auth
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// ValidateAuthConfig enforces authentication requirements
|
|
func (c *Config) ValidateAuthConfig() error {
|
|
// Check if we're in production environment
|
|
isProduction := os.Getenv("FETCH_ML_ENV") == "prod"
|
|
|
|
if isProduction {
|
|
if !c.Enabled {
|
|
return fmt.Errorf("authentication must be enabled in production environment")
|
|
}
|
|
|
|
if len(c.APIKeys) == 0 {
|
|
return fmt.Errorf("at least one API key must be configured in production")
|
|
}
|
|
|
|
// Ensure at least one admin user exists
|
|
hasAdmin := false
|
|
for _, entry := range c.APIKeys {
|
|
if entry.Admin {
|
|
hasAdmin = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !hasAdmin {
|
|
return fmt.Errorf("at least one admin user must be configured in production")
|
|
}
|
|
|
|
// Check for insecure development override
|
|
if os.Getenv("FETCH_ML_ALLOW_INSECURE_AUTH") == "1" {
|
|
log.Printf("WARNING: FETCH_ML_ALLOW_INSECURE_AUTH is enabled in production - this is insecure")
|
|
}
|
|
}
|
|
|
|
// Validate API key format
|
|
for username, entry := range c.APIKeys {
|
|
if string(username) == "" {
|
|
return fmt.Errorf("empty username not allowed")
|
|
}
|
|
|
|
if entry.Hash == "" {
|
|
return fmt.Errorf("user %s has empty API key hash", username)
|
|
}
|
|
|
|
// Validate hash format (should be 64 hex chars for SHA256)
|
|
if len(entry.Hash) != 64 {
|
|
return fmt.Errorf("user %s has invalid API key hash format", username)
|
|
}
|
|
|
|
// Check hash contains only hex characters
|
|
for _, char := range entry.Hash {
|
|
if char < '0' || (char > '9' && char < 'a') || (char > 'f' && char < 'A') || char > 'F' {
|
|
return fmt.Errorf("user %s has invalid API key hash characters", username)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CheckConfigFilePermissions ensures config files have secure permissions
|
|
func CheckConfigFilePermissions(configPath string) error {
|
|
info, err := os.Stat(configPath)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot stat config file: %w", err)
|
|
}
|
|
|
|
// Check file permissions (should be 600)
|
|
perm := info.Mode().Perm()
|
|
if perm&0077 != 0 {
|
|
return fmt.Errorf("config file %s has insecure permissions: %o (should be 600)", configPath, perm)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SanitizeConfig removes sensitive information for logging
|
|
func (c *Config) SanitizeConfig() map[string]interface{} {
|
|
sanitized := map[string]interface{}{
|
|
"enabled": c.Enabled,
|
|
"users": make(map[string]interface{}),
|
|
}
|
|
|
|
for username := range c.APIKeys {
|
|
sanitized["users"].(map[string]interface{})[string(username)] = map[string]interface{}{
|
|
"admin": c.APIKeys[username].Admin,
|
|
"hash": strings.Repeat("*", 8) + "...", // Show only prefix
|
|
}
|
|
}
|
|
|
|
return sanitized
|
|
}
|