On Kubernetes the Helm chart builds the certificates that mutual TLS needs with cert-manager and, optionally, trust-manager. This page explains the CA hierarchy, which listener trusts what, where each file is mounted and what to watch when you substitute your own issuer. For the chart in general see Kubernetes; for the client side of certificates see Client certificates and their renewal. The chart’s own reference is k8s/README.md in the product repository.
Prerequisites
pki.enabled is true by default, so cert-manager and trust-manager have to be installed before the chart: it renders their ClusterIssuer, Certificate and Bundle objects but installs neither. Opt out with pki.enabled: false.
One root, three intermediates
pki bootstraps a root CA (<release>-versola-root-ca) and below it three intermediate CAs, each a ClusterIssuer, one per role:
| Intermediate | Signs |
|---|---|
server | The listener certificates of auth and central |
edge | Edge identities: the URI names urn:versola:edge:<id> and urn:versola:edge-group:<group> |
client | Client certificates |
A certificate is good only where its role says, because a listener trusts an intermediate, never the root, which would admit every role everywhere.
| Listener | Trusts | Bundle (ConfigMap) |
|---|---|---|
auth handshake | Client CA and edge CA (an edge presents its identity to auth too) | <release>-versola-client-ca |
auth’s check of an edge’s name (mutual-tls.edge-trusted-certificates) | The edge CA alone: a certificate may carry urn:versola:edge:* only if it chains to it | <release>-versola-edge-ca |
central handshake | The edge CA alone, so a client certificate fails the handshake, not only an application check | <release>-versola-edge-ca |
Edges pin the leaf of the certificates of auth’s and central’s listeners. Without trust-manager (pki.trustBundle.enabled: false) the chart mounts each leaf Secret’s ca.crt instead, which is the root: everything connects, but nothing is split.
central signs the certificates it issues to edges with the edge issuer (EDGE_CERT_ISSUER_NAME) and to clients with the client issuer (CLIENT_CERT_ISSUER_NAME). Its role may create CertificateRequest objects in the release namespace and nothing else. Leaves and intermediates renew through cert-manager: leaves have duration: 2160h and renewBefore: 720h.
Files the pods mount
The chart generates no env.conf; point yours at these paths.
| Service | Mounted at | Use as |
|---|---|---|
auth | /app/mtls/server/tls.crt, tls.key | mutual-tls.certificate, private-key |
auth | /app/mtls/trust/ca.crt | mutual-tls.trusted-certificates (client and edge CA bundle) |
auth | /app/mtls/edge-trust/ca.crt | mutual-tls.edge-trusted-certificates (the edge CA alone) |
edge | /app/mtls/auth-pin/tls.crt | native.trusted-certificates (a leaf pin; edge gets the certificate, never the key) |
central | /app/central-mtls/server/tls.crt, tls.key | mutual-tls.certificate, private-key |
central | /app/central-mtls/trust/ca.crt | mutual-tls.trusted-certificates (the edge CA alone) |
edge | /app/central-mtls/client/tls.crt, tls.key | central.mutual-tls.certificate, private-key (the in-cluster edge’s identity) |
edge | /app/central-mtls/server-pin/tls.crt | central.mutual-tls.trusted-certificates (a leaf pin on central’s certificate) |
The services read these files once at startup, so a renewal needs a restart. Use Stakater Reloader (pki.reloader) or kubectl rollout restart after cert-manager renews. For zero-downtime rotation of auth’s listener certificate use the two-pin procedure (Edge configuration) instead of relying on automatic renewal. For a CA rotation add the incoming CA’s Secret to pki.trustBundle.sources (or edgeSources) next to the bootstrapped one, reissue what that CA signed, then remove the outgoing source.
Where EST reads its CA
EST hands out the client CA through cacerts, and that file must hold the client CA alone. Do not point est.ca-certificates at /app/mtls/trust/ca.crt: that is the client and edge bundle, and a client must not be told to trust the edge CA. With EST_ENABLED=true the chart mounts the client CA alone, from the <release>-client-only-ca bundle, and gen-env for the k8s target writes the path:
| Service | Mounted at | Use as |
|---|---|---|
auth | /app/mtls/client-trust/ca.crt | est.ca-certificates (the client CA alone) |
See the est block.
Bring your own issuer
pki.issuerRef points all roles at an issuer you already run, and pki.issuerRefs.{server,edge,client} points one role. A role with an issuer of your own bootstraps no CA, and trustBundle.sources or edgeSources must then name that CA.
Giving all roles one issuer makes the edge CA and the client CA the same CA. This is not recommended. The split exists so that a client certificate can never pass for an edge: auth trusts the edge CA to vouch that a call to /userinfo comes from an edge, and a CA that also signs client certificates can sign an edge name for a client. With one issuer for all roles the client-only and edge-only files are the same CA, so EST would tell a client to trust the CA that also issues edge identities: give the edge role its own issuer (pki.issuerRefs.edge) before you enable EST for partners. If you cannot avoid it, central still refuses urn:versola:edge: names in client certificates and auth logs a warning at startup, but the protection rests on that one check. See Sender-constrained tokens.
Next steps
- Kubernetes: the rest of the chart.
- Edge client certificates: how
edgegets its own certificates. - Automatic client certificate renewal (EST)