Skip to content

Thumbprint Mode

Thumbprint mode enables mTLS authentication using self-signed certificates verified by their SHA-256 fingerprint.

ScenarioWhy Thumbprint Mode
Quick setupNo CA infrastructure needed
Small deploymentsManual management is feasible
DevelopmentSimple to configure
Isolated networksNo external CA dependencies
POC/testingMinimal setup time
AspectThumbprintCA-Signed
Setup complexityLowMedium
CA requiredNoYes
ScalabilityManual per-agentAutomated
Certificate managementPer-agentCentralized
Best forSmall deploymentsEnterprise
[tls]
auth_mode = "thumbprint"
# Server certificate
cert_path = "/etc/mantis/certs/server-cert.pem"
key_path = "/etc/mantis/certs/server-key.pem"
# Set this even in thumbprint mode: it is what Thorax authenticates the Mandible
# control plane against, and what it presents when dialling a listen-mode target.
# It does not grant client auto-trust -- only ca_signed mode does that.
# The server's own chain belongs in cert_path, not here.
ca_cert_path = "/etc/mantis/certs/ca-cert.pem"
Terminal window
# Enable thumbprint mode
export MANTIS_TLS_AUTH_MODE=thumbprint
export MANTIS_TLS_CERT_PATH=/etc/mantis/certs/server-cert.pem
export MANTIS_TLS_KEY_PATH=/etc/mantis/certs/server-key.pem
[tls]
cert_path = "/etc/mantis/certs/tarsus-cert.pem"
key_path = "/etc/mantis/certs/tarsus-key.pem"
# CA certificate used to verify the Thorax server and, in listen mode, to
# enforce mTLS on inbound connections. Required in all modes — without it
# Tarsus refuses to start.
ca_cert_path = "/etc/mantis/certs/ca-cert.pem"

Tarsus generates its own self-signed certificate on first run when none is configured, with the EKUs and SANs thumbprint mode needs. That is the certificate this page is about, and it needs no tooling.

mantisctl cert generate-server and generate-client are not a substitute: both issue a leaf signed by the platform CA (they take --ca-cert/--ca-key, defaulting to ./certs/ca-cert.pem and ./certs/ca-key.pem). An agent holding a CA-signed certificate is authenticated by the chain in ca_signed mode, where Thorax auto-trusts and auto-approves it and the pre-registration below is never consulted. On a thumbprint-mode Thorax the chain is not enough: the certificate falls through to thumbprint validation like any other, and a pre-registration is required. Use them for CaSigned mode.

Only if the shipped tooling is unavailable.

The certificate must carry both clientAuth and serverAuth, plus a SAN for the hostname Thorax dials — in listen mode Thorax connects to the agent, so the agent presents this certificate as a server. A clientAuth-only certificate is rejected with UnsupportedCertificate on the first listen-mode connection.

Terminal window
cat > /tmp/agent-ext.cnf <<'EOF'
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, serverAuth
subjectAltName = DNS:web-prod-01.example.com
EOF
openssl req -new -x509 -nodes -newkey rsa:2048 \
-keyout /etc/mantis/certs/tarsus-key.pem \
-out /etc/mantis/certs/tarsus-cert.pem \
-days 365 -sha256 \
-subj "/CN=$(hostname)/O=Mantis Agent" \
-extensions v3_req -config <(cat /etc/ssl/openssl.cnf /tmp/agent-ext.cnf)
systemctl restart tarsus@<instance>

Restarting is required — Tarsus reads its certificate once at startup and does not watch the file or handle SIGHUP.

ParameterRecommended Value
Key size2048 bits (RSA) or P-256 (ECDSA)
Validity365 days
CNHostname or agent name
Extended Key UsageclientAuth, serverAuth
Subject Alt NameDNS name Thorax dials (listen mode)
SignatureSHA-256

Tarsus’s first-run certificate already has these properties; the OpenSSL recipe above is the air-gapped fallback for reproducing them by hand.

Terminal window
# Get thumbprint from certificate
mantisctl cert thumbprint /etc/mantis/certs/tarsus-cert.pem
# Output:
# abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
# Using OpenSSL
openssl x509 -in /etc/mantis/certs/tarsus-cert.pem -fingerprint -sha256 -noout | \
sed 's/SHA256 Fingerprint=//' | tr -d ':' | tr '[:upper:]' '[:lower:]'
FormatExample
Mantisabcdef1234567890abcdef1234567890... (64 chars)
OpenSSLAB:CD:EF:12:34:56:78:90:... (colon-separated)

Convert between formats:

Terminal window
# OpenSSL to Mantis format
echo "AB:CD:EF:12:..." | tr -d ':' | tr '[:upper:]' '[:lower:]'
# Mantis to OpenSSL format (for display)
echo "abcdef12..." | sed 's/../&:/g' | sed 's/:$//' | tr '[:lower:]' '[:upper:]'

Register thumbprint before agent connects:

Terminal window
# 1. On agent: get thumbprint
mantisctl cert thumbprint /etc/mantis/certs/tarsus-cert.pem
# abcdef1234567890...
# 2. Share thumbprint securely with admin
# (phone, encrypted email, secure chat)
# 3. Admin pre-registers thumbprint
mantisctl cert pre-register \
--name "web-prod-01" \
--thumbprint "abcdef1234567890..."
# 4. Agent connects and is auto-approved
tarsus run

This applies to token-based registration whose token does not carry auto_approve. An agent presenting an unknown thumbprint with no token is rejected outright and nothing is recorded — there is no pending row to approve.

Terminal window
# 1. Agent registers with a token that lacks auto_approve
tarsus run
# Status: Pending (awaiting approval)
# 2. Admin sees pending registration
mantisctl cert list --status pending
# CLIENT THUMBPRINT HOSTNAME
# unknown-01 xyz789... server.local
# 3. Admin verifies and approves (by registration ID or thumbprint prefix)
mantisctl cert approve "<REGISTRATION_UUID>" \
--notes "Verified web-prod-01 out of band"
# 4. Agent is now authenticated
┌─────────────────────────────────────────────────────────────┐
│ Pending Registrations │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┬───────────────┬─────────────────────┐ │
│ │ Client │ Thumbprint │ Actions │ │
│ ├────────────────┼───────────────┼─────────────────────┤ │
│ │ unknown-01 │ xyz789... │ [Approve] [Reject] │ │
│ │ unknown-02 │ abc123... │ [Approve] [Reject] │ │
│ └────────────────┴───────────────┴─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

Always verify thumbprints through a secure channel:

ChannelSecurity LevelUse Case
In-personHighestHigh-security
Phone callHighVerbal confirmation
Encrypted emailMediumAudit trail
Secure chatMediumQuick verification
Terminal window
# Secure key file permissions
chmod 600 /etc/mantis/certs/tarsus-key.pem
chown root:root /etc/mantis/certs/tarsus-key.pem
# Verify
ls -la /etc/mantis/certs/tarsus-key.pem
# -rw------- 1 root root 1679 Jan 15 10:00 /etc/mantis/certs/tarsus-key.pem

Self-signed certificates should be renewed before expiry:

Terminal window
# Check expiration
openssl x509 -in /etc/mantis/certs/tarsus-cert.pem -enddate -noout
# notAfter=Jan 15 10:00:00 2026 GMT
# Generate new certificate using OpenSSL
openssl req -new -x509 -nodes \
-keyout /etc/mantis/certs/tarsus-key.pem \
-out /etc/mantis/certs/tarsus-cert.pem \
-days 365 \
-subj "/CN=$(hostname)/O=Mantis Agent"
# Get new thumbprint
mantisctl cert thumbprint /etc/mantis/certs/tarsus-cert.pem
# Swap the certificate in place. This keeps the registration id, the approval
# status and the rotation history, so the agent does not return to the pending
# queue. Run it on the agent host.
tarsus rotate-certificate \
--new-cert /etc/mantis/certs/tarsus-cert.pem \
--new-key /etc/mantis/certs/tarsus-key.pem \
--reason "thumbprint rotation"

For deploying multiple agents:

deploy-agents.sh
#!/bin/bash
SERVERS="web-01 web-02 api-01 api-02"
for server in $SERVERS; do
# Get thumbprint from server (assumes SSH access)
thumbprint=$(ssh $server "mantisctl cert thumbprint /etc/mantis/certs/tarsus-cert.pem")
# Pre-register in Mantis
mantisctl cert pre-register \
--name "$server" \
--thumbprint "$thumbprint"
echo "Registered: $server"
done

Ansible example:

- name: Get Tarsus thumbprint
command: mantisctl cert thumbprint /etc/mantis/certs/tarsus-cert.pem
register: thumbprint
- name: Register with Mantis
uri:
url: '{{ mantis_api_url }}/api/v1/registrations/pre-register'
method: POST
headers:
Authorization: 'Bearer {{ mantis_token }}'
body_format: json
body:
client_name: '{{ inventory_hostname }}'
thumbprint: '{{ thumbprint.stdout }}'
hostname: '{{ inventory_hostname }}'

Pre-registration carries no tenant field: Mandible takes the tenant from the caller’s own token, so a tenant-scoped operator registers the agent into their own tenant and a global administrator registers it globally. To place an agent in a specific tenant, call this with a token scoped to that tenant.

Problem: Agent shows “Connecting…” but never authenticates

Diagnosis:

Terminal window
# Check agent logs
journalctl -u tarsus@<instance> -f
# Verify certificate
openssl x509 -in /etc/mantis/certs/tarsus-cert.pem -text -noout
# Test connection
openssl s_client -connect thorax.example.com:50051 \
-cert /etc/mantis/certs/tarsus-cert.pem \
-key /etc/mantis/certs/tarsus-key.pem

Problem: Agent rejected with “thumbprint mismatch”

Diagnosis:

Terminal window
# Get actual thumbprint from agent
mantisctl cert thumbprint /etc/mantis/certs/tarsus-cert.pem
# Compare with the registered thumbprint (by registration ID or thumbprint prefix)
mantisctl cert show "agent-registration-id"
# If different, certificate was regenerated

Solution:

Terminal window
# Swap the certificate in place from the agent host.
ssh agent-server 'tarsus rotate-certificate \
--new-cert /etc/mantis/certs/tarsus-cert.pem \
--new-key /etc/mantis/certs/tarsus-key.pem \
--reason "thumbprint drift"'
# Only if the agent cannot reach Mandible: revoke and pre-register instead. That
# path discards the registration id, the approval provenance and the rotation
# history, and returns the agent to the pending queue where approval is required.

Problem: Agent rejected with “certificate expired”

Solution:

Terminal window
# On agent: regenerate certificate using OpenSSL
openssl req -new -x509 -nodes \
-keyout /etc/mantis/certs/tarsus-key.pem \
-out /etc/mantis/certs/tarsus-cert.pem \
-days 365 \
-subj "/CN=$(hostname)/O=Mantis Agent"
# Get new thumbprint
mantisctl cert thumbprint /etc/mantis/certs/tarsus-cert.pem
# Swap the certificate in place, then restart the agent.
tarsus rotate-certificate \
--new-cert /etc/mantis/certs/tarsus-cert.pem \
--new-key /etc/mantis/certs/tarsus-key.pem \
--reason "certificate renewal"
systemctl restart tarsus@<instance>
ErrorCauseSolution
no certificate presentedTLS config wrongCheck cert_path path
thumbprint not foundNot registeredPre-register or approve
registration pendingAwaiting approvalApprove in Mantis
registration revokedAgent was revokedRe-register
certificate expiredCert past validityRegenerate certificate

When ready to migrate from thumbprint to CA-signed mode:

  1. Deploy CA infrastructure (see CA-Signed Mode)
  2. Issue CA-signed certificates to agents
  3. Update Thorax configuration to auth_mode = "ca_signed"
  4. Agents re-register with new certificates

Pre-registration is more secure than post-connection approval:

  • Reduces window of vulnerability
  • Ensures only expected agents connect
  • Creates audit trail before deployment
Terminal window
# Good: descriptive names
mantisctl cert pre-register --name "web-prod-01" --thumbprint "..."
mantisctl cert pre-register --name "api-staging-02" --thumbprint "..."
# Bad: generic names
mantisctl cert pre-register --name "server1" --thumbprint "..."
mantisctl cert pre-register --name "agent" --thumbprint "..."

Maintain records of:

  • Who requested the registration
  • Purpose of the agent
  • Expected thumbprint value
  • Verification method used
Terminal window
# List all registrations
mantisctl cert list
# Review pending approvals
mantisctl cert list --status pending