1
0

Add the service-to-service module

This commit is contained in:
2026-08-28 10:02:47 +05:30
parent cad813e1ae
commit 0fceb2cd4e
37 changed files with 2566 additions and 4 deletions

View File

@@ -0,0 +1,90 @@
# 6. mTLS: in the mesh or in the application
*Prev: [5. The gateway](05-the-gateway.md) · Next: [7. Choosing](07-choosing.md)*
Run `./scripts/certs.sh`, then start `MtlsApplication` on 8443. The certificates it generates
are the point: `edge.crt` and `rogue.crt` have **identical subjects** and different issuers.
```
subject=CN = edge-service, OU = payments issuer=CN = Internal Mesh CA
subject=CN = edge-service, OU = payments issuer=CN = Some Other CA
```
Identity under mTLS is not the subject. It is the subject plus the fact that a CA in the trust
store vouched for it.
## In-application mTLS
```yaml
server:
ssl:
bundle: server
client-auth: need
spring:
ssl:
bundle:
pem:
server:
keystore: { certificate: "file:...server.crt", private-key: "file:...server.key" }
truststore: { certificate: "file:...internal-ca.crt" }
```
```java
http.x509((x509) -> x509
.subjectPrincipalRegex("CN=([^,]*)(?:,|$)")
.userDetailsService(certificateUsers()));
```
A good certificate produces a `PreAuthenticatedAuthenticationToken` with the CN as the
principal, and — in Spring Security 7 — a `FACTOR_X509` authority alongside the roles, the
sibling of the `FACTOR_BEARER` you get from a JWT and the `FACTOR_PASSWORD` you get from Basic.
Any assertion using `containsExactly` on authorities will fail on it.
## Three things the transcript shows that a diagram does not
[`docs/output/06-mtls.txt`](output/06-mtls.txt):
**The rogue certificate produces no HTTP status at all.** `curl` exits 56; there is no response
line, no 401, no 403, and nothing in the application log at `INFO`. The handshake failed. Your
application-level metrics will show nothing, because from the application's point of view
nothing happened. Debugging this means `-Djavax.net.debug=ssl:handshake` or the load balancer's
own logs.
**`client-auth: need` is a property of the connector, not of a path.** `/mtls/trusted-header` is
`permitAll()` and it fails exactly the same way without a certificate. You cannot expose a
public health endpoint on an mTLS-only connector; it needs a second connector, or `want`
instead of `need` plus an explicit authorization rule that treats an absent certificate as
anonymous.
**Header-based identity is verified by nothing.** The last call in the transcript presents a
valid certificate for `edge-service` and a header claiming to be `payments-service`, and the
endpoint reports `payments-service`.
## Mesh mTLS
A service mesh terminates TLS in a sidecar. The application receives plain HTTP on localhost and
the peer identity arrives as a header — `X-Forwarded-Client-Cert` in Envoy, carrying the SPIFFE
URI SAN. The trade:
| | Mesh | In-application |
|---|---|---|
| Certificate lifecycle | Handled, rotated automatically | Yours: issuance, rotation, expiry alerts |
| Application code | None | An SSL bundle plus `x509(..)` |
| Identity in the app | A header | A verified `X509Certificate` |
| Works with cert-bound tokens (RFC 8705) | **No** | Yes |
| Failure mode | Anything that bypasses the sidecar can spoof the header | Handshake failure, no HTTP status |
The row that decides it for a security-sensitive service is the RFC 8705 one. Certificate-bound
access tokens — the `cnf` / `x5t#S256` claim, which `X509CertificateThumbprintValidator`
already checks by default (chapter 4) — bind a token to the TLS connection it was issued for, so
a stolen token is useless without the private key. That binding requires the application to see
the client certificate. Terminate mTLS in a sidecar and the strongest defence available against
token theft is off the table.
If you take mesh identity from a header, the header must be **stripped at ingress** on every
path into the pod, and the pod must not be reachable except through the proxy. Both are
infrastructure guarantees that no amount of application code can verify — which is exactly why
`/mtls/trusted-header` answers `"verifiedBy": "nothing. This endpoint believes a header."`.
---
*Prev: [5. The gateway](05-the-gateway.md) · Next: [7. Choosing](07-choosing.md)*