101 lines
3.2 KiB
Go
101 lines
3.2 KiB
Go
// Package audit provides structured audit logging for security-relevant
|
|
// operations. Secrets are never included in audit entries.
|
|
package audit
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/andydixon/nexus/internal/models"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// AuditStore is the persistence interface used by the Logger.
|
|
type AuditStore interface {
|
|
AddAuditEntry(entry *models.AuditEntry) error
|
|
ListAuditEntries(limit int) ([]*models.AuditEntry, error)
|
|
}
|
|
|
|
// Logger writes structured audit entries for connection events, profile
|
|
// changes, secret access, and import/export operations.
|
|
type Logger struct {
|
|
store AuditStore
|
|
}
|
|
|
|
// NewLogger creates a Logger backed by the given store.
|
|
func NewLogger(store AuditStore) *Logger {
|
|
return &Logger{store: store}
|
|
}
|
|
|
|
// Log records a generic audit entry. Fields left empty are stored as-is.
|
|
func (l *Logger) Log(action string, profileID string, sessionID string, details string) {
|
|
entry := &models.AuditEntry{
|
|
ID: uuid.New().String(),
|
|
Timestamp: time.Now(),
|
|
Action: action,
|
|
ProfileID: profileID,
|
|
SessionID: sessionID,
|
|
Details: details,
|
|
}
|
|
// Best-effort logging — errors are silently discarded to avoid
|
|
// disrupting the caller's operation.
|
|
_ = l.store.AddAuditEntry(entry)
|
|
}
|
|
|
|
// LogConnect records a session connection event.
|
|
func (l *Logger) LogConnect(profileID string, sessionID string, protocol string, host string) {
|
|
l.Log("connect", profileID, sessionID,
|
|
fmt.Sprintf("Connected via %s to %s", protocol, host))
|
|
}
|
|
|
|
// LogDisconnect records a session disconnection event.
|
|
func (l *Logger) LogDisconnect(sessionID string) {
|
|
l.Log("disconnect", "", sessionID, "Session disconnected")
|
|
}
|
|
|
|
// LogProfileCreate records the creation of a new profile.
|
|
func (l *Logger) LogProfileCreate(profileID string, name string) {
|
|
l.Log("profile.create", profileID, "",
|
|
fmt.Sprintf("Created profile %q", name))
|
|
}
|
|
|
|
// LogProfileUpdate records an update to an existing profile.
|
|
func (l *Logger) LogProfileUpdate(profileID string, name string) {
|
|
l.Log("profile.update", profileID, "",
|
|
fmt.Sprintf("Updated profile %q", name))
|
|
}
|
|
|
|
// LogProfileDelete records the deletion of a profile.
|
|
func (l *Logger) LogProfileDelete(profileID string, name string) {
|
|
l.Log("profile.delete", profileID, "",
|
|
fmt.Sprintf("Deleted profile %q", name))
|
|
}
|
|
|
|
// LogSecretAccess records access to stored credentials. The secret value is
|
|
// never recorded — only the type of secret accessed.
|
|
func (l *Logger) LogSecretAccess(profileID string, secretType string) {
|
|
l.Log("secret.access", profileID, "",
|
|
fmt.Sprintf("Accessed %s credential", secretType))
|
|
}
|
|
|
|
// LogImport records a bulk profile import.
|
|
func (l *Logger) LogImport(count int) {
|
|
l.Log("import", "", "",
|
|
fmt.Sprintf("Imported %d profile(s)", count))
|
|
}
|
|
|
|
// LogExport records a profile export, noting whether secrets were included.
|
|
func (l *Logger) LogExport(count int, includeSecrets bool) {
|
|
secretsNote := "without secrets"
|
|
if includeSecrets {
|
|
secretsNote = "with secrets"
|
|
}
|
|
l.Log("export", "", "",
|
|
fmt.Sprintf("Exported %d profile(s) %s", count, secretsNote))
|
|
}
|
|
|
|
// ListEntries returns recent audit entries, delegating to the backing store.
|
|
func (l *Logger) ListEntries(limit int) ([]*models.AuditEntry, error) {
|
|
return l.store.ListAuditEntries(limit)
|
|
}
|