Documentation
¶
Overview ¶
Package audit records an append-only trail of destructive operations (backup, restore, sync, prune) — who ran what, against which profile, when, and whether it succeeded. It is a stdlib-only leaf: the app layer holds an Auditor and calls Begin before a destructive verb and End after it.
The same Begin/End seam is the interception point reused by other cross- cutting concerns (2FA gating runs as a pre-check before the verb; telemetry records timing/outcome) — they are layered on top rather than each re-wrapping every verb.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Record ¶
Record runs Begin, returns a function to defer that calls End with the verb's error. Usage in an app verb:
done := audit.Record(ctx, d.Auditor, audit.Event{Op: audit.OpPrune, Profile: p, Actor: actor})
defer func() { done(retErr) }()
A nil auditor yields a no-op done func, so callers need no nil check.
Types ¶
type Auditor ¶
type Auditor interface {
// Begin records the start of op and returns a handle to End. Implementations
// may persist a "started" record or defer all writes to End; the app layer
// does not care which.
Begin(ctx context.Context, ev Event) Handle
}
Auditor records audit events. A nil Auditor is valid and is a no-op, so the app layer can call Begin/End unconditionally without nil checks.
type Event ¶
type Event struct {
Time time.Time `json:"time"`
Op Op `json:"op"`
Profile string `json:"profile,omitempty"`
Target string `json:"target,omitempty"` // e.g. sync destination, dump id
Actor string `json:"actor"` // OS user who ran the command
Outcome string `json:"outcome"` // "ok" | "error" (set at End)
Err string `json:"error,omitempty"` // error text when Outcome == "error"
DurationMS int64 `json:"duration_ms"` // wall time Begin→End
}
Event is one audit record. Outcome and DurationMS are filled in at End; the rest are known at Begin.
type FileAuditor ¶
type FileAuditor struct {
// contains filtered or unexported fields
}
FileAuditor appends audit events as JSONL to a single log file. Writes are serialized by a mutex so concurrent verbs (e.g. parallel jobs) don't interleave partial lines. A write error is swallowed: auditing must never fail the operation it records (a backup that succeeded must not report failure because the audit line couldn't be written) — best-effort, append-only.
func NewFileAuditor ¶
func NewFileAuditor(path string, now func() time.Time) (*FileAuditor, error)
NewFileAuditor returns an Auditor appending to path, creating its parent directory. now defaults to time.Now when nil.
type Handle ¶
type Handle interface {
// End records the operation's outcome. err == nil means success. It is safe
// to call End on a zero/nil Handle (no-op).
End(err error)
}
Handle finalizes one operation's audit record.
type Multi ¶
type Multi struct {
// contains filtered or unexported fields
}
Multi fans one operation's Begin/End out to several Auditors, so the same destructive-op seam feeds both the security audit log and the telemetry recorder without the app layer wiring two hooks. Nil entries are skipped, and Multi of zero live auditors is itself a no-op (returns nil from New).