Skip to content

Configuration reference

Etminan has no configuration-file format of its own. Both binaries are configured entirely through ETMINAN_* environment variables, plus a small set of plain-text data files (plugin allowlists, watched-path profiles, the plugin catalog) whose paths are themselves named by environment variables. In a packaged install the variables live in the systemd EnvironmentFile=:

Binary Env file (packaged) systemd unit
etminan-agent /etc/etminan-agent/agent.env etminan-agent.service
etminan-verifier /etc/etminan-verifier/verifier.env etminan-verifier.service / .timer
etminan-portal (Enterprise entitlement service) /etc/etminan-portal/portal.env (operator-chosen) etminan-portal.service

Copy the shipped deploy/agent.env.example / deploy/verifier.env.example to those paths and edit. Comments (#) and blank lines are fine — the files are sourced by systemd. Everything below is grounded in the code: every default is the literal fallback in the corresponding std::env::var(...) call site.

Reading the tables

  • Default is the exact code default. (unset) means the variable has no default and being unset disables or skips the feature. Unless a row says otherwise, an empty value is treated as unset.
  • Numeric and boolean values are validated, not trusted: a malformed value is generally rejected with a startup warning and the default is used (fail-safe), except the dual-control action list, which fails closed — an unrecognised token is a hard error so a typo can never silently leave a control off.
  • Rows marked with the Enterprise admonition are only meaningful in a build made with the enterprise Cargo feature; in the free Standard binary those variables are read by nothing and setting them has no effect.

etminan-agent

The agent is a relay on each monitored host: it returns a TPM-signed quote plus the IMA log delta and makes no trust decision.

Transport & verifier pinning (mutual TLS)

mTLS is mandatory — there is no plaintext fallback. See Mutual TLS for the full enrolment handshake.

Variable Default Meaning & allowed values Example
ETMINAN_AGENT_LISTEN 0.0.0.0:7620 host:port the quote-serving daemon binds. ETMINAN_AGENT_LISTEN=0.0.0.0:7620
ETMINAN_TLS_DIR /var/lib/etminan-agent/tls Directory holding this agent's own mTLS cert.pem/key.pem, created once by etminan-agent keygen-tls. ETMINAN_TLS_DIR=/var/lib/etminan-agent/tls
ETMINAN_VERIFIER_CERT_FINGERPRINT (unset) SHA-256 fingerprint (exactly 64 hex chars / 32 bytes) of the one verifier this agent accepts connections from — printed by etminan-verifier keygen-tls. Required in normal operation; unset is only valid together with the permissive flag below. ETMINAN_VERIFIER_CERT_FINGERPRINT=9f86d081...
ETMINAN_ALLOW_PERMISSIVE_TLS (unset → off) 1/true/yes (case-insensitive) opts into enrolment-bootstrap mode: real mTLS but the client cert is not pinned, so any caller is accepted, and only Quote is served (the IMA-log cursor is not advanced; SetProfile/PackageLookup are refused). With the fingerprint unset and this flag also unset, the agent refuses to start — a forgotten pin fails loudly, never silently open. Remove it once enrolled. ETMINAN_ALLOW_PERMISSIVE_TLS=1

IMA log & policy

The log must be the SHA-256 variant, and IMA policy has no path filter (filtering happens on the verifier — every log line carries the full path).

Variable Default Meaning & allowed values Example
ETMINAN_IMA_LOG_PATH /sys/kernel/security/ima/ascii_runtime_measurements_sha256 The SHA-256 IMA measurement-log node (securityfs). The runtime_measurements_count file is derived as its sibling. Override only if securityfs is mounted elsewhere. Must be the _sha256 variant. ETMINAN_IMA_LOG_PATH=/sys/kernel/security/ima/ascii_runtime_measurements_sha256
ETMINAN_IMA_POLICY_PATH /sys/kernel/security/ima/policy Where etminan-agent write-ima-policy writes the one-shot boot policy. ETMINAN_IMA_POLICY_PATH=/sys/kernel/security/ima/policy

Watched paths & profiles

There is no baked-in default watch set — an agent with neither ETMINAN_WATCHED_PATHS nor ETMINAN_PROFILE watches nothing and prints a startup warning. Resolution precedence (highest first): ETMINAN_WATCHED_PATHS → a centrally-assigned profile (op assign-profile) → ETMINAN_PROFILE.

Variable Default Meaning & allowed values Example
ETMINAN_WATCHED_PATHS (unset) Comma-separated absolute paths to sweep/measure before every attestation cycle. Direct override — takes precedence over ETMINAN_PROFILE, no profile file involved. ETMINAN_WATCHED_PATHS=/usr/local/bin/fds-proxy,/etc/systemd/system,/etc/fds
ETMINAN_PROFILE (unset) Name of a [section] in profiles.conf whose path list is used. The bootstrap tier — a name pushed by op assign-profile overrides it. ETMINAN_PROFILE=fds-host
ETMINAN_PROFILES_PATH /etc/etminan-agent/profiles.conf Location of the named-profile definitions file (see profiles.conf). ETMINAN_PROFILES_PATH=/etc/etminan-agent/profiles.conf
ETMINAN_ASSIGNED_PROFILE_PATH /var/lib/etminan-agent/assigned_profile Where a profile name pushed by op assign-profile is persisted across restarts (only the selected name is stored/sent — the path list always stays in profiles.conf). Relocate only if /var/lib/etminan-agent is not writable. ETMINAN_ASSIGNED_PROFILE_PATH=/var/lib/etminan-agent/assigned_profile

etminan-verifier

The verifier makes every trust decision. It needs no TPM. Store/identity paths below already match the systemd unit's WorkingDirectory and the AppArmor profile — only change them if you need a different location.

Store, state & identity

Variable Default Meaning & allowed values Example
ETMINAN_BASELINE_DB /var/lib/etminan-verifier/baseline.db The SQLite baseline + hash-chained audit-log store (its .audit-head anchor sits alongside). Managed entirely by the tool — see baseline.db. ETMINAN_BASELINE_DB=/var/lib/etminan-verifier/baseline.db
ETMINAN_VERIFIER_STATE /var/lib/etminan-verifier/state.json Per-host enrolment record + cumulative IMA/PCR-10 replay cursor — see state.json. ETMINAN_VERIFIER_STATE=/var/lib/etminan-verifier/state.json
ETMINAN_TLS_DIR /var/lib/etminan-verifier/tls This verifier's own mTLS identity (etminan-verifier keygen-tls); its fingerprint is what you pin into each agent's ETMINAN_VERIFIER_CERT_FINGERPRINT. ETMINAN_TLS_DIR=/var/lib/etminan-verifier/tls

The daemon (etminan-verifierd)

The privilege-separated daemon owns verifier state and the single signing key; the op client reaches it over the socket. These are the daemon's own paths, distinct from the CLI store vars above.

Variable Default Meaning & allowed values Example
ETMINAN_VERIFIERD_SOCKET /run/etminan-verifierd/etminan-verifierd.sock Unix socket the daemon listens on and the op client connects to (0660 etminan-verifier:etminan-verifier). Override only for tests / non-root dev runs. ETMINAN_VERIFIERD_SOCKET=/run/etminan-verifierd/etminan-verifierd.sock
ETMINAN_VERIFIERD_DB /var/lib/etminan-verifier/baseline.db The baseline + audit store the daemon owns — the same file the CLI reads via ETMINAN_BASELINE_DB. ETMINAN_VERIFIERD_DB=/var/lib/etminan-verifier/baseline.db
ETMINAN_VERIFIERD_KEY /var/lib/etminan-verifier/daemon-signing.key The single Ed25519 key the daemon holds and signs every operator action with. Must be owned by the daemon user (0600); operators hold no key. ETMINAN_VERIFIERD_KEY=/var/lib/etminan-verifier/daemon-signing.key
ETMINAN_TOTP_SESSION_TTL_SECS 28800 (8h) Lifetime of a TOTP op login session before re-authentication is required. ETMINAN_TOTP_SESSION_TTL_SECS=28800
ETMINAN_VERIFIERD_ALLOW_INSECURE_BOOTSTRAP (unset) DEV/TEST ONLY. Permits the first-admin op bootstrap without root. Never set in production. ETMINAN_VERIFIERD_ALLOW_INSECURE_BOOTSTRAP=1

Attestation-loop tuning

Variable Default Meaning & allowed values Example
ETMINAN_MAX_CATCHING_UP_CYCLES 6 After this many consecutive CatchingUp cycles a host escalates to a stale-attestation alarm (backstop against a host that never converges or feeds a fabricated log_truncated delta). 0 disables the backstop (not recommended). Malformed → default. ETMINAN_MAX_CATCHING_UP_CYCLES=6
ETMINAN_PENDING_REVIEW_SLA_HOURS 24 Hours a pending_review item may sit before run/baseline review escalate it to a warning finding and mark it [SLA EXCEEDED]. Must be a positive integer — zero/negative/non-numeric is refused with a loud warning and falls back to 24. ETMINAN_PENDING_REVIEW_SLA_HOURS=72
ETMINAN_TEST_SIMULATE_REPLAY (unset) Test harness only. When set, the check path substitutes a replayed nonce to exercise anti-replay rejection. Never set in production. ETMINAN_TEST_SIMULATE_REPLAY=1

Notifications — email

The only place a finding leaves the box. See Notifications.

Variable Default Meaning & allowed values Example
ETMINAN_NOTIFY_EMAIL (unset → email leg off) Destination address for alarm email (delivered via local sendmail). Unset disables only the email leg. ETMINAN_NOTIFY_EMAIL=security@example.com
ETMINAN_NOTIFY_FROM noreply@etminan From: address for alarm email. ETMINAN_NOTIFY_FROM=etminan@example.com
ETMINAN_NOTIFY_EMAIL_SEVERITIES (unset → all) Comma-separated severity allowlist for the email leg (critical, warning, info). Unset = every severity. ETMINAN_NOTIFY_EMAIL_SEVERITIES=critical,warning
ETMINAN_NOTIFY_EMAIL_FORMAT [{kind}] {host_id}: {text} Per-finding line template for the email body. Placeholders: {kind} {host_id} {text} {severity}. ETMINAN_NOTIFY_EMAIL_FORMAT=[{severity}] {host_id} {kind}: {text}
ETMINAN_NOTIFY_EMAIL_TEMPLATE (unset → built-in layout) Path to a subject/body template file overriding the default email layout (body must contain the findings placeholder). Missing/unreadable → warning + built-in layout, never a dropped alert. ETMINAN_NOTIFY_EMAIL_TEMPLATE=/etc/etminan-verifier/email.tmpl

Notifications — plugin channels

Extra channels (PagerDuty, Slack, generic webhook, or any customer script) are external, allowlisted, SHA-256-pinned scripts — the verifier has no in-process HTTP client. See notify-plugins.conf.

Variable Default Meaning & allowed values Example
ETMINAN_NOTIFY_CHANNELS (unset → off) Comma-separated channel names to enable. Each name must also have a line in the notify allowlist below, or it is skipped with a warning. ETMINAN_NOTIFY_CHANNELS=pagerduty,slack
ETMINAN_NOTIFY_PLUGINS_CONF /etc/etminan-verifier/notify-plugins.conf Path to the notify-plugin allowlist file. ETMINAN_NOTIFY_PLUGINS_CONF=/etc/etminan-verifier/notify-plugins.conf
ETMINAN_NOTIFY_<CHANNEL>_SEVERITIES (unset → all) Dynamic per-channel severity filter — <CHANNEL> is the channel name upper-cased. Comma-separated critical/warning/info. Unset = every severity. ETMINAN_NOTIFY_PAGERDUTY_SEVERITIES=critical
ETMINAN_NOTIFY_<CHANNEL>_FORMAT [{kind}] {host_id}: {text} Dynamic per-channel line template (same placeholders as the email format), overriding the channel's built-in formatted field with no plugin re-hash. ETMINAN_NOTIFY_SLACK_FORMAT={severity}: {host_id} {text}

Concrete <CHANNEL> instances

For the shipped reference plugins these expand to ETMINAN_NOTIFY_PAGERDUTY_SEVERITIES, ETMINAN_NOTIFY_SLACK_SEVERITIES, ETMINAN_NOTIFY_WEBHOOK_SEVERITIES, and the matching _FORMAT triplet. Any channel name you invent gets its own pair automatically.

SIEM output (CEF / LEEF over syslog)

Enterprise edition

The free Standard binary contains no SIEM code — these variables are read by nothing and setting them has no effect (and emits no warning). See SIEM output.

Variable Default Meaning & allowed values Example
ETMINAN_SIEM_FORMAT (unset → off) The on switch and format selector: cef or leef (case-insensitive). Empty/unset/any other value = OFF (an unrecognised value warns and stays off). When on, one syslog event is emitted per finding and per signed operator action. ETMINAN_SIEM_FORMAT=cef
ETMINAN_SIEM_SYSLOG_ADDR (unset → local /dev/log) Set host:port to send UDP to a remote collector instead of the local syslog socket. Facility is local0; severity maps critical→err, warning→warning, operator-action→info. ETMINAN_SIEM_SYSLOG_ADDR=siem-collector.internal:514

Change-source correlation (inbound)

Informational only — never gates or auto-approves anything. External, allowlisted, SHA-256-pinned plugins. See Change sources.

Variable Default Meaning & allowed values Example
ETMINAN_CHANGE_SOURCES (unset → off) Comma-separated change-source names to enable; each needs a line in the allowlist below or it is skipped with a warning. ETMINAN_CHANGE_SOURCES=request-tracker
ETMINAN_CHANGE_SOURCE_PLUGINS_CONF /etc/etminan-verifier/change-source-plugins.conf Path to the change-source allowlist file. ETMINAN_CHANGE_SOURCE_PLUGINS_CONF=/etc/etminan-verifier/change-source-plugins.conf

Dual control / four-eyes

Enterprise edition

In the free Standard binary the four-eyes gate is compiled out and dual-control is an unknown subcommand — a single signature still applies. See Dual control.

Variable Default Meaning & allowed values Example
ETMINAN_DUAL_CONTROL_ACTIONS (unset → off) The env floor for which actions require a second co-signer; off/none/empty = off. The only supported token is baseline-approve (default expands to it). An unknown token is a hard error — never a silent single-signed bypass. The signed op dual-control-policy set --actions baseline-approve [--threshold N] --reason <r> latch (itself four-eyes) is authoritative over this env floor. ETMINAN_DUAL_CONTROL_ACTIONS=baseline-approve
ETMINAN_DUAL_CONTROL_TTL_HOURS 168 Hours a pending request stays open before it expires (7 days). Non-positive/malformed → default. ETMINAN_DUAL_CONTROL_TTL_HOURS=72
ETMINAN_DUAL_CONTROL_THRESHOLD 2 Number of distinct approvers required (M-of-N). Clamped to a minimum of 2 (below that isn't four-eyes). Captured per request at creation, so a later change doesn't affect open ones. ETMINAN_DUAL_CONTROL_THRESHOLD=3

RBAC has no environment switch

RBAC is not configured through an environment variable. Role-based access control is enforced by the etminan-verifierd daemon itself: it authenticates each operator by kernel peer-UID and applies the caller's role (admin / operator / viewer) and host-group scope, default-deny. It is a Standard capability (not gated by the enterprise feature). See RBAC.

Plugin catalog & sandbox

The optional certified-plugin catalog (plugins list/install/update), a convenience layer over the manual allowlists.

Variable Default Meaning & allowed values Example
ETMINAN_PLUGIN_CATALOG_URL https://etminan.dev/downloads/plugins-catalog.json Catalog manifest URL (see manifest.json). Point at a private/offline mirror; relative entry URLs resolve against it. ETMINAN_PLUGIN_CATALOG_URL=https://mirror.internal/etminan/plugins-catalog.json
ETMINAN_PLUGIN_INSTALL_DIR /etc/etminan-verifier Install root — downloaded plugins land in <this>/<type>-plugins/<filename>. ETMINAN_PLUGIN_INSTALL_DIR=/etc/etminan-verifier
ETMINAN_PLUGIN_USER etminan-verifier-plugin Unprivileged user every vetted plugin subprocess is dropped to (setgroups→setgid→setuid) so a compromised plugin can't read baseline.db or the operator key. Set explicitly empty to opt out of the drop (plugins then run as the verifier's own user). A named user that doesn't exist → warning. ETMINAN_PLUGIN_USER=etminan-verifier-plugin
---

Plugin-process environment (passthrough)

The verifier binary never reads these — they are inherited by the plugin subprocess from the same env file (systemd EnvironmentFile=). The verifier clears the environment before exec and passes each plugin only the ETMINAN_* namespace plus basic system vars, so an unrelated secret elsewhere in the file never reaches a plugin. A plugin you write yourself may need entirely different variables — check its own documentation. The values below are what the shipped reference plugins expect. Protect the env file's permissions (root-readable-only); the plaintext-token posture is the same as the verifier's own TLS/operator private keys.

Notify reference plugins

Variable Consumed by Meaning Example
ETMINAN_PAGERDUTY_ROUTING_KEY notify-plugins/pagerduty.sh PagerDuty Events API v2 routing key. ETMINAN_PAGERDUTY_ROUTING_KEY=R0ABCD...
ETMINAN_SLACK_WEBHOOK_URL notify-plugins/slack.sh Slack incoming-webhook URL. ETMINAN_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T000/B000/xxxx
ETMINAN_WEBHOOK_URL notify-plugins/webhook.sh Generic JSON webhook endpoint. ETMINAN_WEBHOOK_URL=https://example.com/hooks/etminan
ETMINAN_WEBHOOK_AUTH_HEADER notify-plugins/webhook.sh Optional auth header sent verbatim. ETMINAN_WEBHOOK_AUTH_HEADER=Authorization: Bearer changeme

Change-source reference plugins

Variable Consumed by Meaning Example
ETMINAN_RT_BASE_URL change-sources/request-tracker.sh Request Tracker base URL. ETMINAN_RT_BASE_URL=https://rt.example.com
ETMINAN_RT_API_TOKEN RT plugin RT 5+ API token (sent as Authorization: token <value>). ETMINAN_RT_API_TOKEN=1-234-abcd...
ETMINAN_RT_HOST_FIELD RT plugin Optional RT custom-field name to also search; unset → Subject-only search. ETMINAN_RT_HOST_FIELD=Host
ETMINAN_RT_WINDOW_HOURS RT plugin Hours before/after a pending entry's first-seen time that count as "near" (default 24 in the plugin). ETMINAN_RT_WINDOW_HOURS=48
ETMINAN_RT_DEBUG_QUERY RT plugin Any value → print the constructed TicketSQL to the plugin's stderr. ETMINAN_RT_DEBUG_QUERY=1
ETMINAN_FREEITSM_BASE_URL change-sources/freeitsm.sh FreeITSM base install URL (API at <this>/api/v1/...). ETMINAN_FREEITSM_BASE_URL=https://itsm.example.com
ETMINAN_FREEITSM_API_TOKEN FreeITSM plugin Token (sent as Authorization: Bearer <token>). ETMINAN_FREEITSM_API_TOKEN=fitsm_changeme
ETMINAN_FREEITSM_COMPANY_ID FreeITSM plugin Optional tenant/company restriction on a multi-tenant install. ETMINAN_FREEITSM_COMPANY_ID=1
ETMINAN_FREEITSM_WINDOW_HOURS FreeITSM plugin Same window semantics as ETMINAN_RT_WINDOW_HOURS. ETMINAN_FREEITSM_WINDOW_HOURS=24
ETMINAN_FREEITSM_DEBUG_QUERY FreeITSM plugin Any value → print the constructed query params to stderr. ETMINAN_FREEITSM_DEBUG_QUERY=1

Not a real variable

ETMINAN_SECRET shows up if you grep the source, but it is not runtime configuration — it only exists as a substring in a passthrough-filter unit test.


Configuration files

agent.env

The complete, annotated agent env file — copy to /etc/etminan-agent/agent.env. Everything is commented because every value has a code default; uncomment only what you override, but you must set either a pinned fingerprint or the permissive bootstrap flag, and either a profile or a watched-path list.

# /etc/etminan-agent/agent.env — sourced by etminan-agent.service

# --- Transport ---------------------------------------------------------------
#ETMINAN_AGENT_LISTEN=0.0.0.0:7620
#ETMINAN_TLS_DIR=/var/lib/etminan-agent/tls

# Pin the one verifier this agent trusts (printed by `etminan-verifier
# keygen-tls`). REQUIRED in normal operation.
ETMINAN_VERIFIER_CERT_FINGERPRINT=<64-hex-char sha256 from etminan-verifier keygen-tls>

# First-enrolment bootstrap ONLY: accept any client cert (still real mTLS,
# just unpinned; serves only Quote). Leave the fingerprint UNSET while this is
# on, then pin it and REMOVE this line. With neither set, the agent refuses to
# start.
#ETMINAN_ALLOW_PERMISSIVE_TLS=1

# --- TPM: no override — the agent always uses the resource-managed /dev/tpmrm0 --

# --- IMA (defaults correct on a standard kernel; log MUST be the sha256 file) -
#ETMINAN_IMA_LOG_PATH=/sys/kernel/security/ima/ascii_runtime_measurements_sha256
#ETMINAN_IMA_POLICY_PATH=/sys/kernel/security/ima/policy

# --- Watched paths (pick ONE approach; there is NO default watch set) --------
# A) named profile from profiles.conf:
ETMINAN_PROFILE=fds-host
#ETMINAN_PROFILES_PATH=/etc/etminan-agent/profiles.conf
#ETMINAN_ASSIGNED_PROFILE_PATH=/var/lib/etminan-agent/assigned_profile
# B) direct override (takes precedence over ETMINAN_PROFILE):
#ETMINAN_WATCHED_PATHS=/usr/local/bin/fds-proxy,/etc/systemd/system,/etc/fds

verifier.env

The complete, annotated verifier env file — copy to /etc/etminan-verifier/verifier.env. The shipped deploy/verifier.env.example is the authoritative long-form; below is a working subset covering the common Standard install plus the Enterprise switches.

# /etc/etminan-verifier/verifier.env — sourced by etminan-verifier.service/.timer

# --- Store, state & identity (defaults match the systemd WorkingDirectory) ---
#ETMINAN_BASELINE_DB=/var/lib/etminan-verifier/baseline.db
#ETMINAN_VERIFIER_STATE=/var/lib/etminan-verifier/state.json
#ETMINAN_TLS_DIR=/var/lib/etminan-verifier/tls

# --- Attestation-loop tuning -------------------------------------------------
#ETMINAN_CHECK_CONCURRENCY=32
#ETMINAN_MAX_CATCHING_UP_CYCLES=6
#ETMINAN_PENDING_REVIEW_SLA_HOURS=24

# --- Notifications: master switch + email ------------------------------------
#ETMINAN_NOTIFY=on
#ETMINAN_NOTIFY_SUPPRESS_KINDS=sla-exceeded
ETMINAN_NOTIFY_EMAIL=security@example.com
#ETMINAN_NOTIFY_FROM=noreply@etminan
#ETMINAN_NOTIFY_EMAIL_SEVERITIES=critical,warning
#ETMINAN_NOTIFY_EMAIL_FORMAT=[{kind}] {host_id}: {text}
#ETMINAN_NOTIFY_EMAIL_TEMPLATE=/etc/etminan-verifier/email.tmpl

# --- Notifications: plugin channels ------------------------------------------
#ETMINAN_NOTIFY_CHANNELS=pagerduty,slack
#ETMINAN_NOTIFY_PLUGINS_CONF=/etc/etminan-verifier/notify-plugins.conf
#ETMINAN_NOTIFY_PAGERDUTY_SEVERITIES=critical
#ETMINAN_NOTIFY_SLACK_SEVERITIES=critical,warning
# Consumed by the plugin processes themselves (not the verifier binary):
#ETMINAN_PAGERDUTY_ROUTING_KEY=changeme
#ETMINAN_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T000/B000/xxxx
#ETMINAN_WEBHOOK_URL=https://example.com/hooks/etminan
#ETMINAN_WEBHOOK_AUTH_HEADER=Authorization: Bearer changeme

# --- SIEM (Enterprise; off unless a format is set; cef or leef) --------------
#ETMINAN_SIEM_FORMAT=cef
#ETMINAN_SIEM_SYSLOG_ADDR=siem-collector.internal:514

# --- Change-source correlation (inbound, informational only) -----------------
#ETMINAN_CHANGE_SOURCES=request-tracker
#ETMINAN_CHANGE_SOURCE_PLUGINS_CONF=/etc/etminan-verifier/change-source-plugins.conf
#ETMINAN_RT_BASE_URL=https://rt.example.com
#ETMINAN_RT_API_TOKEN=changeme

# --- Dual control / four-eyes (Enterprise) -----------------------------------
# Env floor; the signed `op dual-control-policy set` latch is authoritative.
# Only supported token: baseline-approve.
#ETMINAN_DUAL_CONTROL_ACTIONS=baseline-approve
#ETMINAN_DUAL_CONTROL_TTL_HOURS=168
#ETMINAN_DUAL_CONTROL_THRESHOLD=2

# --- Plugin catalog & sandbox ------------------------------------------------
#ETMINAN_PLUGIN_CATALOG_URL=https://etminan.dev/downloads/plugins-catalog.json
#ETMINAN_PLUGIN_INSTALL_DIR=/etc/etminan-verifier
#ETMINAN_PLUGIN_USER=etminan-verifier-plugin
#ETMINAN_PLUGIN_ALLOW_REDIRECTS=1

baseline.db

ETMINAN_BASELINE_DB (default /var/lib/etminan-verifier/baseline.db) is a SQLite database managed entirely by etminan-verifier — never hand-edit it. It holds:

  • the approved baseline (approved_measurements, keyed by host_id) — the (path, sha256) set each host is trusted to report;
  • pending review items (drift proposed, never auto-accepted);
  • the hash-chained, signed audit log of every trust-changing action, whose tamper-evident head is anchored in a sibling .audit-head file;
  • the legacy operator-key registry (retained read-only for verify-signatures), and (Enterprise) the dual-control pending requests and the signed dual-control policy where applicable.

Back it up as a unit with its .audit-head anchor. Editing it directly breaks the audit chain and is itself detectable.

state.json

ETMINAN_VERIFIER_STATE (default /var/lib/etminan-verifier/state.json) is a JSON file, also tool-managed, mapping each host_id to its enrolment record (pinned AK fingerprint, verifier-cert pin, assigned profile name) and the cumulative IMA / PCR-10 replay cursor used to bind the next log delta to the previous state. It is written atomically under an exclusive file lock across the whole load-modify-save cycle. Do not hand-edit while the verifier runs.

The plugin allowlist files

Two separate allowlists, one per plugin category, each named by its own *_PLUGINS_CONF variable. Keeping them separate means allowlisting one category can never be confused with another. Both share the exact same format and the exact same enforcement: a plugin runs only if it has a line here and its file is root-owned and not group/world-writable and its exact byte content matches the pinned SHA-256 — all re-checked fresh before every single execution, never a directory scan.

File (default path) Variable Names matched against
/etc/etminan-verifier/notify-plugins.conf ETMINAN_NOTIFY_PLUGINS_CONF ETMINAN_NOTIFY_CHANNELS
/etc/etminan-verifier/change-source-plugins.conf ETMINAN_CHANGE_SOURCE_PLUGINS_CONF ETMINAN_CHANGE_SOURCES

Format: one plugin per line, three whitespace-separated fields; # comments and blank lines ignored.

# <name>        <absolute-path-to-executable>                       <sha256_hex>
pagerduty       /etc/etminan-verifier/notify-plugins/pagerduty.sh   c4f440fd72dd90390022215201b49eda32fbb82bda74885902cd3fe7fc3382e7
slack           /etc/etminan-verifier/notify-plugins/slack.sh       8dc10f47d8598059339b5a3545896b4e5c91f4ea8513a33fdfb3384d5bd49b96
  • <name> — must match an entry in the corresponding enable variable.
  • <path> — absolute path to the plugin executable.
  • <sha256_hex> — 64 hex chars; recompute after every edit: sha256sum /path/to/plugin.

The hashes above only match the reference plugins as shipped in this release — recompute them if you copy or modify a plugin.

profiles.conf

ETMINAN_PROFILES_PATH (default /etc/etminan-agent/profiles.conf) defines named watched-path sets so many hosts can share one reusable list. A host selects one via ETMINAN_PROFILE (or centrally via op assign-profile). An ETMINAN_ASSIGNED_PROFILE_PATH file records the last centrally-pushed name.

# /etc/etminan-agent/profiles.conf
[fds-host]
/usr/local/bin/fds-proxy
/usr/local/bin/fds-admin
/usr/local/bin/fds-portal
/usr/local/bin/fds-shell
/etc/systemd/system
/etc/fds

[generic-host]
/usr/local/bin/myapp
/etc/myapp

Two parsing behaviours to know:

  1. A repeated [name] section is additive, not "last wins" — [a] /one [a] /two yields a → [/one, /two]. To replace a profile, delete the old section rather than appending a second with the same name.
  2. A path line before the first [name] header is silently discarded — there is no "default" section; always put a header first.

An empty section ([name] with no paths) is valid and watches nothing.

The plugin catalog manifest (manifest.json)

Served at ETMINAN_PLUGIN_CATALOG_URL (default https://etminan.dev/downloads/plugins-catalog.json) for etminan-verifier plugins list/install/update. Real JSON has no comments. The published manifest also requires a detached signature file at the same URL plus .sig (hex-encoded Ed25519 over the exact bytes) — a dedicated catalog key, deliberately not the GPG release-signing key.

{
  "catalog_version": 1,
  "plugins": [
    {
      "name": "pagerduty",
      "type": "notify",
      "version": "1.0.0",
      "sha256": "<sha256 of the plugin file>",
      "url": "https://etminan.dev/downloads/plugins/notify/pagerduty-1.0.0.sh",
      "description": "PagerDuty Events API v2 notification channel"
    },
    {
      "name": "request-tracker",
      "type": "change-source",
      "version": "1.0.0",
      "sha256": "<sha256 of the plugin file>",
      "url": "https://etminan.dev/downloads/plugins/change-source/request-tracker-1.0.0.sh",
      "description": "Request Tracker (RT) REST2 API change-ticket correlation"
    }
  ]
}

Each entry's type is one of notify / change-source. install verifies the downloaded file's SHA-256 against sha256 before writing it to <ETMINAN_PLUGIN_INSTALL_DIR>/<type>-plugins/; you still add the allowlist line yourself. A relative url resolves against the catalog URL, so pointing at an offline mirror relocates the downloads too. In-house plugins you wrote need no catalog and no signature: just the manual allowlist line.


See also