Add the service-to-service module
This commit is contained in:
90
service-to-service/docs/06-mtls.md
Normal file
90
service-to-service/docs/06-mtls.md
Normal 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)*
|
||||
Reference in New Issue
Block a user