- Fix #61: handleRotateKey and handleDeleteUser now zeroize stored privBytes instead of calling Bytes() (which returns a copy). New state populates privBytes; old references nil'd for GC. - Add audit logging subsystem (internal/audit) with structured event recording for cryptographic operations. - Add audit log engine spec (engines/auditlog.md). - Add ValidateName checks across all engines for path traversal (#48). - Update AUDIT.md: all High findings resolved (0 open). - Add REMEDIATION.md with detailed remediation tracking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
80 lines
2.4 KiB
Go
80 lines
2.4 KiB
Go
// Package audit provides structured audit event logging for Metacrypt.
|
|
// Audit events record security-relevant operations (who did what, when,
|
|
// and whether it succeeded) as one JSON object per line.
|
|
package audit
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
)
|
|
|
|
// LevelAudit is a custom slog level for audit events. It sits above Warn
|
|
// so that audit events are never suppressed by log level filtering.
|
|
const LevelAudit = slog.Level(12)
|
|
|
|
func init() {
|
|
// Replace the level name so JSON output shows "AUDIT" instead of "ERROR+4".
|
|
slog.SetLogLoggerLevel(LevelAudit)
|
|
}
|
|
|
|
// Logger writes structured audit events. A nil *Logger is safe to use;
|
|
// all methods are no-ops.
|
|
type Logger struct {
|
|
logger *slog.Logger
|
|
}
|
|
|
|
// New creates an audit logger writing to the given handler. Pass nil to
|
|
// create a disabled logger (equivalent to using a nil *Logger).
|
|
func New(h slog.Handler) *Logger {
|
|
if h == nil {
|
|
return nil
|
|
}
|
|
return &Logger{logger: slog.New(h)}
|
|
}
|
|
|
|
// Event represents a single audit event.
|
|
type Event struct {
|
|
Caller string // MCIAS username, or "operator" for unauthenticated ops
|
|
Roles []string // caller's MCIAS roles
|
|
Operation string // engine operation name (e.g., "issue", "sign-user")
|
|
Engine string // engine type (e.g., "ca", "sshca", "transit", "user")
|
|
Mount string // mount name
|
|
Resource string // policy resource path evaluated
|
|
Outcome string // "success", "denied", or "error"
|
|
Error string // error message (on "error" or "denied" outcomes)
|
|
Detail map[string]interface{} // operation-specific metadata
|
|
}
|
|
|
|
// Log writes an audit event. Safe to call on a nil receiver.
|
|
func (l *Logger) Log(ctx context.Context, e Event) {
|
|
if l == nil {
|
|
return
|
|
}
|
|
|
|
attrs := []slog.Attr{
|
|
slog.String("caller", e.Caller),
|
|
slog.String("operation", e.Operation),
|
|
slog.String("outcome", e.Outcome),
|
|
}
|
|
if len(e.Roles) > 0 {
|
|
attrs = append(attrs, slog.Any("roles", e.Roles))
|
|
}
|
|
if e.Engine != "" {
|
|
attrs = append(attrs, slog.String("engine", e.Engine))
|
|
}
|
|
if e.Mount != "" {
|
|
attrs = append(attrs, slog.String("mount", e.Mount))
|
|
}
|
|
if e.Resource != "" {
|
|
attrs = append(attrs, slog.String("resource", e.Resource))
|
|
}
|
|
if e.Error != "" {
|
|
attrs = append(attrs, slog.String("error", e.Error))
|
|
}
|
|
if len(e.Detail) > 0 {
|
|
attrs = append(attrs, slog.Any("detail", e.Detail))
|
|
}
|
|
|
|
l.logger.LogAttrs(ctx, LevelAudit, "audit", attrs...)
|
|
}
|