Edge client certificates — Versola Docs
VersolaVersola/docs
Pre-1.00.7.1versola.kzGitHub

Edge client certificates

How edge gets the client certificate it presents to the mutual-TLS listener of auth, in three modes (issue, enroll, cert-sync): timings, file locations, CA backends and failure modes.

When a client authenticates with tls_client_auth, edge acts as that client, and auth recognises it by an X.509 certificate presented in the TLS handshake. This page explains the three ways that certificate reaches edge, how it is renewed and what breaks. Read it if you run native apps or an edge-fronted web client with certificate authentication. You need central and auth configured with a mutual-TLS listener (Sender-constrained tokens).

The three modes

You choose the mode per client when you register it. The console wizard chooses for you: a Mobile or desktop app is registered with enrollEdgeClientCertificate, a Web app with issueEdgeClientCertificate.

IssueEnrollcert-sync
Client fieldissueEdgeClientCertificate: trueenrollEdgeClientCertificate: trueedgeClientCertificate supplied by you
Who generates the keycentraleach edge replica, in memorywhatever issues the certificate
Where the key is storedIn central, encrypted, and synced to edge encrypted for its RSA keyOnly in the process of the edge replicaIn central, as in the previous mode
Certificate per replicaOne, sharedOne per replica, same subjectOne, shared
Who renewscentral, renew-before before expiryedge, with a third of the lifetime leftWhatever issues it, and cert-sync copies the new one
Client typesWeb or nativeNative only (applicationType: native)Web or native
StatusKept for existing clientsPreferred for new native clientsFor a CA you operate yourself

The first two are mutually exclusive with each other and with a supplied certificate. Why enroll is native only: a web client’s tokens are bound to its certificate (RFC 8705), so every replica must present the same one, and replicas that each enroll for their own certificate would not. A native app’s tokens are bound to the device DPoP key, so any replica’s certificate works.

All three need the CA behind the certificate to be trusted by auth: it must be in the mutual-tls.trusted-certificates file of auth.

Enrollment, step by step

  1. You register the client with enrollEdgeClientCertificate: true, authMethod: tls_client_auth, applicationType: native and, optionally, mtlsAuth. Without mtlsAuth the client is recognised by the subject CN=<client>,OU=<tenant>,O=Versola. central stores only the fact that the client is enrolled.
  2. edge loads clients from central every configuration-cache-refresh-interval. For an enrolled client the record carries the subject the certificate must name, and no certificate.
  3. The replica generates an EC P-256 key pair in memory and a PKCS#10 request for exactly that subject.
  4. It sends the request to POST {central.url}/configuration/clients/edge-certificate/sign, authenticated with the signed token that edge uses for all sync calls. Only a registered edge can call this endpoint, and it is not part of the admin API.
  5. central checks that the client is enrolled, that this edge serves the client’s tenant, and that the request names exactly the registered subject and alternative names. It asks the CA to sign (the lifetime is client-certificates.validity), checks that the returned certificate carries what mtlsAuth expects, records the serial, time and edge in the table edge_certificate_enrollment, and returns the chain.
  6. edge writes the certificate and key to files, because the TLS library takes file paths, and presents them on calls to auth.

Rotation is part of the same loop. On each client load, a certificate with a third of its lifetime or less left is replaced; for the default 14 days that is about 4 days 16 hours. If the CA is unreachable during the window, edge keeps the current certificate until it expires and logs edge_certificate_renewal_failed. A restart enrolls again, which costs one signing per client.

Timings

SettingWhereDefault
Certificate validitycentral, client-certificates.validity14 days
Renew an issued certificate when less than this remainscentral, client-certificates.renew-before4 days
How often central checks issued certificatescentral, client-certificates.check-interval1 hour
Renew an enrolled certificateedge, fixedat one third of the lifetime left
When edge notices a changeedge, configuration-cache-refresh-intervalgenerated: 5 minutes

central renews only certificates it issued. If you supply a certificate through a client update, central stops managing that client’s certificate. POST /configuration/clients/edge-certificate/renew?clientId=<id> renews one issued certificate immediately.

Where files live

edge never keeps the key or certificate in the database. To present them it writes them under a temporary directory, versola-client-certificates* in the system temp directory, created with mode 700. Each file is created with mode 600 and named after the SHA-256 of its content, so a renewed certificate lands next to the old one. The directory is removed on graceful shutdown. A crash leaves it behind: keep the temp directory off shared or persistent volumes, and restrict who can read the container filesystem.

Central CA backends

central does not hold a CA key. It delegates signing to the backend named in the client-certificates block of its configuration. Exactly one of step-ca and cert-manager is set.

client-certificates {
  validity     = "14 days"
  renew-before = "4 days"
  step-ca {
    url              = "https://step-ca.example.com:9000"
    root-certificate = "/app/ca/root_ca.crt"
    provisioner      = "central"
    provisioner-key  = "/app/ca/provisioner.json"
  }
}
BackendUsed byHow it signs
step-caCompose targets (local Docker, vps)One-time token from a JWK provisioner named central, with a 14-day maximum and a template that keeps the request subject and limits the certificate to client authentication. The provisioner key is the only secret central holds
cert-managerKubernetesA CertificateRequest created with the pod’s service account, which may only create, get and delete those in its namespace. Keys: issuer-name, plus issuer-kind (default ClusterIssuer), issuer-group (default cert-manager.io) and namespace (default: the pod’s)
client-certificate-authorityLocal development onlycentral holds the CA certificate and its key and signs itself. Used when no client-certificates block names a CA. Avoid it elsewhere

A step-ca created before the subject-keeping template existed rewrites the subject to the bare CN; central then refuses a longer subject_dn registration and says why. Without any CA, central refuses a registration that asks for an issued certificate.

cert-sync

For a CA you run yourself, or cert-manager issuing through the chart’s pki.clients, the cert-sync command in the versola-tools image copies certificates into central. It reads $CERT_SYNC_DIR/<client-id>/tls.crt and tls.key (unencrypted PKCS#8) and sends PUT /configuration/clients with only edgeClientCertificate whenever the content changes.

VariableMeaning
CENTRAL_URLCentral API base URL
CENTRAL_SECRET, CENTRAL_SECRET_FILE, CENTRAL_RESOURCE_SECRETThe central resource secret used for Basic authentication
CERT_SYNC_INTERVAL_SECONDSSeconds between passes. 0 runs one pass and exits

It is off by default.

What to monitor

  • edge logs. The request log line carries an error key. edge_certificate_unavailable means a client was dropped because no certificate could be obtained; edge_certificate_renewal_failed means a renewal failed and the old certificate is still in use, with its expiry in the description.
  • central logs. Each signing logs signed certificate <serial> for client '<id>' at edge '<edge>', valid until <time>. Each issuance logs the serial and expiry too. Alert on a client whose last signing is older than two thirds of the validity.
  • Tables. edge_certificate_enrollment (enrolled clients, last signing) and client_certificate_issuance (issued certificates: serial and expiry, no key material) are the audit trail.
  • Symptoms. /native/* answering 404 for a client that exists, or 401 invalid_client from auth, usually means edge holds no valid certificate for it.

Failure modes

SymptomCauseFix
/native/* returns 404 for the clientedge has no certificate for it: enrollment failed, the client is not in this edge’s tenants, or no native blockCheck the edge log for edge_certificate_unavailable and the answer of central
central answers the signing request with a refusalThe client is not enrolled, is not served by this edge, or the request does not name the registered subjectCheck the registration. Retrying does not help
central refuses the signed resultThe CA’s certificate lacks the subject type and value from mtlsAuth (for example a CA template that rewrites the subject)Fix the CA template or the mtlsAuth value
Registration refused: no CAissueEdgeClientCertificate without a client-certificates or client-certificate-authority blockConfigure a backend
auth returns invalid_clientThe issuing CA is not in auth’s mutual-tls.trusted-certificatesAdd the CA to that file and restart auth. During a CA change keep both CAs in the file
Calls fail after the CA expiredThe CA certificate itself ran out, and issuance reports itReplace the CA
edge cannot reach auth’s listenerPin mismatch: native.trusted-certificates does not match the leaf certificate auth presentsPinned certificates

Next steps