audit

package
v0.4.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 17, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ReasonChanged        = "entry or signature was changed"
	ReasonSigRemoved     = "signature was removed"
	ReasonSeqRemoved     = "chain number was removed"
	ReasonUnchained      = "entry without chain number written after chaining began"
	ReasonPrevMissing    = "previous entry was deleted"
	ReasonPrevChanged    = "previous entry was changed"
	ReasonFirstHasParent = "first entry claims a predecessor"
)

Reasons reported next to an invalid entry.

View Source
const (
	IntegrityOK          = "ok"
	IntegrityCompromised = "compromised"
	IntegrityNotStarted  = "not_started"
	IntegrityDisabled    = "disabled"
)

Integrity statuses.

View Source
const (
	ScopeAll = "all"
	ScopeOwn = "own"
)

Integrity scopes.

View Source
const ChainSigPrefix = "v3:"

ChainSigPrefix marks signatures of chained entries: those also cover their sequence number and the previous entry's signature (see the Logger doc).

View Source
const SigPrefix = "v2:"

SigPrefix marks signatures made since entries became verifiable. Before that (v0.4.0 and earlier) the digest covered nanoseconds the database does not store, so those entries can never verify. The marker lets the API tell "old, expected to fail" from "new, so a failure means tampering" without luck: roughly one legacy entry in a thousand verifies by chance, which makes the first entry that merely verifies a poor boundary. v0.4.1 to v0.4.3 wrote bare hex digests with the same algorithm; they still verify (see VerifyAuditSig).

Variables

View Source
var ErrPruneBlocked = errors.New("audit retention paused")

ErrPruneBlocked is returned by Prune when the entries it would delete do not verify, so retention stops instead of erasing evidence.

Functions

This section is empty.

Types

type Event

type Event struct {
	Actor     string
	SessionID *uuid.UUID
	RunID     *uuid.UUID
	Action    string
	Metadata  models.JSONB
}

type Integrity added in v0.4.5

type Integrity struct {
	Status         string    `json:"status"`
	Scope          string    `json:"scope"`
	CheckedAt      time.Time `json:"checked_at"`
	ChainedEntries int64     `json:"chained_entries"`
	LegacyEntries  int64     `json:"legacy_entries"`
	// InvalidEntries = AlteredEntries + AffectedEntries. Altered entries were
	// themselves changed; affected ones are intact but follow a changed entry.
	InvalidEntries  int64    `json:"invalid_entries"`
	AlteredEntries  int64    `json:"altered_entries"`
	AffectedEntries int64    `json:"affected_entries"`
	MissingEntries  int64    `json:"missing_entries"`
	Problems        []string `json:"problems"`
	// For a key that cannot see the whole log: how many of the invalid entries
	// were recorded under its own name and how many belong to other keys.
	OwnInvalidEntries   *int64           `json:"own_invalid_entries,omitempty"`
	OtherInvalidEntries *int64           `json:"other_invalid_entries,omitempty"`
	Entries             []IntegrityEntry `json:"entries,omitempty"`
	InvalidIDs          []uuid.UUID      `json:"invalid_ids,omitempty"`
	MissingSeqs         []string         `json:"missing_seqs,omitempty"`
	// contains filtered or unexported fields
}

Integrity is the verdict over the whole log, not one page of it.

func (*Integrity) ScopedTo added in v0.4.6

func (i *Integrity) ScopedTo(actor string) *Integrity

ScopedTo returns the verdict as seen by one key: the same status, but only the key's own entries are listed; other keys' entries are replaced by a count.

type IntegrityEntry added in v0.4.6

type IntegrityEntry struct {
	ID         uuid.UUID `json:"id"`
	Seq        *int64    `json:"seq,omitempty"`
	Kind       string    `json:"kind"`
	Reason     string    `json:"reason"`
	Detail     string    `json:"detail"`
	OutOfOrder bool      `json:"out_of_order,omitempty"`
	// contains filtered or unexported fields
}

IntegrityEntry names one entry that failed verification.

type Logger

type Logger struct {
	// contains filtered or unexported fields
}

Logger writes audit entries. With a signing key it also chains them: every entry gets the next sequence number and carries the previous entry's signature, both under its own HMAC, and a signed head row (audit_chain) records the newest number, how far retention has pruned and when chaining began. Changing an entry, removing its signature, moving its timestamp, deleting an entry in the middle or cutting off the newest entries all become detectable, none of which per-entry signatures alone could do. Whoever holds the key can still rewrite the whole chain, and a database writer who saved an older copy of the head can restore it after truncating the tail; shipping the log to a store they cannot write (the SIEM export) covers that.

func New

func New(db *sqlx.DB, hmacKey string) *Logger

New creates an audit Logger. hmacKey is the raw HMAC-SHA256 signing key (AUDIT_HMAC_KEY env var). Pass an empty string to disable signing.

func (*Logger) Classify added in v0.4.5

func (l *Logger) Classify(ctx context.Context, entries []*models.AuditLog)

Classify sets SigStatus, SigValid and SigReason on every entry of a page read from the database. It needs AUDIT_HMAC_KEY; without it entries are left untouched.

func (*Logger) Enabled added in v0.4.1

func (l *Logger) Enabled() bool

Enabled reports whether entries are being signed (AUDIT_HMAC_KEY is set).

func (*Logger) Integrity added in v0.4.5

func (l *Logger) Integrity(ctx context.Context, force bool) (*Integrity, error)

Integrity verifies the whole log: every signature, every link of the chain, holes in the numbering, entries cut off the end and the chain head itself. The result is cached briefly so a page refresh does not rescan the table.

func (*Logger) Log

func (l *Logger) Log(ctx context.Context, e Event)

func (*Logger) Prune added in v0.4.5

func (l *Logger) Prune(ctx context.Context, before time.Time) (int64, error)

Prune deletes entries older than before (retention). Chained entries go as a prefix of the chain, and the chain head records how far, so a pruned log still verifies while a hole elsewhere does not.

The prefix is walked from the oldest entry and only extends over entries that are old, numbered without gaps, correctly signed, linked to their predecessor and not dated before it. It ends at the first entry that is not old enough, so an entry whose timestamp was moved back can never pull newer entries into the deletion, and it is refused (ErrPruneBlocked, nothing deleted) when an old entry fails any of those checks, since the signed boundary written afterwards would otherwise make the log verify again.

func (*Logger) SetClock added in v0.4.6

func (l *Logger) SetClock(now func() time.Time)

SetClock replaces the time source for new entries. It exists for tests that need correctly signed entries with old timestamps.

func (*Logger) SigStatus added in v0.4.4

func (l *Logger) SigStatus(entry *models.AuditLog, verifiedSince *time.Time) string

SigStatus classifies an entry read back from the database.

verifiedSince is the time of the earliest entry carrying SigPrefix (see VerifiedSince). Entries from before it were written with signing off, or by a version whose signatures could not always be checked, so a missing or non-matching signature there is "unsigned" or "unverifiable", which is expected. From that moment on every entry must verify, so a mismatch, or a missing signature, means the entry was altered or its signature removed and is reported "invalid". Without the second rule, clearing the sig column would turn a tampered entry into an ordinary-looking unsigned one.

This is evidence, not proof: someone who can rewrite rows could also move an entry's timestamp back before verifiedSince. Ship the log to a store they cannot write (see the SIEM export) when that matters.

func (*Logger) VerifiedSince added in v0.4.4

func (l *Logger) VerifiedSince(ctx context.Context) *time.Time

VerifiedSince returns the time of the earliest entry that carries SigPrefix, or nil when there is none (yet). Once found it is cached: that entry cannot become later.

func (*Logger) VerifyAuditSig

func (l *Logger) VerifyAuditSig(entry *models.AuditLog) bool

VerifyAuditSig re-computes the HMAC for entry and returns true when it matches the stored Sig. Returns false when signing is not configured.

func (*Logger) WatchIntegrity added in v0.4.5

func (l *Logger) WatchIntegrity(ctx context.Context, interval time.Duration)

WatchIntegrity re-verifies the whole log every interval and logs an error while it is compromised, so tampering is noticed without anyone opening the dashboard. It runs until ctx is cancelled.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL