Documentation
¶
Overview ¶
Package audit writes and verifies the hash-chained audit logs (PLAN §5, security.md "Audit log").
There are two kinds of log: the instance log (<data>/audit.db: logins, users, unseal) and one per tenant. Entries that belong to a business change are written to the outbox of the same database in the same transaction (Enqueue) and delivered exactly once by the Deliverer; events without a business change (a failed login, a denied request) are appended directly (Append). Verify walks a chain and finds the first broken entry.
Index ¶
- Constants
- Variables
- func Append(ctx context.Context, db *sql.DB, scope string, e Entry, outboxID string) (seq int64, appended bool, err error)
- func CheckHead(ctx context.Context, q auditdb.DBTX, want Head) error
- func Enqueue(ctx context.Context, tx *sql.Tx, now time.Time, e Entry) error
- func Genesis(scope string) string
- func KeyFingerprint(pub ed25519.PublicKey) string
- type Actor
- type ActorType
- type Backlog
- type Break
- type CheckpointInfo
- type CheckpointLog
- type CheckpointResult
- type Checkpointer
- type Deliverer
- func (d *Deliverer) Backlog(ctx context.Context) ([]Backlog, error)
- func (d *Deliverer) Cleanup(ctx context.Context) (int64, error)
- func (d *Deliverer) DeliverPending(ctx context.Context) (int, error)
- func (d *Deliverer) LogHeads(ctx context.Context)
- func (d *Deliverer) Run(ctx context.Context, wake <-chan struct{}, pollInterval time.Duration)
- type Entry
- type Head
- type Report
- type Result
- type Route
- type Signer
- type Target
- type VerifyOptions
Constants ¶
const ActionCheckpoint = "audit.checkpoint"
ActionCheckpoint marks a signed checkpoint (PHASE1 step 6a). It is a normal entry of the chain: it signs the entry right before it, and deleting or changing it breaks the chain like any other entry. Rewriting the chain before a checkpoint needs its signing key, which only exists while the server is unsealed.
const CheckpointEvery = 1000
CheckpointEvery: a checkpoint is made after this many new entries, besides the ones at unseal, before seal and once a day.
const OutboxRetention = 30 * 24 * time.Hour
OutboxRetention: delivered outbox rows are deleted after this time; they are only needed to redeliver after a crash or restore. Undelivered rows are never deleted.
const TimeFormat = "2006-01-02T15:04:05.000Z"
TimeFormat is used for every time in the audit log and the outbox: UTC with milliseconds and a fixed width, so text order is time order.
Variables ¶
var ErrInvalid = errors.New("invalid audit entry")
ErrInvalid is wrapped by every error about an entry that may not be logged.
var ErrNoSigner = errors.New("checkpoint key unavailable")
ErrNoSigner means no checkpoint key is available – the server is sealed. The Signer wraps it into its own error.
Functions ¶
func Append ¶
func Append(ctx context.Context, db *sql.DB, scope string, e Entry, outboxID string) (seq int64, appended bool, err error)
Append adds e to the log of scope in its own transaction and returns its seq. db must be the log's single writer connection. With a non-empty outboxID an entry that is already in the log is not added again: then Append returns its existing seq and appended=false.
func CheckHead ¶
CheckHead compares the log with a head noted earlier (process log, backup manifest): the entry at that seq must still exist and have that hash. This catches what the chain alone can't – a cut-off end, or a chain that was recomputed from that entry on.
func Enqueue ¶
Enqueue writes e to the outbox of the producing database inside tx – the same transaction as the business change it describes, so both are stored or neither is. now is the outbox row's creation time.
func Genesis ¶
Genesis is prev_hash of the first entry of the log of scope. Binding the start to the scope means a log can't be passed off as another scope's log.
func KeyFingerprint ¶
KeyFingerprint is how a checkpoint key is shown and pinned: the first 16 bytes of SHA-256 over the public key, in hex.
Types ¶
type Backlog ¶
type Backlog struct {
Source string
Pending int64
Oldest time.Time // zero when nothing is pending
}
Backlog is the undelivered part of one outbox.
type CheckpointInfo ¶
CheckpointInfo locates the newest checkpoint.
type CheckpointLog ¶
CheckpointLog is one audit log the Checkpointer signs.
type CheckpointResult ¶
type CheckpointResult struct {
Made bool
Head Head // the checkpoint entry itself
Key string // fingerprint of the signing key
}
CheckpointResult describes a new checkpoint.
func Checkpoint ¶
func Checkpoint(ctx context.Context, db *sql.DB, scope string, s Signer, now time.Time) (res CheckpointResult, err error)
Checkpoint appends a signed checkpoint covering the current head of the log. It does nothing (Made=false) for an empty log or when the head is a checkpoint already.
type Checkpointer ¶
type Checkpointer struct {
Logs []CheckpointLog
Signer Signer
Clock clock.Clock
Log *slog.Logger
Every int64 // 0 = CheckpointEvery
}
Checkpointer makes checkpoints for several logs. The server calls MakeAll after unseal, before seal and daily, and MaybeEvery after deliveries.
func (*Checkpointer) MakeAll ¶
func (c *Checkpointer) MakeAll(ctx context.Context, reason string) error
MakeAll makes a checkpoint in every log that has new entries. A sealed keyring is not an error: there is just no checkpoint.
func (*Checkpointer) MaybeEvery ¶
func (c *Checkpointer) MaybeEvery(ctx context.Context) error
MaybeEvery makes a checkpoint in every log with at least CheckpointEvery entries since its last checkpoint.
type Deliverer ¶
type Deliverer struct {
Routes []Route
Clock clock.Clock
Log *slog.Logger
// Checkpoints, if set, makes a checkpoint after every 1000 entries and
// once a day (step 6a).
Checkpoints *Checkpointer
// contains filtered or unexported fields
}
Deliverer moves outbox rows into the audit logs.
func (*Deliverer) Backlog ¶
Backlog reports what waits in each route's outbox – the server warns with count and age while deliveries fail (owner 2026-10-16: outbox entries may pile up, they are never lost; direct entries are fail-closed instead).
func (*Deliverer) Cleanup ¶
Cleanup deletes outbox rows that were delivered more than OutboxRetention ago. Undelivered rows are never deleted, however old they are.
func (*Deliverer) DeliverPending ¶
DeliverPending delivers every undelivered outbox row of every route and returns how many entries were added. Each row is appended in its own audit transaction and then marked delivered in the producing database. A crash in between is harmless: the row is delivered again and the audit log recognizes its outbox_id. Errors of one route don't stop the others.
func (*Deliverer) LogHeads ¶
LogHeads writes the head of every audit log to the process log. The process log usually ends up somewhere else (journald, a log collector), so it is an anchor for `myownpki audit verify --expect-head`.
type Entry ¶
type Entry struct {
Time time.Time
Actor Actor
Action string // dotted, e.g. "certificate.issue"
Target Target
Result Result
SourceIP string // IP address only (no port, no host name); "" if none
Details map[string]any // never secrets – see forbiddenKey
}
Entry is one event before it gets a place in the chain.
type Head ¶
Head identifies the newest entry of a chain: "seq:hash".
type Report ¶
type Report struct {
Entries int64
Head Head // Seq 0 = empty log
Break *Break // nil = intact
Checkpoints int64
LastCheckpoint *CheckpointInfo // nil = none yet
CheckpointKey string // fingerprint of the key all checkpoints use
}
Report is the result of Verify.
func Verify ¶
Verify walks the whole log of scope and reports the first broken entry: a gap in seq (deleted entry), a prev_hash that doesn't match the previous entry (deleted, inserted or reordered entries) or a hash that doesn't match the entry's content (modified entry). It only reads.
func VerifyWith ¶
func VerifyWith(ctx context.Context, q auditdb.DBTX, scope string, opts VerifyOptions) (Report, error)
VerifyWith is Verify with options. Checkpoints are checked on the way: each must cover the entry before it, carry a valid Ed25519 signature and use the same (and, if pinned, the expected) key.
type Route ¶
type Route struct {
Source string // outbox_id prefix, e.g. "system" or "pki"; unique per audit log
From *sql.DB // writer of the producing database
To *sql.DB // writer of the audit log
Scope string // scope of the audit log
}
Route connects the outbox of one producing database with one audit log.
type Signer ¶
Signer signs checkpoints; *keys.Keyring implements it with an Ed25519 key derived from the root key.
type VerifyOptions ¶
type VerifyOptions struct {
// CheckpointKey pins the checkpoint key: a fingerprint (prefix of at
// least 16 hex digits) noted at init or taken from the process log.
// Without it, Verify only checks that all checkpoints use one key.
CheckpointKey string
}
VerifyOptions tighten Verify.