Your first attestation¶
This walkthrough takes you from two fresh installs to a first verified attestation, end to end. By the finish you will have enrolled one monitored host, approved its initial baseline, and watched the verifier confirm a clean quote.
Before you start
You have installed the agent on the monitored host and
installed the verifier on a separate device, and on the
verifier the TLS identity is generated, the etminan-verifierd daemon is
running, and the first admin operator is bootstrapped — all from the
initialize step. The examples
use host id web-01 at 10.0.0.5:7620; run the op commands below as that
bootstrapped admin operator, and substitute your own host id.
The shape of it¶
Five steps, straddling two machines. Trust is established once, out-of-band, at enrollment; everything after is automatic.
sequenceDiagram
autonumber
participant O as Operator
participant A as etminan-agent<br/>(web-01, has TPM)
participant V as etminan-verifier<br/>(separate device)
Note over A,V: Step 1 — exchange pinned TLS identities
O->>A: etminan-agent keygen-tls
A-->>O: agent cert fingerprint
O->>V: etminan-verifier keygen-tls
V-->>O: verifier cert fingerprint
O->>A: pin verifier fp in agent.env, restart
Note over V,A: Step 2 — enroll (trust-on-first-use)
O->>V: op enroll web-01 --addr … (daemon-signed)
V->>A: fresh nonce → quote request (mTLS)
A->>A: TPM2_Quote over PCR 10 (AK-signed)
A-->>V: quote + IMA log delta
V->>A: EK credential-activation challenge
A-->>V: recovered secret (proves TPM residency)
V-->>O: AK + TLS fingerprints — confirm out of band
Note over V: Step 3 — seed the baseline
O->>V: baseline review --host web-01
O->>V: op approve web-01 (daemon-signed)
Note over V,A: Step 4 — first check
O->>V: check --host web-01 --addr …
V->>A: fresh nonce → quote request
A-->>V: quote + log delta
V->>V: verify sig · nonce · replay PCR 10 · diff baseline
V-->>O: VERIFIED — no drift
Note over V: Step 5 — hand off to the hourly timer
Step 1 — exchange pinned TLS identities¶
Every agent↔verifier connection is mutual TLS pinned by certificate fingerprint; there is no plaintext path. Generate a keypair on each side.
On the monitored host:
etminan-agent keygen-tls
# → writes /var/lib/etminan-agent/tls/{cert,key}.pem and prints the agent's
# SHA-256 fingerprint
On the verifier device (already done during install if you followed that chapter — reprinted here for reference):
Pin the verifier's fingerprint in the agent's environment, then restart the agent:
# /etc/etminan-agent/agent.env
ETMINAN_VERIFIER_CERT_FINGERPRINT=<verifier fingerprint from above>
ETMINAN_WATCHED_PATHS=/usr/local/bin,/etc/systemd/system # or a profile
The one-time bootstrap alternative
If you would rather enrol before pinning, the agent supports a deliberate
one-time bootstrap: leave the fingerprint unset and set
ETMINAN_ALLOW_PERMISSIVE_TLS=1. In that mode the agent accepts any client
certificate but serves only Quote — the log cursor never advances and
SetProfile/PackageLookup are refused. Remove the flag and pin the
verifier's fingerprint immediately after enrolling. Full detail:
Mutual TLS.
Step 2 — enroll the host¶
Enrollment is the single trust-on-first-use moment, performed through the daemon
like every other trust-changing action. Run it as the bootstrapped admin
operator; the daemon authenticates you by peer-UID (SO_PEERCRED), signs the
enrollment on your behalf, and records it on the hash-chained audit log:
See Enrolling a host for the full ceremony and why enrollment is deliberately kept as a rare, root-gated step.
The verifier takes a first quote, validates it self-consistently, and — before
pinning anything — runs an EK credential-activation challenge
(TPM2_MakeCredential → ActivateCredential). It refuses to enrol unless the
host's TPM recovers the sealed secret, proving the AK genuinely resides in a
real TPM rather than a software key of the right shape. On success it prints the
pinned fingerprints:
Enrolled host 'web-01':
AK fingerprint (sha256): 9f2c…
TLS cert fingerprint (sha256): 41ab…
EK certificate: not checked (residency-only enrollment). …
Operator: confirm BOTH fingerprints out-of-band before trusting them …
Signed by operator:<label>. Reason: onboarding web-01
2265 initial measurement(s) recorded as pending — review with
`baseline review --host web-01`, then bulk-approve the known-good image
with `etminan-verifier op approve web-01`.
Confirm the fingerprints out of band
Credential activation proves the AK lives in a TPM; reading the printed AK and TLS fingerprints off the host's own console (or another trusted channel) confirms it is the right host's TPM. Do this before trusting the enrollment — the same discipline as an SSH host key on first connect.
Require genuine hardware (discrete TPMs)
To also prove the TPM is real silicon — not a software/emulated TPM under a
compromised hypervisor — pass --ek-roots <dir> with your TPM
manufacturer's root and intermediate CA certificates. Enrollment then
additionally requires an EK certificate chaining to a trusted manufacturer
root, and refuses a host with no EK cert (typical for vTPMs/swtpm). See
Enrolling a host.
Step 3 — seed the baseline¶
Every measurement from that first quote is recorded as pending, never auto-trusted. Review what was captured, then approve it as the bootstrapped admin operator — the daemon signs the batch on your behalf and gates it against your role — to establish the known-good starting point:
op approve needs no key file and takes no --reason: the daemon authenticates
you by peer-UID (SO_PEERCRED), attributes the signed batch to your operator
label, and records the allow decision on the audit log.
If you skipped the bootstrap
op approve fails closed until at least one identity exists. Bootstrap the
first admin once, as root on the verifier —
sudo etminan-verifier op bootstrap --uid <N> --label <you> — then re-run the
approve. Full model: RBAC.
This bulk approve is a one-time event
Right after enrollment every entry reads (new file, no prior baseline) —
there is no baseline yet, so nothing can look like a modification. That is
expected; you are establishing the starting point, not detecting tampering.
This is the one moment a single bulk op approve web-01 is normal. From the
next hourly run onward, approvals should be small and specific to what
actually changed. See Baseline review & drift.
Step 4 — the first check¶
Now ask the verifier to attest the host interactively:
Under the hood, the verifier: generates a fresh nonce → requests a quote → verifies the AK signature → confirms the nonce matches (replay protection) → replays the returned IMA log, recomputes PCR 10, and confirms it equals the digest the TPM signed → diffs the measurements against the baseline you just approved. With nothing changed since approval, the host reports clean — no findings, no drift.
Any real problem surfaces as a finding, printed as [kind] host: text, for
example:
[signature-invalid] web-01: quote signature failed to verify
[sla-exceeded] web-02: 3 pending item(s) aged past the 24h review SLA
A check that finds anything exits non-zero — the same signal the scheduled
run uses to drive the alarm path.
Step 5 — hand off to the schedule¶
You have proven the loop by hand. Let the timer take over so web-01 is
attested every hour without you:
From here on, each hourly run re-attests every enrolled host. Legitimate new
activity lands in pending_review for you to approve or reject — it is never
silently absorbed, and never silently alarmed.
Where to go next¶
- The attestation loop — what happens on every cycle, in depth.
- Baseline review & drift — approving, rejecting, and excluding day to day.
- Notification channels — get findings out of the terminal and in front of someone.
- Enrolling a host — enrollment options,
--ek-roots, and re-enrollment.