Documentation
¶
Overview ¶
Package auditpg stores audit events in PostgreSQL and queries them for operator APIs (ADR-0036). Store implements audit.Recorder:
store, err := auditpg.NewStore(pool) settingsStore, err := settings.NewStore(ctx, pool, reg, store)
Events are append-only. Before storing, the store fills actor, request and trace fields from the context, validates the event, redacts metadata under sensitive keys such as "password" or "token", and bounds text lengths and metadata size, so one bad event never blocks the rest.
Use Store.RecordTx to commit an event with the change it describes.
Stability: stable (ADR-0015, ADR-0054).
Index ¶
- Constants
- Variables
- type Filter
- type Option
- type Page
- type Stats
- type StatsCount
- type StatsFilter
- type StatsGroup
- type Store
- func (s *Store) DeleteBefore(ctx context.Context, before time.Time, limit int) (int64, error)
- func (s *Store) Get(ctx context.Context, id int64) (StoredEvent, error)
- func (s *Store) List(ctx context.Context, f Filter) (Page, error)
- func (s *Store) Oldest(ctx context.Context) (oldest time.Time, ok bool, err error)
- func (s *Store) Record(ctx context.Context, e audit.Event) error
- func (s *Store) RecordTx(ctx context.Context, db postgres.DBTX, e audit.Event) error
- func (s *Store) Stats(ctx context.Context, f StatsFilter) (Stats, error)
- type StoredEvent
Constants ¶
const ( DefaultStatsWindow = 7 * 24 * time.Hour MaxStatsWindow = 90 * 24 * time.Hour // MaxStatsGroups is the most groups returned, largest first; the rest are // counted in Stats.Other. Days are never cut: a window has at most 91. MaxStatsGroups = 50 )
Bounds of a stats window.
const DefaultMaxMetadataBytes = 16 << 10
DefaultMaxMetadataBytes bounds an event's metadata after redaction.
const DefaultQueryTimeout = 5 * time.Second
DefaultQueryTimeout bounds each Store.List and Store.Stats query, so a filter no index serves can't hold a connection while it scans the table.
Variables ¶
var ( // ErrEventNotFound reports an event ID that doesn't exist, or was // removed by retention. ErrEventNotFound = errors.New("auditpg: audit event not found") // ErrInvalidCursor reports a cursor that wasn't returned by [Store.List]. ErrInvalidCursor = errors.New("auditpg: invalid cursor") // ErrInvalidFilter reports a [Filter] with an unknown outcome, a // malformed action prefix or an empty time range. The wrapping error // says which. ErrInvalidFilter = errors.New("auditpg: invalid filter") // ErrQueryTimeout reports a list or stats query that ran longer than // the store's query timeout, usually because no index serves its // filters. Narrow the filters or the time range. ErrQueryTimeout = errors.New("auditpg: audit query timed out") )
Errors returned by Store methods. Check them with errors.Is.
var Migrations fs.FS = mustSub(migrationFiles, "migrations")
Migrations holds the module's goose migrations: the audit_events table. Apps copy them into db/migrations; tests can apply them directly with pgtest.
Functions ¶
This section is empty.
Types ¶
type Filter ¶
type Filter struct {
ActorKind actor.Kind
ActorID string
// Action matches one action exactly; ActionPrefix matches every action
// starting with it, such as "jobs." or "auth.session.".
Action string
ActionPrefix string
ResourceType string
ResourceID string
OrgID string
Outcome audit.Outcome
RequestID string
// From and To bound OccurredAt: From inclusive, To exclusive.
From time.Time
To time.Time
// Limit is clamped to 1–100; 0 means 50.
Limit int
// Cursor is Page.NextCursor from the previous page.
Cursor string
}
Filter selects events to list. Empty fields match everything.
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
An Option configures NewStore.
func WithMaxMetadataBytes ¶
WithMaxMetadataBytes sets the largest metadata stored, as JSON after redaction. Larger metadata is replaced with {"metadata_dropped": "too_large"}. Default: DefaultMaxMetadataBytes.
func WithQueryTimeout ¶
WithQueryTimeout sets how long a Store.List or Store.Stats query may run before it is cancelled with ErrQueryTimeout. Default: DefaultQueryTimeout.
func WithRedactedKeys ¶
WithRedactedKeys adds metadata keys whose values are replaced with "[REDACTED]", on top of the defaults (password, passcode, secret, token, cookie, authorization, bearer, api_key, private_key, signing_key, encryption_key, credential, jwt, pin, otp, totp, magic_link, and codes qualified as recovery, verification, reset, login, sign_in, mfa, backup, security or access codes). A key matches when it equals a name or contains it as whole snake_case segments, in any case style and singular or plural: "token" matches "refresh_tokens" and "accessToken" but not "tokenizer". A bare "code" isn't redacted by default, since it usually names an error code; add it here if your metadata uses it for secrets.
type Page ¶
type Page struct {
Events []StoredEvent
// NextCursor fetches the next page; empty on the last page.
NextCursor string
}
Page is a page of events, newest first.
type Stats ¶
type Stats struct {
From, To time.Time
GroupBy StatsGroup
Total int64
// Groups are the largest groups, most events first, or every day in
// order for StatsByDay.
Groups []StatsCount
// Other counts events in groups beyond MaxStatsGroups.
Other int64
}
Stats are event counts in a window.
type StatsCount ¶
StatsCount is one group's count. Key is empty for events without a resource type.
type StatsFilter ¶
type StatsFilter struct {
Filter
GroupBy StatsGroup
}
StatsFilter selects the events to count: Filter's fields except Limit and Cursor. From and To default to the DefaultStatsWindow ending now and may be at most MaxStatsWindow apart.
type StatsGroup ¶
type StatsGroup string
StatsGroup is what Store.Stats counts events by.
const ( StatsByAction StatsGroup = "action" StatsByOutcome StatsGroup = "outcome" StatsByActorKind StatsGroup = "actor_kind" StatsByResourceType StatsGroup = "resource_type" // StatsByDay counts events per UTC day, keyed YYYY-MM-DD. StatsByDay StatsGroup = "day" )
Groupings for Store.Stats.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store records audit events in the audit_events table and lists them. It is safe for concurrent use.
func NewStore ¶
NewStore returns a store on pool. The audit_events table comes from Migrations; apply them first.
func (*Store) DeleteBefore ¶
DeleteBefore deletes up to limit events that occurred before before, oldest first, and returns how many it deleted. Retention calls it until it deletes fewer than limit (ADR-0051).
func (*Store) Get ¶
Get returns one event, or ErrEventNotFound.
func (*Store) List ¶
List returns events matching f, newest first. It returns an error wrapping ErrInvalidFilter or ErrQueryTimeout, or ErrInvalidCursor. Filters on actor ID, action, resource, organisation and request use an index; others, such as outcome or an action prefix alone, scan events newest first until the page fills or the query timeout expires.
func (*Store) Oldest ¶
Oldest returns when the oldest stored event occurred; ok is false when there are none.
func (*Store) Record ¶
Record stores e. Empty actor, organisation, request and trace fields are filled from ctx, and a zero OccurredAt becomes now. It returns an error for an invalid event or when the event can't be stored.
func (*Store) RecordTx ¶
RecordTx stores e through db, usually a pgx.Tx, so the event commits or rolls back with the change it describes.
func (*Store) Stats ¶
Stats counts events matching f by f.GroupBy. It returns an error wrapping ErrInvalidFilter for an unknown grouping or a window over MaxStatsWindow, or ErrQueryTimeout.
type StoredEvent ¶
type StoredEvent struct {
// ID increases in recording order.
ID int64
// RecordedAt is when the database stored the event, by its clock;
// OccurredAt is when the action happened, by the recording instance's.
// Both are UTC.
RecordedAt time.Time
audit.Event
}
StoredEvent is a recorded audit event.