Siddhant Deval
Siddhant Deval
backend21 min read

Security, Multi-Region Replication, and Disaster Recovery

A messaging pipeline with no authentication, no encryption, and no cross-region replica is a single point of failure on three independent axes. This article implements Kafka mTLS and SASL/SCRAM, MSK IAM auth, MirrorMaker 2 active-passive replication, and a concrete disaster recovery runbook for promoting a passive cluster and resuming consumer groups from translated offsets.

Series·Part 6 of 6

Messaging at Cloud Scale

Security, Multi-Region Replication, and Disaster Recovery

At 2:14 a.m., a penetration tester connects to the Kafka broker on port 9092 with no credentials. The broker accepts the connection. The tester publishes 10,000 synthetic payment.charged events to the production topic. The fulfillment service processes them. The company dispatches 10,000 phantom orders before a monitoring alert fires 12 minutes later. The root cause is a Kafka cluster with security.inter.broker.protocol=PLAINTEXT and allow.everyone.if.no.acl.found=true — the defaults on a self-managed Kafka cluster that was "just for development" six months ago and quietly became production.

Separately: us-east-1 goes dark at 7:03 a.m. due to an AWS availability zone impairment. The Kafka cluster is in us-east-1. The consumer groups have no idea what offset to start from in the eu-west-1 passive cluster. They reset to earliest. Two million messages are reprocessed. The downstream services, which are not idempotent for this volume, produce duplicate charges, duplicate shipments, and duplicate notification emails for six hours.

Both failures are preventable. Neither requires exotic tooling.

Architectural Note

Series positioning: This is Part 6 and the final article of Messaging at Cloud Scale (Series 2). It covers the operational hardening layer that makes every pattern from Series 1 and Series 2 production-safe. The prerequisite for the IAM section is AWS Security: IAM, VPC, and Networking.


1. Kafka Authentication

1.1 mTLS: Mutual Authentication

mTLS verifies both the broker's identity (client knows it is talking to a legitimate broker) and the client's identity (broker knows who is connecting). Neither SASL nor plaintext authentication verifies the broker.

BASH
# Step 1: Generate CA, broker keystore, client keystore
# CA key and certificate
openssl req -new -x509 -keyout ca.key -out ca.crt -days 365 \
  -subj "/CN=Kafka-CA/O=Company"

# Broker: generate key, CSR, sign with CA
keytool -keystore broker.keystore.jks -alias broker -validity 365 \
  -keyalg RSA -genkey -dname "CN=kafka-broker-1,OU=Backend,O=Company"

keytool -keystore broker.keystore.jks -alias broker -certreq \
  -file broker.csr

openssl x509 -req -CA ca.crt -CAkey ca.key -in broker.csr \
  -out broker-signed.crt -days 365 -CAcreateserial

keytool -keystore broker.keystore.jks -alias CARoot -import -file ca.crt
keytool -keystore broker.keystore.jks -alias broker -import -file broker-signed.crt

# Client: same pattern with client.keystore.jks
PROPERTIES
# server.properties — Kafka broker with mTLS enabled
listeners=SSL://0.0.0.0:9093
advertised.listeners=SSL://kafka-broker-1.internal:9093
ssl.keystore.location=/etc/kafka/certs/broker.keystore.jks
ssl.keystore.password=${KEYSTORE_PASSWORD}
ssl.truststore.location=/etc/kafka/certs/broker.truststore.jks
ssl.truststore.password=${TRUSTSTORE_PASSWORD}
ssl.client.auth=required         # mutual: client MUST present a certificate
ssl.endpoint.identification.algorithm=HTTPS  # broker hostname verification
TYPESCRIPT
// kafkajs client with mTLS
import { readFileSync } from 'fs'

const kafka = new Kafka({
  clientId: 'payment-service',
  brokers:  ['kafka-broker-1.internal:9093'],
  ssl: {
    rejectUnauthorized: true,
    ca:   [readFileSync('/etc/certs/ca.crt')],
    cert:  readFileSync('/etc/certs/client.crt'),
    key:   readFileSync('/etc/certs/client.key'),
  }
})

1.2 SASL/SCRAM: Client-Only Authentication

SASL/SCRAM is simpler to operate than mTLS but only authenticates the client — the broker identity is unverified (susceptible to rogue broker injection in a compromised network):

TYPESCRIPT
// kafkajs SASL/SCRAM-SHA-512
const kafka = new Kafka({
  clientId: 'analytics-service',
  brokers:  ['kafka-broker-1.internal:9092'],
  sasl: {
    mechanism: 'scram-sha-512',
    username:  process.env.KAFKA_USERNAME!,
    password:  process.env.KAFKA_PASSWORD!,
  }
})
// Create user: kafka-configs.sh --bootstrap-server ... --alter --add-config
//   'SCRAM-SHA-512=[iterations=8192,password=xxx]' --entity-type users --entity-name analytics-service
Mechanism Verifies broker Verifies client Credential rotation Best for
PLAINTEXT N/A Local dev only
SASL/SCRAM Manual (rotate password) Internal services, simpler setup
mTLS Certificate rotation (PKI) External clients, regulated environments
MSK IAM ✅ (AWS-managed) ✅ (IAM role) Never — IAM rotates automatically AWS-native workloads

1.3 Kafka ACLs

BASH
# Grant analytics-service read access to payments topic only
kafka-acls.sh --bootstrap-server kafka:9092 \
  --add --allow-principal "User:analytics-service" \
  --operation Read \
  --topic payments.charged

# Grant payment-service write access to payments topic
kafka-acls.sh --bootstrap-server kafka:9092 \
  --add --allow-principal "User:payment-service" \
  --operation Write \
  --topic payments.charged

# Deny all other principals by default (set allow.everyone.if.no.acl.found=false)

2. MSK IAM Authentication (Zero-Credential Model)

TYPESCRIPT
// ✅ MSK IAM: no username, no password, no certificate — IAM role IS the identity
// The ECS task role or EC2 instance profile provides credentials automatically

import { generateAuthToken } from 'aws-msk-iam-sasl-signer-js'

const kafka = new Kafka({
  clientId: 'payment-service',
  brokers:  [process.env.MSK_BOOTSTRAP!],
  ssl:      true,
  sasl: {
    mechanism: 'oauthbearer',
    oauthBearerProvider: async () => {
      const { token, expiryTime } = await generateAuthToken({ region: 'us-east-1' })
      return { value: token }
    }
  }
})
// MSK rotates the token automatically via the IAM SDK
// No password to store, no certificate to rotate, no secret to leak
// Audit: every connection attempt appears in CloudTrail as an IAM event
Pro Tip & Optimization

MSK IAM auth eliminates credential rotation entirely. The IAM role attached to the ECS task IS the client identity — AWS rotates the underlying credentials automatically via the EC2 metadata service. Audit every connection via CloudTrail: kafka-cluster:Connect events appear per client, per cluster, per timestamp. This is the correct model for any AWS-native Kafka deployment.


3. Multi-Region Replication with MirrorMaker 2

3.1 Active-Passive Architecture

3.2 MirrorMaker 2 Configuration

PROPERTIES
# mm2.properties — MirrorMaker 2 active-passive replication
clusters=us-east-1, eu-west-1

us-east-1.bootstrap.servers=kafka-primary:9092
eu-west-1.bootstrap.servers=kafka-replica:9092

us-east-1->eu-west-1.enabled=true
us-east-1->eu-west-1.topics=payments.charged,orders.created,inventory.events
us-east-1->eu-west-1.groups=fulfillment-service,analytics-service,notification-service

# Offset translation: MirrorMaker 2 maps us-east-1 offsets to eu-west-1 equivalents
# Stored in: eu-west-1.mm2-offset-syncs.us-east-1.internal topic
us-east-1->eu-west-1.emit.checkpoints.enabled=true
us-east-1->eu-west-1.sync.group.offsets.enabled=true
us-east-1->eu-west-1.sync.group.offsets.interval.seconds=60

3.3 Disaster Recovery Runbook

BASH
# DR RUNBOOK: Promoting eu-west-1 passive cluster to active
# Estimated time to recovery: ~15 minutes with pre-staged consumers

# Step 1: Verify replication lag (should be < 30s in steady state)
kafka-consumer-groups.sh --bootstrap-server eu-west-1-kafka:9092 \
  --describe --group us-east-1.fulfillment-service

# Step 2: Translate consumer offsets from us-east-1 → eu-west-1
# MirrorMaker 2 stores translated offsets in:
# eu-west-1.mm2-offset-syncs.us-east-1.internal
# Use MirrorMaker 2 RemoteClusterUtils to get translated offsets:
kafka-mirror-maker-offsets.sh \
  --source-cluster us-east-1 \
  --consumer-group fulfillment-service \
  --bootstrap-server eu-west-1-kafka:9092

# Step 3: Set consumer group offsets on eu-west-1 to translated positions
kafka-consumer-groups.sh --bootstrap-server eu-west-1-kafka:9092 \
  --group fulfillment-service \
  --reset-offsets --from-file translated-offsets.csv \
  --topic us-east-1.payments.charged --execute

# Step 4: Update producer endpoints to eu-west-1 (DNS failover or config change)
# Step 5: Start consumer groups on eu-west-1 — they resume from translated offsets
# Step 6: Monitor for duplicate processing (at-least-once: ~60s of lag may reprocess)
Performance / Safety Warning

MirrorMaker 2 translates consumer group offsets across clusters, but the translation is not instantaneous. With sync.group.offsets.interval.seconds=60, consumers resuming in the passive cluster may replay up to 60 seconds of messages. Consumers must be idempotent — this is not an edge case during DR, it is the expected behaviour. Without idempotency, a 60-second replay produces 60 seconds of duplicate side effects.


4. RPO and RTO Model

Metric Self-Managed MM2 Amazon MSK Multi-Region Amazon EventBridge Global Endpoints
RPO (data loss) 60s (sync interval) < 1s (native replication) < 1s
RTO (recovery time) 10–20 min (manual runbook) 5–10 min (auto-failover) < 1 min (automatic)
Operational burden High Low Very low
Cost MirrorMaker EC2 MSK x2 regions Per-event pricing
Kafka API preserved Yes Yes No (EventBridge only)

Summary

Concept Rule
mTLS vs SASL mTLS authenticates both broker and client identity; SASL/SCRAM authenticates only the client — use mTLS when the broker identity must also be verified (preventing rogue broker injection).
MirrorMaker 2 offset translation MirrorMaker 2 translates consumer group offsets across clusters; without offset translation, a failover restores consumers to the beginning of the topic, not to their last committed position.
MSK IAM zero-credential MSK IAM auth eliminates credential rotation entirely: the IAM role attached to the ECS task / EC2 instance IS the client identity — rotate nothing, audit everything via CloudTrail.

Series 2 — Messaging at Cloud Scale — is complete. Together with Series 1, these 18 articles form the complete Distributed Messaging Systems curriculum: from first principles (why async messaging) to cloud-native production hardening (security, multi-region DR, disaster recovery).

Research & Synthesis Note

This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.

#Kafka Security#mTLS#SASL#MirrorMaker 2#MSK#Disaster Recovery#Multi-Region#Backend
Siddhant Deval

Written by Siddhant Deval

Senior Full-Stack Engineer building high-scale architectures, browser performance engineering systems, and SaaS platforms.