Deine Microservices teilen sich ein VPC, deshalb vertrauen sie einander. Dieses Vertrauen ist Ambient Authority: Die Berechtigung, einen Service aufzurufen, wird nicht durch kryptographischen Beweis erteilt, sondern durch die Netzwerktopologie. Ein Angreifer, der den Perimeter durchbricht, erbt das gesamte Vertrauen.

Services können durchaus ohne Ambient Authority authentifizieren. Die schwierigere Frage ist, warum deine Plattform es sich wie ein Verrat anfühlen lässt.

Wie Ambient Authority in der Produktion aussieht

Die meiste Service-zu-Service-”Authentifizierung” ist in Wahrheit nur Netzwerksegmentierung. Services leben in einem privaten Subnetz, hinter einem API Gateway oder innerhalb eines Kubernetes-Clusters mit einem Service Mesh. Die Annahme ist einfach: Wenn die Anfrage von einer internen IP kommt, ist sie legitim.

Dieses Modell bricht zusammen, wenn der Perimeter fällt. Ein kompromittierter Pod, laterale Bewegung nach einem Credentials-Leak oder eine Supply-Chain-Injection bringen den Angreifer in die Vertrauenszone. Ab diesem Zeitpunkt ist jeder Service erreichbar und die meisten verlangen keinen Identitätsnachweis. Der Angreifer muss keine weiteren Credentials stehlen. Er erbt einfach die Ambient Authority des Netzwerks.

Echte Authentifizierung zwischen Services bedeutet, dass jeder Aufrufer beweist, wer er ist, und jeder Aufgerufene diesen Beweis verifiziert. Das Netzwerk ist nur ein Transport. Es sollte kein Credential sein.

Wie capability-basierte Service-Authentifizierung funktioniert

Es gibt zwei praktische Ansätze: kryptographische Identität (jeder Service hat einen beweisbaren Namen) und Capability-Tokens (jede Anfrage trägt eine unverfälschbare, eingegrenzte Autorisierung).

Kryptographische Identität mit mTLS

Bei mutual TLS präsentieren sowohl der Client als auch der Server X.509-Zertifikate. Der Server verifiziert das Zertifikat des Clients, bevor er die Anfrage bearbeitet. Der Client verifiziert das Zertifikat des Servers, bevor er Daten sendet. Keine Seite vertraut der anderen wegen der IP-Adresse.

Die Infrastruktur-Herausforderung ist die Zertifikatsverteilung. SPIFFE und SPIRE lösen dies, indem sie jeder Workload eine kryptographische Identität zuweisen, die aus ihren Eigenschaften abgeleitet wird, nicht aus ihrem Standort. Ein Pod, der die payments-api ausführt, erhält eine SPIFFE ID wie spiffe://prod.example.com/payments-api. Das Zertifikat ist kurzlebig und wird automatisch rotiert.

So sieht das in Go aus:

package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "log"
    "net/http"
    "os"
)

// loadClientTLS returns a config that verifies the server AND presents a client cert.
func loadClientTLS(caCert, clientCert, clientKey []byte) *tls.Config {
    pool := x509.NewCertPool()
    pool.AppendCertsFromPEM(caCert)

    cert, err := tls.X509KeyPair(clientCert, clientKey)
    if err != nil {
        log.Fatalf("failed to load client cert: %v", err)
    }

    return &tls.Config{
        Certificates: []tls.Certificate{cert},
        RootCAs:      pool,
        // Do not skip verification based on IP or DNS name alone.
        InsecureSkipVerify: false,
    }
}

func main() {
    caCert, _ := os.ReadFile("ca.crt")
    clientCert, _ := os.ReadFile("service.crt")
    clientKey, _ := os.ReadFile("service.key")

    client := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: loadClientTLS(caCert, clientCert, clientKey),
        },
    }

    resp, err := client.Get("https://orders-api.internal:8443/orders")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(resp.Status)
}

Die kritische Zeile ist InsecureSkipVerify: false. Ohne sie bist du wieder bei Ambient Trust. Mit ihr verweigert der Client die Verbindung, es sei denn, der Server präsentiert ein gültiges, von der CA signiertes Zertifikat. Der Server macht dasselbe für den Client.

Capability-Tokens für eingegrenzte Autorisierung

Zertifikate beweisen Identität. Capabilities beweisen Autorisierung. In vielen Systemen willst du beides: beweisen, wer du bist, und dann beweisen, dass du dieses spezifische Ding tun darfst.

Ein Capability-Token bindet eine Identität, eine Aktion und eine Ressource in ein unverfälschbares Paket. Der Service mintet ihn. Der Service verifiziert ihn. Es gibt keinen zentralen Session-Store, den man abfragen müsste.

import hmac
import hashlib
import secrets
import time

SECRET = secrets.token_bytes(32)

def mint_service_capability(
    caller_id: str,
    service: str,
    action: str,
    resource: str,
    ttl_seconds: int = 300
) -> str:
    """Mint a time-bound capability for a specific service action."""
    expires = int(time.time()) + ttl_seconds
    payload = f"{caller_id}:{service}:{action}:{resource}:{expires}"
    sig = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()[:24]
    return f"{payload}:{sig}"

def verify_service_capability(
    token: str,
    expected_service: str,
    expected_action: str,
    expected_resource: str
) -> bool:
    """Verify a capability token is unexpired and correctly scoped."""
    try:
        caller_id, svc, action, resource, expires, sig = token.rsplit(":", 5)
    except ValueError:
        return False

    if int(expires) < time.time():
        return False
    if svc != expected_service or action != expected_action or resource != expected_resource:
        return False

    payload = f"{caller_id}:{svc}:{action}:{resource}:{expires}"
    expected_sig = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()[:24]
    return hmac.compare_digest(sig, expected_sig)

# Service A requests a capability to call Service B.
token = mint_service_capability("payments-api", "orders-api", "read", "order-123")

# Service B verifies the capability before handling the request.
if not verify_service_capability(token, "orders-api", "read", "order-123"):
    raise PermissionError("Invalid or expired capability")

Der Token ist in sich geschlossen und kurzlebig. Service B muss keinen Authorization-Server aufrufen. Er prüft einfach den HMAC und das Ablaufdatum. Der Scope ist explizit: Dieser Token ist nur zum Lesen von order-123 aus orders-api gültig.

Warum das schwieriger ist als es sein sollte

Die Technologie existiert. Die Schwierigkeit ist operativ.

Bootstrapping von Trust ist das erste Problem. Wenn jeder Service ein Zertifikat braucht, muss etwas diese Zertifikate ausstellen. Dieses Etwas, die Certificate Authority oder der SPIRE-Server, wird zu einem neuen Ziel. Du hast Trust nicht eliminiert. Du hast ihn konzentriert.

Es gibt auch das Revocation-Problem. Wenn ein Service kompromittiert wird, wie invalidierst du seine Identität? mTLS-Zertifikate können kurzlebig sein, was hilft, aber das Widerrufen eines spezifischen Zertifikats erfordert immer noch die Verteilung einer Certificate Revocation List oder die Nutzung von OCSP. Capability-Tokens laufen schnell ab, was den Schaden begrenzt, aber ein gestohlener Signing Key ermöglicht es einem Angreifer, unbegrenzt viele Tokens zu minten.

Performance ist ein weiteres Thema. TLS-Handshakes erhöhen die Latenz. In einem Service Mesh mit Sidecars ist der Overhead normalerweise akzeptabel (einige Millisekunden), aber bei High-Throughput-Pfaden summieren sich diese Millisekunden. Capability-Tokens vermeiden einen Roundtrip zu einem Auth-Server, aber HMAC-Verifizierung ist im großen Maßstab nicht kostenlos.

Wo Ambient Authority immer noch Sinn macht

Nicht jeder interne Aufruf braucht kryptographischen Beweis. Ein Cron-Job, der im selben Container wie der Datenbank-Client läuft, braucht kein mTLS, um mit localhost zu sprechen. Health Checks zwischen einem Load Balancer und einem Node brauchen keine Capability-Tokens.

Das Ziel ist nicht null Ambient Authority. Das Ziel ist zu wissen, wo deine Ambient Authority existiert und ob der Blast Radius akzeptabel ist. Ein geteiltes VPC mit 200 Services und keiner internen Authentifizierung ist ein großer, unsichtbarer Blast Radius. Ein lokaler Unix-Socket mit Dateiberechtigung 0600 ist ein kleiner, expliziter Blast Radius.

Wie man beginnt, Ambient Authority aus Service-Aufrufen zu entfernen

Du brauchst kein Service Mesh am ersten Tag. Beginne mit den Services, die sensible Daten berühren oder an Trust Boundaries sitzen.

Erstens: Identifiziere deine Deputy-Services. Welche Services besitzen Privilegien, die andere nicht haben? Welche internen APIs wären gefährlich, wenn sie vom falschen Service aufgerufen würden? Das sind deine Kandidaten.

Zweitens: Füge mTLS zur API-Schicht hinzu. Wenn du einen Reverse Proxy wie Envoy oder NGINX verwendest, kannst du dort mTLS terminieren, ohne den Anwendungscode zu ändern. Der Proxy übergibt einen Header mit der verifizierten Client-Identität an den Upstream. Der Upstream prüft diesen Header.

Drittens: Grenze deine Tokens ein. Wenn du bereits JWTs für die Service-Authentifizierung verwendest, höre auf, role: service in den Payload zu schreiben. Schreibe stattdessen allowed_services: ["orders-api"] und allowed_resources: ["order:*"]. Mache die Autorisierung so spezifisch wie die Operation.

Häufig gestellte Fragen zur Service-Authentifizierung ohne Ambient Authority

Reicht mTLS, oder brauche ich auch Capabilities?

mTLS beweist Identität. Capabilities beweisen Autorisierung. Sie lösen unterschiedliche Probleme. Ein Service mit einem gültigen Zertifikat sollte trotzdem nicht beliebige Daten löschen dürfen. Verwende mTLS für Transport-Sicherheit und Identität, Capabilities für Access Control.

Was ist mit Service Meshes wie Istio oder Linkerd?

Service Meshes automatisieren mTLS und Identität zwischen Pods. Sie sind ein praktischer Weg, Ambient Authority zu entfernen, ohne jeden Service neu zu schreiben. Das Mesh übernimmt Certificate Rotation, Identity Attestation und Traffic-Verschlüsselung. Der Trade-off ist Komplexität und eine neue Control Plane, die gesichert werden muss.

Funktioniert das für Serverless Functions?

Ja, aber es ist schwieriger. Serverless Functions sind ephemeral, deshalb müssen Certificate Rotation und Identity Attestation zum Aufrufzeitpunkt stattfinden. Cloud-Provider bieten dafür Workload Identity (AWS IAM Roles for Service Accounts, GCP Workload Identity). Diese sind capability-ähnlich: Die Funktion erhält einen Token, der auf das eingegrenzt ist, was sie tun darf, nicht ein pauschales Credential.

Was ist der Unterschied zwischen einem Service Account und einer Capability?

Ein Service Account ist eine Identität, die standardmäßig Ambient Authority trägt. Wenn Service A als payments-sa läuft, kann er normalerweise alles tun, was payments-sa tun darf. Eine Capability ist auf eine spezifische Aktion an einer spezifischen Ressource eingegrenzt. Selbst wenn die Capability gestohlen wird, kann sie nicht gegen ein anderes Ziel wiederverwendet werden.

Beginne mit dem Perimeter, den du bereits hast

Dein VPC war nie eine Security Boundary. Es war eine Netzwerk-Boundary, die wir als Security Boundary ausgaben. Ambient Authority zu entfernen bedeutet, jeden Service-Aufruf so zu behandeln, als würde er diese Boundary überschreiten. Kryptographische Identität und eingegrenzte Capabilities sind die Werkzeuge. Der schwierige Teil ist zuzugeben, dass das alte Modell Bequemlichkeit war, die als Security verkleidet wurde.

Wähle eine interne API. Füge Client-Zertifikatsverifizierung hinzu. Beobachte, was bricht. Meistens ist das, was bricht, eine Annahme, von der du nicht wusstest, dass dein Code sie macht.