mirror of
https://github.com/oarkflow/mq.git
synced 2025-09-27 12:22:08 +08:00
681 lines
17 KiB
Go
681 lines
17 KiB
Go
package mq
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// SecurityManager handles authentication, authorization, and security policies
|
|
type SecurityManager struct {
|
|
authProviders map[string]AuthProvider
|
|
roleManager *RoleManager
|
|
rateLimiter *SecurityRateLimiter
|
|
auditLogger *AuditLogger
|
|
sessionManager *SessionManager
|
|
encryptionKey []byte
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// AuthProvider interface for different authentication methods
|
|
type AuthProvider interface {
|
|
Name() string
|
|
Authenticate(ctx context.Context, credentials map[string]any) (*User, error)
|
|
ValidateToken(token string) (*User, error)
|
|
}
|
|
|
|
// User represents an authenticated user
|
|
type User struct {
|
|
ID string `json:"id"`
|
|
Username string `json:"username"`
|
|
Roles []string `json:"roles"`
|
|
Permissions []string `json:"permissions"`
|
|
Metadata map[string]any `json:"metadata,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
|
|
}
|
|
|
|
// RoleManager manages user roles and permissions
|
|
type RoleManager struct {
|
|
roles map[string]*Role
|
|
permissions map[string]*Permission
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// Role represents a user role with associated permissions
|
|
type Role struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Permissions []string `json:"permissions"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// Permission represents a specific permission
|
|
type Permission struct {
|
|
Name string `json:"name"`
|
|
Resource string `json:"resource"`
|
|
Action string `json:"action"`
|
|
Description string `json:"description"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// SecurityRateLimiter implements rate limiting for security operations
|
|
type SecurityRateLimiter struct {
|
|
attempts map[string]*RateLimitEntry
|
|
maxAttempts int
|
|
window time.Duration
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// RateLimitEntry tracks rate limiting for a specific key
|
|
type RateLimitEntry struct {
|
|
Count int
|
|
WindowStart time.Time
|
|
}
|
|
|
|
// AuditLogger logs security-related events
|
|
type AuditLogger struct {
|
|
events []AuditEvent
|
|
maxEvents int
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// AuditEvent represents a security audit event
|
|
type AuditEvent struct {
|
|
ID string `json:"id"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
EventType string `json:"event_type"`
|
|
UserID string `json:"user_id,omitempty"`
|
|
Resource string `json:"resource"`
|
|
Action string `json:"action"`
|
|
IPAddress string `json:"ip_address,omitempty"`
|
|
UserAgent string `json:"user_agent,omitempty"`
|
|
Success bool `json:"success"`
|
|
Details map[string]any `json:"details,omitempty"`
|
|
}
|
|
|
|
// SessionManager manages user sessions
|
|
type SessionManager struct {
|
|
sessions map[string]*Session
|
|
maxAge time.Duration
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// Session represents a user session
|
|
type Session struct {
|
|
ID string `json:"id"`
|
|
UserID string `json:"user_id"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
IPAddress string `json:"ip_address"`
|
|
UserAgent string `json:"user_agent"`
|
|
Data map[string]any `json:"data,omitempty"`
|
|
}
|
|
|
|
// NewSecurityManager creates a new security manager
|
|
func NewSecurityManager() *SecurityManager {
|
|
key := make([]byte, 32)
|
|
rand.Read(key)
|
|
|
|
return &SecurityManager{
|
|
authProviders: make(map[string]AuthProvider),
|
|
roleManager: NewRoleManager(),
|
|
rateLimiter: NewSecurityRateLimiter(5, time.Minute*15), // 5 attempts per 15 minutes
|
|
auditLogger: NewAuditLogger(10000),
|
|
sessionManager: NewSessionManager(time.Hour * 24), // 24 hour sessions
|
|
encryptionKey: key,
|
|
}
|
|
}
|
|
|
|
// NewRoleManager creates a new role manager
|
|
func NewRoleManager() *RoleManager {
|
|
rm := &RoleManager{
|
|
roles: make(map[string]*Role),
|
|
permissions: make(map[string]*Permission),
|
|
}
|
|
|
|
// Initialize default permissions
|
|
rm.AddPermission(&Permission{
|
|
Name: "task.publish",
|
|
Resource: "task",
|
|
Action: "publish",
|
|
Description: "Publish tasks to queues",
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
rm.AddPermission(&Permission{
|
|
Name: "task.consume",
|
|
Resource: "task",
|
|
Action: "consume",
|
|
Description: "Consume tasks from queues",
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
rm.AddPermission(&Permission{
|
|
Name: "queue.manage",
|
|
Resource: "queue",
|
|
Action: "manage",
|
|
Description: "Manage queues",
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
rm.AddPermission(&Permission{
|
|
Name: "admin.system",
|
|
Resource: "system",
|
|
Action: "admin",
|
|
Description: "System administration",
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
// Initialize default roles
|
|
rm.AddRole(&Role{
|
|
Name: "publisher",
|
|
Description: "Can publish tasks",
|
|
Permissions: []string{"task.publish"},
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
rm.AddRole(&Role{
|
|
Name: "consumer",
|
|
Description: "Can consume tasks",
|
|
Permissions: []string{"task.consume"},
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
rm.AddRole(&Role{
|
|
Name: "admin",
|
|
Description: "Full system access",
|
|
Permissions: []string{"task.publish", "task.consume", "queue.manage", "admin.system"},
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
return rm
|
|
}
|
|
|
|
// AddPermission adds a permission to the role manager
|
|
func (rm *RoleManager) AddPermission(perm *Permission) {
|
|
rm.mu.Lock()
|
|
defer rm.mu.Unlock()
|
|
rm.permissions[perm.Name] = perm
|
|
}
|
|
|
|
// AddRole adds a role to the role manager
|
|
func (rm *RoleManager) AddRole(role *Role) {
|
|
rm.mu.Lock()
|
|
defer rm.mu.Unlock()
|
|
rm.roles[role.Name] = role
|
|
}
|
|
|
|
// HasPermission checks if a user has a specific permission
|
|
func (rm *RoleManager) HasPermission(user *User, permission string) bool {
|
|
rm.mu.RLock()
|
|
defer rm.mu.RUnlock()
|
|
|
|
for _, roleName := range user.Roles {
|
|
if role, exists := rm.roles[roleName]; exists {
|
|
for _, perm := range role.Permissions {
|
|
if perm == permission {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetUserPermissions returns all permissions for a user
|
|
func (rm *RoleManager) GetUserPermissions(user *User) []string {
|
|
rm.mu.RLock()
|
|
defer rm.mu.RUnlock()
|
|
|
|
permissions := make(map[string]bool)
|
|
for _, roleName := range user.Roles {
|
|
if role, exists := rm.roles[roleName]; exists {
|
|
for _, perm := range role.Permissions {
|
|
permissions[perm] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
result := make([]string, 0, len(permissions))
|
|
for perm := range permissions {
|
|
result = append(result, perm)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// NewSecurityRateLimiter creates a new security rate limiter
|
|
func NewSecurityRateLimiter(maxAttempts int, window time.Duration) *SecurityRateLimiter {
|
|
return &SecurityRateLimiter{
|
|
attempts: make(map[string]*RateLimitEntry),
|
|
maxAttempts: maxAttempts,
|
|
window: window,
|
|
}
|
|
}
|
|
|
|
// IsAllowed checks if an action is allowed based on rate limiting
|
|
func (rl *SecurityRateLimiter) IsAllowed(key string) bool {
|
|
rl.mu.Lock()
|
|
defer rl.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
entry, exists := rl.attempts[key]
|
|
|
|
if !exists {
|
|
rl.attempts[key] = &RateLimitEntry{
|
|
Count: 1,
|
|
WindowStart: now,
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Check if we're in a new window
|
|
if now.Sub(entry.WindowStart) >= rl.window {
|
|
entry.Count = 1
|
|
entry.WindowStart = now
|
|
return true
|
|
}
|
|
|
|
// Check if we've exceeded the limit
|
|
if entry.Count >= rl.maxAttempts {
|
|
return false
|
|
}
|
|
|
|
entry.Count++
|
|
return true
|
|
}
|
|
|
|
// Reset resets the rate limit for a key
|
|
func (rl *SecurityRateLimiter) Reset(key string) {
|
|
rl.mu.Lock()
|
|
defer rl.mu.Unlock()
|
|
delete(rl.attempts, key)
|
|
}
|
|
|
|
// NewAuditLogger creates a new audit logger
|
|
func NewAuditLogger(maxEvents int) *AuditLogger {
|
|
return &AuditLogger{
|
|
events: make([]AuditEvent, 0),
|
|
maxEvents: maxEvents,
|
|
}
|
|
}
|
|
|
|
// LogEvent logs a security event
|
|
func (al *AuditLogger) LogEvent(event AuditEvent) {
|
|
al.mu.Lock()
|
|
defer al.mu.Unlock()
|
|
|
|
event.ID = generateID()
|
|
event.Timestamp = time.Now()
|
|
|
|
al.events = append(al.events, event)
|
|
|
|
// Keep only the most recent events
|
|
if len(al.events) > al.maxEvents {
|
|
al.events = al.events[len(al.events)-al.maxEvents:]
|
|
}
|
|
}
|
|
|
|
// GetEvents returns audit events with optional filtering
|
|
func (al *AuditLogger) GetEvents(userID, eventType string, limit int) []AuditEvent {
|
|
al.mu.RLock()
|
|
defer al.mu.RUnlock()
|
|
|
|
var filtered []AuditEvent
|
|
for _, event := range al.events {
|
|
if (userID == "" || event.UserID == userID) &&
|
|
(eventType == "" || event.EventType == eventType) {
|
|
filtered = append(filtered, event)
|
|
}
|
|
}
|
|
|
|
// Return most recent events
|
|
if len(filtered) > limit && limit > 0 {
|
|
start := len(filtered) - limit
|
|
return filtered[start:]
|
|
}
|
|
|
|
return filtered
|
|
}
|
|
|
|
// NewSessionManager creates a new session manager
|
|
func NewSessionManager(maxAge time.Duration) *SessionManager {
|
|
sm := &SessionManager{
|
|
sessions: make(map[string]*Session),
|
|
maxAge: maxAge,
|
|
}
|
|
|
|
// Start cleanup routine
|
|
go sm.cleanupRoutine()
|
|
|
|
return sm
|
|
}
|
|
|
|
// CreateSession creates a new session for a user
|
|
func (sm *SessionManager) CreateSession(userID, ipAddress, userAgent string) *Session {
|
|
sm.mu.Lock()
|
|
defer sm.mu.Unlock()
|
|
|
|
session := &Session{
|
|
ID: generateID(),
|
|
UserID: userID,
|
|
CreatedAt: time.Now(),
|
|
ExpiresAt: time.Now().Add(sm.maxAge),
|
|
IPAddress: ipAddress,
|
|
UserAgent: userAgent,
|
|
Data: make(map[string]any),
|
|
}
|
|
|
|
sm.sessions[session.ID] = session
|
|
return session
|
|
}
|
|
|
|
// GetSession retrieves a session by ID
|
|
func (sm *SessionManager) GetSession(sessionID string) (*Session, bool) {
|
|
sm.mu.RLock()
|
|
defer sm.mu.RUnlock()
|
|
|
|
session, exists := sm.sessions[sessionID]
|
|
if !exists {
|
|
return nil, false
|
|
}
|
|
|
|
// Check if session has expired
|
|
if time.Now().After(session.ExpiresAt) {
|
|
return nil, false
|
|
}
|
|
|
|
return session, true
|
|
}
|
|
|
|
// DeleteSession deletes a session
|
|
func (sm *SessionManager) DeleteSession(sessionID string) {
|
|
sm.mu.Lock()
|
|
defer sm.mu.Unlock()
|
|
delete(sm.sessions, sessionID)
|
|
}
|
|
|
|
// cleanupRoutine periodically cleans up expired sessions
|
|
func (sm *SessionManager) cleanupRoutine() {
|
|
ticker := time.NewTicker(time.Hour)
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
sm.mu.Lock()
|
|
now := time.Now()
|
|
for id, session := range sm.sessions {
|
|
if now.After(session.ExpiresAt) {
|
|
delete(sm.sessions, id)
|
|
}
|
|
}
|
|
sm.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
// AddAuthProvider adds an authentication provider
|
|
func (sm *SecurityManager) AddAuthProvider(provider AuthProvider) {
|
|
sm.mu.Lock()
|
|
defer sm.mu.Unlock()
|
|
sm.authProviders[provider.Name()] = provider
|
|
}
|
|
|
|
// Authenticate authenticates a user using available providers
|
|
func (sm *SecurityManager) Authenticate(ctx context.Context, credentials map[string]any) (*User, error) {
|
|
sm.mu.RLock()
|
|
providers := make(map[string]AuthProvider)
|
|
for name, provider := range sm.authProviders {
|
|
providers[name] = provider
|
|
}
|
|
sm.mu.RUnlock()
|
|
|
|
var lastErr error
|
|
for _, provider := range providers {
|
|
user, err := provider.Authenticate(ctx, credentials)
|
|
if err == nil {
|
|
// Log successful authentication
|
|
sm.auditLogger.LogEvent(AuditEvent{
|
|
EventType: "authentication",
|
|
UserID: user.ID,
|
|
Action: "login",
|
|
Success: true,
|
|
Details: map[string]any{
|
|
"provider": provider.Name(),
|
|
},
|
|
})
|
|
|
|
// Update user permissions
|
|
user.Permissions = sm.roleManager.GetUserPermissions(user)
|
|
return user, nil
|
|
}
|
|
lastErr = err
|
|
}
|
|
|
|
// Log failed authentication
|
|
sm.auditLogger.LogEvent(AuditEvent{
|
|
EventType: "authentication",
|
|
Action: "login",
|
|
Success: false,
|
|
Details: map[string]any{
|
|
"error": lastErr.Error(),
|
|
},
|
|
})
|
|
|
|
return nil, fmt.Errorf("authentication failed: %w", lastErr)
|
|
}
|
|
|
|
// Authorize checks if a user is authorized for an action
|
|
func (sm *SecurityManager) Authorize(user *User, resource, action string) error {
|
|
permission := fmt.Sprintf("%s.%s", resource, action)
|
|
|
|
if !sm.roleManager.HasPermission(user, permission) {
|
|
// Log authorization failure
|
|
sm.auditLogger.LogEvent(AuditEvent{
|
|
EventType: "authorization",
|
|
UserID: user.ID,
|
|
Resource: resource,
|
|
Action: action,
|
|
Success: false,
|
|
})
|
|
|
|
return fmt.Errorf("user %s does not have permission %s", user.Username, permission)
|
|
}
|
|
|
|
// Log successful authorization
|
|
sm.auditLogger.LogEvent(AuditEvent{
|
|
EventType: "authorization",
|
|
UserID: user.ID,
|
|
Resource: resource,
|
|
Action: action,
|
|
Success: true,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// ValidateSession validates a session token
|
|
func (sm *SecurityManager) ValidateSession(sessionID string) (*User, error) {
|
|
session, exists := sm.sessionManager.GetSession(sessionID)
|
|
if !exists {
|
|
return nil, fmt.Errorf("invalid session")
|
|
}
|
|
|
|
// Create a user object from session data
|
|
user := &User{
|
|
ID: session.UserID,
|
|
Username: session.UserID, // In a real implementation, you'd fetch from database
|
|
}
|
|
|
|
// Update user permissions
|
|
user.Permissions = sm.roleManager.GetUserPermissions(user)
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// CheckRateLimit checks if an action is rate limited
|
|
func (sm *SecurityManager) CheckRateLimit(key string) error {
|
|
if !sm.rateLimiter.IsAllowed(key) {
|
|
sm.auditLogger.LogEvent(AuditEvent{
|
|
EventType: "rate_limit",
|
|
Action: "exceeded",
|
|
Success: false,
|
|
Details: map[string]any{
|
|
"key": key,
|
|
},
|
|
})
|
|
|
|
return fmt.Errorf("rate limit exceeded for key: %s", key)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Encrypt encrypts data using the security manager's key
|
|
func (sm *SecurityManager) Encrypt(data []byte) ([]byte, error) {
|
|
// Simple encryption using SHA256 hash of key + data
|
|
// In production, use proper encryption like AES
|
|
hash := sha256.Sum256(append(sm.encryptionKey, data...))
|
|
return hash[:], nil
|
|
}
|
|
|
|
// Decrypt decrypts data (placeholder for proper decryption)
|
|
func (sm *SecurityManager) Decrypt(data []byte) ([]byte, error) {
|
|
// Placeholder - in production, implement proper decryption
|
|
return data, nil
|
|
}
|
|
|
|
// BasicAuthProvider implements basic username/password authentication
|
|
type BasicAuthProvider struct {
|
|
users map[string]*User
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func NewBasicAuthProvider() *BasicAuthProvider {
|
|
return &BasicAuthProvider{
|
|
users: make(map[string]*User),
|
|
}
|
|
}
|
|
|
|
func (bap *BasicAuthProvider) Name() string {
|
|
return "basic"
|
|
}
|
|
|
|
func (bap *BasicAuthProvider) Authenticate(ctx context.Context, credentials map[string]any) (*User, error) {
|
|
username, ok := credentials["username"].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("username required")
|
|
}
|
|
|
|
password, ok := credentials["password"].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("password required")
|
|
}
|
|
|
|
bap.mu.RLock()
|
|
user, exists := bap.users[username]
|
|
bap.mu.RUnlock()
|
|
|
|
if !exists {
|
|
return nil, fmt.Errorf("user not found")
|
|
}
|
|
|
|
// In production, compare hashed passwords
|
|
if password != "password" { // Placeholder
|
|
return nil, fmt.Errorf("invalid password")
|
|
}
|
|
|
|
// Update last login
|
|
now := time.Now()
|
|
user.LastLoginAt = &now
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (bap *BasicAuthProvider) ValidateToken(token string) (*User, error) {
|
|
// Basic token validation - in production, use JWT or similar
|
|
parts := strings.Split(token, ":")
|
|
if len(parts) != 2 {
|
|
return nil, fmt.Errorf("invalid token format")
|
|
}
|
|
|
|
username := parts[0]
|
|
return bap.Authenticate(context.Background(), map[string]any{
|
|
"username": username,
|
|
"password": "token", // Placeholder
|
|
})
|
|
}
|
|
|
|
func (bap *BasicAuthProvider) AddUser(user *User, password string) error {
|
|
bap.mu.Lock()
|
|
defer bap.mu.Unlock()
|
|
|
|
// In production, hash the password
|
|
user.CreatedAt = time.Now()
|
|
bap.users[user.Username] = user
|
|
|
|
return nil
|
|
}
|
|
|
|
// generateID generates a random ID
|
|
func generateID() string {
|
|
bytes := make([]byte, 16)
|
|
rand.Read(bytes)
|
|
return hex.EncodeToString(bytes)
|
|
}
|
|
|
|
// SecurityMiddleware provides security middleware for HTTP handlers
|
|
type SecurityMiddleware struct {
|
|
securityManager *SecurityManager
|
|
}
|
|
|
|
// NewSecurityMiddleware creates a new security middleware
|
|
func NewSecurityMiddleware(sm *SecurityManager) *SecurityMiddleware {
|
|
return &SecurityMiddleware{
|
|
securityManager: sm,
|
|
}
|
|
}
|
|
|
|
// AuthenticateRequest authenticates a request with credentials
|
|
func (sm *SecurityMiddleware) AuthenticateRequest(credentials map[string]any, ipAddress string) (*User, error) {
|
|
user, err := sm.securityManager.Authenticate(context.Background(), credentials)
|
|
if err != nil {
|
|
// Log failed authentication attempt
|
|
sm.securityManager.auditLogger.LogEvent(AuditEvent{
|
|
EventType: "authentication",
|
|
Action: "login",
|
|
Success: false,
|
|
Details: map[string]any{
|
|
"ip_address": ipAddress,
|
|
"error": err.Error(),
|
|
},
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// AuthorizeRequest authorizes a request
|
|
func (sm *SecurityMiddleware) AuthorizeRequest(user *User, resource, action string) error {
|
|
return sm.securityManager.Authorize(user, resource, action)
|
|
}
|
|
|
|
// GetClientIP gets the real client IP from a network connection
|
|
func GetClientIP(conn net.Conn) string {
|
|
if conn == nil {
|
|
return ""
|
|
}
|
|
|
|
host, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
|
if err != nil {
|
|
return conn.RemoteAddr().String()
|
|
}
|
|
|
|
return host
|
|
}
|