Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Anchor ¶
Anchor records the current chain head (event count + head hash) so a future Verify can detect tail truncation or a re-seal. Call it after a successful Verify. It is a no-op on an empty log.
func VerifyAnchorSignature ¶
func VerifyAnchorSignature(rec AnchorRecord, keys map[string]*rsa.PublicKey) error
VerifyAnchorSignature checks a record's signature against the public key named by its kid. Fails closed on an unknown kid.
Types ¶
type AnchorRecord ¶
type AnchorRecord struct {
Count int64 `json:"count"` // events at the time of anchoring
HeadHash string `json:"head_hash"` // hash of the head event
HeadSeq int64 `json:"head_seq"` // seq of the head event
CreatedAt int64 `json:"created_at"` // unix seconds when anchored
KID string `json:"kid"` // signing key id (look up in the JWKS)
Signature string `json:"signature"` // base64 RS256 over canonicalAnchor(...)
}
AnchorRecord is a self-describing, signed checkpoint of the audit chain. Ship it to an append-only / off-box store; `legant audit anchor --check` validates the live database against it. The signature covers the canonical tuple below, so a shipped copy cannot be forged without the signing key.
func AnchorSigned ¶
func AnchorSigned(ctx context.Context, pool *pgxpool.Pool, signer AnchorSigner) (AnchorRecord, error)
AnchorSigned verifies the chain, signs a checkpoint with the active key, stores it, and returns the signed record. Refuses to anchor a broken or empty chain.
type AnchorSigner ¶
type AnchorSigner interface {
ActiveKID() string
ActiveSigner() *rsa.PrivateKey
}
AnchorSigner provides the active signing key — the SAME key published in the issuer's JWKS — so a signed anchor shares the existing trust root (the keystore satisfies this, as it does for the revocation feed).
type Event ¶
type Event struct {
ID int64 `json:"id"`
Seq *int64 `json:"seq,omitempty"`
ActorType string `json:"actor_type"`
ActorID string `json:"actor_id,omitempty"`
Action string `json:"action"`
ResourceType string `json:"resource_type,omitempty"`
ResourceID string `json:"resource_id,omitempty"`
OnBehalfOf string `json:"on_behalf_of_sub,omitempty"`
ActorChain []string `json:"actor_chain,omitempty"`
DelegationID *string `json:"delegation_id,omitempty"`
GrantJTI string `json:"grant_jti,omitempty"`
OrgID *string `json:"org_id,omitempty"`
Metadata json.RawMessage `json:"metadata,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
Event is a read-model row of the audit trail, including the full delegation provenance (on_behalf_of_sub + actor_chain) of who acted for whom.
type Filter ¶
type Filter struct {
ActorType string
ActorID string
Action string
OnBehalfOf string
DelegationID string
GrantJTI string
Since *time.Time
Until *time.Time
Limit int
Offset int
}
Filter narrows an audit query. Empty/zero fields are not applied.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler serves the read side of the tamper-evident audit trail.
func NewHandler ¶
func (*Handler) List ¶
func (h *Handler) List(w http.ResponseWriter, r *http.Request)
List returns audit events matching the query filters, newest first, paginated.
GET /audit?actor_type=&actor_id=&action=&on_behalf_of_sub=&delegation_id=
&grant_jti=&since=&until=&limit=&offset=
func (*Handler) Routes ¶
Routes mounts the audit read endpoints (intended behind admin authorization).
func (*Handler) VerifyChain ¶
func (h *Handler) VerifyChain(w http.ResponseWriter, r *http.Request)
VerifyChain reports whether the audit hash chain is intact.
GET /audit/verify
type VerifyResult ¶
type VerifyResult struct {
Events int64 // total events scanned
OK bool // true if the whole chain verifies (and matches the last anchor)
HeadHash string // hash of the most recent event
HeadSeq int64 // seq of the most recent event
BreakID int64 // id of the first broken event (0 when OK or for a count regression)
BreakKind string // "content" | "link" | "truncation" (rows removed vs the last anchor) | "prefix" (anchored prefix changed)
}
VerifyResult is the outcome of an audit hash-chain verification.
func CheckAgainstAnchor ¶
func CheckAgainstAnchor(ctx context.Context, pool *pgxpool.Pool, ext AnchorRecord, keys map[string]*rsa.PublicKey) (VerifyResult, error)
CheckAgainstAnchor is the off-box tamper-evidence check: it verifies the live chain internally, validates the external anchor's signature, and then proves the live chain still matches that trusted anchor — so it detects truncation or a rewritten prefix even if the database's own audit_anchors table was forged.
func Verify ¶
Verify recomputes the audit hash chain in seq order and reports the first break. It reuses the in-database audit_row_hash function — the same one the insert trigger uses — so the verifier and the writer can never disagree.
Per-row checks:
- content: stored hash == audit_row_hash(stored prev_hash, …fields). A mismatch means a field was edited in place.
- link: stored prev_hash == the previous row's stored hash. A mismatch means a row was inserted, deleted, or reordered mid-chain.
Tail truncation and a full re-seal do not break the chain internally, so Verify additionally compares against the most recent anchor (see Anchor): a smaller event count is reported as "truncation", and a changed anchored prefix as "prefix".