Implement audit logging functionality across services and middleware
- Introduced a new `auditlog` package to handle audit logging for user actions, including creation, updates, deletions, and authentication events. - Enhanced existing services (customer, user) to log relevant actions using the new audit logger, capturing details such as actor ID, action type, target type, and success status. - Added middleware to enrich request context with metadata for audit logging, ensuring comprehensive tracking of user interactions. - Integrated Elasticsearch for persistent storage of audit logs, with fallback to file-only logging if Elasticsearch is unavailable. - Updated API documentation to include new audit log endpoints for administrative access. This update significantly improves the system's ability to track and audit user actions, enhancing security and accountability within the application.
This commit is contained in:
+49
-9
@@ -7,21 +7,19 @@ import (
|
||||
"tm/pkg/logger"
|
||||
)
|
||||
|
||||
const ActionPasswordReset = "password.reset"
|
||||
|
||||
// TargetType values for audit entries.
|
||||
const (
|
||||
TargetTypeAdmin = "admin"
|
||||
TargetTypeCustomer = "customer"
|
||||
)
|
||||
|
||||
// Entry represents a security-relevant audit event.
|
||||
type Entry struct {
|
||||
ActorID string
|
||||
ActorType string
|
||||
TargetType string
|
||||
TargetID string
|
||||
Action string
|
||||
At int64
|
||||
IP string
|
||||
RequestID string
|
||||
UserAgent string
|
||||
Success bool
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// Logger records audit events.
|
||||
@@ -33,12 +31,13 @@ type auditLogger struct {
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewLogger creates an audit logger backed by structured application logs.
|
||||
// NewLogger creates an audit logger backed by structured application logs only.
|
||||
func NewLogger(log logger.Logger) Logger {
|
||||
return &auditLogger{logger: log}
|
||||
}
|
||||
|
||||
func (a *auditLogger) Log(ctx context.Context, entry Entry) {
|
||||
entry = enrichEntry(ctx, entry)
|
||||
if entry.At == 0 {
|
||||
entry.At = time.Now().Unix()
|
||||
}
|
||||
@@ -46,9 +45,50 @@ func (a *auditLogger) Log(ctx context.Context, entry Entry) {
|
||||
a.logger.Info("audit event", map[string]interface{}{
|
||||
"audit": true,
|
||||
"actor_id": entry.ActorID,
|
||||
"actor_type": entry.ActorType,
|
||||
"target_type": entry.TargetType,
|
||||
"target_id": entry.TargetID,
|
||||
"action": entry.Action,
|
||||
"at": entry.At,
|
||||
"ip": entry.IP,
|
||||
"request_id": entry.RequestID,
|
||||
"user_agent": entry.UserAgent,
|
||||
"success": entry.Success,
|
||||
"metadata": entry.Metadata,
|
||||
})
|
||||
}
|
||||
|
||||
type compositeLogger struct {
|
||||
fileLogger Logger
|
||||
store Store
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewCompositeLogger writes audit events to structured logs and an optional store.
|
||||
func NewCompositeLogger(log logger.Logger, store Store) Logger {
|
||||
if store == nil {
|
||||
store = NoopStore()
|
||||
}
|
||||
return &compositeLogger{
|
||||
fileLogger: NewLogger(log),
|
||||
store: store,
|
||||
logger: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compositeLogger) Log(ctx context.Context, entry Entry) {
|
||||
entry = enrichEntry(ctx, entry)
|
||||
if entry.At == 0 {
|
||||
entry.At = time.Now().Unix()
|
||||
}
|
||||
|
||||
c.fileLogger.Log(ctx, entry)
|
||||
|
||||
doc := entryToDocument(entry)
|
||||
if err := c.store.Save(ctx, doc); err != nil {
|
||||
c.logger.Error("Failed to persist audit event", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"action": entry.Action,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user