package security import ( "crypto/rand" "crypto/sha256" "crypto/subtle" "encoding/base64" "encoding/hex" "fmt" "net" "regexp" "strings" "sync" "time" "github.com/gofiber/fiber/v2" "golang.org/x/crypto/bcrypt" ) // ═══════════════════════════════════════════════════════════ // 🔐 CONSTANTES DE SEGURANÇA // ═══════════════════════════════════════════════════════════ const ( // Bcrypt cost (12-14 recomendado para produção) BcryptCost = 12 // Tamanho mínimo de senha MinPasswordLength = 12 // Tamanho da API Key APIKeyLength = 32 // Prefixo da API Key APIKeyPrefix = "ophion_" // Rate limit padrão (requests por minuto) DefaultRateLimit = 100 // Rate limit para auth (tentativas por minuto) AuthRateLimit = 5 // Tempo de bloqueio após falhas (minutos) LockoutDuration = 15 // Máximo de tentativas antes do bloqueio MaxFailedAttempts = 5 ) // ═══════════════════════════════════════════════════════════ // 🔑 API KEY MANAGEMENT // ═══════════════════════════════════════════════════════════ // GenerateAPIKey cria uma nova API key segura func GenerateAPIKey() (key string, hash string, prefix string) { bytes := make([]byte, APIKeyLength) if _, err := rand.Read(bytes); err != nil { panic("failed to generate random bytes") } key = APIKeyPrefix + hex.EncodeToString(bytes) hash = HashAPIKey(key) prefix = key[:len(APIKeyPrefix)+8] // ophion_xxxxxxxx return key, hash, prefix } // HashAPIKey cria hash SHA256 da API key func HashAPIKey(key string) string { hash := sha256.Sum256([]byte(key)) return hex.EncodeToString(hash[:]) } // ValidateAPIKeyFormat verifica formato da API key func ValidateAPIKeyFormat(key string) bool { if !strings.HasPrefix(key, APIKeyPrefix) { return false } // ophion_ + 64 hex chars return len(key) == len(APIKeyPrefix)+64 } // SecureCompare compara strings em tempo constante (previne timing attacks) func SecureCompare(a, b string) bool { return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1 } // ═══════════════════════════════════════════════════════════ // 🔒 PASSWORD MANAGEMENT // ═══════════════════════════════════════════════════════════ // PasswordPolicy define regras de senha type PasswordPolicy struct { MinLength int RequireUppercase bool RequireLowercase bool RequireNumbers bool RequireSpecial bool } // DefaultPasswordPolicy retorna política padrão func DefaultPasswordPolicy() PasswordPolicy { return PasswordPolicy{ MinLength: 12, RequireUppercase: true, RequireLowercase: true, RequireNumbers: true, RequireSpecial: true, } } // ValidatePassword verifica se senha atende à política func ValidatePassword(password string, policy PasswordPolicy) (bool, []string) { var errors []string if len(password) < policy.MinLength { errors = append(errors, fmt.Sprintf("Senha deve ter no mínimo %d caracteres", policy.MinLength)) } if policy.RequireUppercase && !regexp.MustCompile(`[A-Z]`).MatchString(password) { errors = append(errors, "Senha deve conter letra maiúscula") } if policy.RequireLowercase && !regexp.MustCompile(`[a-z]`).MatchString(password) { errors = append(errors, "Senha deve conter letra minúscula") } if policy.RequireNumbers && !regexp.MustCompile(`[0-9]`).MatchString(password) { errors = append(errors, "Senha deve conter número") } if policy.RequireSpecial && !regexp.MustCompile(`[!@#$%^&*(),.?":{}|<>]`).MatchString(password) { errors = append(errors, "Senha deve conter caractere especial") } // Verificar senhas comuns if isCommonPassword(password) { errors = append(errors, "Senha muito comum, escolha outra") } return len(errors) == 0, errors } // HashPassword cria hash bcrypt da senha func HashPassword(password string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(password), BcryptCost) return string(bytes), err } // VerifyPassword verifica senha contra hash func VerifyPassword(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } // isCommonPassword verifica senhas comuns func isCommonPassword(password string) bool { common := []string{ "password", "123456", "12345678", "qwerty", "abc123", "monkey", "1234567", "letmein", "trustno1", "dragon", "baseball", "iloveyou", "master", "sunshine", "ashley", "passw0rd", "shadow", "123123", "654321", "superman", "senha", "mudar123", "admin123", "root123", } lower := strings.ToLower(password) for _, p := range common { if strings.Contains(lower, p) { return true } } return false } // ═══════════════════════════════════════════════════════════ // 🚦 RATE LIMITING // ═══════════════════════════════════════════════════════════ // RateLimiter controla taxa de requests type RateLimiter struct { sync.RWMutex requests map[string]*rateLimitEntry limit int window time.Duration } type rateLimitEntry struct { count int firstSeen time.Time blocked bool blockedAt time.Time } // NewRateLimiter cria novo rate limiter func NewRateLimiter(limit int, window time.Duration) *RateLimiter { rl := &RateLimiter{ requests: make(map[string]*rateLimitEntry), limit: limit, window: window, } // Cleanup goroutine go rl.cleanup() return rl } // Allow verifica se request é permitido func (rl *RateLimiter) Allow(identifier string) bool { rl.Lock() defer rl.Unlock() now := time.Now() entry, exists := rl.requests[identifier] if !exists { rl.requests[identifier] = &rateLimitEntry{ count: 1, firstSeen: now, } return true } // Verificar se está bloqueado if entry.blocked { if now.Sub(entry.blockedAt) < time.Duration(LockoutDuration)*time.Minute { return false } // Desbloquear entry.blocked = false entry.count = 0 entry.firstSeen = now } // Verificar janela de tempo if now.Sub(entry.firstSeen) > rl.window { entry.count = 1 entry.firstSeen = now return true } entry.count++ if entry.count > rl.limit { return false } return true } // Block bloqueia um identificador func (rl *RateLimiter) Block(identifier string) { rl.Lock() defer rl.Unlock() entry, exists := rl.requests[identifier] if !exists { entry = &rateLimitEntry{} rl.requests[identifier] = entry } entry.blocked = true entry.blockedAt = time.Now() } // cleanup remove entradas antigas func (rl *RateLimiter) cleanup() { ticker := time.NewTicker(5 * time.Minute) for range ticker.C { rl.Lock() now := time.Now() for id, entry := range rl.requests { if now.Sub(entry.firstSeen) > rl.window*2 && !entry.blocked { delete(rl.requests, id) } } rl.Unlock() } } // ═══════════════════════════════════════════════════════════ // 🛡️ BRUTE FORCE PROTECTION // ═══════════════════════════════════════════════════════════ // LoginAttemptTracker rastreia tentativas de login type LoginAttemptTracker struct { sync.RWMutex attempts map[string]*loginAttempt } type loginAttempt struct { failures int lastAttempt time.Time lockedUntil time.Time } // NewLoginAttemptTracker cria tracker func NewLoginAttemptTracker() *LoginAttemptTracker { return &LoginAttemptTracker{ attempts: make(map[string]*loginAttempt), } } // RecordFailure registra falha de login func (t *LoginAttemptTracker) RecordFailure(identifier string) (blocked bool, remainingAttempts int) { t.Lock() defer t.Unlock() now := time.Now() attempt, exists := t.attempts[identifier] if !exists { t.attempts[identifier] = &loginAttempt{ failures: 1, lastAttempt: now, } return false, MaxFailedAttempts - 1 } // Reset se última tentativa foi há muito tempo if now.Sub(attempt.lastAttempt) > time.Duration(LockoutDuration)*time.Minute { attempt.failures = 1 attempt.lastAttempt = now attempt.lockedUntil = time.Time{} return false, MaxFailedAttempts - 1 } attempt.failures++ attempt.lastAttempt = now if attempt.failures >= MaxFailedAttempts { attempt.lockedUntil = now.Add(time.Duration(LockoutDuration) * time.Minute) return true, 0 } return false, MaxFailedAttempts - attempt.failures } // RecordSuccess registra sucesso de login func (t *LoginAttemptTracker) RecordSuccess(identifier string) { t.Lock() defer t.Unlock() delete(t.attempts, identifier) } // IsLocked verifica se está bloqueado func (t *LoginAttemptTracker) IsLocked(identifier string) (bool, time.Duration) { t.RLock() defer t.RUnlock() attempt, exists := t.attempts[identifier] if !exists { return false, 0 } if attempt.lockedUntil.IsZero() { return false, 0 } remaining := time.Until(attempt.lockedUntil) if remaining <= 0 { return false, 0 } return true, remaining } // ═══════════════════════════════════════════════════════════ // 🔍 INPUT VALIDATION & SANITIZATION // ═══════════════════════════════════════════════════════════ // SanitizeEmail limpa e valida email func SanitizeEmail(email string) (string, error) { email = strings.TrimSpace(strings.ToLower(email)) emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) if !emailRegex.MatchString(email) { return "", fmt.Errorf("email inválido") } return email, nil } // SanitizeHostname limpa hostname func SanitizeHostname(hostname string) (string, error) { hostname = strings.TrimSpace(hostname) // Permitir apenas alfanuméricos, hífens e pontos hostnameRegex := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9.-]{0,253}[a-zA-Z0-9]$`) if !hostnameRegex.MatchString(hostname) && len(hostname) > 1 { return "", fmt.Errorf("hostname inválido") } return hostname, nil } // SanitizeSQL previne SQL injection (para queries dinâmicas) func SanitizeSQL(input string) string { // Remover caracteres perigosos dangerous := []string{"'", "\"", ";", "--", "/*", "*/", "xp_", "sp_"} result := input for _, d := range dangerous { result = strings.ReplaceAll(result, d, "") } return result } // ValidateIPAddress valida endereço IP func ValidateIPAddress(ip string) bool { return net.ParseIP(ip) != nil } // ═══════════════════════════════════════════════════════════ // 🛡️ SECURITY HEADERS MIDDLEWARE // ═══════════════════════════════════════════════════════════ // SecurityHeaders adiciona headers de segurança func SecurityHeaders() fiber.Handler { return func(c *fiber.Ctx) error { // Prevenir clickjacking c.Set("X-Frame-Options", "DENY") // Prevenir MIME type sniffing c.Set("X-Content-Type-Options", "nosniff") // XSS Protection c.Set("X-XSS-Protection", "1; mode=block") // Content Security Policy c.Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss: https:") // HSTS (apenas se HTTPS) if c.Protocol() == "https" { c.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload") } // Referrer Policy c.Set("Referrer-Policy", "strict-origin-when-cross-origin") // Permissions Policy c.Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()") // Remove Server header c.Set("Server", "") return c.Next() } } // ═══════════════════════════════════════════════════════════ // 📝 AUDIT LOGGING // ═══════════════════════════════════════════════════════════ // AuditEvent representa um evento de auditoria type AuditEvent struct { Timestamp time.Time `json:"timestamp"` EventType string `json:"event_type"` UserID string `json:"user_id,omitempty"` OrgID string `json:"org_id,omitempty"` IP string `json:"ip"` UserAgent string `json:"user_agent"` Resource string `json:"resource"` Action string `json:"action"` Status string `json:"status"` Details map[string]string `json:"details,omitempty"` } // AuditEventType tipos de eventos const ( AuditLogin = "auth.login" AuditLogout = "auth.logout" AuditLoginFailed = "auth.login_failed" AuditAPIKeyCreated = "apikey.created" AuditAPIKeyRevoked = "apikey.revoked" AuditUserCreated = "user.created" AuditUserDeleted = "user.deleted" AuditConfigChanged = "config.changed" AuditAlertCreated = "alert.created" AuditDataExport = "data.export" ) // AuditLogger interface para logging de auditoria type AuditLogger interface { Log(event AuditEvent) error } // ═══════════════════════════════════════════════════════════ // 🔐 SECRETS MANAGEMENT // ═══════════════════════════════════════════════════════════ // GenerateSecureToken gera token seguro func GenerateSecureToken(length int) string { bytes := make([]byte, length) if _, err := rand.Read(bytes); err != nil { panic("failed to generate secure token") } return base64.URLEncoding.EncodeToString(bytes)[:length] } // MaskSecret mascara segredos para logging func MaskSecret(secret string) string { if len(secret) <= 8 { return "****" } return secret[:4] + "****" + secret[len(secret)-4:] } // ═══════════════════════════════════════════════════════════ // 🌐 IP FILTERING // ═══════════════════════════════════════════════════════════ // IPWhitelist gerencia whitelist de IPs type IPWhitelist struct { sync.RWMutex allowed map[string]bool cidrs []*net.IPNet } // NewIPWhitelist cria whitelist func NewIPWhitelist(ips []string, cidrs []string) *IPWhitelist { wl := &IPWhitelist{ allowed: make(map[string]bool), } for _, ip := range ips { wl.allowed[ip] = true } for _, cidr := range cidrs { _, network, err := net.ParseCIDR(cidr) if err == nil { wl.cidrs = append(wl.cidrs, network) } } return wl } // IsAllowed verifica se IP está permitido func (wl *IPWhitelist) IsAllowed(ip string) bool { wl.RLock() defer wl.RUnlock() // Verificar lista direta if wl.allowed[ip] { return true } // Verificar CIDRs parsedIP := net.ParseIP(ip) if parsedIP == nil { return false } for _, network := range wl.cidrs { if network.Contains(parsedIP) { return true } } return false }