TLS everywhere
Produktions-sicher heißt TLS auf jeder Netzwerkoberfläche. Jede Komponente des Frameworks hat ihre eigene TLS-Konfiguration; diese Seite ist der Katalog, wo TLS zu aktivieren ist und welches Cert-Material jede Komponente braucht.
Cluster-Transport
Abschnitt betitelt „Cluster-Transport“import { TcpTransport, Cluster, ClusterOptions } from 'actor-ts/cluster';
const transport = new TcpTransport(self, log, { cert: fs.readFileSync('./tls/cluster.crt'), key: fs.readFileSync('./tls/cluster.key'), ca: fs.readFileSync('./tls/ca.crt'), rejectUnauthorized: true,});
const clusterOptions = ClusterOptions.create() .withHost(host) .withPort(port) .withSeeds(seeds) .withTransport(transport);await Cluster.join(system, clusterOptions);Mutually authenticated TLS zwischen Cluster-Nodes. Siehe Cluster-Sicherheit für die vollständige Diskussion.
Jedes Feld hier nimmt das Zertifikatsmaterial selbst entgegen, niemals einen Pfad darauf — deshalb liest das Beispiel die Dateien selbst ein. Nichts im Transport greift auf das Dateisystem zu, und weder Bun noch Node noch Deno akzeptiert in diesen Feldern einen Dateinamen.
cert und key sind auf einem Listener nur gemeinsam gültig.
Ein tls-Objekt mit nur einem von beiden brachte den Listener
bisher zu dem Schluss „kein TLS”, und er lauschte im Klartext —
während dieselbe Konfiguration weiterhin über TLS hinauswählte. Der
Cluster bildete sich also, und nichts sah falsch aus. Diese
Kombination wird jetzt beim Bind abgelehnt; ein so
fehlkonfigurierter Knoten degradiert damit nicht mehr still,
sondern scheitert laut.
Nicht überspringen — auch in internen Netzwerken gilt Defense in Depth.
HTTP-Server
Abschnitt betitelt „HTTP-Server“import { HttpExtensionId } from 'actor-ts/http';
const http = system.extension(HttpExtensionId);
await http.newServerAt('0.0.0.0', 8443) .useBackend(new FastifyBackend({ https: { cert: fs.readFileSync('./tls/http.crt'), key: fs.readFileSync('./tls/http.key'), }, })) .bind(routes);Für HTTPS auf der Anwendungsebene. Oft am Load Balancer terminiert stattdessen — in K8s übernimmt Service/Ingress TLS, die App spricht intern Plain-HTTP. Wählen je nach Form deiner Infrastruktur.
Management-Endpunkt
Abschnitt betitelt „Management-Endpunkt“import { FastifyBackend } from 'actor-ts/http';import { managementRoutes } from 'actor-ts/management';
const routes = managementRoutes(system, cluster);
// TLS is a property of the HTTP backend — Fastify's `https` option.const tlsBackend = new FastifyBackend({ https: { cert: fs.readFileSync('./tls/mgmt.crt'), key: fs.readFileSync('./tls/mgmt.key'), },});await system.http(8558, { backend: tlsBackend }).bind(routes);Der Management-Server ist standardmäßig nur intern — aber TLS hilft trotzdem:
- Schützt vor lateraler Bewegung in einem kompromittierten Netzwerk.
- Wird von manchen Compliance-Regimen unabhängig von der Netzwerk-Topologie verlangt.
Broker-Actors
Abschnitt betitelt „Broker-Actors“Jeder Broker-Actor nimmt TLS in zwei getrennten Schritten entgegen, und die beiden zu verwechseln ist der übliche Grund dafür, dass eine Verbindung zwar TLS sagt, aber nicht das verifiziert, was gemeint war:
- TLS einschalten macht das URL-Schema —
amqps://,mqtts://,rediss://,wss://— oderwithSsl(true)bei Kafka. Der Treiber verifiziert den Broker dann gegen den System-Trust-Store, was für ein öffentlich vertrauenswürdiges Zertifikat genügt. - Zertifikatsmaterial übergeben macht
withTls({ … }): eine private CA, der vertraut werden soll, oder ein Client-Zertifikat für mTLS. Es konfiguriert den Handshake, statt einen zu starten — das TLS-Schema bleibt also zusätzlich nötig.
Wie beim Cluster-Transport trägt jedes Feld das Material selbst, nie
einen Pfad darauf — deshalb liest jedes Beispiel die Datei selbst.
cert und key ergeben nur gemeinsam Sinn; eines ohne das andere wird
beim Start des Actors abgelehnt, statt im Handshake zu werfen und als
Verbindungsfehler endlos wiederholt zu werden.
Nichts davon ist aus HOCON lesbar, und das mit Absicht: Ein Private Key
gehört nicht in eine Config-Datei. withTls gibt es nur im Code, und
Kafkas ssl-Config-Leaf bleibt boolean-only.
Kafka hat kein URL-Schema, das TLS tragen könnte — deshalb erledigt
ssl beide Aufgaben: true für den System-Trust-Store, oder das
Material selbst:
const kafkaOptions = KafkaOptions.create() .withBrokers(['kafka-1:9093']) .withSsl({ ca: fs.readFileSync('./tls/ca.crt', 'utf8'), cert: fs.readFileSync('./tls/client.crt', 'utf8'), key: fs.readFileSync('./tls/client.key', 'utf8'), }) .withSasl({ mechanism: 'scram-sha-512', username: process.env.KAFKA_USER!, password: process.env.KAFKA_PASS!, });new KafkaActor(kafkaOptions);const mqttOptions = MqttOptions.create() .withBrokerUrl('mqtts://mqtt.example.com:8883') .withCredentials(process.env.MQTT_USER, process.env.MQTT_PASS) .withTls({ ca: fs.readFileSync('./tls/ca.crt', 'utf8'), });new MqttActor(mqttOptions);mqtts:// startet den Handshake; withTls entscheidet, wem er
vertraut. Ohne withTls fällt die Verifizierung auf die Defaults des
mqtt-Pakets zurück, also auf den System-Trust-Store.
const amqpOptions = AmqpOptions.create() .withUrl('amqps://rabbitmq.example.com:5671') .withTls({ ca: fs.readFileSync('./tls/ca.crt', 'utf8'), });new AmqpActor(amqpOptions);amqps:// plus withTls, das amqplib als dessen
socketOptions-Argument erreicht. AMQP-URL-Query-Parameter tragen
heartbeat, frameMax, channelMax und locale — nie
Zertifikatsmaterial; in der URL lässt sich das also gar nicht ausdrücken.
const natsOptions = NatsOptions.create() .withServers(['nats://nats.example.com:4222']) .withTls({ ca: fs.readFileSync('./tls/ca.crt', 'utf8'), cert: fs.readFileSync('./tls/client.crt', 'utf8'), key: fs.readFileSync('./tls/client.key', 'utf8'), });new NatsActor(natsOptions);mTLS via withTls — üblich für Produktions-NATS. NATS hat kein
tls-URL-Schema, deshalb ist withTls hier zugleich das, was den
Treiber überhaupt zum TLS-Aushandeln bringt. JetStreamActor nimmt
dieselbe Option.
Redis Streams
Abschnitt betitelt „Redis Streams“const redisStreamsOptions = RedisStreamsOptions.create() .withUrl('rediss://redis.example.com:6380') .withTls({ ca: fs.readFileSync('./tls/ca.crt', 'utf8'), });new RedisStreamsActor(redisStreamsOptions);rediss://-URL-Schema (beachte das doppelte s). Producer- und
Consumer-Client werden aus demselben Material gebaut.
TCP-Listener
Abschnitt betitelt „TCP-Listener“Der einzige Broker-Actor auf der eingehenden Seite. Er nimmt dasselbe
Material wie der Cluster-Transport, und ein gesetztes ca schaltet die
Client-Zertifikatsprüfung ein, sofern nicht requestClientCert: false
etwas anderes sagt:
const tcpServerOptions = TcpServerOptions.create() .withBindPort(9500) .withTarget(handler) .withTls({ cert: fs.readFileSync('./tls/server.crt', 'utf8'), key: fs.readFileSync('./tls/server.key', 'utf8'), ca: fs.readFileSync('./tls/ca.crt', 'utf8'), });new TcpServerActor(tcpServerOptions);const grpcClientOptions = GrpcClientOptions.create() .withEndpoint('orders.example.com:50051') .withCredentials({ kind: 'tls', rootCerts: fs.readFileSync('./tls/ca.crt'), });new GrpcClientActor(grpcClientOptions);Für Mutual TLS cert + key ergänzen.
WebSocket
Abschnitt betitelt „WebSocket“const webSocketClientOptions = WebsocketClientOptions.create().withUrl('wss://realtime.example.com/feed');new WebsocketClientActor(webSocketClientOptions);wss://-URL-Schema. Cert-Verifizierung folgt den TLS-Defaults der
Runtime.
Nur System-Trust-Store
Abschnitt betitelt „Nur System-Trust-Store“Zwei Client-Actors nehmen weiterhin kein Zertifikatsmaterial und erreichen einen Broker daher nur, wenn dessen Zertifikat auf eine öffentlich vertrauenswürdige Wurzel zurückführt:
WebsocketClientActor(oben).TcpSocketActor— der ausgehende TCP-Client. Sein Listener-PendantTcpServerActornimmt Material; die wählende Hälfte nicht.
Hinter einer privaten CA heißt das: TLS an einem Proxy mit öffentlichem
Zertifikat terminieren, oder die CA in den Trust-Store des Hosts
aufnehmen. Es heißt nicht NODE_TLS_REJECT_UNAUTHORIZED=0 — das
deaktiviert die Verifizierung für jede TLS-Verbindung des Prozesses,
Cluster-Transport und sämtliche HTTP-Clients eingeschlossen.
Persistence-Backends
Abschnitt betitelt „Persistence-Backends“Nicht zutreffend — lokaler Datei-Zugriff. TLS nicht anwendbar.Cassandra
Abschnitt betitelt „Cassandra“const cassandraJournalOptions = CassandraJournalOptions.create() .withContactPoints(['cass-1.example.com:9042']) .withClient(clientWithMtls);new CassandraJournal(cassandraJournalOptions); // mTLS-Cert-Material (cert / key / ca) wird auf dem via withClient() // übergebenen cassandra-driver-Client konfiguriertCassandra-Cluster laufen in Produktion typischerweise mit mTLS.
Object Storage
Abschnitt betitelt „Object Storage“const s3ObjectStorageOptions = S3ObjectStorageOptions.create().withRegion('eu-west-1');const objectStorageDurableStateStoreOptions = ObjectStorageDurableStateStoreOptions.create().withBackend(new S3ObjectStorageBackend(s3ObjectStorageOptions));new ObjectStorageDurableStateStore(objectStorageDurableStateStoreOptions); // S3 nutzt per Default HTTPSCloud-Object-Storage (S3, GCS, Azure Blob) nutzt immer TLS. Keine Konfiguration nötig außer dem Endpoint.
Cert-Provisionierung
Abschnitt betitelt „Cert-Provisionierung“Drei Muster in K8s-Produktion:
cert-manager
Abschnitt betitelt „cert-manager“apiVersion: cert-manager.io/v1kind: Certificatemetadata: name: actor-ts-clusterspec: secretName: actor-ts-cluster-tls issuerRef: name: actor-ts-ca kind: ClusterIssuer commonName: actor-ts dnsNames: [actor-ts-cluster.svc] duration: 8760h renewBefore: 720hcert-manager erneuert Certs automatisch vor Ablauf. Die meisten Produktions-K8s-Setups nutzen ihn.
Vault Agent
Abschnitt betitelt „Vault Agent“Vault Agent läuft als Sidecar; zieht Certs in das Pod-Filesystem; erneuert automatisch. Nützlich, wenn du schon HashiCorp Vault hast.
Manuell + geplante Rotation
Abschnitt betitelt „Manuell + geplante Rotation“Für Nicht-K8s-Umgebungen geplante Cron-Jobs, die frische Certs aus einer internen CA ziehen + die betroffenen Services neu starten. Funktioniert, erfordert aber sorgfältige operative Disziplin.
Was ist mit der CA-Kette?
Abschnitt betitelt „Was ist mit der CA-Kette?“ca: fs.readFileSync('./tls/ca.crt'),Die meisten internen Setups: eine CA, signiert alle Client- + Server-Certs. Die Cert-Verifizierung braucht das CA-Cert auf beiden Enden.
Cloud-managed Certificates (Let’s Encrypt, AWS ACM): nutze
öffentliche CA-Bundles — in den meisten Runtimes bereits
vorhanden. Kein expliziter ca nötig.
Wohin als nächstes
Abschnitt betitelt „Wohin als nächstes“- Cluster-Sicherheit — das Detail zum Cluster-Transport.
- Master-Key-Rotation — für Daten at-rest.
- Kubernetes-Deployment — das K8s-Rezept + cert-manager-Integration.
- Operations-Überblick — die Produktions-Sicherheits-Checkliste.
