Zum Inhalt springen

Zero-Trust-Netzwerke für Anwendungsentwickler

Zero-Trust-Netzwerke aus Entwicklersicht: mutual TLS, Service-Mesh-Authentifizierung und Autorisierung auf Anfrageebene für jeden Aufruf.

3 Min. Lesezeit
Eine Netzwerktopologie, in der jeder Aufruf von Dienst zu Dienst Authentifizierungs- und Autorisierungsprüfungen durchläuft

Der Perimeter ist tot

Traditionelle Netzwerksicherheit zieht eine Linie um die Infrastruktur: Alles innerhalb des Perimeters gilt als vertrauenswürdig, alles außerhalb nicht. Dieses Modell versagt, sobald ein einziger kompromittierter Dienst sich lateral durch das gesamte Netzwerk bewegen kann. Zero Trust geht von keinerlei implizitem Vertrauen aus – jede Anfrage, von jeder Quelle, muss ihre Identität und Berechtigung nachweisen.

Gegenseitiges TLS zwischen Diensten

Beim Standard-TLS legt nur der Server ein Zertifikat vor. Gegenseitiges TLS (mTLS) verlangt, dass sich sowohl Client als auch Server authentifizieren. Jeder Dienst besitzt ein eigenes Zertifikat, und jede Verbindung verifiziert beide Seiten.

tstypescript
import { createServer, createSecureContext } from "node:tls";
import { readFileSync } from "node:fs";
 
// Server setup with mTLS
const server = createServer(
  {
    key: readFileSync("/certs/service-a.key"),
    cert: readFileSync("/certs/service-a.crt"),
    ca: readFileSync("/certs/ca.crt"),
    requestCert: true,          // Require client certificate
    rejectUnauthorized: true,   // Reject if client cert is invalid
  },
  (socket) => {
    const clientCert = socket.getPeerCertificate();
    console.log(`Authenticated client: ${clientCert.subject.CN}`);
  }
);
 
// HTTP client with mTLS
async function callService(url: string, body: unknown): Promise<Response> {
  const agent = new Agent({
    cert: readFileSync("/certs/service-b.crt"),
    key: readFileSync("/certs/service-b.key"),
    ca: readFileSync("/certs/ca.crt"),
  });
 
  return fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
    // @ts-expect-error -- Node fetch supports dispatcher
    dispatcher: agent,
  });
}

Dienstidentität und Autorisierung

mTLS weist die Identität nach. Die Autorisierung entscheidet, was diese Identität tun darf. Extrahieren Sie die Dienstidentität aus dem Zertifikat und prüfen Sie sie gegen eine Richtlinie, bevor Sie die Anfrage verarbeiten.

tstypescript
// ❌ Trusting any request from the internal network
app.post("/api/internal/process-payment", async (req, res) => {
  // No identity check — any service on the network can call this
  await processPayment(req.body);
  res.json({ success: true });
});
 
// ✅ Verify service identity and check authorization
interface ServicePolicy {
  allowedCallers: string[];
  requiredScopes?: string[];
}
 
const endpointPolicies: Record<string, ServicePolicy> = {
  "/api/internal/process-payment": {
    allowedCallers: ["checkout-service", "subscription-service"],
    requiredScopes: ["payments:write"],
  },
  "/api/internal/get-user": {
    allowedCallers: ["*"], // Any authenticated service
  },
};
 
function authorizeService(
  req: Request,
  res: Response,
  next: NextFunction
): void {
  const clientCert = (req.socket as TLSSocket).getPeerCertificate();
 
  if (!clientCert || !clientCert.subject) {
    res.status(401).json({ error: "No client certificate" });
    return;
  }
 
  const serviceId = clientCert.subject.CN;
  const policy = endpointPolicies[req.path];
 
  if (!policy) {
    res.status(403).json({ error: "No policy defined for endpoint" });
    return;
  }
 
  const isAllowed =
    policy.allowedCallers.includes("*") ||
    policy.allowedCallers.includes(serviceId);
 
  if (!isAllowed) {
    console.warn(
      `Service ${serviceId} denied access to ${req.path}`
    );
    res.status(403).json({ error: "Service not authorized" });
    return;
  }
 
  next();
}

Propagierung des Anfragekontexts

Zero Trust geht über die Dienstidentität hinaus. Jede Anfrage trägt Kontext – Nutzeridentität, Scopes, Trace-IDs –, den nachgelagerte Dienste unabhängig validieren, statt den Angaben vorgelagerter Dienste blind zu vertrauen.

tstypescript
interface RequestContext {
  userId: string;
  roles: string[];
  scopes: string[];
  traceId: string;
  sourceService: string;
  requestTimestamp: string;
}
 
// Middleware: extract and verify request context
function extractRequestContext(
  req: Request,
  res: Response,
  next: NextFunction
): void {
  const contextHeader = req.headers["x-request-context"];
 
  if (!contextHeader || typeof contextHeader !== "string") {
    res.status(401).json({ error: "Missing request context" });
    return;
  }
 
  try {
    // Context is signed by the API gateway
    const verified = verifySignedContext(contextHeader);
    req.context = verified;
    next();
  } catch (error) {
    res.status(401).json({ error: "Invalid request context signature" });
  }
}
 
// Every service independently verifies — never trust upstream
function verifySignedContext(token: string): RequestContext {
  const decoded = jwt.verify(token, process.env.CONTEXT_PUBLIC_KEY!, {
    algorithms: ["ES256"],
    maxAge: "5m", // Context expires quickly
  });
 
  return decoded as RequestContext;
}

Netzwerkrichtlinien in Kubernetes

Zero Trust auf Anwendungsebene ergänzt Kontrollen auf Netzwerkebene. Kubernetes-NetworkPolicies schränken ein, welche Pods miteinander kommunizieren dürfen, und sorgen so für zusätzliche Verteidigungstiefe.

ymlyaml
# Only allow checkout-service to reach payment-service
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: payment-service-ingress
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: payment-service
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: checkout-service
        - podSelector:
            matchLabels:
              app: subscription-service
      ports:
        - protocol: TCP
          port: 8443
tstypescript
// Health check for zero-trust readiness
interface ZeroTrustCheck {
  name: string;
  check: () => Promise<boolean>;
}
 
const readinessChecks: ZeroTrustCheck[] = [
  {
    name: "mTLS certificates valid",
    check: async () => {
      const cert = readFileSync("/certs/service.crt", "utf8");
      const parsed = new crypto.X509Certificate(cert);
      return new Date(parsed.validTo) > new Date();
    },
  },
  {
    name: "Policy engine reachable",
    check: async () => {
      const response = await fetch("http://policy-engine:8181/health");
      return response.ok;
    },
  },
  {
    name: "Context signing key loaded",
    check: async () => {
      return !!process.env.CONTEXT_PUBLIC_KEY;
    },
  },
];
 
async function checkZeroTrustReadiness(): Promise<{
  ready: boolean;
  checks: Array<{ name: string; passed: boolean }>;
}> {
  const results = await Promise.all(
    readinessChecks.map(async (c) => ({
      name: c.name,
      passed: await c.check().catch(() => false),
    }))
  );
 
  return {
    ready: results.every((r) => r.passed),
    checks: results,
  };
}

Die wichtigsten Erkenntnisse

Zero Trust bedeutet, dass jede Anfrage ihre Identität und Berechtigung nachweisen muss – auch „interner" Traffic bildet keine Ausnahme. Implementieren Sie mTLS zwischen den Diensten, sodass jede Verbindung gegenseitig authentifiziert ist. Definieren Sie explizite Autorisierungsrichtlinien pro Endpoint und legen Sie fest, welche Dienste welche Endpoints aufrufen dürfen.

Propagieren Sie den Anfragekontext mit signierten, kurzlebigen Tokens, die jeder nachgelagerte Dienst unabhängig verifiziert. Legen Sie Netzwerkrichtlinien über die Kontrollen auf Anwendungsebene, um zusätzliche Verteidigungstiefe zu erreichen. Die zusätzliche Komplexität ist real, aber die Alternative – ein einzelner kompromittierter Dienst, der Ihr gesamtes Netzwerk kontrolliert – ist schlimmer. Beginnen Sie mit mTLS und Autorisierung auf Endpoint-Ebene, und fügen Sie Kontextpropagierung und Richtlinien-Engines hinzu, sobald die Zahl Ihrer Dienste wächst.

Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX