Skip to content

CLI reference

The exhaustive command reference for the Etminan binaries. Every subcommand and sub-subcommand that etminan-verifier, etminan-verifierd, and etminan-agent dispatch is documented here with its one-line purpose, full synopsis (every flag, with type and default where the source reveals it), and at least one worked example.

This page is grounded in the same facts as the in-terminal --help (verifier/src/help.rs) and the actual argument parsing in verifier/src/main.rs / agent/src/main.rs. It is the authoritative operator reference; man etminan-verifier / man etminan-verifierd / man etminan-agent remain the deeper source for the complete ETMINAN_* environment variable, FILES, and EXIT STATUS lists.

The operator interface is now the daemon, not key files

Day-to-day trust-changing actions go through etminan-verifier op <cmd>, which talks to the local etminan-verifierd daemon over a Unix socket. The daemon authenticates the caller by kernel peer-UID (SO_PEERCRED) — there is no operator key file to pass, and the daemon (not the operator) holds the only Ed25519 signing key and signs on the operator's behalf as operator:<label>. Roles are admin / operator / viewer, the model is default-deny / fail-closed, and every decision — allow and deny — is written to the audit log. This is Standard (free) behaviour, not Enterprise. See op below.

Conventions used throughout this page

  • No operator key files. Every trust-changing action goes through the op group, which authenticates by kernel UID; the daemon holds the one signing key and signs on the operator's behalf. Where an action takes a --reason <text>, a missing, empty, or whitespace-only reason is rejected — a blank justification can never be signed.
  • Flag parsing is a minimal --flag value scanner. A flag whose next token is itself ---prefixed is treated as present-but-empty (the next token is reparsed as its own flag). A single-dash value like -h is a legal value and is consumed normally.
  • --help / -h is honoured anywhere in a subcommand's own argument list (e.g. op approve web-01 --help works), and is checked before the command runs — no partial/invalid invocation ever executes first.
  • version / --version / -V prints the binary version and exits 0, recognised before subcommand dispatch on both binaries.
  • Commands marked Enterprise are compiled only into the enterprise build; a Standard binary has no such subcommand and its top-level help never mentions them. See Editions.
  • An unknown subcommand exits 2 (usage error), reserving 1 for a runtime failure inside a recognised command.

Table of contents

etminan-verifier

  • The operator interface — op — whoami, login, logout, enroll-totp, review, check, verify-signatures, enroll, approve, reject, exclude list|create|revoke, assign-profile, rotate-tls, rotate-ak, identity list|add|revoke, bootstrap, totp-policy, dual-control-policy set|show, ping
  • Setup & identity — keygen-tls, keygen-catalog
  • Hosts — check, run
  • Baseline & drift review — baseline review | verify-signatures
  • Plugins — plugins verify | list | install | update
  • Notifications — notify-preview, notify-test
  • Diagnostics — doctor, version

etminan-verifierd

etminan-agent

  • Agent commands — daemon, write-ima-policy, keygen-tls, doctor, version

etminan-verifier

The verifier makes every trust decision. Its top-level dispatch is a match subcommand over the families below. The primary operator interface is the op command group, which forwards each trust-changing action to the etminan-verifierd daemon — the daemon authenticates the caller by kernel UID and signs the canonical payload with the one daemon-held key before appending it to the hash-chained audit log. The remaining top-level families are read-only or host-local utilities (check, run, keygen-tls, keygen-catalog, baseline review/verify-signatures, plugins, notify-*, version).

etminan-verifier op

The primary operator interface. etminan-verifier op <cmd> is how operators drive the verifier day to day. Each invocation connects to the local etminan-verifierd socket (ETMINAN_VERIFIERD_SOCKET, default /run/etminan-verifierd/etminan-verifierd.sock); the daemon reads the caller's kernel-supplied peer credentials (SO_PEERCRED), maps the UID to a registered identity, and applies that identity's role and scope. No key file is passed and none is held by the operator — the daemon owns the single Ed25519 signing key and signs on the operator's behalf as operator:<label>. The model is default-deny / fail-closed: an unmapped UID, an out-of-scope host, or an unreachable daemon is a refusal, and both the allow and the deny are written to the audit log.

Standard (free) — not Enterprise

The daemon, the op interface, the admin/operator/viewer roles, and the optional per-role TOTP second factor are all part of the Standard, free edition and are the default access-control model. Only SIEM output and dual control remain Enterprise. See Editions.

Command Purpose
op whoami Show the identity, role, and scope the daemon maps your UID to.
op login Open an authenticated session (supply --code when TOTP is required for your role).
op logout End the current session immediately.
op enroll-totp Enrol your own TOTP second factor (per-operator, self-service).
op review [host] Show the pending baseline review — drift awaiting a decision. Read-only.
op check <host> --addr <ip:port> Run a one-shot attestation of a host now, recording any drift as pending.
op verify-signatures Re-verify the hash-chained audit log and every signed action. Read-only.
op enroll <host> --addr <ip:port> --reason <why> Enrol a host through the daemon (daemon-signed, trust-on-first-use).
op approve <host> Approve a host's pending baseline ([--matching <path> --hash <sha256>] to target one hash).
op reject <host> --path <p> --hash <sha256> --reason <why> Reject a specific pending measurement as a confirmed incident.
op exclude <create\|list\|revoke> Manage exclusion rules (daemon-signed).
op assign-profile <host> --profile <name> --reason <why> Set a host's monitoring profile.
op rotate-tls <host> --reason <why> Re-pin a host after its agent's TLS keypair is regenerated.
op rotate-ak <host> --reason <why> Re-bind a host's AK within the same TPM.
op identity list List every mapped identity (UID, role, scope, label).
op identity add Map a UID to a role/scope/label (admin only).
op identity revoke Remove a UID mapping (admin only).
op directory list List the OS groups that grant a role.
op directory map Make an OS group grant a role and scope (admin only, four-eyes).
op directory unmap Withdraw a group mapping (admin only, four-eyes).
op bootstrap One-time, root-only: seed the first admin identity.
op totp-policy Set whether a role must present a TOTP code (admin only).
op dual-control-policy <set\|show> (Enterprise) Inspect or latch the signed four-eyes policy (setting it is itself four-eyes).
op audit-export --out <file> Write a signed, offline-verifiable evidence bundle of the whole audit chain.
op anchor Mail the audit head off-host now (admin). run also does this once a day.
op self-attest Emit one signed integrity summary about this verifier's own audit head.
op ping Liveness check against the daemon socket.

op whoami

Print who the daemon believes you are — the identity your kernel UID maps to, its role (admin / operator / viewer), its host-group scope, and whether a TOTP session is currently active. Pure read; the first command to run when a call is unexpectedly refused.

etminan-verifier op whoami

Takes no flags.

etminan-verifier op whoami
uid=1007  identity=operator:bob  role=operator  scope=region-a-*  totp=active(7h58m left)

op login

Open (or refresh) an authenticated session for your UID. When totp-policy marks your role as requiring a second factor, pass the current RFC 6238 code with --code; the session then lasts ETMINAN_TOTP_SESSION_TTL_SECS (default 28800 = 8h). With TOTP off for your role, login simply confirms the mapping.

etminan-verifier op login [--code <n>]
Flag Type Default Notes
--code <n> integer none The current TOTP code. Required when your role's TOTP policy is required; ignored (and unnecessary) when it is not.
etminan-verifier op login --code 481920

op logout

End the current authenticated session now, before its TTL expires — the next trust-changing op call will require a fresh login.

etminan-verifier op logout

Takes no flags.

etminan-verifier op logout

op enroll-totp

Self-enrol your own TOTP second factor. Prints the secret to register in your authenticator; afterwards op login takes the six-digit code.

etminan-verifier op enroll-totp

Takes no arguments.

op review

List pending (new/changed, unapproved) measurements through the daemon — the daemon-authorized equivalent of baseline review, scoped to the hosts your identity may see.

etminan-verifier op review [<host>]
Argument Type Default Notes
<host> string all hosts in scope Positional. Restrict to one host.
etminan-verifier op review web-01

op check

Attest one host now and print the verdict, without waiting for the scheduled run. Read-only: it changes no baseline and approves nothing.

etminan-verifier op check <host> --addr <ip:port>
Argument Type Default Notes
<host> string required Positional. The host to attest.
--addr <ip:port> socket addr required Where that host's agent is listening.
etminan-verifier op check web-01 --addr 10.0.3.11:7620

op verify-signatures

Re-verify every signed row and the full audit-log hash chain through the daemon — the same pass as baseline verify-signatures. Also re-checks signatures made by the retired operator-key path on a store that predates 0.10.

etminan-verifier op verify-signatures

Takes no arguments.

op enroll

Enrol a monitored host: the daemon runs the first-quote validation and the AK/TLS trust-on-first-use pinning, authorizes you by kernel UID, and signs the enrollment record with its own key (operator:<label>). Confirm the printed AK fingerprint out of band before trusting it.

etminan-verifier op enroll <host> --addr <ip:port> --reason <why> [--force] [--ek-roots <dir>]
Argument Type Default Notes
<host> string required Positional. Identifier this host is known by from now on.
--addr <ip:port> socket addr required Where the agent is listening.
--reason <text> string required Folded into the signed enrollment record.
--force boolean flag off Re-enrol an already-enrolled host, discarding its pinned AK/TLS identity. Only after re-confirming the new AK fingerprint out of band.
--ek-roots <dir> path none Require manufacturer EK-certificate provenance.

No --key: authorization is your UID, and the daemon signs and audits the action for you.

etminan-verifier op enroll web-01 --addr 10.0.3.11:7620 \
  --reason "onboarding web-01" \
  --ek-roots /etc/etminan-verifier/ek-roots/

op approve

Approve a host's pending baseline items through the daemon — the daemon-mediated replacement for the retired baseline approve --host. Requires your identity to have operator (or admin) role and the host to fall within your scope; an out-of-scope host is refused and the refusal is audited.

etminan-verifier op approve <host>
Argument Type Default Notes
<host> string required The host whose pending items to approve. Must be in your scope.
etminan-verifier op approve web-01

op approve-boot

Approve a measured-boot state: the golden PCR values a host is enforced against. Etminan records a host's enforced boot PCRs the first time it attests genuinely, and reports a boot-policy-mismatch finding whenever they change afterwards — a new kernel or initramfs, an edited kernel command line, a different bootloader, a change in Secure Boot state. This command is how you say "that change was ours".

It approves the values the host is observed booting now, not "whatever it reports next": the digests go into the signed payload, so whoever approves is signing those exact values.

etminan-verifier op approve-boot --host <h> --reason <r>
etminan-verifier op approve-boot --from-host <h> --reason <r> [--clear yes]
Argument Type Default Notes
--host <h> string — Approve this host's current boot state. operator or admin, scope-checked.
--from-host <h> string — Promote this host's boot state to the golden state for its whole OS group (distro/release/arch). Admin only — it decides what counts as a good boot for every host of that group.
--clear yes flag off With --from-host: drop that group's golden state instead, so its hosts fall back to their own per-host state.
--reason <r> string required Recorded in the audit trail and inside the signed payload.

Exactly one of --host and --from-host is given.

Enforced PCRs are 4 (bootloader), 7 (Secure Boot state), 8 (kernel command line) and 9 (kernel + initramfs). Firmware PCRs 0–3, 5 and 6 drift on any legitimate BIOS update and are deliberately not enforced.

Under four-eyes (dual-control-policy set --actions approve-boot, Enterprise) a second distinct operator must co-sign before the state is applied — this is an action that makes an alarm stop, so it carries the same control an exclusion does.

# the kernel update on web-01 was ours
etminan-verifier op approve-boot --host web-01 --reason "kernel 6.12.4 rollout"

# one approval for the whole debian/trixie/amd64 fleet
etminan-verifier op approve-boot --from-host web-01 --reason "fleet kernel 6.12.4"

op reject

Reject one pending measurement — a declaration of a confirmed incident, which fires the full alarm path immediately. Use the exact path and hash from op review. Idempotent: re-rejecting an already-rejected entry does not double-record it.

etminan-verifier op reject <host> --path <p> --hash <sha256>
Argument Type Default Notes
<host> string required Positional. The host the measurement belongs to.
--path <p> path required Exact path, as printed by op review.
--hash <sha256> hex required Exact hash, as printed by op review.
etminan-verifier op reject web-01 --path /usr/local/bin/fds-proxy --hash 9f2c…

op exclude

Manage exclusion rules: paths whose measurements are recorded but kept out of review, for content that legitimately changes every cycle. An exclusion suppresses review, not measurement — every suppressed match is still recorded, so there is a permanent record of what a rule ever hid.

etminan-verifier op exclude list
etminan-verifier op exclude create --path <p> --reason <why> [--host <id>] [--confirm yes]
etminan-verifier op exclude revoke --id <n> --reason <why>
Argument Type Default Notes
--path <p> path required for create Exact path, or a trailing-* plain string prefix.
--reason <text> string required for create/revoke Folded into the signed rule record.
--host <id> string every enrolled host Scope the rule to one host.
--confirm yes flag off Required when a trailing-* prefix would affect already-approved measurements.
--id <n> integer required for revoke Rule id, as printed by exclude list.
etminan-verifier op exclude create --path /var/log/myapp/current.log \
  --reason "rotates hourly, content never security-relevant"

exclude create can be put under four-eyes

exclude-create is one of the two dual-control action tokens on this line (the other is baseline-approve). See Dual control.

op assign-profile

Assign a watched-path profile to a host; the agent picks it up on its next cycle and measures that profile's path set.

etminan-verifier op assign-profile <host> --profile <name>
Argument Type Default Notes
<host> string required Positional. The host to reassign.
--profile <name> string required A profile the agent knows; an unknown name is refused.
etminan-verifier op assign-profile web-01 --profile web-server

op rotate-tls

Re-pin a host's transport certificate after its agent regenerated its mTLS keypair. TOFU-captures the new certificate, but the host's AK fingerprint must still match — a rotated transport cert can never sneak a different host past AK validation.

etminan-verifier op rotate-tls <host> --reason <why>
Argument Type Default Notes
<host> string required Positional. The enrolled host.
--reason <text> string required Folded into the signed rotation record.
etminan-verifier op rotate-tls web-01 --reason "rotated agent TLS keypair"

op rotate-ak

Re-bind a host to a new attestation key inside the same TPM, preserving its baseline, TLS pin and PCR replay state. The EK is the continuity anchor: a different EK means a different TPM and is refused as a re-enroll, not a rotation.

etminan-verifier op rotate-ak <host> --reason <why> [--ek-roots <dir>]
Argument Type Default Notes
<host> string required Positional. The enrolled host whose AK changed.
--reason <text> string required Folded into the signed rotation record.
--ek-roots <dir> path none Also re-verify the manufacturer EK-certificate chain.
etminan-verifier op rotate-ak web-01 --reason "TPM cleared during motherboard swap"

op directory

Say which OS group grants which role, so operators can come from Active Directory or LDAP instead of a hand-kept list. list is readable by any mapped identity; map and unmap are admin-only, and four-eyes wherever that covers the operator registry — a group mapping changes who may act.

Etminan speaks no LDAP and holds no directory credential. The host is joined to the domain the way every other Linux server is, sssd or winbind answers group membership, and this maps a group name to a role. With no mapping configured nothing is asked of the operating system at all, which is the default and stays the default until somebody runs map.

etminan-verifier op directory list
etminan-verifier op directory map --group <name> --role <admin|operator|viewer> --scope <host-globs>
etminan-verifier op directory unmap --group <name>
Sub-action Flag Type Default Notes
list (none) Every active mapping: group, role, scope.
map --group <name> string required The OS group, as getgrouplist reports it.
map --role <r> string required admin, operator, or viewer.
map --scope <host-globs> string required Required, not defaulted: one mapping can grant a whole department at once, and an omitted scope must not silently mean every host. --scope '*' is the deliberate fleet-wide grant.
unmap --group <name> string required The mapping to withdraw. Members lose the role at the next lookup.
etminan-verifier op directory map --group sec-admins --role admin --scope '*'
etminan-verifier op directory list
etminan-verifier op directory unmap --group sec-admins

An explicit op identity row always wins over a group. Somebody in two mapped groups gets the higher role. The audit row names the account and the group that carried the role, because groups get re-mapped and a row has to explain itself later.

What a disabled account does and does not stop

A disabled directory account cannot log in, so it never obtains a UID on this host and never reaches the daemon. That is where access ends. It does not end a session that is already open, it does not override sssd's offline cache while the directory is unreachable, and it does not touch an SSH key in authorized_keys. Those are host configuration, and the first is what the session idle timeout is for.

op identity

Manage the UID→identity registry the daemon enforces. list is readable by any mapped identity; add and revoke are admin-only. This registry lives in baseline.db and is what replaces per-operator key files.

etminan-verifier op identity list
etminan-verifier op identity add --uid <n> --role <admin|operator|viewer> --label <name> [--scope <host-globs>]
etminan-verifier op identity revoke --uid <n>
Sub-action Flag Type Default Notes
list (none) Every mapping: UID, role, scope, label.
add --uid <n> integer required The kernel UID to map.
add --role <r> string required admin, operator, or viewer.
add --label <name> string required Human label; appears in audit rows as operator:<label>.
add --scope <host-globs> string unscoped Restrict an operator to a host group (comma-separated globs, e.g. region-a-*). admin/viewer are unscoped.
revoke --uid <n> integer required The UID mapping to remove.
etminan-verifier op identity list

etminan-verifier op identity add --uid 1007 --role operator \
  --scope 'region-a-*' --label bob

etminan-verifier op identity revoke --uid 1007

op bootstrap

One-time genesis: seed the first admin identity so the registry has an owner. Must be run as root on the verifier host, and only when no admin identity exists yet; the action is recorded in the audit log. After this, further identities are added with op identity add.

etminan-verifier op bootstrap --uid <n> --label <name>
Flag Type Default Notes
--uid <n> integer required The UID to install as the first admin.
--label <name> string required Human label for that admin identity.
sudo etminan-verifier op bootstrap --uid 1000 --label alice

Development-only insecure bootstrap

ETMINAN_VERIFIERD_ALLOW_INSECURE_BOOTSTRAP relaxes the bootstrap preconditions for DEV/TEST only and must never be set in production. See Verifier configuration.

op totp-policy

Set, per role, whether an authenticated session requires a TOTP second factor. Optional (RFC 6238), off by default, and admin only. When a role is set --required true, its members must pass --code at op login.

etminan-verifier op totp-policy --role <admin|operator|viewer> --required <true|false>
Flag Type Default Notes
--role <r> string required The role whose policy to set.
--required <bool> boolean required true to require TOTP for that role, false to relax it.
etminan-verifier op totp-policy --role admin --required true

op dual-control-policy

Show or set the signed four-eyes policy. The policy is itself a four-eyes change, and it latches: a signed policy can only ever add coverage on top of the ETMINAN_DUAL_CONTROL_ACTIONS env floor, so unsetting the env var cannot quietly remove it.

etminan-verifier op dual-control-policy show
etminan-verifier op dual-control-policy set --actions <list> [--threshold <n>] --reason <why>
Argument Type Default Notes
--actions <list> string required for set Comma-separated. Accepted on this line: baseline-approve, exclude-create, default (expands to baseline-approve), or off/none. An unrecognized token is a hard error, never a silent single-signed bypass.
--threshold <n> integer policy default How many distinct authorized operators must co-sign.
--reason <text> string required for set Folded into the signed policy record.
etminan-verifier op dual-control-policy set \
  --actions baseline-approve,exclude-create --reason "SOC 2 change control"

op audit-export

etminan-verifier op audit-export --out /var/backups/etminan/audit-2026-08-28.json

Writes the whole audit chain, from genesis, as a signed self-contained bundle. The chain is verified before it is signed, so a broken chain is refused rather than exported. Any mapped role may run it, and it is audited like every other op verb — so the export appears as a record in the next export.

The daemon signs and hands back the bytes; this client writes the file, mode 0600. The daemon never learns the path: a non-root daemon opening an operator-chosen path would be an arbitrary-write primitive running as the account that owns baseline.db and the signing key.

audit-export-verify

etminan-witness  audit-export-verify --file <bundle> --expect-identity <64 hex chars>
etminan-verifier audit-export-verify --file <bundle> --expect-identity <64 hex chars>

The reader half, and not an op command: no database, no key, no daemon, so it runs on the auditor's own machine. An export that can only be checked where it was made is not checked at all.

Both spellings are the same command. Use etminan-witness on the machine the bundle was delivered to — its package is one binary with no services and no key, and needs neither verifier installed. The etminan-verifier spelling is for a quick look on the verifier itself, and proves nothing on its own.

--expect-identity is required rather than read from the bundle — one verified against its own embedded key would verify perfectly after an attacker re-signed a rewritten chain. The daemon prints its public half at every start.

See Exporting the audit trail.

op anchor

etminan-verifier op anchor

Mails the audit head off-host immediately, signed with the daemon key, to ETMINAN_ANCHOR_TO. Admin-only: it is the one op verb whose effect leaves the building. run publishes the head this way once a calendar day by itself; this forces one now — before touching a host you are about to investigate.

Refused if the recipient is on the same mail domain the verifier sends from, and refused if the chain does not verify. See The audit log.

op self-attest

etminan-verifier op self-attest | mail -s "etminan integrity summary" auditor@example.org

Emits one small signed statement about this verifier's own audit log: the head (seq + hash), a counter that only ever goes up, and the time. Nothing else. It goes to stdout and nowhere else, so the JSON stays clean for whatever delivers it — a mail, a file on a share, an append-only bucket.

The chain is verified before it is attested, exactly as op audit-export and op anchor verify before they act: a signed summary over a broken chain would be an endorsement of the break.

Any mapped role may run it, deliberately — it changes no trust state, and its worth comes from running often. A verb only an admin could run would push you towards giving a scheduled job an admin identity, which is worse than what it would prevent. Who ran it is audited anyway, so the emission shows up in the chain the next summary attests.

It signs with the daemon key, the same one op audit-export uses. There is no second key to generate and none to look after.

Check it with verify-summary, somewhere else. See The audit log.

verify-summary

etminan-witness verify-summary --expect-identity <64 hex chars> \
                               [--file <path>] \
                               [--state /var/lib/etminan-witness/last-seen.json] \
                               [--max-age-seconds 5400]

The receiver half, and like audit-export-verify not an op command: no database, no key, no daemon. It runs where the summary was delivered, which is the entire point — a verifier vouching for itself, checked only on itself, proves nothing.

That is why it has a package of its own. etminan-witness is one binary with no daemon, no timer, no service user and no key, and it installs on the receiving machine without dragging a second verifier onto it. The identical command still exists as etminan-verifier verify-summary; running it there tells you the file parses, and nothing more.

It asks three things of every summary:

Signed by the identity you pinned not by whatever key the summary carries
The counter went up so last month's "all fine" cannot be sent again
The head did not go backwards and entry n still carries the hash entry n carried

--state is the memory that makes the second and third checks possible: the high-water mark from the last summary. Keep it on the receiving side. It is not advanced when a summary is rejected — accepting a rolled-back head as the new baseline would launder the rollback it just caught.

--max-age-seconds turns a late summary into an alarm; set it a little above your emit cadence. Leave it off to check an archived summary years later, where being old is expected.

Exit 0 verified, 3 integrity alarm, 1 input it could not evaluate. The three are distinct because "the history was rewritten" and "you handed me a truncated file" should not page the same person the same way.

A summary that never arrives is also a finding

Nothing runs when the verifier goes quiet, so this command cannot report it. Alert on the schedule — "no summary in N minutes" — not only on the output of the runs that did happen.

op ping

Liveness probe: confirm the daemon socket is present and answering. Prints OK and exits 0 on success; a non-zero exit means the daemon is down or the socket is unreachable (and, because the model is fail-closed, that every trust-changing op call would currently be refused).

etminan-verifier op ping

Takes no flags.

etminan-verifier op ping

Setup & identity

Run these once when standing up a verifier device. The two keys are deliberately separate — the verifier's mTLS transport identity (op keygen-tls) and the off-host plugin-catalog signing key (keygen-catalog) — so rotating one never rotates the other. Operators have no key of their own: they are authorized by kernel UID through the daemon, which holds the one signing key.

Command Purpose
op keygen-tls Create this verifier's own mTLS identity and print its SHA-256 fingerprint.
keygen-catalog Off-host, run-once: the dedicated plugin-catalog signing keypair.

op keygen-tls

Generate this verifier's own self-signed mTLS keypair under $ETMINAN_TLS_DIR (default /var/lib/etminan-verifier/tls) and print its SHA-256 fingerprint. Copy that fingerprint into ETMINAN_VERIFIER_CERT_FINGERPRINT in every agent's agent.env. Run once per verifier device, before its first enroll or run.

etminan-verifier op keygen-tls

Takes no flags.

etminan-verifier op keygen-tls
Generated verifier TLS keypair at /var/lib/etminan-verifier/tls
Fingerprint (sha256): 4d1e…c0ffee
Copy this fingerprint into ETMINAN_VERIFIER_CERT_FINGERPRINT in every agent.env …

keygen-catalog

Generate the dedicated single-purpose Ed25519 keypair a real plugin-catalog release would be signed with — deliberately not an operator key and not the GPG release key. Run once, offline, wherever catalog releases are built — never on a verifier host.

etminan-verifier keygen-catalog --out <path>
Flag Type Default Notes
--out <path> path required Private key (mode 0600). Replace CATALOG_VERIFYING_KEY_HEX in verifier/src/plugin_catalog.rs with the printed public key, then rebuild.
etminan-verifier keygen-catalog --out ./catalog-signing.key

Hosts

Check monitored hosts. Enrolment and re-pinning are done through the op group (op enroll, op rotate-tls, op rotate-ak, op assign-profile) — the two commands here are the read/attest actions.

Command Purpose
check Check one enrolled host now, interactively.
run Check every host + plugins + audit chain and fire alarms (the hourly job).

check

Check one enrolled host interactively: fresh quote, verify signature and AK fingerprint against the enrollment record, replay the IMA log against the signed PCR digest, and diff new measurements against the approved baseline. Prints FINDING (<kind>): … and exits 1 if anything failed — an unreachable host is itself a finding, not a soft skip.

etminan-verifier check --host <id> --addr <ip:port>
Flag Type Default Notes
--host <id> string required The enrolled host to check.
--addr <ip:port> socket addr required Where its agent is listening.
etminan-verifier check --host web-01 --addr 10.0.3.11:7620

run

The scheduled entry point (what etminan-verifier.timer calls hourly). Runs check against every enrolled host in one pass, fires the full alarm path (local structured log, audit log, email, notify-plugin channels) for anything that failed, and exits non-zero if any host produced a finding or a plugin failed verification. Also verifies every configured change-source/notify plugin (same as plugins verify) and the audit-log hash chain (same as baseline verify-signatures) every cycle.

etminan-verifier run

Takes no flags.

etminan-verifier run   # normally invoked by etminan-verifier.timer

Baseline & drift review

The baseline review and baseline verify-signatures commands here are read-only. Approving, rejecting, and excluding drift is done through the op group (op approve, op reject, op exclude) — drift is always proposed, never auto-accepted, and every decision is signed and audited.

Action Purpose
baseline review List pending (new/changed, unapproved) measurements. Read-only.
baseline verify-signatures Re-verify every signed row + the audit chain in one pass. Read-only.

baseline review

List every pending measurement, optionally filtered to one host. Each entry prints a plain-language ownership verdict, a change-source-correlation line (if configured), a package trust line (if a package match exists), and the raw sha256/owner/size/mtime detail. Entries older than ETMINAN_PENDING_REVIEW_SLA_HOURS (default 24) are marked [SLA EXCEEDED].

etminan-verifier baseline review [--host <id>]
Flag Type Default Notes
--host <id> string all hosts Restrict to one host.
etminan-verifier baseline review --host web-01

baseline verify-signatures

Re-check every approval batch's signature, every exclusion rule's create/revoke signature, every rejection's signature, and the full audit-log hash chain, in one pass. A row written before signing existed for its action is reported as "unsigned, predates this feature", not a failure. Exits non-zero if anything fails.

etminan-verifier baseline verify-signatures

Takes no flags.

etminan-verifier baseline verify-signatures

Plugins

The certified change-source / notify plugin catalog. Installed plugins are held to the same verify-then-exec bar (root-owned, not group/world-writable, content hash matches) whether hand-installed or catalog-installed.

Run etminan-verifier plugins <action> --help for one action's detail.

Action Purpose
plugins verify Check every configured change-source/notify plugin (allowlisted + verifies), without executing it.
plugins list Fetch + verify the certified catalog; show each entry's install status.
plugins install <name> Download, re-verify, and allowlist a catalog plugin.
plugins update <name> Re-pin an installed plugin to the catalog's current version.

op plugins verify

Check every plugin referenced by ETMINAN_CHANGE_SOURCES or ETMINAN_NOTIFY_CHANNELS is allowlisted and passes verification, without running it. Exits non-zero and prints exactly what's wrong on any failure. (This same check runs automatically, silently on success, at the start of every run.)

etminan-verifier op plugins verify

Takes no flags.

etminan-verifier op plugins verify
  change-source 'apt-history' — OK
  notify 'slack-webhook' — OK
Everything verified.

op plugins list

Fetch and verify the certified catalog (signature checked against the compiled-in trust anchor first), then print every entry with its install status: not installed / installed and up to date / installed with an update available. Never modifies anything.

etminan-verifier op plugins list

Takes no flags.

etminan-verifier op plugins list

plugins install

Download a certified catalog entry, re-verify its content hash against the catalog's pinned value, write it under /etc/etminan-verifier/<type>-plugins/, and append the matching allowlist entry. Must run as root. Does not add the plugin to ETMINAN_NOTIFY_CHANNELS/ETMINAN_CHANGE_SOURCES — it prints the exact line to add and a reminder to restart the service.

etminan-verifier plugins install <name>
Argument Type Default Notes
<name> string required Must appear in plugins list. Refuses if already installed (use plugins update).
sudo etminan-verifier plugins install slack-webhook

plugins update

Re-fetch and re-verify the catalog; if the pinned hash for <name> changed, download the new content, re-hash it, and re-pin the allowlist entry. Never automatic — runs only when you run it.

etminan-verifier plugins update <name>
Argument Type Default Notes
<name> string required Must already be installed (use plugins install first).
sudo etminan-verifier plugins update slack-webhook

Notifications

Preview and test the notification path without waiting for a real finding.

Command Purpose
notify-preview Render current templates against sample findings (sends nothing).
notify-test Send one synthetic finding through a channel's real plugin.

op notify-preview

Render four representative fake findings through whatever _TEMPLATE/_FORMAT/_SEVERITIES is configured, and print the result: the full email (subject + body) and, per channel, the raw JSON payload the channel's plugin would receive on stdin. Never executes a plugin, never calls sendmail — safe any time, even against a channel not yet allowlisted.

etminan-verifier op notify-preview [--channel <name>]
Flag Type Default Notes
--channel <name> string every channel in ETMINAN_NOTIFY_CHANNELS Preview just one channel.
etminan-verifier op notify-preview --channel slack-webhook

op notify-test

Send one synthetic test finding through <name>'s real plugin invocation — the same verify-then-exec path a live run uses — and report the plugin's actual exit status and any stderr. Deliberately ignores that channel's configured _SEVERITIES filter.

etminan-verifier op notify-test --channel <name>
Flag Type Default Notes
--channel <name> string required Must already be allowlisted.
etminan-verifier op notify-test --channel slack-webhook

Diagnostics

Command Purpose
doctor Local, read-only self-check (OK/WARN/FAIL); sends nothing; exits non-zero only on a hard FAIL.
version Print the version and exit (also --version, -V).

doctor

Answers "is this verifier healthy and internally consistent?" without contacting anything. Prints an OK/WARN/FAIL checklist and exits non-zero only on a hard FAIL — a WARN alone still exits 0. Read-only: no network, no host is contacted, no trust state is touched, and nothing is written to the audit log.

It judges the service's configuration, loading /etc/etminan-verifier/verifier.env the way systemd does, so running it by hand grades what the daemon actually sees rather than whatever your shell exports. A variable already set in your environment still wins.

etminan-verifier doctor

Takes no flags.

Group Checks
Identity & keys verifier mTLS identity loads; certificate not expired (WARN within 30 days); the operator identity registry the daemon authorizes against
Local store baseline.db opens with its schema; audit-log hash chain + external anchor; the signed-state cross-check (same scan as baseline verify-signatures)
Configuration which environment file was judged; every enrolled host has an address; SIEM and notify config parse
Trust daemon etminan-verifierd unit state; its socket exists; something is actually listening on it
System disk headroom; systemd unit/timer state

Why the daemon gets its own group

Nothing that changes trust happens without etminan-verifierd — so a verifier whose daemon is down cannot be operated at all, however healthy its store looks. The three observations are deliberately separate: a running unit with no socket, and a stale socket left by a crashed daemon, are different faults with different fixes.

Checks are independent and degrade to WARN, so one root cause — an unopenable store, say — cannot cascade into a dozen apparent failures.

version

etminan-verifier version
etminan-verifier --version
etminan-verifier -V

Takes no flags. Prints etminan-verifier <version> and exits 0.

etminan-verifier --version
etminan-verifier 0.16.0

etminan-verifierd

The trust daemon. It is the sole owner of the verifier's trust-changing actions and of the one Ed25519 signing key; the op commands are thin clients that connect to its Unix socket. It runs on the verifier host as a long-lived service (etminan-verifierd.service) and is configured entirely through ETMINAN_* environment variables — it takes no subcommands.

etminan-verifierd

Takes no flags. Started by systemd; sources the same /etc/etminan-verifier/verifier.env (or a daemon drop-in) as the rest of the tool.

On start it opens the baseline DB, loads (or, on first run, expects) its signing key, and binds the peer-credentialed socket. It then authenticates each op client by SO_PEERCRED UID, maps the UID to a registered identity, enforces default-deny/fail-closed authorization, signs authorized actions as operator:<label>, and records every decision — allow and deny — on the hash-chained audit log.

Variable Default Purpose
ETMINAN_VERIFIERD_SOCKET /run/etminan-verifierd/etminan-verifierd.sock The Unix socket op clients connect to; peer-UID is read from it.
ETMINAN_VERIFIERD_DB /var/lib/etminan-verifier/baseline.db The baseline DB, which also holds the UID→identity registry and audit log.
ETMINAN_VERIFIERD_KEY /var/lib/etminan-verifier/daemon-signing.key The only Ed25519 signing key; the daemon signs operator actions with it (mode 0600).
ETMINAN_TOTP_SESSION_TTL_SECS 28800 (8h) How long an op login TOTP session stays valid.
ETMINAN_VERIFIERD_ALLOW_INSECURE_BOOTSTRAP (unset) DEV/TEST ONLY — relaxes op bootstrap preconditions. Never set in production.
etminan-verifierd.service (shipped unit — abridged)
[Service]
Type=notify
User=etminan-verifier
Group=etminan-verifier
WorkingDirectory=/var/lib/etminan-verifier
EnvironmentFile=-/etc/etminan-verifier/verifier.env
ExecStart=/usr/bin/etminan-verifierd
RuntimeDirectory=etminan-verifierd
NoNewPrivileges=true

See Verifier configuration for standing the daemon up and bootstrapping the first admin.


etminan-agent

The agent is a relay: with no subcommand it starts the long-running attestation daemon. It never decides what is trustworthy — it only produces signed, verifiable evidence; every judgment is made by etminan-verifier on a separate device. The handful of subcommands are one-shot setup and diagnostics. The agent requires a real TPM and is Linux-only.

Invocation Purpose
etminan-agent Start the long-running daemon.
etminan-agent write-ima-policy Write this host's IMA measurement policy, then exit.
etminan-agent keygen-tls Create the agent's mTLS identity and print its fingerprint.
etminan-agent doctor Local, read-only host self-check.
etminan-agent version Print the version and exit (also --version, -V).

etminan-agent doctor

Is this host healthy and valid for attestation? Read-only and network-free — it runs before any daemon setup, issues no TPM command and sends nothing anywhere. Exits non-zero only on a hard FAIL.

Like the verifier's, it judges the service's configuration, loading /etc/etminan-agent/agent.env as systemd would; without that, a hand-run on a correctly configured host would report the pinning it cannot see as missing.

etminan-agent doctor

Takes no flags.

Checks /dev/tpmrm0 is present and openable; the IMA SHA-256 measurement log is readable and actually measuring; CAP_DAC_READ_SEARCH is present (needed to read the root:root 0440 IMA log); the mTLS identity loads and is unexpired; the watched-path set resolves; the verifier fingerprint is pinned (or permissive mode explicitly opted into); and the listen address parses.

Run it the way the service runs

The capability check reports what the calling process holds. Run by hand as root it always passes, because root holds everything. To see what the unprivileged service user actually gets, ask systemd:

systemd-run --pipe -p User=etminan-agent \
  -p AmbientCapabilities=CAP_DAC_READ_SEARCH \
  -p EnvironmentFile=/etc/etminan-agent/agent.env \
  /usr/bin/etminan-agent doctor

etminan-agent (daemon — no subcommand)

Start the long-running daemon: listen for mutual-TLS connections from etminan-verifier and, on request, take a TPM quote over PCR 10, read the IMA measurement-log delta from the last consumed offset, and return both together so the verifier can prove they describe the same boot state.

etminan-agent

Takes no flags — configured entirely through ETMINAN_* environment variables (see Configuration / man etminan-agent). Key variables: ETMINAN_AGENT_LISTEN (default 0.0.0.0:7620), ETMINAN_VERIFIER_CERT_FINGERPRINT (the pinned verifier fingerprint — required unless ETMINAN_ALLOW_PERMISSIVE_TLS=1 is set for the one-time enrollment bootstrap), ETMINAN_TLS_DIR, ETMINAN_IMA_LOG_PATH, ETMINAN_WATCHED_PATHS / ETMINAN_PROFILE.

Fail-closed transport

The daemon refuses to start if ETMINAN_VERIFIER_CERT_FINGERPRINT is unset and ETMINAN_ALLOW_PERMISSIVE_TLS is not enabled. Permissive mode (accept any client cert) is an explicit opt-in for enrollment only; in it the agent serves only the enrollment quote (without advancing the IMA-log cursor) and refuses SetProfile/PackageLookup.

# Normal operation (systemd sources agent.env):
etminan-agent

# One-time enrollment bootstrap:
ETMINAN_ALLOW_PERMISSIVE_TLS=1 etminan-agent

etminan-agent write-ima-policy

Write this host's IMA measurement policy to /sys/kernel/security/ima/policy (or ETMINAN_IMA_POLICY_PATH if set) and exit. Meant to run once, early at boot, before the daemon starts — see etminan-agent-ima-policy.service. Idempotent: a policy already active this boot is treated as success (the write node can even disappear from securityfs once loaded), not failure, as long as measurements are already flowing.

etminan-agent write-ima-policy

Takes no flags.

etminan-agent write-ima-policy

etminan-agent keygen-tls

Generate the agent's own self-signed mTLS keypair under $ETMINAN_TLS_DIR (default /var/lib/etminan-agent/tls) and print its SHA-256 fingerprint. Give that fingerprint to the verifier operator to confirm during etminan-verifier op enroll, where it's pinned alongside the AK fingerprint. Run once per host, before its first connection from a verifier.

etminan-agent keygen-tls

Takes no flags.

etminan-agent keygen-tls
Generated agent TLS keypair at /var/lib/etminan-agent/tls
Fingerprint (sha256): a1b2…f9
Give this fingerprint to the verifier operator to confirm during `etminan-verifier op enroll` …

etminan-agent version

etminan-agent version
etminan-agent --version
etminan-agent -V

Takes no flags. Prints etminan-agent <version> and exits 0.

etminan-agent --version
etminan-agent 0.16.0

See also

  • Configuration reference — every ETMINAN_* environment variable.
  • Verifier configuration — standing up etminan-verifierd and bootstrapping the first admin.
  • Editions — the daemon/RBAC are Standard; what Enterprise adds.
  • Audit log — where every signed action (and every deny) lands.
  • Troubleshooting — diagnosing a failing host.
  • man etminan-verifier, man etminan-verifierd, man etminan-agent — the deepest per-variable references.