Etminan measures every monitored host from the kernel, seals the result in a TPM, and hands the verdict to a separate device that the host can never reach. If root is compromised, it can refuse to answer — it cannot lie and get away with it.
AIDE, Tripwire, and auditd-based integrity checks all share the same weak point: the checksum database and the alarm live on the box being watched.
Under a full-root-compromise threat model, that's fatal. A userspace agent alone can't be trusted here — root can patch the hashing binary in memory, edit the baseline it compares against, or simply lie about the result. The alarm never fires, because the thing that would fire it is the thing that got compromised.
Etminan is built to answer that threat model directly, not to catch a wider net of generic issues. Measurements have to come from somewhere root cannot forge after the fact — that means the kernel's IMA subsystem extending a TPM PCR, and a signed, remotely-verified quote checked by a device the compromised host never touches.
The monitored host never judges itself. Every decision — pass, fail, or "needs a human" — is made on hardware the host cannot reach.
Linux IMA measures file executions and reads under policy and extends the results into TPM PCR 10 — a one-way operation even root cannot rewrite after the fact.
The verifier pulls a signed TPM2_Quote on its own schedule,
replays the host's IMA log, and confirms it matches the signed digest. A
compromised host can stay silent — it cannot fabricate a validly signed quote.
Any change from the last approved baseline lands in review. It only becomes trusted after an explicit, signed operator approval with a required reason.
Two things every operator sees: the plain-language verdict on a pending change, and exactly what fires when something's actually wrong.
Every new or changed measurement lands here first — a plain-language read before the raw hash-level detail, so a generalist operator can act on the first line.
A genuine finding never waits for a human to notice — it's printed, signed into the audit log, and pushed to every configured channel in the same pass.
A single host is never both agent and verifier — the verifier's trust value comes from
being administratively separate from the hosts it checks. On the verifier side two
processes run: the etminan-verifier CLI and the
etminan-verifierd RBAC daemon, which owns the state and the
signing key. The reasoning is applied once more to the verifier itself — what it says
about its own integrity is checked by etminan-witness on a
third machine.
A relay, not a trust anchor. On request, it sweeps in-scope paths, requests a signed TPM quote, and returns it with the IMA log delta.
No TPM required — quote verification is pure offline signature math against the enrolled AK public key. Not just no runtime device access: zero TPM library dependency in the shipped binary, which cross-compiles clean to a static musl target. The only place an alarm ever fires from.
etminan-verifierd) — operators run
etminan-verifier op <cmd>, are authenticated by their
Unix UID (no key files), and the daemon signs the action into the tamper-evident audit
log.The RBAC daemon, and the only process that touches verifier state or the signing key.
Operators reach it as etminan-verifier op <cmd> and hold
no key themselves.
SO_PEERCRED,
unforgeable for a non-root peer, against a default-deny role and host scope.The verifier does not check itself. A verifier that has been taken over keeps signing perfectly well, and will hand you a fresh, valid summary over a frozen audit head — so the evidence it produces about its own integrity is checked somewhere else.
Two shapes, chosen deliberately per integration. Package-manager checks are an in-process Rust trait — a closed, well-known set of package managers. Change-ticket systems and alert channels are external plugins under the shared Etminan Plugin API — nobody is switching ticket systems or paging tools for an attestation tool, so the verifier ships zero in-process HTTP clients for either and instead runs a separately-vetted, allowlisted, SHA-256-pinned script per integration.
Does an installed package own the changed path? Checked against dpkg/rpm on a dedicated, non-quote-taking connection.
op review.PackageChecker trait — apk/pacman are a new
impl away.Was this change actually planned? The verifier runs every enabled plugin and shows every confirmation it finds — never just the first match. Request Tracker ships today as a reference plugin, built directly against RT's REST2 API — not yet verified against a live instance, so treat it as a working starting point to test against your own deployment, not a production guarantee out of the box.
Beyond the self-contained, hash-chained audit log and email: PagerDuty ships today as a reference plugin under the same Plugin API as change correlation — built against PagerDuty's documented Events API, not yet verified against a live account.
etminan-verifier plugins verify checks every configured plugin
on demand, turning it into an ordinary warning-severity finding.These are one example each — see the Plugin Directory for every plugin shipped today (FreeITSM, Slack, and a generic webhook too), each with an honest status, not a blanket "supported" claim. Writing one for your own ticket system or paging tool is the same recipe — see writing a new plugin — and contributions are welcome.
Etminan ships as a free Standard edition with everything core, and an Enterprise edition that adds SIEM output and dual-control approvals for larger, regulated deployments — opt-in and off by default. The Enterprise features are in development and not yet published. See the full side-by-side editions comparison on the Enterprise page.
The short path below covers the common case. For profiles, package-manager plugins, and day-to-day review, see the full user guide.
The postinst step creates the etminan-agent (group
tss) and etminan-verifier
system users and their config/state directories — but enables nothing. You choose
which role(s) this host plays.
# install the package sudo dpkg -i etminan_<version>_amd64.deb # monitored host — needs a TPM. Enable the IMA-policy unit now; start # etminan-agent.service only AFTER writing agent.env (next step). sudo systemctl enable --now etminan-agent-ima-policy.service # ... configure agent.env, then: sudo systemctl enable --now etminan-agent.service # verifier device sudo systemctl enable --now etminan-verifier.timer
Copy deploy/agent.env.example to
/etc/etminan-agent/agent.env before you start the
agent service. There is
no baked-in default for watched paths — an
agent started with neither setting watches nothing and warns loudly on startup, rather
than silently assuming another project's file list. And with no verifier fingerprint
pinned yet, the agent refuses to start unless
you opt into the enrollment bootstrap with
ETMINAN_ALLOW_PERMISSIVE_TLS=1 — after enrolling (below) you
pin ETMINAN_VERIFIER_CERT_FINGERPRINT and remove that flag.
# listen address for quote requests from the verifier ETMINAN_AGENT_LISTEN=0.0.0.0:7620 # first boot, before you know the verifier's fingerprint: opt into the # enrollment bootstrap. Remove this and pin the fingerprint after enrolling. ETMINAN_ALLOW_PERMISSIVE_TLS=1 #ETMINAN_VERIFIER_CERT_FINGERPRINT=<sha256 hex from `etminan-verifier keygen-tls`> # pick ONE: a named profile from profiles.conf, or an inline override ETMINAN_PROFILE=fds-host #ETMINAN_WATCHED_PATHS=/usr/local/bin/myapp,/etc/myapp
Copy deploy/verifier.env.example to
/etc/etminan-verifier/verifier.env and point it at where
enrollment state and the baseline database should live.
ETMINAN_VERIFIER_STATE=/var/lib/etminan-verifier/state.json ETMINAN_BASELINE_DB=/var/lib/etminan-verifier/baseline.db ETMINAN_NOTIFY_EMAIL=security@example.com # unset disables the email alarm leg
Operators hold no signing key: the etminan-verifierd RBAC
daemon holds the single signing key and signs on their behalf. Start it, bootstrap the
first admin by Unix UID, and give each device its mandatory-mTLS identity (the
agent↔verifier channel is mTLS-only, no plaintext fallback).
# on the verifier — start the RBAC daemon, then bootstrap the first admin (as root) sudo systemctl enable --now etminan-verifierd.service sudo etminan-verifier op bootstrap --uid 1000 --label "admin1" # on both the agent and the verifier — mTLS identity etminan-agent keygen-tls etminan-verifier keygen-tls
From the verifier, for each monitored host. This takes a first quote, proves the AK
lives in a real TPM via an EK credential-activation
challenge (refusing a software-generated key), and prints the AK fingerprint —
confirm it out-of-band (e.g. read it off the
host's console) before trusting it. The residency proof establishes it's a genuine TPM;
this out-of-band step confirms it's the right host's. On discrete hardware you can also
add --ek-roots <dir> to require the TPM's manufacturer
EK certificate chain, proving genuine hardware rather than only same-TPM
residency.
etminan-verifier op enroll web-01 --addr 10.0.0.5:7620 --reason "onboard web-01"
Every measurement from the first quote lands as a pending candidate, never auto-trusted. Review it once, then approve it through the RBAC daemon. Which operators may approve is set by role and scope in the daemon's identity registry; the daemon authenticates you by your Unix UID and signs the approval itself — there is no operator key file to hold. See the operator guide.
etminan-verifier op review web-01 etminan-verifier op approve web-01
Hosted directly from this site for the moment — every Standard release is a GPG-signed native
.deb and .rpm package. Always verify the
signature before installing; never trust an unsigned package.
etminan-agent, etminan-verifier and its
privilege-separated daemon etminan-verifierd — three binaries from one
source, one version (the daemon ships inside the verifier package).
etminan.deb + .rpm.deb / .rpm by
arrangement — not downloaded hereMandatory access control — one profile for each of the three binaries (the agent, the verifier,
and its daemon). Packaged for Debian with release CI; no signed package is published here yet —
build via a tagged release, then drop the
.deb/.rpm into downloads.
apparmor-etminan-agentapparmor-etminan-verifierapparmor-etminan-verifierdNot started yet. Tracked as a follow-on once the AppArmor packages are published and the policy-authoring pass has a go/no-go.
selinux-etminan-agent (planned name)selinux-etminan-verifier (planned name)selinux-etminan-verifierd (planned name)Open a ticket at our support desk and follow it through to resolution — questions about deploying Etminan, feedback on the design, or a security report each have their own queue. Prefer email? Reach us directly.
Reporting something security-sensitive? Submit it privately through the write-only
security intake — or email it
encrypted with the same key packages on this site are signed with —
7387 4214 090F 9137 862D 0AF1 E1E5 41B7 B424 36DF, fetchable from
hkps://keys.openpgp.org (see the Downloads section above).