Ein TLS-Zertifikat auf dem Server ist nett – aber wenn jeder Client ungefragt reinspazieren darf, ist das eher Türsteher mit Augenklappe. Bei mTLS drehen wir den Spieß ein Stück weiter: Nicht nur der Server beweist seine Identität, auch der Client muss ein gültiges Zertifikat vorzeigen. Service A spricht also nur mit Service B, wenn beide sagen können: „Ja, ich bin wirklich ich.“ Klingt nach Paranoia. Ist aber produktive Paranoia.
Wir haben das im Test mit zwei kleinen Services nachgebaut: `orders-service` ruft `payment-service` auf. Ohne mTLS reicht ein gültiger DNS-Name und TLS. Mit mTLS braucht `orders-service` zusätzlich ein Client-Zertifikat, das vom `payment-service` akzeptiert wird.
Erstmal brauchen wir eine eigene kleine CA. Nicht für die große PKI-Weltherrschaft, sondern für interne Service-Zertifikate:
```bash
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-out ca.crt -subj "/CN=PLUTEX Internal Service CA"
```
Dann bekommt der Server sein Zertifikat:
```bash
openssl genrsa -out payment.key 2048
openssl req -new -key payment.key -out payment.csr \
-subj "/CN=payment-service.internal"
openssl x509 -req -in payment.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out payment.crt -days 365 -sha256
```
Und jetzt der wichtige Teil: der Client bekommt ebenfalls ein Zertifikat:
```bash
openssl genrsa -out orders.key 2048
openssl req -new -key orders.key -out orders.csr \
-subj "/CN=orders-service"
openssl x509 -req -in orders.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out orders.crt -days 365 -sha256
```
Auf Serverseite muss nun nicht nur TLS aktiv sein, sondern auch Client-Zertifikate müssen geprüft werden. Beispiel mit Nginx als Reverse Proxy vor dem `payment-service`:
```nginx
server {
listen 443 ssl;
server_name payment-service.internal;
ssl_certificate /etc/nginx/certs/payment.crt;
ssl_certificate_key /etc/nginx/certs/payment.key;
ssl_client_certificate /etc/nginx/certs/ca.crt;
ssl_verify_client on;
location / {
proxy_pass http://payment-backend:8080;
proxy_set_header X-Client-Cert $ssl_client_s_dn;
}
}
```
Der kleine Schalter mit großer Wirkung ist:
```nginx
ssl_verify_client on;
```
Ohne gültiges Client-Zertifikat gibt es dann keinen Kaffee, sondern einen TLS-Handshake-Abbruch. Sehr befriedigend.
Test ohne Client-Zertifikat:
```bash
curl https://payment-service.internal
```
Ergebnis: scheitert. Gut so.
Test mit Client-Zertifikat:
```bash
curl https://payment-service.internal \
--cert orders.crt \
--key orders.key \
--cacert ca.crt
```
Jetzt klappt der Request – vorausgesetzt, Zertifikat, Key und CA passen zusammen. Wenn nicht, bekommst du OpenSSL-Fehlertexte, die aussehen wie aus einem Dämonenbeschwörungsbuch. Klassiker.
Wichtig im Betrieb: Zertifikate brauchen Lebenszyklen. Ablaufdaten, Rotation, Sperrung, Automatisierung. Wer Client-Zertifikate manuell per SCP verteilt, baut sich früher oder später ein Museum für kaputte Deployments. Besser: cert-manager in Kubernetes, Vault PKI, SPIFFE/SPIRE oder eine interne CA mit automatisierter Ausgabe.
Auch wichtig: mTLS ersetzt keine Autorisierung. Nur weil `orders-service` echt ist, darf er nicht automatisch alles. Identität ist Schritt eins. Danach kommen Policies: Welcher Service darf welchen Endpoint aufrufen?
Unser Fazit nach dem Selbstversuch: mTLS ist nicht kompliziert, aber gnadenlos präzise. Falscher CN, falsche CA, abgelaufenes Zertifikat – zack, Funkstille. Genau das ist der Punkt. Service-zu-Service-Kommunikation sollte nicht auf „wird schon intern sein“ basieren. Intern ist kein Sicherheitskonzept. Intern ist nur ein Ort, an dem Angreifer nach dem ersten Treffer besonders gerne weitermachen.