MAQPNADocs

Anchor the ledger in a transparency log

Publish signed ledger heads to Sigstore Rekor or a private Rekor, so a third party can prove the audit ledger existed at a time and was not rewritten, without trusting your keys.

The audit ledger is hash-chained, and signed checkpoints attest its head. Both rest on keys you hold. Someone who controls the installation and the checkpoint key could rewrite the ledger, re-hash it and re-sign the checkpoints. Anchoring closes that gap. The gateway publishes the ledger head to an append-only transparency log (Sigstore Rekor), so a third party can prove:

the ledger up to seq N, with head hash H, existed by time T and has not been rewritten since.

They need only the stored anchor, a copy of the ledger and the log's public key.

sequenceDiagram
    participant G as Gateway loop
    participant L as Ledger
    participant R as Rekor
    participant F as anchors.jsonl
    participant V as Verifier
    G->>L: read head (seq, hash)
    G->>G: in-toto statement, chained to previous anchor
    G->>G: DSSE envelope signed with checkpoint key
    G->>R: submit entry (v1 dsse or v2 hashedrekord)
    R-->>G: log index, inclusion proof, signed checkpoint
    G->>G: verify against pinned log key
    G->>F: append anchor
    G->>L: record audit/anchored
    V->>F: maqpna audit verify --anchors
    V->>V: chain, signature, inclusion, time

When to use it#

  • An auditor or regulator must be able to check the ledger without trusting your keys.
  • You want tamper evidence that survives a full compromise of the installation.

What leaves the installation#

Only hashes and sequence numbers. Each anchor is an in-toto Statement with predicate type https://maqpna.com/ledger-checkpoint/v1: ledger ID (hash of record 1), seq, head hash, the hash of the previous anchor, time and key ID. No session, agent, user, tool, argument, decision or reason is sent.

The log does learn your checkpoint public key, how many records existed at each anchor and how often you anchor. Publishing to the public-good log is irrevocable and public, so anchoring is off by default. Air-gapped and sovereign installations can use a private Rekor.

Prerequisites#

  • Signed checkpoints: audit.checkpointSigning.enabled: true with a key (see Read and verify the audit ledger). That key signs the anchors. Any key URI works: file, KMS, Azure Key Vault or PKCS#11.
  • A Rekor log reachable from the gateway. With a sovereignty policy, the connection goes through the residency dialer; a log outside the allowed jurisdictions is refused.
  • The log's public key from a trusted source. For Sigstore's public instances, use trusted_root.json from Sigstore's TUF repository, not GET /api/v1/log/publicKey from the log itself.
Log URL API
Sigstore public good https://rekor.sigstore.dev v1
Sigstore public good, Rekor v2 The current yearly shard from Sigstore's signing config v2
Sigstore staging (tests only) https://rekor.sigstage.dev v1, v2
Private Rekor Your URL v1 or v2

Step 1: Try it on a ledger file#

Anchor a local ledger first. This needs no gateway:

maqpna audit anchor .maqpna/audit.jsonl \
  --key file:///path/to/audit-signing.pem \
  --rekor-url https://rekor.sigstage.dev \
  --rekor-key staging_trusted_root.json

It writes .maqpna/audit.jsonl.anchors.jsonl. The command refuses to anchor a chain that does not verify. --record also appends an audit/anchored record to the file; use it only while no gateway writes that file.

Step 2: Turn on anchoring in the gateway#

The chart has no dedicated values for the transparency log, so set the block in gateway.config (the gateway's gateway.json):

audit:
  checkpointSigning:
    enabled: true
    keyURI: kms://aws/alias/maqpna-audit      # ES256 works with v1 and v2
gateway:
  config:
    transparencyLog:
      enabled: true
      url: https://rekor.sigstore.dev
      api: v1
      intervalSeconds: 3600
      publicKeyFiles: ["/mnt/secrets-store/trusted_root.json"]
      timeoutSeconds: 30
  secretsStoreCSI:                             # one way to mount the log key as a file
    enabled: true
    secretProviderClass: maqpna-gateway
Field Default Meaning
enabled false Opt-in. A url without enabled is a configuration error.
url โ€” Rekor base URL. Required.
api v1 v1 or v2.
intervalSeconds 0 How often the head is anchored when it advanced. 0 anchors only on demand.
publicKeyFiles โ€” Pinned log key: PEM PUBLIC KEY files or a Sigstore trusted_root.json. Recommended: every entry is verified before it is stored.
timeoutSeconds 30 Bound on one submission.

Anchoring never runs on the request path. If Rekor is unreachable or answers 5xx or 429, the loop retries with backoff (5 s, doubling, at most 5 minutes or one interval). Requests are never blocked.

Anchors go to <auditPath>.anchors.jsonl, and each one is recorded in the ledger as audit/anchored with ext anchorSeq, anchorHead, anchorLog and anchorLogIndex.

Step 3: Anchor on demand#

maqpna audit anchor --gateway https://gateway.example.eu

This calls POST /v1/audit/anchors:anchor (role admin). It answers {"anchored": false} when the head has not advanced, and 502 when the log is unavailable. GET /v1/audit/anchors returns the anchor file (roles auditor or admin).

Verify#

Fetch the ledger, the JWKS and the anchors, then verify offline:

maqpna audit verify audit.jsonl --anchors --rekor-key trusted_root.json --jwks jwks.json

The command checks the chain and checkpoints, then for every anchor: the statement, that the recomputed ledger has that hash at that seq, the DSSE signature, that the log entry commits to the envelope, the Merkle inclusion proof against the log's signed checkpoint and, for v1, the signed entry timestamp. Exit status 3 means tampered. Records after the last anchor are reported as not anchored yet.

maqpna evidence generate --ledger FILE adds the anchors covering the period to the evidence pack.

Watch the gateway metrics:

Metric Alert when
maqpna_gateway_audit_anchor_errors_total It grows
maqpna_gateway_audit_anchor_last_success_timestamp_seconds Older than a few intervals
maqpna_gateway_audit_anchors_total, maqpna_gateway_audit_anchor_seq โ€”

Limits#

  • Rekor v2 has no integrated time: a v2 anchor proves inclusion and order, not by when. Use v1 when the time matters.
  • Rekor accepts no ML-DSA keys. With a hybrid checkpoint key, only the classical half signs the anchor.
  • Not yet built: consistency proofs between checkpoints, witness cosignature checks, anchors from the leader only on shared ledgers, and re-verification of anchors by maqpna evidence verify.

Troubleshooting#

Symptom Cause Fix
Gateway refuses to start with api: v2 Ed25519 checkpoint key Use an ES256 or RS256 key, or api: v1
Configuration error about url url set without enabled: true Set enabled: true, or remove url
anchor_errors_total grows Log unreachable, rate-limited, or refused by the residency dialer Check egress; use a private Rekor in-jurisdiction
Entry refused after submission Answer did not verify against publicKeyFiles Check you pinned the right log key
verify --anchors exits 3: ledger rewritten The ledger differs from what was anchored Compare with the WORM copy; treat as an incident
verify --anchors exits 3 with no anchors Missing or empty anchor file Pass --anchors-file, or fetch GET /v1/audit/anchors

Next steps#