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
opgroup, 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 valuescanner. 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-his a legal value and is consumed normally. --help/-his honoured anywhere in a subcommand's own argument list (e.g.op approve web-01 --helpworks), and is checked before the command runs — no partial/invalid invocation ever executes first.version/--version/-Vprints the binary version and exits 0, recognised before subcommand dispatch on both binaries.- Commands marked Enterprise are compiled only into the
enterprisebuild; 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
- The trust daemon — the socket, env vars, and systemd unit
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.
Takes no flags.
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.
| 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. |
op logout¶
End the current authenticated session now, before its TTL expires — the next
trust-changing op call will require a fresh login.
Takes no flags.
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.
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.
| Argument | Type | Default | Notes |
|---|---|---|---|
<host> |
string | all hosts in scope | Positional. Restrict to one host. |
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.
| 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. |
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.
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.
| 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.
| Argument | Type | Default | Notes |
|---|---|---|---|
<host> |
string | required | The host whose pending items to approve. Must be in your scope. |
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.
| 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. |
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.
| 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. |
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.
| Argument | Type | Default | Notes |
|---|---|---|---|
<host> |
string | required | Positional. The enrolled host. |
--reason <text> |
string | required | Folded into the signed rotation record. |
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.
| 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. |
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.
| 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. |
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.
| 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. |
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¶
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¶
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¶
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).
Takes no flags.
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.
Takes no flags.
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.
| 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. |
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.
| Flag | Type | Default | Notes |
|---|---|---|---|
--host <id> |
string | required | The enrolled host to check. |
--addr <ip:port> |
socket addr | required | Where its agent is listening. |
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.
Takes no flags.
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].
| Flag | Type | Default | Notes |
|---|---|---|---|
--host <id> |
string | all hosts | Restrict to one host. |
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.
Takes no flags.
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.)
Takes no flags.
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.
Takes no flags.
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.
| Argument | Type | Default | Notes |
|---|---|---|---|
<name> |
string | required | Must appear in plugins list. Refuses if already installed (use plugins update). |
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.
| Argument | Type | Default | Notes |
|---|---|---|---|
<name> |
string | required | Must already be installed (use plugins install first). |
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.
| Flag | Type | Default | Notes |
|---|---|---|---|
--channel <name> |
string | every channel in ETMINAN_NOTIFY_CHANNELS |
Preview just one channel. |
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.
| Flag | Type | Default | Notes |
|---|---|---|---|
--channel <name> |
string | required | Must already be allowlisted. |
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.
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¶
Takes no flags. Prints etminan-verifier <version> and exits 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.
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. |
[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.
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:
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.
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.
Takes no flags.
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.
Takes no flags.
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¶
Takes no flags. Prints etminan-agent <version> and exits 0.
See also¶
- Configuration reference — every
ETMINAN_*environment variable. - Verifier configuration — standing up
etminan-verifierdand 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.