audit

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package audit records every mutating admin request against the control plane. Flipping a value here changes the behaviour of every consuming microservice within milliseconds, so "who changed what, when" has to survive the request that made the change — the KV bucket only ever holds the current value.

An audit write is best effort by design: it is recorded after the handler has already committed, and a failure is logged rather than propagated. Losing the record of a change is bad; refusing a legitimate production change because the audit table is unavailable is worse.

Index

Constants

View Source
const MaxStoredBody = 3800

MaxStoredBody bounds the recorded body. It is a byte budget, while REQUEST_BODY's VARCHAR(4000) is measured in characters, so the budget is deliberately conservative: a body of multi-byte characters is truncated earlier than the column requires, never later. That is the safe direction to be wrong in — this insert is on the request path, and a body the column rejects is a failed INSERT rather than a shortened row. The headroom covers the truncation marker.

Variables

This section is empty.

Functions

func Redact

func Redact(body []byte) string

Redact prepares a request body for storage: secret-looking values are masked and the result is bounded. A body that is not JSON is dropped entirely rather than stored blind — the API only accepts JSON, so anything else is either a probe or a mistake and neither is worth persisting verbatim.

func WithEnvironments

func WithEnvironments(r *http.Request, envs []int64) *http.Request

WithEnvironments narrows what the request may read to the given environment ids. The admin API resolves the reader's credential and applies its scope here, so a request that arrives unnarrowed reads the whole trail — which is what a full-scope token, and a deployment with auth disabled, is entitled to.

Types

type Entry

type Entry struct {
	ID            int64     `json:"id"`
	OccurredAt    time.Time `json:"occurredAt"`
	Actor         string    `json:"actor"`
	Method        string    `json:"method"`
	Path          string    `json:"path"`
	Domain        string    `json:"domain,omitempty"`
	TargetID      string    `json:"targetId,omitempty"`
	EnvironmentID int64     `json:"environmentId,omitempty"`
	StatusCode    int       `json:"statusCode"`
	RemoteAddr    string    `json:"remoteAddr,omitempty"`
	RequestBody   string    `json:"requestBody,omitempty"`
}

Entry is one recorded mutation. Domain/TargetID/EnvironmentID are best-effort — the middleware fills them from whatever the route makes knowable, and a request that never reached a route (404, 401) carries only the envelope.

type Filter

type Filter struct {
	Actor        string
	From         time.Time
	To           time.Time
	Environments []int64
	Limit        int
	Offset       int
}

Filter narrows the audit listing. A zero From/To is an open end of the range.

Environments is the reader's own scope rather than a query parameter: a nil slice is full scope, and any other value restricts the listing to rows recorded against those environments. A row whose environment is unknown — the 404 and 401 envelopes, and the writes that belong to no environment — is outside every narrowed scope, because its body is the one thing about it that is not already known.

type Handler

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

Handler serves the audit log.

GET /audit?actor=&from=&to=&limit=&offset=&environmentId=

It follows the list-endpoint conventions of the domain handlers: ParsePage for the page, a flat JSON array, and no internal detail in error bodies. The rows carry request bodies from every environment, so the reader's scope (WithEnvironments) narrows the listing the same way it narrows a write.

func NewHandler

func NewHandler(store *Store) *Handler

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

type Store

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

Store reads and writes CONFIG_AUDIT_LOG. Unlike the domain repositories there is a single implementation: the two statements differ only in bind syntax and how a timestamp is bound, so a dialect flag is cheaper than a second file that would have to be kept in sync by hand.

func NewStore

func NewStore(db *sql.DB, driver string) *Store

NewStore picks the dialect from the same DB_DRIVER value the repositories use.

func (*Store) Insert

func (s *Store) Insert(ctx context.Context, e Entry) error

func (*Store) List

func (s *Store) List(ctx context.Context, filter Filter) ([]Entry, error)

List returns the most recent entries first — an audit reader is almost always asking "what just happened".

Jump to

Keyboard shortcuts

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