Add RBAC support with roles, permissions, and session management. Implement middleware for authentication and CSRF protection. Enhance audit logging with additional fields. Update HTTP handlers and routes for new features.

This commit is contained in:
2025-12-13 17:44:09 +00:00
parent d69e01bbaf
commit 8100f87686
44 changed files with 3262 additions and 76 deletions

View File

@@ -2,7 +2,9 @@ package audit
import (
"context"
"crypto/sha256"
"database/sql"
"encoding/hex"
"encoding/json"
"log"
"time"
@@ -19,6 +21,12 @@ type Event struct {
ResourceID string
Success bool
Details map[string]any
// Enhanced fields
Actor string // Username or user identifier
Resource string // Full resource identifier (e.g., "pool:my-pool")
PayloadHash string // SHA256 hash of request payload
Result string // Success/failure message or status
ClientIP string // Client IP address
}
type AuditLogger interface {
@@ -40,12 +48,67 @@ func (l *SQLAuditLogger) Record(ctx context.Context, e Event) error {
if e.Timestamp.IsZero() {
e.Timestamp = time.Now()
}
detailsJSON, _ := json.Marshal(e.Details)
_, err := l.DB.ExecContext(ctx, `INSERT INTO audit_events (id, ts, user_id, action, resource_type, resource_id, success, details) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, e.ID, e.Timestamp, e.UserID, e.Action, e.ResourceType, e.ResourceID, boolToInt(e.Success), string(detailsJSON))
if err != nil {
log.Printf("audit record failed: %v", err)
// Set actor from UserID if not provided
if e.Actor == "" {
e.Actor = e.UserID
}
return err
// Build resource string from ResourceType and ResourceID
if e.Resource == "" {
if e.ResourceID != "" {
e.Resource = e.ResourceType + ":" + e.ResourceID
} else {
e.Resource = e.ResourceType
}
}
// Set result from Success if not provided
if e.Result == "" {
if e.Success {
e.Result = "success"
} else {
e.Result = "failure"
}
}
detailsJSON, _ := json.Marshal(e.Details)
// Try to insert with all columns, fallback to basic columns if enhanced columns don't exist
_, err := l.DB.ExecContext(ctx,
`INSERT INTO audit_events (id, ts, user_id, action, resource_type, resource_id, success, details, actor, resource, payload_hash, result, client_ip)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
e.ID, e.Timestamp, e.UserID, e.Action, e.ResourceType, e.ResourceID, boolToInt(e.Success), string(detailsJSON),
e.Actor, e.Resource, e.PayloadHash, e.Result, e.ClientIP)
if err != nil {
// Fallback to basic insert if enhanced columns don't exist yet
_, err2 := l.DB.ExecContext(ctx,
`INSERT INTO audit_events (id, ts, user_id, action, resource_type, resource_id, success, details)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
e.ID, e.Timestamp, e.UserID, e.Action, e.ResourceType, e.ResourceID, boolToInt(e.Success), string(detailsJSON))
if err2 != nil {
log.Printf("audit record failed: %v (fallback also failed: %v)", err, err2)
return err2
}
log.Printf("audit record inserted with fallback (enhanced columns may not exist): %v", err)
}
return nil
}
// HashPayload computes SHA256 hash of a payload (JSON string or bytes)
func HashPayload(payload interface{}) string {
var data []byte
switch v := payload.(type) {
case []byte:
data = v
case string:
data = []byte(v)
default:
jsonData, _ := json.Marshal(payload)
data = jsonData
}
hash := sha256.Sum256(data)
return hex.EncodeToString(hash[:])
}
func boolToInt(b bool) int {