fetch_ml/internal/auth/validator.go
Jeremie Fraeys 37c4d4e9c7
feat(crypto,auth): harden KMS and improve permission handling
KMS improvements:
- cache.go: add LRU eviction with memory-bounded caches
- provider.go: refactor provider initialization and key rotation
- tenant_keys.go: per-tenant key isolation with envelope encryption

Auth layer updates:
- hybrid.go: refine hybrid auth flow for API key + JWT
- permissions_loader.go: faster permission caching with hot-reload
- validator.go: stricter validation with detailed error messages

Security middleware:
- security.go: add rate limiting headers and CORS refinement

Testing and benchmarks:
- Add KMS cache and protocol unit tests
- Add KMS benchmark tests for encryption throughput
- Update KMS integration tests for tenant isolation
2026-03-12 12:04:32 -04:00

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]any {
sanitized := map[string]any{
"enabled": c.Enabled,
"users": make(map[string]any),
}
for username := range c.APIKeys {
sanitized["users"].(map[string]any)[string(username)] = map[string]any{
"admin": c.APIKeys[username].Admin,
"hash": strings.Repeat("*", 8) + "...", // Show only prefix
}
}
return sanitized
}