Documentation
¶
Index ¶
- Constants
- Variables
- type Event
- type Integrity
- type IntegrityEntry
- type Logger
- func (l *Logger) Classify(ctx context.Context, entries []*models.AuditLog)
- func (l *Logger) Enabled() bool
- func (l *Logger) Integrity(ctx context.Context, force bool) (*Integrity, error)
- func (l *Logger) Log(ctx context.Context, e Event)
- func (l *Logger) Prune(ctx context.Context, before time.Time) (int64, error)
- func (l *Logger) SetClock(now func() time.Time)
- func (l *Logger) SigStatus(entry *models.AuditLog, verifiedSince *time.Time) string
- func (l *Logger) VerifiedSince(ctx context.Context) *time.Time
- func (l *Logger) VerifyAuditSig(entry *models.AuditLog) bool
- func (l *Logger) WatchIntegrity(ctx context.Context, interval time.Duration)
Constants ¶
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.
const ( IntegrityOK = "ok" IntegrityCompromised = "compromised" IntegrityNotStarted = "not_started" IntegrityDisabled = "disabled" )
Integrity statuses.
const ( ScopeAll = "all" ScopeOwn = "own" )
Integrity scopes.
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).
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 ¶
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 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.
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 ¶
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
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
Enabled reports whether entries are being signed (AUDIT_HMAC_KEY is set).
func (*Logger) Integrity ¶ added in v0.4.5
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) Prune ¶ added in v0.4.5
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
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
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
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 ¶
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
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.