Skip to content

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):

etminan-verifier keygen-tls
# → prints the verifier's SHA-256 fingerprint

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
sudo systemctl restart etminan-agent.service

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:

etminan-verifier op enroll web-01 --addr 10.0.0.5:7620 \
    --reason "onboarding web-01"

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_MakeCredentialActivateCredential). 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:

etminan-verifier baseline review --host web-01
etminan-verifier op approve web-01

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:

etminan-verifier check --host web-01 --addr 10.0.0.5:7620

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:

sudo systemctl enable --now etminan-verifier.timer

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