mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-09 16:10:48 +02:00
- Added new routes and handlers for managing storage providers, including creation, editing, and deletion. - Introduced a new StorageProvider form component for user input. - Enhanced the database schema to support storage provider references in transfer configurations. - Implemented encryption for sensitive fields in storage provider data. - Added tests for storage provider API endpoints and integration with the database. - Updated frontend components to support storage provider selection and testing.
401 lines
9.8 KiB
Go
401 lines
9.8 KiB
Go
package audit
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/starfleetcptn/gomft/internal/encryption"
|
|
)
|
|
|
|
// EventType represents the type of encryption-related event
|
|
type EventType string
|
|
|
|
// Event types for encryption operations
|
|
const (
|
|
EventEncrypt EventType = "encrypt"
|
|
EventDecrypt EventType = "decrypt"
|
|
EventKeyAccess EventType = "key_access"
|
|
EventKeyRotation EventType = "key_rotation"
|
|
EventKeyGeneration EventType = "key_generation"
|
|
EventDecryptionFailure EventType = "decryption_failure"
|
|
EventEncryptionFailure EventType = "encryption_failure"
|
|
)
|
|
|
|
// SecurityLevel represents the severity/importance of an audit event
|
|
type SecurityLevel string
|
|
|
|
// Security levels for events
|
|
const (
|
|
LevelInfo SecurityLevel = "info"
|
|
LevelWarning SecurityLevel = "warning"
|
|
LevelAlert SecurityLevel = "alert"
|
|
LevelError SecurityLevel = "error"
|
|
)
|
|
|
|
// AuditEvent represents a single encryption-related security event
|
|
type AuditEvent struct {
|
|
Timestamp time.Time `json:"timestamp"`
|
|
EventType EventType `json:"event_type"`
|
|
Level SecurityLevel `json:"level"`
|
|
Operation string `json:"operation"`
|
|
FieldType string `json:"field_type,omitempty"`
|
|
ModelType string `json:"model_type,omitempty"`
|
|
Description string `json:"description"`
|
|
Success bool `json:"success"`
|
|
Error string `json:"error,omitempty"`
|
|
KeyVersion string `json:"key_version,omitempty"`
|
|
UserID uint `json:"user_id,omitempty"`
|
|
RemoteIP string `json:"remote_ip,omitempty"`
|
|
Duration int64 `json:"duration_ns,omitempty"` // Operation duration in nanoseconds
|
|
}
|
|
|
|
// SecurityAuditor is responsible for logging security-related events
|
|
type SecurityAuditor struct {
|
|
enabled bool
|
|
logWriter io.Writer
|
|
errorWriter io.Writer
|
|
mutex sync.Mutex
|
|
detailedMode bool
|
|
logFilePath string
|
|
errorFilePath string
|
|
}
|
|
|
|
// New creates a new SecurityAuditor with default configuration
|
|
func New() (*SecurityAuditor, error) {
|
|
return &SecurityAuditor{
|
|
enabled: true,
|
|
logWriter: os.Stdout, // Default to stdout for regular logs
|
|
errorWriter: os.Stderr, // Default to stderr for error logs
|
|
detailedMode: false,
|
|
}, nil
|
|
}
|
|
|
|
// NewWithFileLogging creates a new SecurityAuditor with file-based logging
|
|
func NewWithFileLogging(logFilePath, errorFilePath string) (*SecurityAuditor, error) {
|
|
logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open log file: %w", err)
|
|
}
|
|
|
|
var errorWriter io.Writer
|
|
if errorFilePath == logFilePath {
|
|
errorWriter = logFile
|
|
} else {
|
|
errorFile, err := os.OpenFile(errorFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
logFile.Close()
|
|
return nil, fmt.Errorf("failed to open error log file: %w", err)
|
|
}
|
|
errorWriter = errorFile
|
|
}
|
|
|
|
return &SecurityAuditor{
|
|
enabled: true,
|
|
logWriter: logFile,
|
|
errorWriter: errorWriter,
|
|
logFilePath: logFilePath,
|
|
errorFilePath: errorFilePath,
|
|
detailedMode: false,
|
|
}, nil
|
|
}
|
|
|
|
// Close properly closes any open resources
|
|
func (a *SecurityAuditor) Close() error {
|
|
a.mutex.Lock()
|
|
defer a.mutex.Unlock()
|
|
|
|
// Check if we need to close file writers
|
|
if closer, ok := a.logWriter.(io.Closer); ok {
|
|
if err := closer.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Don't close errorWriter if it's the same as logWriter
|
|
if a.errorFilePath != a.logFilePath {
|
|
if closer, ok := a.errorWriter.(io.Closer); ok {
|
|
if err := closer.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Enable turns on the auditor
|
|
func (a *SecurityAuditor) Enable() {
|
|
a.mutex.Lock()
|
|
defer a.mutex.Unlock()
|
|
a.enabled = true
|
|
}
|
|
|
|
// Disable turns off the auditor
|
|
func (a *SecurityAuditor) Disable() {
|
|
a.mutex.Lock()
|
|
defer a.mutex.Unlock()
|
|
a.enabled = false
|
|
}
|
|
|
|
// SetDetailedMode toggles detailed logging mode
|
|
func (a *SecurityAuditor) SetDetailedMode(detailed bool) {
|
|
a.mutex.Lock()
|
|
defer a.mutex.Unlock()
|
|
a.detailedMode = detailed
|
|
}
|
|
|
|
// IsEnabled returns whether auditing is enabled
|
|
func (a *SecurityAuditor) IsEnabled() bool {
|
|
a.mutex.Lock()
|
|
defer a.mutex.Unlock()
|
|
return a.enabled
|
|
}
|
|
|
|
// LogEvent records a security event to the audit log
|
|
func (a *SecurityAuditor) LogEvent(event AuditEvent) {
|
|
if !a.IsEnabled() {
|
|
return
|
|
}
|
|
|
|
a.mutex.Lock()
|
|
defer a.mutex.Unlock()
|
|
|
|
// Ensure timestamp is set
|
|
if event.Timestamp.IsZero() {
|
|
event.Timestamp = time.Now()
|
|
}
|
|
|
|
// Convert the event to JSON
|
|
jsonData, err := json.Marshal(event)
|
|
if err != nil {
|
|
fmt.Fprintf(a.errorWriter, "Error marshaling audit event: %v\n", err)
|
|
return
|
|
}
|
|
|
|
// Choose the right writer based on event level
|
|
writer := a.logWriter
|
|
if event.Level == LevelError || event.Level == LevelAlert {
|
|
writer = a.errorWriter
|
|
}
|
|
|
|
// Write to the appropriate log
|
|
fmt.Fprintln(writer, string(jsonData))
|
|
}
|
|
|
|
// LogEncryptionEvent logs an encryption operation event
|
|
func (a *SecurityAuditor) LogEncryptionEvent(operation string, fieldType, modelType string, success bool, err error, keyVersion string, userID uint, duration time.Duration) {
|
|
if !a.IsEnabled() {
|
|
return
|
|
}
|
|
|
|
event := AuditEvent{
|
|
Timestamp: time.Now(),
|
|
EventType: EventEncrypt,
|
|
Level: LevelInfo,
|
|
Operation: operation,
|
|
FieldType: fieldType,
|
|
ModelType: modelType,
|
|
Success: success,
|
|
KeyVersion: keyVersion,
|
|
UserID: userID,
|
|
Duration: duration.Nanoseconds(),
|
|
}
|
|
|
|
if !success {
|
|
event.EventType = EventEncryptionFailure
|
|
event.Level = LevelWarning
|
|
if err != nil {
|
|
event.Error = encryption.SanitizeError(err.Error())
|
|
}
|
|
}
|
|
|
|
a.LogEvent(event)
|
|
}
|
|
|
|
// LogDecryptionEvent logs a decryption operation event
|
|
func (a *SecurityAuditor) LogDecryptionEvent(operation string, fieldType, modelType string, success bool, err error, keyVersion string, userID uint, duration time.Duration) {
|
|
if !a.IsEnabled() {
|
|
return
|
|
}
|
|
|
|
event := AuditEvent{
|
|
Timestamp: time.Now(),
|
|
EventType: EventDecrypt,
|
|
Level: LevelInfo,
|
|
Operation: operation,
|
|
FieldType: fieldType,
|
|
ModelType: modelType,
|
|
Success: success,
|
|
KeyVersion: keyVersion,
|
|
UserID: userID,
|
|
Duration: duration.Nanoseconds(),
|
|
}
|
|
|
|
if !success {
|
|
event.EventType = EventDecryptionFailure
|
|
event.Level = LevelWarning
|
|
if err != nil {
|
|
event.Error = encryption.SanitizeError(err.Error())
|
|
}
|
|
}
|
|
|
|
a.LogEvent(event)
|
|
}
|
|
|
|
// LogKeyAccessEvent logs when an encryption key is accessed
|
|
func (a *SecurityAuditor) LogKeyAccessEvent(keyVersion string, success bool, err error, userID uint) {
|
|
if !a.IsEnabled() {
|
|
return
|
|
}
|
|
|
|
event := AuditEvent{
|
|
Timestamp: time.Now(),
|
|
EventType: EventKeyAccess,
|
|
Level: LevelInfo,
|
|
Operation: "key_access",
|
|
Success: success,
|
|
KeyVersion: keyVersion,
|
|
UserID: userID,
|
|
}
|
|
|
|
if !success {
|
|
event.Level = LevelAlert
|
|
if err != nil {
|
|
event.Error = encryption.SanitizeError(err.Error())
|
|
}
|
|
}
|
|
|
|
// Key access failures are security-critical and should be logged at a higher level
|
|
if !success {
|
|
event.Description = "Failed key access attempt"
|
|
}
|
|
|
|
a.LogEvent(event)
|
|
}
|
|
|
|
// LogKeyRotationEvent logs when encryption keys are rotated
|
|
func (a *SecurityAuditor) LogKeyRotationEvent(oldVersion, newVersion string, success bool, err error, userID uint) {
|
|
if !a.IsEnabled() {
|
|
return
|
|
}
|
|
|
|
event := AuditEvent{
|
|
Timestamp: time.Now(),
|
|
EventType: EventKeyRotation,
|
|
Level: LevelInfo,
|
|
Operation: "key_rotation",
|
|
Description: fmt.Sprintf("Key rotation from version %s to %s", oldVersion, newVersion),
|
|
Success: success,
|
|
KeyVersion: newVersion,
|
|
UserID: userID,
|
|
}
|
|
|
|
if !success {
|
|
event.Level = LevelError
|
|
if err != nil {
|
|
event.Error = encryption.SanitizeError(err.Error())
|
|
}
|
|
}
|
|
|
|
a.LogEvent(event)
|
|
}
|
|
|
|
// LogKeyRotationEventWithDescription logs when encryption keys are rotated with a custom description
|
|
func (a *SecurityAuditor) LogKeyRotationEventWithDescription(oldVersion, newVersion string, success bool, description string, userID uint) {
|
|
if !a.IsEnabled() {
|
|
return
|
|
}
|
|
|
|
event := AuditEvent{
|
|
Timestamp: time.Now(),
|
|
EventType: EventKeyRotation,
|
|
Level: LevelInfo,
|
|
Operation: "key_rotation",
|
|
Description: description,
|
|
Success: success,
|
|
KeyVersion: newVersion,
|
|
UserID: userID,
|
|
}
|
|
|
|
if !success {
|
|
event.Level = LevelError
|
|
}
|
|
|
|
a.LogEvent(event)
|
|
}
|
|
|
|
// LogKeyGenerationEvent logs when a new encryption key is generated
|
|
func (a *SecurityAuditor) LogKeyGenerationEvent(keyVersion string, success bool, err error, userID uint) {
|
|
if !a.IsEnabled() {
|
|
return
|
|
}
|
|
|
|
event := AuditEvent{
|
|
Timestamp: time.Now(),
|
|
EventType: EventKeyGeneration,
|
|
Level: LevelInfo,
|
|
Operation: "key_generation",
|
|
Description: "New encryption key generated",
|
|
Success: success,
|
|
KeyVersion: keyVersion,
|
|
UserID: userID,
|
|
}
|
|
|
|
if !success {
|
|
event.Level = LevelError
|
|
if err != nil {
|
|
event.Error = encryption.SanitizeError(err.Error())
|
|
}
|
|
}
|
|
|
|
a.LogEvent(event)
|
|
}
|
|
|
|
// global is the default security auditor instance
|
|
var global *SecurityAuditor
|
|
var globalOnce sync.Once
|
|
|
|
// GetGlobalAuditor returns the global security auditor instance
|
|
func GetGlobalAuditor() *SecurityAuditor {
|
|
globalOnce.Do(func() {
|
|
var err error
|
|
global, err = New()
|
|
if err != nil {
|
|
// Fall back to a disabled auditor if there's an error
|
|
global = &SecurityAuditor{enabled: false}
|
|
}
|
|
})
|
|
return global
|
|
}
|
|
|
|
// InitializeWithFileLogging initializes the global auditor with file logging
|
|
func InitializeWithFileLogging(logFilePath, errorFilePath string) error {
|
|
auditor, err := NewWithFileLogging(logFilePath, errorFilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
globalOnce.Do(func() {
|
|
global = auditor
|
|
})
|
|
|
|
// If global auditor was already initialized, replace it
|
|
if global != auditor {
|
|
if closer, ok := global.logWriter.(io.Closer); ok {
|
|
closer.Close()
|
|
}
|
|
if global.errorFilePath != global.logFilePath {
|
|
if closer, ok := global.errorWriter.(io.Closer); ok {
|
|
closer.Close()
|
|
}
|
|
}
|
|
global = auditor
|
|
}
|
|
|
|
return nil
|
|
}
|