auditpg

package module
v0.3.4 Latest Latest
Warning

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

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

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

View Source
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.

View Source
const DefaultMaxMetadataBytes = 16 << 10

DefaultMaxMetadataBytes bounds an event's metadata after redaction.

View Source
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

View Source
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.

View Source
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

func WithMaxMetadataBytes(n int) Option

WithMaxMetadataBytes sets the largest metadata stored, as JSON after redaction. Larger metadata is replaced with {"metadata_dropped": "too_large"}. Default: DefaultMaxMetadataBytes.

func WithQueryTimeout

func WithQueryTimeout(d time.Duration) Option

WithQueryTimeout sets how long a Store.List or Store.Stats query may run before it is cancelled with ErrQueryTimeout. Default: DefaultQueryTimeout.

func WithRedactedKeys

func WithRedactedKeys(names ...string) Option

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

type StatsCount struct {
	Key   string
	Count int64
}

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

func NewStore(pool *pgxpool.Pool, opts ...Option) (*Store, error)

NewStore returns a store on pool. The audit_events table comes from Migrations; apply them first.

func (*Store) DeleteBefore

func (s *Store) DeleteBefore(ctx context.Context, before time.Time, limit int) (int64, error)

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

func (s *Store) Get(ctx context.Context, id int64) (StoredEvent, error)

Get returns one event, or ErrEventNotFound.

func (*Store) List

func (s *Store) List(ctx context.Context, f Filter) (Page, error)

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

func (s *Store) Oldest(ctx context.Context) (oldest time.Time, ok bool, err error)

Oldest returns when the oldest stored event occurred; ok is false when there are none.

func (*Store) Record

func (s *Store) Record(ctx context.Context, e audit.Event) error

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

func (s *Store) RecordTx(ctx context.Context, db postgres.DBTX, e audit.Event) error

RecordTx stores e through db, usually a pgx.Tx, so the event commits or rolls back with the change it describes.

func (*Store) Stats

func (s *Store) Stats(ctx context.Context, f StatsFilter) (Stats, error)

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.

Jump to

Keyboard shortcuts

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