audit

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 7, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package audit centralizes bootstrap-period audit-chain helpers used by auditcore's auditappendbootstrap slice handler.

The bootstrap auth-fail event flow is:

  1. runtime/auth.NewBootstrapMiddleware calls the observer closure on 401/429.
  2. The observer (constructed in cellmodules/accesscore) calls cells/accesscore/slices/setup.Service.RecordBootstrapAuthFail, which emits event.auth.bootstrap-failed.v1 via outbox (inside a transaction).
  3. auditcore's auditappendbootstrap slice consumes the event and calls AppendBootstrapAuthFail → BootstrapLedgerStore.Append.

The emit path is covered by EMIT-DECL-COVER-01 + the event contract. The MODULE-PROVIDE-NO-VALUE-HANDOFF-01 archtest ensures the composition root cannot pass a BootstrapLedgerStore across cell boundaries via ModuleExports. The bootstrap namespace is physically isolated from the relay chain via BootstrapNamespace() (ADR 202605270230).

Index

Constants

View Source
const (
	ReasonMissingHeader    = "missing_header"
	ReasonWrongCredentials = "wrong_credentials"
	ReasonRateLimited      = "rate_limited"
)

Bootstrap auth-fail reason values are the authoritative source for valid reason strings. These constants mirror the literal vocabulary used by runtime/auth/bootstrap.go and are re-declared in cells/accesscore/internal/dto (which cannot import runtime/audit) and in cells/accesscore/slices/setup (reason whitelist for RecordBootstrapAuthFail).

The event contract (event.auth.bootstrap-failed.v1 payload.schema.json) only constrains reason to type string — it does NOT express the closed {missing_header, wrong_credentials, rate_limited} set (no JSON-schema enum). The closed set is maintained at runtime by validBootstrapAuthFailReasons (consumer) + the setup reason whitelist (producer), and the cross-package equivalence of these declared sets is enforced statically by the BOOTSTRAP-REASON-SET-EQUIVALENCE archtest. Schema = type guard; archtest + whitelists = closed-set guard.

Variables

This section is empty.

Functions

func AppendBootstrapAuthFail

func AppendBootstrapAuthFail(
	ctx context.Context, store *BootstrapLedgerStore, clk clock.Clock,
	eventID, reason, clientIPHash string,
) error

AppendBootstrapAuthFail constructs a bootstrap.auth.fail ledger entry and persists it via the sealed *BootstrapLedgerStore. clientIPHash is the keyed, non-reversible hash of the client IP (#1488); it is empty when no IP was available (e.g. health probes, or the request did not flow through middleware that sets ctxkeys.RealIP).

clientIPHash is typed string (not redaction.IPHash) by design: this is the CONSUMER side — the sealed type that blocks plaintext lives on the producer (cells/accesscore setup.Service). This function still validates the untrusted wire value at the trust boundary with redaction.IsIPHashString before writing the ledger. Callers are responsible for passing an already-hashed value; the only producer is the bootstrap observer, which hashes via redaction.HashIP.

eventID is the stable source-event identity (the consuming slice passes outbox.Entry.ID()). It becomes the ledger EventID, which is the SOLE idempotency key (ledger.IdempotencyContentFingerprint hashes EventID only, see runtime/audit/ledger/mem_store.go). Using the stable entry ID — rather than a freshly minted uuid.NewString() — is what makes at-least-once redelivery idempotent: a redelivered event carries the same outbox UUID, so the second Append collapses to ErrAuditLedgerAlreadyExists instead of appending a duplicate ledger row. This mirrors the relay-chain appender (cells/auditcore/internal/appender) which also keys on entry.ID(). Callers must treat ErrAuditLedgerAlreadyExists (KindConflict) as an idempotent SUCCESS and Ack — see auditappendbootstrap.Service.HandleEvent.

Consistency level: L1 LocalTx — store.Append runs in a single PG transaction with the advisory-lock-fenced chain tail read; no outbox emit follows, so L2 atomicity coverage does not apply. The bootstrap chain is physically distinct from the auditcore relay chain via BootstrapNamespace(): the *BootstrapLedgerStore typed handle makes accidental injection of the auditcore-namespace store a compile error rather than a runtime fork (issue #1121 / ADR 202605270230 — Vault per-device-salt + Trillian per-tree partition patterns).

Payload JSON shape (camelCase, additive evolution):

{"reason": "<one of: missing_header | wrong_credentials | rate_limited>",
 "clientIpHash": "<keyed HMAC hex hash of the client IP, or empty>"}

Errors:

  • ErrValidationFailed when store / clock is nil, eventID is empty, or reason is not in {missing_header, wrong_credentials, rate_limited}.
  • The wrapped Append error otherwise — most commonly ErrAuditLedgerAlreadyExists (idempotent replay; callers Ack) or a chain-write failure (transient; callers Requeue). The wrap preserves the inner *errcode.Error code so callers can classify via errors.As.

func BootstrapNamespace

func BootstrapNamespace() ledger.NamespaceID

BootstrapNamespace returns the canonical ledger.NamespaceID used by every bootstrap auth-fail audit chain. It is the only sanctioned way to obtain this NamespaceID at composition root; archtest AUDIT-NS-DISJOINT-01 locks the composition site to call this function rather than passing the literal string.

func VerifyBootstrapTailOnStartup

func VerifyBootstrapTailOnStartup(ctx context.Context, store *BootstrapLedgerStore, logger *slog.Logger) error

VerifyBootstrapTailOnStartup runs the strict tail-verify protocol against the bootstrap audit chain: reads the tail snapshot, then re-computes the HMAC for every entry [1, tail.SeqNo] and checks chain linkage.

Called by cmd/corebundle.AuditCoreModule.Provide immediately after the bootstrap store is constructed. The bootstrap chain lives outside the auditcore cell (it is the only audit chain owned directly by the composition root), so its tail-verify cannot reuse cells/auditcore's strictTailVerifyOnStartup helper. The two helpers share the same fail- closed posture: a corrupted or tampered bootstrap chain surfaces at process startup rather than at the first 401/429, blocking k8s readiness instead of silently appending to a forked chain.

Empty chain (tail.SeqNo == 0) returns nil — no entries to verify.

ref: cells/auditcore/cell.go:strictTailVerifyOnStartup — auditcore-side counterpart for the relay-driven chain. ref: google/trillian log/sequencer.go IntegrateBatch — tree integrity must be verified before accepting new leaves (same fail-fast invariant).

Types

type BootstrapLedgerStore

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

BootstrapLedgerStore is a sealed handle for the bootstrap audit chain. It is the only typed value accepted by AppendBootstrapAuthFail — passing the auditcore-namespace store there is a compile error rather than a runtime fork of the hash chain.

Construct with NewBootstrapLedgerStore(inner) at the composition root (cellmodules/auditcore or examples/*/app.go) after building the BootstrapNamespace()-scoped ledger.Store. The handle is wired into the auditcore cell via auditcore.WithBootstrapStore (Wave-1 #1423); the auditappendbootstrap subscriber slice calls AppendBootstrapAuthFail when event.auth.bootstrap-failed.v1 arrives.

ref: google/trillian storage.ReadWriteTransaction(ctx, *trillian.Tree, ...) — typed *Tree pointer prevents cross-tree writes at the type-system layer (the same downstream pattern applied here to the bootstrap chain handle).

Enforcement note (AI-robust grading, see ADR 202605270230 §AI-robust): downstream is Hard (the type-checker rejects every bare `ledger.Store` argument at the AppendBootstrapAuthFail call sites); upstream is also Hard because ledger.Store exposes Protocol() and NewBootstrapLedgerStore rejects any store not scoped to BootstrapNamespace(). AUDIT-NS-DISJOINT-01 remains a composition-root backstop for production wiring.

func NewBootstrapLedgerStore

func NewBootstrapLedgerStore(inner ledger.Store) (*BootstrapLedgerStore, error)

NewBootstrapLedgerStore wraps an inner ledger.Store as the bootstrap-chain handle. Returns an error when:

  • inner is nil or typed-nil (validation.IsNilInterface)
  • inner.Protocol() is nil
  • inner.Protocol().Namespace() is not BootstrapNamespace()

func (*BootstrapLedgerStore) Append

Append delegates to the wrapped Store. Callers should normally invoke AppendBootstrapAuthFail instead, which constructs the canonical bootstrap.auth.fail entry envelope before calling Append.

func (*BootstrapLedgerStore) RepoReady

func (s *BootstrapLedgerStore) RepoReady(ctx context.Context) error

RepoReady delegates to the wrapped Store so the bootstrap chain participates in the same differentiated readiness probe surface as the auditcore chain. Without this delegate, registering *BootstrapLedgerStore via the cellgen RepoProber typed funnel (cells/auditcore/healthz_gen.go) would lose the table-level probe — schema/migration drift would only surface at first 401/429 instead of at probe-fail-fast time.

Note: in the current corebundle wiring the relay-chain store is the only one registered with the audit cell's RepoProber; the bootstrap chain shares the same audit_entries table and PG pool, so a separate probe would be "禁止同时暴露多个同义 ready probe" per observability.md. Future deployments that physically split the chains across pools should register both probes.

func (*BootstrapLedgerStore) Tail

Tail delegates to the wrapped Store. Exposed for VerifyBootstrapTailOnStartup and integration tests; the bootstrap observer path does not call it.

func (*BootstrapLedgerStore) Verify

func (s *BootstrapLedgerStore) Verify(ctx context.Context, fromSeq, toSeq int64) (bool, int64, error)

Verify delegates to the wrapped Store. Used by VerifyBootstrapTailOnStartup and integration tests to confirm chain integrity.

Directories

Path Synopsis
Package ledger provides a typed Protocol primitive and Store interface for append-only, HMAC-linked audit chains.
Package ledger provides a typed Protocol primitive and Store interface for append-only, HMAC-linked audit chains.
storetest
Package storetest provides a reusable Protocol-driven contract test suite for ledger.Store implementations.
Package storetest provides a reusable Protocol-driven contract test suite for ledger.Store implementations.

Jump to

Keyboard shortcuts

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