Skip to main content

Architecture

Before the configuration, the model. Three ideas make the rest of this book make sense: where the CA lives, what the trust chain looks like, and the fact that every internal connection uses two certificates, not one.

Where the CA lives

The certificate authority runs on an internal control-plane host, reachable only from inside the private network. It is never exposed publicly. Its only job is to answer certificate requests from hosts that have already proven they belong in the environment, and to hold the signing key that every host trusts. Because that key is the root of all internal trust, the CA host is treated as one of the most sensitive systems in the environment, on par with the identity provider and the password manager.

The trust chain

step-ca is initialized with a root certificate and an intermediate certificate. The root signs the intermediate; the intermediate signs the certificates issued to hosts. Every host in the environment is configured to trust the root. From then on, a certificate is trusted if it chains back to that root, root signed the intermediate, intermediate signed the host cert. This is why nginx is configured with ssl_verify_depth 2 later on: the chain is two links deep, and verification has to be allowed to walk both.

Trust is established once, out of band, by pinning the root's fingerprint when a host bootstraps (covered on the next page). After that, no host ever has to be told about individual certificates, it trusts anything the CA signs, and distrusts everything else.

The two-certificate model

This is the idea that trips people up, so it's worth stating plainly: a single mutually-authenticated connection involves two certificates, one presented by each end.

Take the most common internal path in this environment, the web application firewall (WAF) forwarding a user's request to a backend service:

  • The backend presents a server certificate identifying it as service.int.example. This proves to the WAF that it reached the right backend and not an impostor.
  • The WAF presents a client certificate identifying it as the WAF (waf-to-service). This proves to the backend that the caller is the authorized WAF and not some other host that happens to be on the network.

Each end verifies the other's certificate against the shared CA root. If either certificate is missing, expired, or not signed by the CA, the connection is refused. That mutual check is the whole point: the backend won't serve just anyone who can reach it, and the WAF won't forward to just any host claiming to be the backend.

So for a given backend host there are two certificates in play:

Certificate Held by Proves Type
service.int.example the backend host "I am the real backend" server auth
waf-to-service the WAF "I am the authorized caller" client auth

step-ca issues both, and the issuance policy restricts each to its purpose, a server-auth cert can't be used as a client-auth cert and vice versa. That restriction is deliberate least privilege applied to certificates: a certificate can do exactly one job.

The request path, end to end

Putting it together, a user request crosses two TLS boundaries:

  1. User to WAF, ordinary public TLS. The WAF holds a public certificate (from a public CA) for the name the user typed. This is the only place public TLS is involved, and it terminates here.
  2. WAF to backend, mutual TLS with private certificates. The WAF re-originates the request to the backend over a connection where both ends authenticate with CA-issued certificates.

Public TLS terminates at the edge; mutual TLS begins there and governs everything internal. A reader who wants the visual version of this can see it in the environment's architecture and certificate-lifecycle diagrams. The pages that follow build it from the ground up: first the CA, then the certificates, then the nginx configuration that enforces all of the above.