CA-Signed Mode
CA-Signed Mode
Section titled “CA-Signed Mode”CA-signed mode uses a Certificate Authority to issue and validate client certificates for Tarsus agents.
Overview
Section titled “Overview”How CA-Signed Mode Works
Section titled “How CA-Signed Mode Works”Authentication Flow
Section titled “Authentication Flow”When to Use CA-Signed Mode
Section titled “When to Use CA-Signed Mode”Ideal Use Cases
Section titled “Ideal Use Cases”| Scenario | Why CA-Signed Mode |
|---|---|
| Enterprise environments | Centralized certificate management |
| Large deployments | Scalable agent onboarding |
| Compliance requirements | PKI audit trail |
| Existing PKI | Reuse existing infrastructure |
| Automated provisioning | Certificate lifecycle management |
Comparison with Thumbprint Mode
Section titled “Comparison with Thumbprint Mode”| Aspect | Thumbprint | CA-Signed |
|---|---|---|
| Certificate issuance | Self-signed per agent | Central CA |
| Trust model | Individual trust | Hierarchical trust |
| Onboarding | Manual per agent | Automated possible |
| Revocation | Per-registration | Per-registration, or CRL |
| Complexity | Lower | Higher |
| Scalability | Limited | Excellent |
CA Infrastructure
Section titled “CA Infrastructure”CA Options
Section titled “CA Options”| Option | Description | Complexity |
|---|---|---|
| Self-managed CA | Create your own CA | Medium |
| HashiCorp Vault | Vault PKI secrets engine | Medium |
| AWS Private CA | AWS Certificate Manager PCA | Low |
| Step CA | Open source ACME CA | Medium |
| Enterprise CA | Active Directory CS | High |
Self-Managed CA Setup
Section titled “Self-Managed CA Setup”Use the shipped tooling first. mantisctl cert generates every certificate
Mantis needs with the correct EKUs and SANs — hand-rolled openssl is the source
of most cert misconfigurations (wrong EKUs, missing SANs, wrong thumbprints) and
should be a last resort for air-gapped hosts without mantisctl.
# Recommended: generate the CA (CN defaults to "Mantis Development CA",# --days 3650, --output-dir ./certs)mantisctl cert generate-ca --output-dir /etc/mantis/certs
# Server identity (Thorax listener + Mandible internal gRPC) — --sans is REQUIRED:mantisctl cert generate-server --name server \ --sans thorax --sans mandible --sans <hosts...> --output-dir /etc/mantis/certs
# Client and the CN-"mandible" dispatch identity:mantisctl cert generate-client --name client --output-dir /etc/mantis/certsmantisctl cert generate-client --name dispatch --cn mandible --output-dir /etc/mantis/certsThe generated CA carries basicConstraints: CA:TRUE, pathlen:1 — it may sign one
level of intermediate, and that intermediate signs only leaf certificates. A CA
you bring yourself needs at least the same depth; an intermediate cannot sign a
further CA.
mantisctl creates every private key readable only by its owner, so keys it
generates need no follow-up chmod. The permissions below apply to certificates
you install by hand.
Every certificate the CA signs expires no later than the CA itself. A request for a longer validity is shortened to the CA’s own expiry rather than issuing a certificate that stops verifying while still presenting itself as valid.
Other helpers: mantisctl cert thumbprint <path>, cert scan-expiry,
cert list-expiring, cert acme; and on agents, tarsus rotate-certificate,
tarsus cert-import / cert-export.
Bringing the CA under Mandible
Section titled “Bringing the CA under Mandible”Mandible signs every certificate this mode issues, so it needs the root’s private key. Thorax forwards an agent’s signing request to Mandible rather than signing it, which is what keeps CA key material off the Thorax fleet: a compromised Thorax yields no ability to mint identities.
Until a root is stored, enrollment fails with
No certificate authority is configured. Set [certificate_authority] in
mandible.toml and it imports the CA on startup, or import it explicitly:
mantisctl cert import-ca \ --cert /etc/mantis/certs/ca-cert.pem \ --key /etc/mantis/certs/ca-key.pemEither way the private key is encrypted at rest, bound to the CA record. The import is refused if the key does not match the certificate, or if a platform root is already stored. Certificates the CA issued before the import keep verifying — the CA itself is unchanged.
Per-tenant certificate authorities
Section titled “Per-tenant certificate authorities”A tenant may hold its own signing CA, issued as an intermediate beneath the platform root. This is optional: a deployment with none has the root sign every agent certificate directly, which is the default.
# Issue a tenant's CA (platform administrators only)curl -X POST -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' -d '{"validity_days": 825}' \ "$MANTIS_URL/api/v1/tenants/<tenant-id>/certificate-authority"
# Describe itcurl -H "Authorization: Bearer $TOKEN" \ "$MANTIS_URL/api/v1/tenants/<tenant-id>/certificate-authority"The intermediate carries pathlen:0, so it signs agent certificates and never
another CA, and its expiry is clamped to the root’s. Thorax’s trust store is
unaffected: every intermediate chains to the root it already trusts.
Infrastructure certificates — the mandible, thorax, client and server
common names — must be signed by the platform root directly. A certificate
bearing one of those names signed by a tenant intermediate is refused, so a
tenant CA cannot mint a control-plane identity.
Air-gapped fallback: hand-rolled openssl
Section titled “Air-gapped fallback: hand-rolled openssl”Only if mantisctl is unavailable. You must set the SANs and EKUs yourself —
rustls/webpki matches on SAN (no CN fallback) and enforces the EKU.
# Create CA directory structuremkdir -p /etc/mantis/ca/{certs,crl,newcerts,private}chmod 700 /etc/mantis/ca/privatetouch /etc/mantis/ca/index.txtecho 1000 > /etc/mantis/ca/serial
# Generate CA private keyopenssl genrsa -aes256 \ -out /etc/mantis/ca/private/ca.key 4096chmod 400 /etc/mantis/ca/private/ca.key
# Generate CA certificateopenssl req -new -x509 \ -key /etc/mantis/ca/private/ca.key \ -out /etc/mantis/ca/certs/ca.crt \ -days 3650 \ -subj "/CN=Mantis Internal CA/O=Your Organization/C=US"CA Configuration File
Section titled “CA Configuration File”Create /etc/mantis/ca/openssl.cnf:
[ ca ]default_ca = CA_default
[ CA_default ]dir = /etc/mantis/cacerts = $dir/certscrl_dir = $dir/crlnew_certs_dir = $dir/newcertsdatabase = $dir/index.txtserial = $dir/serialprivate_key = $dir/private/ca.keycertificate = $dir/certs/ca.crtdefault_days = 365default_md = sha256policy = policy_loose
[ policy_loose ]countryName = optionalstateOrProvinceName = optionallocalityName = optionalorganizationName = optionalorganizationalUnitName = optionalcommonName = supplied
[ agent_cert ]basicConstraints = CA:FALSEkeyUsage = digitalSignature, keyEnciphermentextendedKeyUsage = clientAuth, serverAuthsubjectKeyIdentifier = hashauthorityKeyIdentifier = keyid,issuersubjectAltName = DNS:web-prod-01.example.comserverAuth and the SAN are not optional for listen-mode agents: Thorax dials
them, so the agent presents this certificate as a server and rustls verifies the
hostname against the SAN. A clientAuth-only certificate fails with
UnsupportedCertificate the first time Thorax connects. Poll-mode-only agents
can drop serverAuth and the SAN, but issuing both keeps a single template
usable for either mode.
Thorax Configuration
Section titled “Thorax Configuration”Enable CA-Signed Mode
Section titled “Enable CA-Signed Mode”[tls]auth_mode = "ca_signed"
# Server certificatecert_path = "/etc/mantis/certs/server-cert.pem"key_path = "/etc/mantis/certs/server-key.pem"
# CA certificate for validating clientsca_cert_path = "/etc/mantis/ca/certs/ca.crt"Environment Variables
Section titled “Environment Variables”export MANTIS_TLS_AUTH_MODE=ca_signedexport MANTIS_TLS_CERT_PATH=/etc/mantis/certs/server-cert.pemexport MANTIS_TLS_KEY_PATH=/etc/mantis/certs/server-key.pemexport MANTIS_TLS_CA_CERT_PATH=/etc/mantis/ca/certs/ca.crtAgent Approval
Section titled “Agent Approval”Agents presenting a certificate the CA did not sign fall back to thumbprint
validation and do follow the approval path. To approve those automatically during
onboarding, issue each one a single-use registration token (configured via
TARSUS__REGISTRATION__TOKEN). Otherwise they land in the pending
queue for manual approval.
Issuing Client Certificates
Section titled “Issuing Client Certificates”Issuing with mantisctl
Section titled “Issuing with mantisctl”mantisctl issues agent certificates with the extensions Mantis requires. Prefer
it over hand-rolled OpenSSL:
# On the CA host, with ca-cert.pem and ca-key.pem in ./certsmantisctl cert generate-server \ --name web-prod-01 \ --sans web-prod-01.example.com \ --output-dir ./certsAgent certificates need both clientAuth and serverAuth, because Thorax dials
the agent in listen mode, plus a SAN matching the hostname Thorax will dial.
generate-server sets all three; --sans is required for exactly this reason.
An agent used only in poll mode can use generate-client instead.
Install on the agent:
# Paths must match tarsus.toml — /etc/mantis/certs, not /etc/mantisscp ./certs/web-prod-01-cert.pem agent-server:/etc/mantis/certs/tarsus-cert.pemscp ./certs/web-prod-01-key.pem agent-server:/etc/mantis/certs/tarsus-key.pemscp ./certs/ca-cert.pem agent-server:/etc/mantis/certs/ca-cert.pem
ssh agent-server 'systemctl restart tarsus@<instance>'Automated Certificate Issuance
Section titled “Automated Certificate Issuance”Using HashiCorp Vault:
# Enable PKI secrets enginevault secrets enable pkivault secrets tune -max-lease-ttl=87600h pki
# Generate root CAvault write pki/root/generate/internal \ common_name="Mantis CA" \ ttl=87600h
# Create role for agent certificatesvault write pki/roles/mantis-agent \ allowed_domains="mantis.local" \ allow_subdomains=true \ max_ttl=8760h \ key_type=rsa \ key_bits=2048
# Issue certificate for agentvault write pki/issue/mantis-agent \ common_name="web-prod-01.mantis.local" \ ttl=8760hCertificate Templates
Section titled “Certificate Templates”Standard agent certificate template:
| Field | Value |
|---|---|
| CN | Agent hostname or identifier |
| O | Team or tenant name |
| Key Usage | Digital Signature, Key Encipherment |
| Extended Key Usage | Client Authentication, Server Authentication |
| Subject Alt Name | DNS name Thorax dials (listen mode) |
| Validity | 365 days |
Reserved CNs are rejected at enrollment: mandible (and mandible.<region>) is
Mandible’s dispatch identity, and thorax, client and server are reserved as
well. Pick agent CNs that do not collide with these.
Tarsus Configuration
Section titled “Tarsus Configuration”Client Certificate Setup
Section titled “Client Certificate Setup”[tls]cert_path = "/etc/mantis/certs/tarsus-cert.pem"key_path = "/etc/mantis/certs/tarsus-key.pem"
# CA certificate for verifying Thoraxca_cert_path = "/etc/mantis/certs/ca-cert.pem"Certificate Installation
Section titled “Certificate Installation”# Install certificate filescp agent.crt /etc/mantis/certs/tarsus-cert.pemcp agent.key /etc/mantis/certs/tarsus-key.pemcp ca.crt /etc/mantis/certs/ca-cert.pem
# Set permissionschmod 644 /etc/mantis/certs/tarsus-cert.pem /etc/mantis/certs/ca-cert.pemchmod 600 /etc/mantis/certs/tarsus-key.pemchown root:root /etc/mantis/certs/*.pem /etc/mantis/certs/ca-cert.pemCertificate Lifecycle
Section titled “Certificate Lifecycle”Renewal Process
Section titled “Renewal Process”Automated Renewal
Section titled “Automated Renewal”Using cert-manager (Kubernetes):
apiVersion: cert-manager.io/v1kind: Certificatemetadata: name: tarsus-clientspec: secretName: tarsus-client-tls duration: 8760h # 1 year renewBefore: 720h # 30 days before issuerRef: name: mantis-ca kind: ClusterIssuer commonName: web-prod-01 usages: - client auth - digital signatureRevocation
Section titled “Revocation”Certificate revocation options:
| Method | Applies to | Takes effect |
|---|---|---|
| Mantis revocation | Both TLS modes | Within 60s |
| CRL | CaSigned mode, if crl_path is set | Next CRL reload |
Revoking an agent
Section titled “Revoking an agent”mantisctl cert revoke works in both modes, including for CA-signed agents:
mantisctl cert revoke "<REGISTRATION_UUID>" \ --reason "Certificate compromised"Thorax re-checks revocation on every gRPC call and caches the answer for 60 seconds, so a revoked agent stops being served within that window. The check fails closed: if Mandible is unreachable, Thorax rejects every call rather than assume the agent is still valid.
Revocation is permanent. A revoked thumbprint can never be registered again, so recovering an agent revoked by mistake means issuing it a new certificate.
Revoking at the CA
Section titled “Revoking at the CA”For agents you want cut off at the TLS layer — before the application sees them at all — point Thorax at a CRL:
[tls]auth_mode = "ca_signed"ca_cert_path = "/etc/mantis/ca/certs/ca.crt"crl_path = "/etc/mantis/ca/crl" # a PEM CRL, or a directory of themOnly the agent (end-entity) certificate’s revocation status is checked. Requiring a CRL for every issuer in the chain would reject all clients whenever an intermediate’s CRL was missing, which is a worse failure than the one this guards against.
Security Considerations
Section titled “Security Considerations”CA Security
Section titled “CA Security”| Aspect | Recommendation |
|---|---|
| CA key storage | HSM or encrypted storage |
| CA access | Limited, audited access |
| CA backup | Secure offline backup |
| CA validity | 10+ years |
Defense in Depth
Section titled “Defense in Depth”CA-signed mode provides layered security:
┌─────────────────────────────────────────────────────────────┐│ Security Layers │├─────────────────────────────────────────────────────────────┤│ ││ Layer 1: TLS encryption ││ All communication encrypted ││ ││ Layer 2: CA validation ││ Only CA-signed certificates accepted ││ ││ Layer 3: Revocation check ││ Re-checked per gRPC call; fails closed ││ │└─────────────────────────────────────────────────────────────┘Thumbprint verification and registration status are not layers in this mode — a CA-signed certificate bypasses both. They apply only to agents whose certificate the CA did not sign.
Layer 3 fails closed: if Mandible is unreachable, Thorax treats every
certificate as revoked and rejects all calls. A Mandible outage therefore
presents as fleet-wide certificate has been revoked errors rather than as a
connectivity error.
Certificate Subject Validation
Section titled “Certificate Subject Validation”In CA-signed mode, trust is established by the CA signature: any certificate
signed by the configured CA is accepted at the TLS layer, and the SHA-256
thumbprint is checked at the application layer. There are no required_org /
required_ou subject-field filters — restrict which certificates are issued by
controlling your CA’s issuance policy.
Multi-CA Setup
Section titled “Multi-CA Setup”Intermediate CA
Section titled “Intermediate CA”For large deployments, use intermediate CAs:
Root CA (offline) └── Intermediate CA (online) ├── Agent certificates └── Server certificatesMultiple CAs
Section titled “Multiple CAs”Trust multiple CAs for different environments:
[tls]# Concatenate multiple CA certificates into a single fileca_cert_path = "/etc/mantis/certs/combined-ca.crt"Or concatenate CA certificates:
cat production-ca.crt staging-ca.crt > combined-ca.crtTroubleshooting
Section titled “Troubleshooting”Certificate Not Trusted
Section titled “Certificate Not Trusted”Problem: certificate verify failed: unable to get local issuer certificate
Diagnosis:
# Verify certificate chainopenssl verify -CAfile /etc/mantis/ca/certs/ca.crt \ /etc/mantis/certs/tarsus-cert.pem
# Check CA file on Thoraxcat /etc/mantis/certs/ca.crtSolution:
# Ensure CA certificate matchesdiff /etc/mantis/ca/certs/ca.crt /path/used/by/thorax/ca.crt
# If intermediate CA, include full chaincat intermediate.crt root.crt > ca-chain.crtThumbprint Mismatch After Renewal
Section titled “Thumbprint Mismatch After Renewal”Problem: Agent rejected after certificate renewal
Solution:
# In CA-signed mode trust derives from the CA signature, so a renewed cert from the same# CA is accepted without re-registration. If a registration tracks a specific thumbprint,# re-register the new thumbprint (revoke old, pre-register new):mantisctl cert revoke "$OLD_REGISTRATION_ID" --reason "certificate renewal"mantisctl cert pre-register --name "web-prod-01" --thumbprint "$(mantisctl cert thumbprint /etc/mantis/certs/tarsus-cert.pem)"Agent Stuck in Pending
Section titled “Agent Stuck in Pending”Problem: CA-signed clients register but stay pending approval
Check:
# Confirm the agent was given a registration token (token-based auto-approval)journalctl -u tarsus@<instance> | grep -i "registration"
# Otherwise approve it manuallymantisctl cert approve "$REGISTRATION_ID"A CA-signed agent will not appear in the pending queue at all — it is approved on
first connection. Only agents presenting a certificate the CA did not sign reach
that queue; approve those from the queue, or issue them a single-use registration
token (TARSUS__REGISTRATION__TOKEN).
Common Errors
Section titled “Common Errors”| Error | Cause | Solution |
|---|---|---|
certificate signed by unknown authority | CA not trusted | Add CA to ca_cert_path |
certificate has expired | Cert expired | Renew certificate |
certificate is not valid for any names | Wrong CN | Check certificate CN |
registration pending | Awaiting approval | Use a token or manually approve |
Migration from Thumbprint Mode
Section titled “Migration from Thumbprint Mode”Migration Steps
Section titled “Migration Steps”- Deploy CA infrastructure
- Configure Thorax for CA-signed mode
- Issue CA-signed certificates to agents
- Update agent configurations
- Agents re-connect with new certificates
Cutover Migration
Section titled “Cutover Migration”auth_mode accepts only thumbprint or ca_signed — there is no combined
“both” mode. Migration is a cutover: keep Thorax in thumbprint mode until every
agent holds a CA-signed certificate, then switch Thorax to ca_signed and
restart.
[tls]# After all agents have CA-signed certs, switch to ca_signed and restart Thoraxauth_mode = "ca_signed"ca_cert_path = "/etc/mantis/ca/certs/ca.crt"Rollback Plan
Section titled “Rollback Plan”If issues occur during migration:
# Revert to thumbprint mode[tls]auth_mode = "thumbprint"Existing thumbprint registrations remain valid.
Best Practices
Section titled “Best Practices”1. Protect the CA
Section titled “1. Protect the CA”- Store CA private key in HSM or encrypted storage
- Limit CA access to authorized personnel
- Maintain offline backups
- Audit all certificate issuance
2. Use Short-Lived Certificates
Section titled “2. Use Short-Lived Certificates”| Certificate Type | Recommended Validity |
|---|---|
| Root CA | 10-20 years |
| Intermediate CA | 5-10 years |
| Agent certificates | 1 year |
3. Automate Certificate Management
Section titled “3. Automate Certificate Management”Use tools like:
- cert-manager (Kubernetes)
- HashiCorp Vault
- Step CA
- ACME protocol
4. Monitor Expiration
Section titled “4. Monitor Expiration”# List client certificates expiring within the threshold (default 30 days)curl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.example.com/api/v1/certificates/expiring?days=30"5. Plan for CA Renewal
Section titled “5. Plan for CA Renewal”- Document CA renewal procedure
- Test CA renewal in staging
- Plan for certificate re-issuance
- Communicate timeline to teams
Next Steps
Section titled “Next Steps”- mTLS Authentication - mTLS overview
- Thumbprint Mode - Alternative authentication
- Key Rotation - Certificate renewal procedures
