Skip to main content
Glama

Keeper Secrets Manager - MCP

logger.go11.4 kB
package audit import ( "encoding/json" "fmt" "os" "path/filepath" "strings" "sync" "time" ) // EventType represents the type of audit event type EventType string const ( // Security events EventAuth EventType = "AUTH" EventAuthFailed EventType = "AUTH_FAILED" EventAccess EventType = "ACCESS" EventAccessDenied EventType = "ACCESS_DENIED" EventModification EventType = "MODIFICATION" // Operation events EventProfileCreate EventType = "PROFILE_CREATE" EventProfileUpdate EventType = "PROFILE_UPDATE" EventProfileDelete EventType = "PROFILE_DELETE" EventSecretAccess EventType = "SECRET_ACCESS" EventSecretCreate EventType = "SECRET_CREATE" EventSecretUpdate EventType = "SECRET_UPDATE" EventSecretDelete EventType = "SECRET_DELETE" // System events EventStartup EventType = "STARTUP" EventShutdown EventType = "SHUTDOWN" EventError EventType = "ERROR" EventConfigChange EventType = "CONFIG_CHANGE" ) // Severity represents the severity level of an audit event type Severity string const ( SeverityDebug Severity = "DEBUG" SeverityInfo Severity = "INFO" SeverityWarning Severity = "WARNING" SeverityError Severity = "ERROR" SeverityCritical Severity = "CRITICAL" ) // AuditEvent represents a single audit log entry type AuditEvent struct { ID string `json:"id"` Timestamp time.Time `json:"timestamp"` Type EventType `json:"type"` Severity Severity `json:"severity"` Source string `json:"source"` User string `json:"user,omitempty"` Profile string `json:"profile,omitempty"` Resource string `json:"resource,omitempty"` Action string `json:"action"` Result string `json:"result"` Details map[string]interface{} `json:"details,omitempty"` Error string `json:"error,omitempty"` CorrelationID string `json:"correlation_id,omitempty"` } // Logger provides audit logging functionality type Logger struct { mu sync.Mutex file *os.File filepath string maxSize int64 maxAge time.Duration encoder *json.Encoder eventChan chan *AuditEvent stopChan chan struct{} wg sync.WaitGroup } // Config represents logger configuration type Config struct { FilePath string MaxSize int64 // Maximum file size in bytes MaxAge time.Duration // Maximum age of log files } // NewLogger creates a new audit logger func NewLogger(config Config) (*Logger, error) { // Ensure directory exists dir := filepath.Dir(config.FilePath) if err := os.MkdirAll(dir, 0700); err != nil { return nil, fmt.Errorf("failed to create audit log directory: %w", err) } // Open log file file, err := os.OpenFile(config.FilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) if err != nil { return nil, fmt.Errorf("failed to open audit log file: %w", err) } logger := &Logger{ file: file, filepath: config.FilePath, maxSize: config.MaxSize, maxAge: config.MaxAge, encoder: json.NewEncoder(file), eventChan: make(chan *AuditEvent, 100), stopChan: make(chan struct{}), } // Start background worker logger.wg.Add(1) go logger.worker() // Log startup event logger.LogSystem(EventStartup, "Audit logger started", nil) return logger, nil } // Log writes an audit event func (l *Logger) Log(event *AuditEvent) { if event.ID == "" { event.ID = generateEventID() } if event.Timestamp.IsZero() { event.Timestamp = time.Now().UTC() } select { case l.eventChan <- event: case <-time.After(time.Second): // Timeout to prevent blocking fmt.Fprintf(os.Stderr, "Failed to log audit event: timeout\n") } } // LogAuth logs an authentication event func (l *Logger) LogAuth(success bool, user, profile string, details map[string]interface{}) { eventType := EventAuth result := "SUCCESS" severity := SeverityInfo if !success { eventType = EventAuthFailed result = "FAILED" severity = SeverityWarning } l.Log(&AuditEvent{ Type: eventType, Severity: severity, Source: "auth", User: user, Profile: profile, Action: "authenticate", Result: result, Details: details, }) } // LogAccess logs a resource access event func (l *Logger) LogAccess(resource, action, user, profile string, allowed bool, details map[string]interface{}) { eventType := EventAccess result := "ALLOWED" severity := SeverityInfo if !allowed { eventType = EventAccessDenied result = "DENIED" severity = SeverityWarning } l.Log(&AuditEvent{ Type: eventType, Severity: severity, Source: "access", User: user, Profile: profile, Resource: resource, Action: action, Result: result, Details: details, }) } // LogSecretOperation logs a secret operation func (l *Logger) LogSecretOperation(operation EventType, secretUID, user, profile string, success bool, details map[string]interface{}) { result := "SUCCESS" severity := SeverityInfo if !success { result = "FAILED" severity = SeverityError } // Never log sensitive data if details != nil { sanitizedDetails := make(map[string]interface{}) for k, v := range details { if !isSensitiveKey(k) { sanitizedDetails[k] = v } } details = sanitizedDetails } l.Log(&AuditEvent{ Type: operation, Severity: severity, Source: "secrets", User: user, Profile: profile, Resource: secretUID, Action: string(operation), Result: result, Details: details, }) } // LogError logs an error event func (l *Logger) LogError(source string, err error, details map[string]interface{}) { l.Log(&AuditEvent{ Type: EventError, Severity: SeverityError, Source: source, Action: "error", Result: "ERROR", Error: err.Error(), Details: details, }) } // LogSystem logs a system event func (l *Logger) LogSystem(eventType EventType, message string, details map[string]interface{}) { l.Log(&AuditEvent{ Type: eventType, Severity: SeverityInfo, Source: "system", Action: string(eventType), Result: message, Details: details, }) } // LogWithCorrelation logs an event with a correlation ID func (l *Logger) LogWithCorrelation(event *AuditEvent, correlationID string) { event.CorrelationID = correlationID l.Log(event) } // worker processes audit events in the background func (l *Logger) worker() { defer l.wg.Done() ticker := time.NewTicker(time.Hour) defer ticker.Stop() for { select { case event := <-l.eventChan: l.writeEvent(event) case <-ticker.C: l.performMaintenance() case <-l.stopChan: // Drain remaining events for { select { case event := <-l.eventChan: l.writeEvent(event) default: return } } } } } // writeEvent writes an event to the log file func (l *Logger) writeEvent(event *AuditEvent) { l.mu.Lock() defer l.mu.Unlock() if err := l.encoder.Encode(event); err != nil { fmt.Fprintf(os.Stderr, "Failed to write audit event: %v\n", err) } // Check if rotation is needed if l.maxSize > 0 { if info, err := l.file.Stat(); err == nil && info.Size() > l.maxSize { l.rotate() } } } // rotate performs log rotation func (l *Logger) rotate() { // Close current file _ = l.file.Close() // Rename current file with timestamp timestamp := time.Now().Format("20060102-150405") rotatedPath := fmt.Sprintf("%s.%s", l.filepath, timestamp) _ = os.Rename(l.filepath, rotatedPath) // Open new file file, err := os.OpenFile(l.filepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) if err != nil { fmt.Fprintf(os.Stderr, "Failed to open new audit log file: %v\n", err) return } l.file = file l.encoder = json.NewEncoder(file) } // performMaintenance removes old log files func (l *Logger) performMaintenance() { if l.maxAge <= 0 { return } dir := filepath.Dir(l.filepath) base := filepath.Base(l.filepath) entries, err := os.ReadDir(dir) if err != nil { return } cutoff := time.Now().Add(-l.maxAge) for _, entry := range entries { if entry.IsDir() { continue } name := entry.Name() if !strings.HasPrefix(name, base) { continue } info, err := entry.Info() if err != nil { continue } if info.ModTime().Before(cutoff) { _ = os.Remove(filepath.Join(dir, name)) } } } // Close closes the audit logger func (l *Logger) Close() error { // Log shutdown event l.LogSystem(EventShutdown, "Audit logger shutting down", nil) // Stop worker close(l.stopChan) l.wg.Wait() // Close file l.mu.Lock() defer l.mu.Unlock() return l.file.Close() } // generateEventID generates a unique event ID func generateEventID() string { return fmt.Sprintf("%d-%d", time.Now().UnixNano(), os.Getpid()) } // isSensitiveKey checks if a key contains sensitive information func isSensitiveKey(key string) bool { sensitiveKeys := []string{ "password", "secret", "key", "token", "auth", "credential", "private", "passphrase", "pin", "code", "signature", } keyLower := strings.ToLower(key) for _, sensitive := range sensitiveKeys { if strings.Contains(keyLower, sensitive) { return true } } return false } // Query represents an audit log query type Query struct { StartTime time.Time EndTime time.Time EventTypes []EventType Severities []Severity Users []string Resources []string CorrelationID string Limit int } // Search searches the audit log (basic implementation) func (l *Logger) Search(query Query) ([]*AuditEvent, error) { // This is a basic implementation that reads the entire file // For production, consider using a database or indexed storage l.mu.Lock() defer l.mu.Unlock() // Open file for reading file, err := os.Open(l.filepath) if err != nil { return nil, fmt.Errorf("failed to open audit log: %w", err) } defer file.Close() var events []*AuditEvent decoder := json.NewDecoder(file) for { var event AuditEvent if err := decoder.Decode(&event); err != nil { break // EOF or error } // Apply filters if !query.StartTime.IsZero() && event.Timestamp.Before(query.StartTime) { continue } if !query.EndTime.IsZero() && event.Timestamp.After(query.EndTime) { continue } if len(query.EventTypes) > 0 && !contains(query.EventTypes, event.Type) { continue } if len(query.Severities) > 0 && !containsSeverity(query.Severities, event.Severity) { continue } if len(query.Users) > 0 && !containsString(query.Users, event.User) { continue } if len(query.Resources) > 0 && !containsString(query.Resources, event.Resource) { continue } if query.CorrelationID != "" && event.CorrelationID != query.CorrelationID { continue } events = append(events, &event) if query.Limit > 0 && len(events) >= query.Limit { break } } return events, nil } // Helper functions func contains(slice []EventType, item EventType) bool { for _, v := range slice { if v == item { return true } } return false } func containsSeverity(slice []Severity, item Severity) bool { for _, v := range slice { if v == item { return true } } return false } func containsString(slice []string, item string) bool { for _, v := range slice { if v == item { return true } } return false }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Keeper-Security/keeper-mcp-golang-docker'

If you have feedback or need assistance with the MCP directory API, please join our Discord server