MyOwnPKI documentation Source on GitHub

The audit log

Every change and every refusal leaves an entry. The log is append-only and hash-chained, so an edit, a deleted entry or a cut-off end is detected when you verify it. This guide shows how to read the log, how to check it, and what to keep outside the server.

When to use this

The audit log page with its filters and the instance log: unseal, settings, user creation and sign-in entries

What is logged

There are two logs. The instance log (audit.db) holds sign-ins, user management, unseal and seal, backups and restores, and the tenant settings (tenant.settings, because they live in system.db). The tenant log (tenants/<tenant>/audit.db) holds everything about the PKI: CAs, certificates, revocations, CRLs, the legacy import, ACME and agents.

Each entry records who (a user, an API token, the system, an ACME account or an agent), what, when, from which address, the target and the result. It does not record a user agent or free text from a request, and the details never contain secrets: keys that look like passwords or tokens are refused. Examples of actions:

Action When
user.login, user.step_up, user.create, user.role_set Sign-ins, second-factor confirmations, user changes
system.unseal, system.seal, system.backup, system.restore Key ring and backups
certificate.issue, certificate.revoke Certificates
ca.create, ca.import_root, ca.csr_create, ca.certificate_install, ca.renew, ca.retire CA lifecycle
crl.upload, import.legacy CRL upload, the legacy import
tenant.settings A change of the tenant settings (in the instance log)
audit.checkpoint A signed checkpoint of the log itself

A command at the server’s console is logged too, with an actor such as console:root.

The hash chain and the checkpoints

Each entry carries a SHA-256 over its own content and the hash of the entry before it. Changing one entry breaks every hash after it. The database also refuses updates and deletes by trigger, but the chain is what catches someone editing the file directly.

A chain alone cannot show that its end was cut off or that someone with write access recomputed all of it. So the server also keeps:

A checkpoint needs an unsealed server, so a long-sealed server has none that are recent.

1. Verify from the command line

runuser -u myownpki -- /usr/local/bin/myownpki audit verify --config /etc/myownpki/config.yaml --checkpoint-key <fingerprint>

It walks every chain and the checkpoints and writes nothing. Each log gets a line, for example OK – 17 entries, head 17:…. Exit code 0 means intact, 1 means broken or unreadable, 3 means a log does not exist yet. With the fingerprint it also proves that the log was not re-signed with another key. Without it, audit verify still works but cannot prove that the log is the one you had.

To check one log against a head you noted earlier (from the server log or a backup manifest):

runuser -u myownpki -- /usr/local/bin/myownpki audit verify --scope instance --expect-head <seq>:<hash> --config /etc/myownpki/config.yaml

--scope is instance or tenant:default, and --expect-head needs it. Where to find a head:

journalctl -u myownpki -o cat | grep 'audit chain head'

Over the API, an admin can call POST /api/v1/audit/verify with scope and checkpoint_key.

2. Read the log

In the UI open Administration → Audit log. The filters are Log (the tenant log, and for instance admins the instance log), Action begins with and Result. The page shows 50 entries, newest first, and Older entries continues. The tenant log is for admins; viewers and operators cannot read it, because user names and addresses are personal data. The instance log is for instance admins.

3. Export

There is no export button and no export command in this release. Read entries through the API instead; the answer is JSON, newest first, with a cursor:

curl -sS --cacert root.pem -H "Authorization: Bearer $MYOWNPKI_TOKEN" "https://pki.homelab.lan:9443/api/v1/audit?limit=100&since=2026-11-01T00:00:00Z"

Parameters: scope (tenant or instance), action (a prefix such as certificate.), result, since, until, limit (1 to 100) and before (the next_before of the previous page). Every entry includes its hash and prev_hash. Export to a SIEM or syslog is planned; it does not exist yet. Until then, copy exports and backups to a place the server cannot write to, which is what makes a copy a useful anchor.

4. A refused action

A refusal is an entry like any other, with the result denied or failure:

The UI shows denied in red and failure in yellow. Filter with Result to see only them.

Retention

Entries cannot be edited or deleted one by one; that is what keeps the chain trustworthy. There is no automatic expiry and no command to archive a log in this release. The design intends erasure requests to be handled for a whole log (archive it, start a new chain after the retention period) and says this must be assessed by your data protection officer. Entries hold user names and source addresses, so document the period and the legal basis in your own records. This is not legal advice.

Check it worked

When it fails

Next steps