Skip to content

v0.12 — Tamper-evident audit

Choose a tag to compare

@sethbergman sethbergman released this 25 Aug 20:29
· 59 commits to main since this release
333e34b

v0.11 made the audit trail outlive the node it describes. It could still
be edited in place, and nothing would say so.

The collector appended bytes, so anything able to write to the volume
could remove the record of what it did — and a shorter log is
indistinguishable from a quieter day.

The chain

Every entry now also produces a link in a parallel chain file:

<seq> <sha256(entry)> <sha256(previous_chain_hash + entry_hash)>

Each link covers the one before it, so removing, altering or inserting an
entry breaks everything after it. scripts/verify-audit-chain.sh
recomputes the chain from the entries, not from the chain — reading
the chain and checking it is self-consistent would pass on any internally
consistent forgery — and reports the first sequence that diverges.

It separates the verdicts, because they call for different responses:

Verdict What happened
chain mismatch at N an entry at or before N was altered, removed or inserted
log longer than chain the tail was never chained; a crash does this, so does appending by hand
chain longer than log entries were deleted, and the chain still remembers them
anchor mismatch the chain itself was rewritten

The log is written exactly as before — raw entries, one per line, no added
fields — so anything already consuming it is unaffected.

Why the chain alone would not have been enough

A hash chain catches whoever cannot recompute it and does nothing against
whoever can. Delete the entries recording what you did, recompute from
that point, and the result verifies perfectly: internally consistent and
completely false.

So audit-anchor runs as its own service, recording the chain head to
its own volume with the audit volume mounted read-only. Neither
container can write where the other reads. A head recorded for sequence N
still remembers what the trail said, and any later rewrite at or before N
disagrees with it.

The test suite asserts this directly rather than describing it: it builds
a trail, anchors it, deletes an entry, recomputes the whole chain, and
checks the result passes verification without anchors and fails
with them. The passing half is the point — it is the evidence that
anchoring is load-bearing rather than decoration.

What this is not

One host. Both volumes live on the same Docker daemon, so a sufficiently
privileged compromise reaches the anchors too. This is tamper evidence,
not tamper proofing, and only the first is claimed.

A production anchor belongs where the Vault host cannot write at all —
object-lock storage, a different account, or an external timestamping
service. The anchor format is three fields of text precisely so that
shipping it elsewhere is not a redesign.

Off-host audit collection remains a v1.0 blocker.

See docs/audit.md.