- Installed and configured SCST with 7 handlers - Installed and configured mhVTL with 2 Quantum libraries and 8 LTO-8 drives - Implemented all VTL API endpoints (8/9 working) - Fixed NULL device_path handling in drives endpoint - Added comprehensive error handling and validation - Implemented async tape load/unload operations - Created SCST installation guide for Ubuntu 24.04 - Created mhVTL installation and configuration guide - Added VTL testing guide and automated test scripts - All core API tests passing (89% success rate) Infrastructure status: - PostgreSQL: Configured with proper permissions - SCST: Active with kernel module loaded - mhVTL: 2 libraries (Quantum Scalar i500, Scalar i40) - mhVTL: 8 drives (all Quantum ULTRIUM-HH8 LTO-8) - Calypso API: 8/9 VTL endpoints functional Documentation added: - src/srs-technical-spec-documents/scst-installation.md - src/srs-technical-spec-documents/mhvtl-installation.md - VTL-TESTING-GUIDE.md - scripts/test-vtl.sh Co-Authored-By: Warp <agent@warp.dev>
149 lines
3.4 KiB
Go
149 lines
3.4 KiB
Go
package audit
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/atlasos/calypso/internal/common/database"
|
|
"github.com/atlasos/calypso/internal/common/logger"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Middleware provides audit logging functionality
|
|
type Middleware struct {
|
|
db *database.DB
|
|
logger *logger.Logger
|
|
}
|
|
|
|
// NewMiddleware creates a new audit middleware
|
|
func NewMiddleware(db *database.DB, log *logger.Logger) *Middleware {
|
|
return &Middleware{
|
|
db: db,
|
|
logger: log,
|
|
}
|
|
}
|
|
|
|
// LogRequest creates middleware that logs all mutating requests
|
|
func (m *Middleware) LogRequest() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Only log mutating methods
|
|
method := c.Request.Method
|
|
if method == "GET" || method == "HEAD" || method == "OPTIONS" {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// Capture request body
|
|
var bodyBytes []byte
|
|
if c.Request.Body != nil {
|
|
bodyBytes, _ = io.ReadAll(c.Request.Body)
|
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
|
}
|
|
|
|
// Process request
|
|
c.Next()
|
|
|
|
// Get user information
|
|
userID, _ := c.Get("user_id")
|
|
username, _ := c.Get("username")
|
|
|
|
// Capture response status
|
|
status := c.Writer.Status()
|
|
|
|
// Log to database
|
|
go m.logAuditEvent(
|
|
userID,
|
|
username,
|
|
method,
|
|
c.Request.URL.Path,
|
|
c.ClientIP(),
|
|
c.GetHeader("User-Agent"),
|
|
bodyBytes,
|
|
status,
|
|
)
|
|
}
|
|
}
|
|
|
|
// logAuditEvent logs an audit event to the database
|
|
func (m *Middleware) logAuditEvent(
|
|
userID interface{},
|
|
username interface{},
|
|
method, path, ipAddress, userAgent string,
|
|
requestBody []byte,
|
|
responseStatus int,
|
|
) {
|
|
var userIDStr, usernameStr string
|
|
if userID != nil {
|
|
userIDStr, _ = userID.(string)
|
|
}
|
|
if username != nil {
|
|
usernameStr, _ = username.(string)
|
|
}
|
|
|
|
// Determine action and resource from path
|
|
action, resourceType, resourceID := parsePath(path)
|
|
// Override action with HTTP method
|
|
action = strings.ToLower(method)
|
|
|
|
// Truncate request body if too large
|
|
bodyJSON := string(requestBody)
|
|
if len(bodyJSON) > 10000 {
|
|
bodyJSON = bodyJSON[:10000] + "... (truncated)"
|
|
}
|
|
|
|
query := `
|
|
INSERT INTO audit_log (
|
|
user_id, username, action, resource_type, resource_id,
|
|
method, path, ip_address, user_agent,
|
|
request_body, response_status, created_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, NOW())
|
|
`
|
|
|
|
var bodyJSONPtr *string
|
|
if len(bodyJSON) > 0 {
|
|
bodyJSONPtr = &bodyJSON
|
|
}
|
|
|
|
_, err := m.db.Exec(query,
|
|
userIDStr, usernameStr, action, resourceType, resourceID,
|
|
method, path, ipAddress, userAgent,
|
|
bodyJSONPtr, responseStatus,
|
|
)
|
|
if err != nil {
|
|
m.logger.Error("Failed to log audit event", "error", err)
|
|
}
|
|
}
|
|
|
|
// parsePath extracts action, resource type, and resource ID from a path
|
|
func parsePath(path string) (action, resourceType, resourceID string) {
|
|
// Example: /api/v1/iam/users/123 -> action=update, resourceType=user, resourceID=123
|
|
if len(path) < 8 || path[:8] != "/api/v1/" {
|
|
return "unknown", "unknown", ""
|
|
}
|
|
|
|
remaining := path[8:]
|
|
parts := strings.Split(remaining, "/")
|
|
if len(parts) == 0 {
|
|
return "unknown", "unknown", ""
|
|
}
|
|
|
|
// First part is usually the resource type (e.g., "iam", "tasks")
|
|
resourceType = parts[0]
|
|
|
|
// Determine action from HTTP method (will be set by caller)
|
|
action = "unknown"
|
|
|
|
// Last part might be resource ID if it's a UUID or number
|
|
if len(parts) > 1 {
|
|
lastPart := parts[len(parts)-1]
|
|
// Check if it looks like a UUID or ID
|
|
if len(lastPart) > 10 {
|
|
resourceID = lastPart
|
|
}
|
|
}
|
|
|
|
return action, resourceType, resourceID
|
|
}
|
|
|