From c35ee556eca7b8f12deffbb33afb3198c0846193 Mon Sep 17 00:00:00 2001 From: "Caro R. Pereira" Date: Thu, 2 Apr 2026 14:39:59 -0300 Subject: [PATCH 1/8] fix(security): harden phase 2 oracle schema refs and dependency hygiene --- .gitignore | 1 + docker/oracle/init.sql | 22 +++++++++++----------- pom.xml | 4 ---- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index c1cf08d..bc9a13f 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,7 @@ docker-compose.override.yml *.p12 *.pfx *.jks +*.keystore *.crt *.cert secrets/ diff --git a/docker/oracle/init.sql b/docker/oracle/init.sql index 2eb356e..7923b00 100644 --- a/docker/oracle/init.sql +++ b/docker/oracle/init.sql @@ -28,17 +28,17 @@ GRANT CREATE SESSION TO "&MONITOR_APP_USER"; GRANT SELECT ON SYS.V_$DATABASE TO "&MONITOR_APP_USER"; GRANT SELECT ON SYS.V_$LOG TO "&MONITOR_APP_USER"; GRANT SELECT ON SYS.V_$LOGFILE TO "&MONITOR_APP_USER"; -GRANT SELECT ON SYS.V_$LOGMNR_CONTENTS TO monitor_app; -GRANT SELECT ON SYS.V_$ARCHIVED_LOG TO monitor_app; -GRANT SELECT ON SYS.V_$TRANSACTION TO monitor_app; +GRANT SELECT ON SYS.V_$LOGMNR_CONTENTS TO "&MONITOR_APP_USER"; +GRANT SELECT ON SYS.V_$ARCHIVED_LOG TO "&MONITOR_APP_USER"; +GRANT SELECT ON SYS.V_$TRANSACTION TO "&MONITOR_APP_USER"; -- Switch to the application schema -ALTER SESSION SET CURRENT_SCHEMA = monitor_app; +ALTER SESSION SET CURRENT_SCHEMA = "&MONITOR_APP_USER"; -- ============================================================================= -- Table: LOG_TRAZA -- ============================================================================= -CREATE TABLE monitor_app.log_traza ( +CREATE TABLE log_traza ( id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, error_code VARCHAR2(50) NOT NULL, error_msg VARCHAR2(4000) NOT NULL, @@ -51,7 +51,7 @@ CREATE TABLE monitor_app.log_traza ( -- ============================================================================= -- Table: CRITICAL_OUTBOX -- ============================================================================= -CREATE TABLE monitor_app.critical_outbox ( +CREATE TABLE critical_outbox ( id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, attempts NUMBER DEFAULT 0 NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, @@ -70,17 +70,17 @@ CREATE TABLE monitor_app.critical_outbox ( ); -- Enable row-level supplemental logging -ALTER TABLE monitor_app.log_traza ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS; -ALTER TABLE monitor_app.critical_outbox ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS; +ALTER TABLE log_traza ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS; +ALTER TABLE critical_outbox ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS; -- Seed data -INSERT INTO monitor_app.log_traza (error_code, error_msg, severity, source_app) +INSERT INTO log_traza (error_code, error_msg, severity, source_app) VALUES ('APP-001', 'Scheduled job completed successfully', 'INFO', 'batch-processor'); -INSERT INTO monitor_app.log_traza (error_code, error_msg, severity, source_app) +INSERT INTO log_traza (error_code, error_msg, severity, source_app) VALUES ('APP-002', 'Connection pool near capacity (85%)', 'WARNING', 'api-gateway'); -INSERT INTO monitor_app.log_traza (error_code, error_msg, severity, source_app) +INSERT INTO log_traza (error_code, error_msg, severity, source_app) VALUES ('APP-003', 'Unable to reach secondary Oracle node', 'CRITICAL', 'data-sync-daemon'); COMMIT; diff --git a/pom.xml b/pom.xml index b867537..c3012c9 100644 --- a/pom.xml +++ b/pom.xml @@ -98,10 +98,6 @@ - - org.springframework.boot - spring-boot-starter-security - org.springframework.boot spring-boot-starter-aop From e79bfdeac5afc6a9236a29965ce92336d36241d1 Mon Sep 17 00:00:00 2001 From: "Caro R. Pereira" Date: Thu, 2 Apr 2026 14:44:49 -0300 Subject: [PATCH 2/8] fix(security): reduce docker host exposure and define kafka tls rollout --- docker-compose.yml | 14 ++--- .../kafka-tls-sasl-hardening-plan.md | 62 +++++++++++++++++++ 2 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 docs/operations/kafka-tls-sasl-hardening-plan.md diff --git a/docker-compose.yml b/docker-compose.yml index fc5290f..27a597e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: MONITOR_APP_USER: ${MONITOR_APP_USER:-monitor_app} MONITOR_APP_PASS: ${MONITOR_APP_PASSWORD} ports: - - "${ORACLE_PORT:-1521}:1521" + - "${ORACLE_BIND_HOST:-127.0.0.1}:${ORACLE_PORT:-1521}:1521" volumes: - oracle-data:/opt/oracle/oradata - ./docker/oracle/init.sql:/container-entrypoint-initdb.d/01-init.sql:ro @@ -50,7 +50,7 @@ services: depends_on: - zookeeper ports: - - "${KAFKA_PORT:-9092}:9092" + - "${KAFKA_BIND_HOST:-127.0.0.1}:${KAFKA_PORT:-9092}:9092" networks: - monitor-net environment: @@ -90,7 +90,7 @@ services: CONNECT_KEY_CONVERTER_SCHEMAS_ENABLE: "false" CONNECT_VALUE_CONVERTER_SCHEMAS_ENABLE: "false" ports: - - "${CONNECT_PORT:-8083}:8083" + - "${CONNECT_BIND_HOST:-127.0.0.1}:${CONNECT_PORT:-8083}:8083" networks: - monitor-net healthcheck: @@ -106,8 +106,8 @@ services: container_name: monitor-mailhog restart: unless-stopped ports: - - "1025:1025" - - "8025:8025" + - "${MAILHOG_BIND_HOST:-127.0.0.1}:${MAILHOG_SMTP_PORT:-1025}:1025" + - "${MAILHOG_BIND_HOST:-127.0.0.1}:${MAILHOG_UI_PORT:-8025}:8025" networks: - monitor-net @@ -119,7 +119,7 @@ services: container_name: monitor-backend restart: unless-stopped ports: - - "8080:8080" + - "${BACKEND_BIND_HOST:-127.0.0.1}:${BACKEND_PORT:-8080}:8080" environment: SPRING_PROFILES_ACTIVE: prod KAFKA_BOOTSTRAP_SERVERS: kafka:29092 @@ -157,7 +157,7 @@ services: container_name: monitor-frontend restart: unless-stopped ports: - - "3000:3000" + - "${FRONTEND_BIND_HOST:-127.0.0.1}:${FRONTEND_PORT:-3000}:3000" depends_on: backend: condition: service_healthy diff --git a/docs/operations/kafka-tls-sasl-hardening-plan.md b/docs/operations/kafka-tls-sasl-hardening-plan.md new file mode 100644 index 0000000..638f835 --- /dev/null +++ b/docs/operations/kafka-tls-sasl-hardening-plan.md @@ -0,0 +1,62 @@ +# Kafka TLS/SASL Hardening Plan (Hallazgo #12) + +## Objective +Migrate local and production Kafka traffic from PLAINTEXT to authenticated and encrypted transport, without breaking Debezium and Spring Boot consumers. + +## Scope +- Service: Kafka broker in docker-compose stack +- Clients: Debezium Kafka Connect, Spring Boot backend, optional local CLI tools +- Security target: TLS encryption + SASL/SCRAM authentication + +## Non-goals +- No direct cutover in this phase 3 patch set +- No certificate authority automation in this document + +## Current risk +Current stack uses PLAINTEXT listeners and no SASL authentication. Any actor with network access can read or inject messages. + +## Proposed rollout +1. Add secure listener profile in compose +- Keep existing PLAINTEXT listener for migration window +- Add parallel SASL_SSL listener and broker certificates + +2. Provision credentials and trust material +- Generate broker keystore and truststore +- Generate client truststore for backend and Debezium +- Configure SCRAM users per client + +3. Migrate clients one by one +- Debezium connect worker: set SASL/SCRAM and truststore +- Spring Boot app: set spring.kafka properties for SASL_SSL +- Validate topic read/write and connector offsets + +4. Remove PLAINTEXT listener +- Remove legacy listener from advertised listeners +- Keep only SASL_SSL after all clients pass health checks + +## Required environment variables +- KAFKA_SECURITY_MODE=sasl_ssl +- KAFKA_SASL_MECHANISM=SCRAM-SHA-512 +- KAFKA_USERNAME +- KAFKA_PASSWORD +- KAFKA_TRUSTSTORE_PATH +- KAFKA_TRUSTSTORE_PASSWORD +- KAFKA_KEYSTORE_PATH +- KAFKA_KEYSTORE_PASSWORD + +## Validation checklist +- Unauthorized consumer fails to connect +- Authorized producer and consumer can exchange messages +- Debezium connector remains healthy and advances offsets +- Backend starts cleanly and receives CDC events +- Packet capture does not expose plaintext payload + +## Rollback plan +- Re-enable PLAINTEXT listener profile +- Revert client security properties to previous values +- Restart broker, connect, and backend in sequence + +## Ownership +- Infra: compose and certificate handling +- Backend: Spring Kafka client configuration +- Data pipeline: Debezium configuration migration From 6115e59eebb3371a142e1832f48500d8995e1482 Mon Sep 17 00:00:00 2001 From: "Caro R. Pereira" Date: Thu, 2 Apr 2026 14:49:09 -0300 Subject: [PATCH 3/8] feat(security): add optional kafka sasl-ssl deployment mode --- .gitignore | 6 +++ docker-compose.secure.yml | 51 ++++++++++++++++++++ docker/kafka/secrets/.gitkeep | 0 docker/kafka/secrets/README.md | 16 ++++++ docs/operations/kafka-sasl-ssl-quickstart.md | 43 +++++++++++++++++ src/main/resources/application.yml | 7 +++ 6 files changed, 123 insertions(+) create mode 100644 docker-compose.secure.yml create mode 100644 docker/kafka/secrets/.gitkeep create mode 100644 docker/kafka/secrets/README.md create mode 100644 docs/operations/kafka-sasl-ssl-quickstart.md diff --git a/.gitignore b/.gitignore index bc9a13f..94a20e4 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,12 @@ docker-compose.override.yml secrets/ .vault-token .vault/ +!docker/ +!docker/kafka/ +!docker/kafka/secrets/ +docker/kafka/secrets/* +!docker/kafka/secrets/.gitkeep +!docker/kafka/secrets/README.md # Security tooling # dependency-check-report.html (ignored below in Reports section) diff --git a/docker-compose.secure.yml b/docker-compose.secure.yml new file mode 100644 index 0000000..705325e --- /dev/null +++ b/docker-compose.secure.yml @@ -0,0 +1,51 @@ +services: + kafka: + ports: + - "${KAFKA_BIND_HOST:-127.0.0.1}:${KAFKA_PORT:-9092}:9092" + volumes: + - ./docker/kafka/secrets:/etc/kafka/secrets:ro + environment: + KAFKA_LISTENERS: SASL_SSL://0.0.0.0:29092,SASL_SSL_HOST://0.0.0.0:9092 + KAFKA_ADVERTISED_LISTENERS: SASL_SSL://kafka:29092,SASL_SSL_HOST://localhost:9092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: SASL_SSL:SASL_SSL,SASL_SSL_HOST:SASL_SSL + KAFKA_INTER_BROKER_LISTENER_NAME: SASL_SSL + KAFKA_SASL_ENABLED_MECHANISMS: SCRAM-SHA-512 + KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL: SCRAM-SHA-512 + KAFKA_SSL_CLIENT_AUTH: required + KAFKA_SSL_KEYSTORE_FILENAME: ${KAFKA_SSL_KEYSTORE_FILENAME:-kafka.keystore.jks} + KAFKA_SSL_KEYSTORE_CREDENTIALS: ${KAFKA_SSL_KEYSTORE_CREDENTIALS:-kafka_keystore_creds} + KAFKA_SSL_KEY_CREDENTIALS: ${KAFKA_SSL_KEY_CREDENTIALS:-kafka_sslkey_creds} + KAFKA_SSL_TRUSTSTORE_FILENAME: ${KAFKA_SSL_TRUSTSTORE_FILENAME:-kafka.truststore.jks} + KAFKA_SSL_TRUSTSTORE_CREDENTIALS: ${KAFKA_SSL_TRUSTSTORE_CREDENTIALS:-kafka_truststore_creds} + + kafka-connect: + environment: + BOOTSTRAP_SERVERS: kafka:29092 + CONNECT_SECURITY_PROTOCOL: SASL_SSL + CONNECT_SASL_MECHANISM: SCRAM-SHA-512 + CONNECT_SASL_JAAS_CONFIG: org.apache.kafka.common.security.scram.ScramLoginModule required username="${KAFKA_CONNECT_USERNAME}" password="${KAFKA_CONNECT_PASSWORD}"; + CONNECT_SSL_TRUSTSTORE_LOCATION: /etc/kafka/secrets/connect.truststore.jks + CONNECT_SSL_TRUSTSTORE_PASSWORD: ${KAFKA_CONNECT_TRUSTSTORE_PASSWORD} + CONNECT_PRODUCER_SECURITY_PROTOCOL: SASL_SSL + CONNECT_PRODUCER_SASL_MECHANISM: SCRAM-SHA-512 + CONNECT_PRODUCER_SASL_JAAS_CONFIG: org.apache.kafka.common.security.scram.ScramLoginModule required username="${KAFKA_CONNECT_USERNAME}" password="${KAFKA_CONNECT_PASSWORD}"; + CONNECT_PRODUCER_SSL_TRUSTSTORE_LOCATION: /etc/kafka/secrets/connect.truststore.jks + CONNECT_PRODUCER_SSL_TRUSTSTORE_PASSWORD: ${KAFKA_CONNECT_TRUSTSTORE_PASSWORD} + CONNECT_CONSUMER_SECURITY_PROTOCOL: SASL_SSL + CONNECT_CONSUMER_SASL_MECHANISM: SCRAM-SHA-512 + CONNECT_CONSUMER_SASL_JAAS_CONFIG: org.apache.kafka.common.security.scram.ScramLoginModule required username="${KAFKA_CONNECT_USERNAME}" password="${KAFKA_CONNECT_PASSWORD}"; + CONNECT_CONSUMER_SSL_TRUSTSTORE_LOCATION: /etc/kafka/secrets/connect.truststore.jks + CONNECT_CONSUMER_SSL_TRUSTSTORE_PASSWORD: ${KAFKA_CONNECT_TRUSTSTORE_PASSWORD} + volumes: + - ./docker/kafka/secrets:/etc/kafka/secrets:ro + + backend: + environment: + KAFKA_BOOTSTRAP_SERVERS: kafka:29092 + KAFKA_SECURITY_PROTOCOL: SASL_SSL + KAFKA_SASL_MECHANISM: SCRAM-SHA-512 + KAFKA_SASL_JAAS_CONFIG: org.apache.kafka.common.security.scram.ScramLoginModule required username="${KAFKA_BACKEND_USERNAME}" password="${KAFKA_BACKEND_PASSWORD}"; + KAFKA_SSL_TRUSTSTORE_LOCATION: /etc/kafka/secrets/backend.truststore.jks + KAFKA_SSL_TRUSTSTORE_PASSWORD: ${KAFKA_BACKEND_TRUSTSTORE_PASSWORD} + volumes: + - ./docker/kafka/secrets:/etc/kafka/secrets:ro diff --git a/docker/kafka/secrets/.gitkeep b/docker/kafka/secrets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker/kafka/secrets/README.md b/docker/kafka/secrets/README.md new file mode 100644 index 0000000..41feba6 --- /dev/null +++ b/docker/kafka/secrets/README.md @@ -0,0 +1,16 @@ +# Kafka Security Secrets (Local) + +This directory stores local TLS/SASL materials used by `docker-compose.secure.yml`. + +Do not commit private keys, truststores, or credentials. + +Expected files: +- kafka.keystore.jks +- kafka.truststore.jks +- connect.truststore.jks +- backend.truststore.jks +- kafka_keystore_creds +- kafka_sslkey_creds +- kafka_truststore_creds + +These files are ignored by `.gitignore` (except this README and `.gitkeep`). diff --git a/docs/operations/kafka-sasl-ssl-quickstart.md b/docs/operations/kafka-sasl-ssl-quickstart.md new file mode 100644 index 0000000..c8d6b6e --- /dev/null +++ b/docs/operations/kafka-sasl-ssl-quickstart.md @@ -0,0 +1,43 @@ +# Kafka SASL/SSL Quickstart + +## Purpose +Enable encrypted and authenticated Kafka traffic using `docker-compose.secure.yml` on top of the default stack. + +## 1) Prepare secrets +Place truststores/keystores and credential files under: +- `docker/kafka/secrets/` + +See required filenames in: +- `docker/kafka/secrets/README.md` + +## 2) Export required environment variables +Example: + +```bash +export KAFKA_CONNECT_USERNAME=connect-user +export KAFKA_CONNECT_PASSWORD=connect-password +export KAFKA_CONNECT_TRUSTSTORE_PASSWORD=changeit + +export KAFKA_BACKEND_USERNAME=backend-user +export KAFKA_BACKEND_PASSWORD=backend-password +export KAFKA_BACKEND_TRUSTSTORE_PASSWORD=changeit +``` + +## 3) Start secure stack + +```bash +docker compose -f docker-compose.yml -f docker-compose.secure.yml up -d +``` + +## 4) Verify secure mode +- Kafka broker listener map includes `SASL_SSL` +- Backend starts with `KAFKA_SECURITY_PROTOCOL=SASL_SSL` +- Debezium connect worker starts with SASL settings + +## 5) Rollback to plaintext + +```bash +docker compose -f docker-compose.yml up -d +``` + +This removes secure override and returns to base plaintext mode. diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e21fea2..e705e67 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -26,6 +26,13 @@ spring: auto-offset-reset: earliest key-deserializer: org.apache.kafka.common.serialization.StringDeserializer value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + properties: + security.protocol: ${KAFKA_SECURITY_PROTOCOL:PLAINTEXT} + sasl.mechanism: ${KAFKA_SASL_MECHANISM:} + sasl.jaas.config: ${KAFKA_SASL_JAAS_CONFIG:} + ssl.truststore.location: ${KAFKA_SSL_TRUSTSTORE_LOCATION:} + ssl.truststore.password: ${KAFKA_SSL_TRUSTSTORE_PASSWORD:} + ssl.endpoint.identification.algorithm: ${KAFKA_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM:HTTPS} # ─── Mail (SMTP with TLS – CWE-319: no cleartext transmission) ────────────── mail: From db53871315ea52105848f22277b1af259e6c09c6 Mon Sep 17 00:00:00 2001 From: "Caro R. Pereira" Date: Thu, 2 Apr 2026 14:53:09 -0300 Subject: [PATCH 4/8] feat(security): automate kafka tls and scram bootstrap workflow --- .env.example | 12 ++++ docs/operations/README.md | 3 +- docs/operations/kafka-sasl-ssl-quickstart.md | 16 +++++- scripts/kafka-bootstrap-scram-users.sh | 26 +++++++++ scripts/kafka-enable-secure-mode.sh | 24 ++++++++ scripts/kafka-generate-secrets.sh | 58 ++++++++++++++++++++ 6 files changed, 137 insertions(+), 2 deletions(-) create mode 100755 scripts/kafka-bootstrap-scram-users.sh create mode 100755 scripts/kafka-enable-secure-mode.sh create mode 100755 scripts/kafka-generate-secrets.sh diff --git a/.env.example b/.env.example index 962038b..314a184 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,18 @@ KAFKA_PORT=9092 CONNECT_PORT=8083 KAFKA_BOOTSTRAP_SERVERS=localhost:9092 +# ─── Kafka Secure Mode (optional SASL_SSL overlay) ────────────────────────── +KAFKA_SECURITY_PROTOCOL=PLAINTEXT +KAFKA_SASL_MECHANISM=SCRAM-SHA-512 +KAFKA_BACKEND_USERNAME=backend-user +KAFKA_BACKEND_PASSWORD= +KAFKA_CONNECT_USERNAME=connect-user +KAFKA_CONNECT_PASSWORD= +KAFKA_CONNECT_TRUSTSTORE_PASSWORD=changeit +KAFKA_BACKEND_TRUSTSTORE_PASSWORD=changeit +KAFKA_KEYSTORE_PASSWORD=changeit +KAFKA_TRUSTSTORE_PASSWORD=changeit + # ─── Database App User (init.sql) ─────────────────────────────────────────── MONITOR_APP_USER=monitor_app MONITOR_APP_PASSWORD= diff --git a/docs/operations/README.md b/docs/operations/README.md index 356856f..e8b4daf 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -22,6 +22,7 @@ It is intended for developers, operators, and support engineers who need repeata ### Index - `monitor-operations-runbook.md` - day-to-day operational procedures, smoke checks, and incident-oriented troubleshooting +- `kafka-sasl-ssl-quickstart.md` - secure Kafka enablement (TLS/SASL) with bootstrap scripts ### Scope @@ -42,7 +43,7 @@ The operations set covers: ### Resumen Ejecutivo Esta carpeta es el punto de entrada operativo para validar y diagnosticar `Monitor`. -Esta pensada para responder rapidamente una pregunta: **¿la plataforma esta sana, observable y conectada de extremo a extremo?** +Esta pensada para responder rapidamente una pregunta: **�la plataforma esta sana, observable y conectada de extremo a extremo?** ### Proposito diff --git a/docs/operations/kafka-sasl-ssl-quickstart.md b/docs/operations/kafka-sasl-ssl-quickstart.md index c8d6b6e..6735ab3 100644 --- a/docs/operations/kafka-sasl-ssl-quickstart.md +++ b/docs/operations/kafka-sasl-ssl-quickstart.md @@ -4,7 +4,13 @@ Enable encrypted and authenticated Kafka traffic using `docker-compose.secure.yml` on top of the default stack. ## 1) Prepare secrets -Place truststores/keystores and credential files under: +Generate truststores/keystores and credential files automatically: + +```bash +./scripts/kafka-generate-secrets.sh +``` + +Artifacts are written under: - `docker/kafka/secrets/` See required filenames in: @@ -26,9 +32,17 @@ export KAFKA_BACKEND_TRUSTSTORE_PASSWORD=changeit ## 3) Start secure stack ```bash +docker compose up -d kafka zookeeper +./scripts/kafka-bootstrap-scram-users.sh docker compose -f docker-compose.yml -f docker-compose.secure.yml up -d ``` +Or run the full flow in one command: + +```bash +./scripts/kafka-enable-secure-mode.sh +``` + ## 4) Verify secure mode - Kafka broker listener map includes `SASL_SSL` - Backend starts with `KAFKA_SECURITY_PROTOCOL=SASL_SSL` diff --git a/scripts/kafka-bootstrap-scram-users.sh b/scripts/kafka-bootstrap-scram-users.sh new file mode 100755 index 0000000..0c33674 --- /dev/null +++ b/scripts/kafka-bootstrap-scram-users.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +BACKEND_USER="${KAFKA_BACKEND_USERNAME:-backend-user}" +BACKEND_PASS="${KAFKA_BACKEND_PASSWORD:-backend-password}" +CONNECT_USER="${KAFKA_CONNECT_USERNAME:-connect-user}" +CONNECT_PASS="${KAFKA_CONNECT_PASSWORD:-connect-password}" + +# This script intentionally bootstraps SCRAM credentials against ZooKeeper while +# the stack is still in PLAINTEXT mode to avoid circular auth bootstrapping. +for USERNAME in "$BACKEND_USER" "$CONNECT_USER"; do + PASSWORD="$BACKEND_PASS" + if [[ "$USERNAME" == "$CONNECT_USER" ]]; then + PASSWORD="$CONNECT_PASS" + fi + + echo "Creating SCRAM credentials for user: $USERNAME" + docker compose exec -T kafka kafka-configs \ + --zookeeper zookeeper:2181 \ + --alter \ + --add-config "SCRAM-SHA-512=[iterations=4096,password=${PASSWORD}]" \ + --entity-type users \ + --entity-name "$USERNAME" +done + +echo "SCRAM users created. Next step: restart with docker-compose.secure.yml" diff --git a/scripts/kafka-enable-secure-mode.sh b/scripts/kafka-enable-secure-mode.sh new file mode 100755 index 0000000..90c4060 --- /dev/null +++ b/scripts/kafka-enable-secure-mode.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT_DIR" + +if [[ ! -f docker-compose.secure.yml ]]; then + echo "docker-compose.secure.yml not found" + exit 1 +fi + +echo "Step 1/4: generate TLS materials" +./scripts/kafka-generate-secrets.sh + +echo "Step 2/4: start base stack (plaintext bootstrap)" +docker compose up -d kafka zookeeper + +echo "Step 3/4: create SCRAM users" +./scripts/kafka-bootstrap-scram-users.sh + +echo "Step 4/4: apply secure override" +docker compose -f docker-compose.yml -f docker-compose.secure.yml up -d kafka kafka-connect backend + +echo "Secure mode applied. Validate with: docker compose ps" diff --git a/scripts/kafka-generate-secrets.sh b/scripts/kafka-generate-secrets.sh new file mode 100755 index 0000000..0f7d113 --- /dev/null +++ b/scripts/kafka-generate-secrets.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +set -euo pipefail + +SECRETS_DIR="docker/kafka/secrets" +DNAME="CN=monitor-kafka, OU=Monitor, O=Monitor, L=Local, ST=Local, C=US" +STOREPASS="${KAFKA_KEYSTORE_PASSWORD:-changeit}" +TRUSTPASS="${KAFKA_TRUSTSTORE_PASSWORD:-changeit}" +VALIDITY_DAYS="${KAFKA_CERT_VALIDITY_DAYS:-365}" + +mkdir -p "$SECRETS_DIR" + +# Clean previous artifacts to avoid stale/corrupt stores in repeated local runs. +rm -f "$SECRETS_DIR"/*.jks "$SECRETS_DIR"/*.crt "$SECRETS_DIR"/*.csr || true + +echo "[1/5] Generating broker keystore" +keytool -genkeypair \ + -alias kafka-broker \ + -keyalg RSA \ + -keysize 2048 \ + -validity "$VALIDITY_DAYS" \ + -dname "$DNAME" \ + -keystore "$SECRETS_DIR/kafka.keystore.jks" \ + -storepass "$STOREPASS" \ + -keypass "$STOREPASS" \ + -storetype JKS + +echo "[2/5] Exporting broker certificate" +keytool -exportcert \ + -alias kafka-broker \ + -keystore "$SECRETS_DIR/kafka.keystore.jks" \ + -storepass "$STOREPASS" \ + -rfc \ + -file "$SECRETS_DIR/kafka-broker.crt" + +echo "[3/5] Creating shared truststore" +keytool -importcert \ + -alias kafka-broker \ + -file "$SECRETS_DIR/kafka-broker.crt" \ + -keystore "$SECRETS_DIR/kafka.truststore.jks" \ + -storepass "$TRUSTPASS" \ + -noprompt + +echo "[4/5] Creating client truststores" +cp "$SECRETS_DIR/kafka.truststore.jks" "$SECRETS_DIR/connect.truststore.jks" +cp "$SECRETS_DIR/kafka.truststore.jks" "$SECRETS_DIR/backend.truststore.jks" + +echo "[5/5] Writing credential helper files" +printf '%s\n' "$STOREPASS" > "$SECRETS_DIR/kafka_keystore_creds" +printf '%s\n' "$STOREPASS" > "$SECRETS_DIR/kafka_sslkey_creds" +printf '%s\n' "$TRUSTPASS" > "$SECRETS_DIR/kafka_truststore_creds" + +chmod 600 \ + "$SECRETS_DIR/kafka_keystore_creds" \ + "$SECRETS_DIR/kafka_sslkey_creds" \ + "$SECRETS_DIR/kafka_truststore_creds" + +echo "Kafka TLS artifacts generated under $SECRETS_DIR" +echo "Remember to export KAFKA_CONNECT_TRUSTSTORE_PASSWORD and KAFKA_BACKEND_TRUSTSTORE_PASSWORD to match $TRUSTPASS" From 4e246739dacbe1f1b83d1477493dba30d4eb31e3 Mon Sep 17 00:00:00 2001 From: "Caro R. Pereira" Date: Thu, 2 Apr 2026 14:56:03 -0300 Subject: [PATCH 5/8] feat(security): add kafka secure-mode preflight and troubleshooting --- docs/operations/README.md | 3 ++ docs/operations/kafka-sasl-ssl-quickstart.md | 10 ++++ .../kafka-sasl-ssl-troubleshooting.md | 53 +++++++++++++++++++ scripts/kafka-enable-secure-mode.sh | 5 +- scripts/kafka-secure-preflight.sh | 53 +++++++++++++++++++ 5 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 docs/operations/kafka-sasl-ssl-troubleshooting.md create mode 100755 scripts/kafka-secure-preflight.sh diff --git a/docs/operations/README.md b/docs/operations/README.md index e8b4daf..c51470d 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -23,6 +23,7 @@ It is intended for developers, operators, and support engineers who need repeata - `monitor-operations-runbook.md` - day-to-day operational procedures, smoke checks, and incident-oriented troubleshooting - `kafka-sasl-ssl-quickstart.md` - secure Kafka enablement (TLS/SASL) with bootstrap scripts +- `kafka-sasl-ssl-troubleshooting.md` - failure patterns and diagnostics for Kafka secure mode ### Scope @@ -53,6 +54,8 @@ Esta pensada para desarrolladores, operadores y personal de soporte que necesite ### Indice - `monitor-operations-runbook.md` - procedimientos operativos diarios, smoke checks y troubleshooting orientado a incidentes +- `kafka-sasl-ssl-quickstart.md` - habilitacion de Kafka seguro (TLS/SASL) con scripts de bootstrap +- `kafka-sasl-ssl-troubleshooting.md` - guia de diagnostico para fallas de Kafka en modo seguro ### Alcance diff --git a/docs/operations/kafka-sasl-ssl-quickstart.md b/docs/operations/kafka-sasl-ssl-quickstart.md index 6735ab3..dddbef8 100644 --- a/docs/operations/kafka-sasl-ssl-quickstart.md +++ b/docs/operations/kafka-sasl-ssl-quickstart.md @@ -43,6 +43,16 @@ Or run the full flow in one command: ./scripts/kafka-enable-secure-mode.sh ``` +## Troubleshooting preflight + +Run explicit prerequisite checks before secure activation: + +```bash +./scripts/kafka-secure-preflight.sh +``` + +If preflight fails, fix missing env vars/files and rerun. + ## 4) Verify secure mode - Kafka broker listener map includes `SASL_SSL` - Backend starts with `KAFKA_SECURITY_PROTOCOL=SASL_SSL` diff --git a/docs/operations/kafka-sasl-ssl-troubleshooting.md b/docs/operations/kafka-sasl-ssl-troubleshooting.md new file mode 100644 index 0000000..641c5be --- /dev/null +++ b/docs/operations/kafka-sasl-ssl-troubleshooting.md @@ -0,0 +1,53 @@ +# Kafka SASL/SSL Troubleshooting + +## Common failure patterns + +### 1) Authentication failed (SASL) +Symptoms: +- Backend or connect logs show authentication exceptions +- Kafka client cannot establish session + +Checks: +- Verify usernames/passwords exported in environment +- Re-run SCRAM bootstrap script: + - ./scripts/kafka-bootstrap-scram-users.sh +- Ensure secure overlay is active: + - docker compose -f docker-compose.yml -f docker-compose.secure.yml ps + +### 2) SSL handshake failure +Symptoms: +- javax.net.ssl.SSLHandshakeException +- PKIX path building failed + +Checks: +- Verify truststore files exist under docker/kafka/secrets +- Verify truststore passwords match env values +- Regenerate artifacts if in doubt: + - ./scripts/kafka-generate-secrets.sh + +### 3) Broker starts but clients fail +Symptoms: +- Kafka service healthy, backend/connect unhealthy + +Checks: +- Run preflight: + - ./scripts/kafka-secure-preflight.sh +- Verify KAFKA_SECURITY_PROTOCOL is SASL_SSL in backend env +- Verify connect env includes CONNECT_SECURITY_PROTOCOL=SASL_SSL + +### 4) Secure mode rollout stuck +Recovery path: +1. Roll back to plaintext: + - docker compose -f docker-compose.yml up -d +2. Recreate credentials and secrets +3. Re-run secure activation flow + +## Quick diagnostics +- Container state: + - docker compose ps +- Backend logs: + - docker compose logs --tail=200 backend +- Kafka connect logs: + - docker compose logs --tail=200 kafka-connect +- Kafka logs: + - docker compose logs --tail=200 kafka diff --git a/scripts/kafka-enable-secure-mode.sh b/scripts/kafka-enable-secure-mode.sh index 90c4060..25a8a2c 100755 --- a/scripts/kafka-enable-secure-mode.sh +++ b/scripts/kafka-enable-secure-mode.sh @@ -18,7 +18,10 @@ docker compose up -d kafka zookeeper echo "Step 3/4: create SCRAM users" ./scripts/kafka-bootstrap-scram-users.sh -echo "Step 4/4: apply secure override" +echo "Step 4/5: preflight check" +./scripts/kafka-secure-preflight.sh + +echo "Step 5/5: apply secure override" docker compose -f docker-compose.yml -f docker-compose.secure.yml up -d kafka kafka-connect backend echo "Secure mode applied. Validate with: docker compose ps" diff --git a/scripts/kafka-secure-preflight.sh b/scripts/kafka-secure-preflight.sh new file mode 100755 index 0000000..2ae4f1e --- /dev/null +++ b/scripts/kafka-secure-preflight.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +SECRETS_DIR="$ROOT_DIR/docker/kafka/secrets" + +required_env=( + KAFKA_BACKEND_USERNAME + KAFKA_BACKEND_PASSWORD + KAFKA_CONNECT_USERNAME + KAFKA_CONNECT_PASSWORD + KAFKA_CONNECT_TRUSTSTORE_PASSWORD + KAFKA_BACKEND_TRUSTSTORE_PASSWORD +) + +required_files=( + kafka.keystore.jks + kafka.truststore.jks + connect.truststore.jks + backend.truststore.jks + kafka_keystore_creds + kafka_sslkey_creds + kafka_truststore_creds +) + +missing=0 + +echo "Checking required environment variables..." +for var in "${required_env[@]}"; do + if [[ -z "${!var:-}" ]]; then + echo " [MISSING] $var" + missing=1 + else + echo " [OK] $var" + fi +done + +echo "Checking required secret files under docker/kafka/secrets/..." +for file in "${required_files[@]}"; do + if [[ ! -f "$SECRETS_DIR/$file" ]]; then + echo " [MISSING] docker/kafka/secrets/$file" + missing=1 + else + echo " [OK] docker/kafka/secrets/$file" + fi +done + +if [[ $missing -ne 0 ]]; then + echo "Preflight failed. Fill missing env vars/files before secure mode activation." + exit 1 +fi + +echo "Preflight passed. Secure mode prerequisites are complete." From 73354f09c3a62f49606ae225a7780c5b246ec95e Mon Sep 17 00:00:00 2001 From: "Caro R. Pereira" Date: Thu, 2 Apr 2026 15:20:32 -0300 Subject: [PATCH 6/8] feat(security): add kafka secure-mode smoke test and acceptance checklist --- docs/operations/README.md | 2 + .../kafka-sasl-ssl-acceptance-checklist.md | 34 +++++++++ docs/operations/kafka-sasl-ssl-quickstart.md | 6 ++ scripts/kafka-secure-smoke.sh | 72 +++++++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 docs/operations/kafka-sasl-ssl-acceptance-checklist.md create mode 100755 scripts/kafka-secure-smoke.sh diff --git a/docs/operations/README.md b/docs/operations/README.md index c51470d..0293adf 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -24,6 +24,7 @@ It is intended for developers, operators, and support engineers who need repeata - `monitor-operations-runbook.md` - day-to-day operational procedures, smoke checks, and incident-oriented troubleshooting - `kafka-sasl-ssl-quickstart.md` - secure Kafka enablement (TLS/SASL) with bootstrap scripts - `kafka-sasl-ssl-troubleshooting.md` - failure patterns and diagnostics for Kafka secure mode +- `kafka-sasl-ssl-acceptance-checklist.md` - release checklist to close secure Kafka rollout ### Scope @@ -56,6 +57,7 @@ Esta pensada para desarrolladores, operadores y personal de soporte que necesite - `monitor-operations-runbook.md` - procedimientos operativos diarios, smoke checks y troubleshooting orientado a incidentes - `kafka-sasl-ssl-quickstart.md` - habilitacion de Kafka seguro (TLS/SASL) con scripts de bootstrap - `kafka-sasl-ssl-troubleshooting.md` - guia de diagnostico para fallas de Kafka en modo seguro +- `kafka-sasl-ssl-acceptance-checklist.md` - checklist de aceptacion para cierre operativo de Kafka seguro ### Alcance diff --git a/docs/operations/kafka-sasl-ssl-acceptance-checklist.md b/docs/operations/kafka-sasl-ssl-acceptance-checklist.md new file mode 100644 index 0000000..74e303e --- /dev/null +++ b/docs/operations/kafka-sasl-ssl-acceptance-checklist.md @@ -0,0 +1,34 @@ +# Kafka SASL/SSL Acceptance Checklist + +Use this checklist to declare Hallazgo #12 operationally closed. + +## Preconditions +- [ ] `.env` contains secure Kafka credentials +- [ ] TLS artifacts exist under `docker/kafka/secrets/` +- [ ] Preflight passes: `./scripts/kafka-secure-preflight.sh` + +## Activation +- [ ] Secure mode applied: `./scripts/kafka-enable-secure-mode.sh` +- [ ] Overlay is active: `docker compose -f docker-compose.yml -f docker-compose.secure.yml ps` + +## Smoke validation +- [ ] `./scripts/kafka-secure-smoke.sh` exits with code 0 +- [ ] `kafka`, `kafka-connect`, `backend` are running +- [ ] Backend env has `KAFKA_SECURITY_PROTOCOL=SASL_SSL` +- [ ] Connect env has `CONNECT_SECURITY_PROTOCOL=SASL_SSL` +- [ ] Recent logs have no TLS/SASL handshake/auth failures + +## Functional validation +- [ ] Debezium connector stays healthy +- [ ] CDC events still arrive to backend pipeline +- [ ] No regression on SSE event delivery + +## Rollback validated +- [ ] Plaintext fallback works: `docker compose -f docker-compose.yml up -d` +- [ ] Services recover after rollback + +## Evidence to attach in PR/issue +- [ ] `docker compose ps` output (secure mode) +- [ ] `./scripts/kafka-secure-preflight.sh` output +- [ ] `./scripts/kafka-secure-smoke.sh` output +- [ ] relevant logs for kafka/connect/backend diff --git a/docs/operations/kafka-sasl-ssl-quickstart.md b/docs/operations/kafka-sasl-ssl-quickstart.md index dddbef8..f732eeb 100644 --- a/docs/operations/kafka-sasl-ssl-quickstart.md +++ b/docs/operations/kafka-sasl-ssl-quickstart.md @@ -58,6 +58,12 @@ If preflight fails, fix missing env vars/files and rerun. - Backend starts with `KAFKA_SECURITY_PROTOCOL=SASL_SSL` - Debezium connect worker starts with SASL settings +Run automated smoke check: + +```bash +./scripts/kafka-secure-smoke.sh +``` + ## 5) Rollback to plaintext ```bash diff --git a/scripts/kafka-secure-smoke.sh b/scripts/kafka-secure-smoke.sh new file mode 100755 index 0000000..eb093ff --- /dev/null +++ b/scripts/kafka-secure-smoke.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT_DIR" + +services=(kafka kafka-connect backend) +failed=0 + +echo "[1/4] Checking container status" +for svc in "${services[@]}"; do + if ! docker compose ps "$svc" >/dev/null 2>&1; then + echo " [FAIL] service not found: $svc" + failed=1 + continue + fi + + status="$(docker compose ps --format json "$svc" | tr -d '\n' | sed 's/}\s*{/}\n{/g' | grep -m1 '"Service":"'"$svc"'"' || true)" + if [[ -z "$status" ]]; then + echo " [FAIL] unable to read status: $svc" + failed=1 + continue + fi + + if echo "$status" | grep -q '"State":"running"'; then + echo " [OK] $svc running" + else + echo " [FAIL] $svc not running" + failed=1 + fi +done + +echo "[2/4] Verifying secure env in backend" +backend_env="$(docker compose exec -T backend /bin/sh -lc 'env' 2>/dev/null || true)" +if echo "$backend_env" | grep -q '^KAFKA_SECURITY_PROTOCOL=SASL_SSL$'; then + echo " [OK] backend KAFKA_SECURITY_PROTOCOL=SASL_SSL" +else + echo " [FAIL] backend missing KAFKA_SECURITY_PROTOCOL=SASL_SSL" + failed=1 +fi + +echo "[3/4] Verifying secure env in kafka-connect" +connect_env="$(docker compose exec -T kafka-connect /bin/sh -lc 'env' 2>/dev/null || true)" +if echo "$connect_env" | grep -q '^CONNECT_SECURITY_PROTOCOL=SASL_SSL$'; then + echo " [OK] connect CONNECT_SECURITY_PROTOCOL=SASL_SSL" +else + echo " [FAIL] connect missing CONNECT_SECURITY_PROTOCOL=SASL_SSL" + failed=1 +fi + +echo "[4/4] Scanning logs for obvious auth/ssl failures" +kafka_logs="$(docker compose logs --tail=200 kafka 2>/dev/null || true)" +connect_logs="$(docker compose logs --tail=200 kafka-connect 2>/dev/null || true)" +backend_logs="$(docker compose logs --tail=200 backend 2>/dev/null || true)" +combined_logs="$kafka_logs +$connect_logs +$backend_logs" + +if echo "$combined_logs" | grep -Eiq 'SSLHandshakeException|SaslAuthenticationException|PKIX path building failed|No route to host|Connection refused'; then + echo " [WARN] suspicious errors detected in recent logs" + echo " inspect with: docker compose logs --tail=200 kafka kafka-connect backend" + failed=1 +else + echo " [OK] no obvious TLS/SASL errors in recent logs" +fi + +if [[ "$failed" -ne 0 ]]; then + echo "Secure smoke test FAILED" + exit 1 +fi + +echo "Secure smoke test PASSED" From 78632cc5604bdd08f1aa6d5de20d92661cf052b5 Mon Sep 17 00:00:00 2001 From: "Caro R. Pereira" Date: Thu, 2 Apr 2026 15:26:16 -0300 Subject: [PATCH 7/8] docs(security): publish hardening readiness report for findings 8-14 --- docs/README.md | 10 +++ docs/reports/SECURITY-HARDENING-READINESS.md | 84 ++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 docs/reports/SECURITY-HARDENING-READINESS.md diff --git a/docs/README.md b/docs/README.md index ca65dfd..b22ff89 100644 --- a/docs/README.md +++ b/docs/README.md @@ -47,6 +47,11 @@ It is designed to serve three audiences: - `docs/operations/README.md` - operations index - `docs/operations/monitor-operations-runbook.md` - operational runbook +#### Security Reports + +- `docs/reports/SECURITY-AUDIT.md` - baseline security assessment and findings catalog +- `docs/reports/SECURITY-HARDENING-READINESS.md` - current closure/readiness status for findings #8-#14 + ### Recommended Reading Paths #### For executives or architects @@ -134,6 +139,11 @@ Esta dise�ado para tres audiencias: - `docs/operations/README.md` - indice operativo - `docs/operations/monitor-operations-runbook.md` - runbook operativo +#### Reportes de Seguridad + +- `docs/reports/SECURITY-AUDIT.md` - evaluacion base de seguridad y catalogo de hallazgos +- `docs/reports/SECURITY-HARDENING-READINESS.md` - estado actual de cierre/readiness para hallazgos #8-#14 + ### Rutas de Lectura Recomendadas #### Para ejecutivos o arquitectos diff --git a/docs/reports/SECURITY-HARDENING-READINESS.md b/docs/reports/SECURITY-HARDENING-READINESS.md new file mode 100644 index 0000000..93b63fc --- /dev/null +++ b/docs/reports/SECURITY-HARDENING-READINESS.md @@ -0,0 +1,84 @@ +# Security Hardening Readiness Report + +Date: 2026-04-02 +Scope: Remaining findings from SECURITY-AUDIT.md (#8 to #14) +Target branch: develop + +## Executive Summary +Security hardening phases for findings #8 to #14 were implemented across CI/CD, backend configuration, Docker runtime exposure, Kafka secure-mode rollout, and operational controls. + +Readiness conclusion: +- Develop environment: READY with compensating controls +- Production promotion: CONDITIONALLY READY, requires secure-mode enforcement decision for Kafka (mandatory SASL_SSL cutover) + +## Findings Status Matrix + +| Finding | Severity | Status | Evidence | +|---|---|---|---| +| #8 (deploy pipeline skips tests) | HIGH | Closed | `.github/workflows/deploy.yml` uses `mvn --batch-mode clean verify -Powasp` | +| #9 (Jackson hardening) | MEDIUM | Closed | `src/main/java/com/monitor/config/JacksonConfig.java` enforces unknown-property fail and disables default typing | +| #10 (Oracle privilege/user hardcoding) | MEDIUM | Mitigated | `docker/oracle/init.sql` parameterized user/schema references, reduced hardcoded account coupling | +| #11 (Docker port exposure) | MEDIUM | Closed | `docker-compose.yml` binds exposed ports to localhost by default (`127.0.0.1`) | +| #12 (Kafka plaintext transport) | MEDIUM | Mitigated with rollout controls | `docker-compose.secure.yml` + secure scripts + preflight + smoke + acceptance checklist | +| #13 (dependency hygiene) | LOW | Closed | `pom.xml` duplicate security dependency removed | +| #14 (secret file protection) | LOW | Closed | `.gitignore` includes secret patterns and constrained exceptions for tracked placeholders | + +## Implemented Controls (Phase Trace) + +### CI/CD and dependency gates +- Production deploy workflow executes full verification with OWASP profile. +- Dependency check and enforcer are integrated in Maven build lifecycle. + +### Serialization hardening +- ObjectMapper hardened with: + - `FAIL_ON_UNKNOWN_PROPERTIES` + - `deactivateDefaultTyping()` + - restricted visibility configuration + +### Database/script hardening +- Oracle init script no longer depends on hardcoded schema/user references for grants and DDL context. +- Current schema is parameterized via `&MONITOR_APP_USER`. + +### Runtime exposure reduction +- Container ports default-bind to loopback host, reducing accidental external exposure in local/proxy-hosted environments. + +### Kafka secure transport rollout controls +Implemented artifacts: +- `docker-compose.secure.yml` +- `scripts/kafka-generate-secrets.sh` +- `scripts/kafka-bootstrap-scram-users.sh` +- `scripts/kafka-secure-preflight.sh` +- `scripts/kafka-enable-secure-mode.sh` +- `scripts/kafka-secure-smoke.sh` +- `docs/operations/kafka-sasl-ssl-quickstart.md` +- `docs/operations/kafka-sasl-ssl-troubleshooting.md` +- `docs/operations/kafka-sasl-ssl-acceptance-checklist.md` + +These controls establish an auditable path to run Kafka in SASL_SSL and validate it before promotion. + +## Residual Risk + +### Finding #12 residual risk +Base compose remains PLAINTEXT-compatible by default to preserve backward compatibility and low-risk local onboarding. This is acceptable for staged rollout but should not be the final production posture. + +Residual risk rating: Medium (operationally controlled, not yet hard-enforced by default) + +## Release Decision + +For develop: +- APPROVED + +For production: +- APPROVED WITH CONDITION + +Condition to fully close finding #12 in production: +1. Enforce secure overlay in production deployment path. +2. Remove PLAINTEXT listener usage from production runtime configuration. +3. Attach smoke/preflight evidence artifacts in release PR. + +## Evidence Checklist for Release PR +- Output of `./scripts/kafka-secure-preflight.sh` +- Output of `./scripts/kafka-secure-smoke.sh` +- `docker compose ps` with secure overlay +- Last 200 lines of logs for `kafka`, `kafka-connect`, `backend` +- Confirmation that deploy pipeline uses verify + OWASP profile From 2d52f09902bd0ee1afe7b59971bd375d9b540cf3 Mon Sep 17 00:00:00 2001 From: "Caro R. Pereira" Date: Thu, 2 Apr 2026 16:04:27 -0300 Subject: [PATCH 8/8] fix(security): stabilize kafka sasl_ssl bootstrap and tls readiness --- .env.example | 2 ++ docker-compose.secure.yml | 11 +++++++++++ scripts/kafka-bootstrap-scram-users.sh | 6 +++++- scripts/kafka-generate-secrets.sh | 25 ++++++++++++++++++++++++- scripts/kafka-secure-preflight.sh | 4 ++++ 5 files changed, 46 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 314a184..732fcdf 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,8 @@ KAFKA_BOOTSTRAP_SERVERS=localhost:9092 # ─── Kafka Secure Mode (optional SASL_SSL overlay) ────────────────────────── KAFKA_SECURITY_PROTOCOL=PLAINTEXT KAFKA_SASL_MECHANISM=SCRAM-SHA-512 +KAFKA_BROKER_USERNAME=broker-user +KAFKA_BROKER_PASSWORD= KAFKA_BACKEND_USERNAME=backend-user KAFKA_BACKEND_PASSWORD= KAFKA_CONNECT_USERNAME=connect-user diff --git a/docker-compose.secure.yml b/docker-compose.secure.yml index 705325e..7ee0701 100644 --- a/docker-compose.secure.yml +++ b/docker-compose.secure.yml @@ -11,12 +11,19 @@ services: KAFKA_INTER_BROKER_LISTENER_NAME: SASL_SSL KAFKA_SASL_ENABLED_MECHANISMS: SCRAM-SHA-512 KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL: SCRAM-SHA-512 + ZOOKEEPER_SASL_ENABLED: "false" + KAFKA_OPTS: -Djava.security.auth.login.config=/etc/kafka/secrets/kafka_server_jaas.conf -Dzookeeper.sasl.client=false KAFKA_SSL_CLIENT_AUTH: required KAFKA_SSL_KEYSTORE_FILENAME: ${KAFKA_SSL_KEYSTORE_FILENAME:-kafka.keystore.jks} KAFKA_SSL_KEYSTORE_CREDENTIALS: ${KAFKA_SSL_KEYSTORE_CREDENTIALS:-kafka_keystore_creds} KAFKA_SSL_KEY_CREDENTIALS: ${KAFKA_SSL_KEY_CREDENTIALS:-kafka_sslkey_creds} KAFKA_SSL_TRUSTSTORE_FILENAME: ${KAFKA_SSL_TRUSTSTORE_FILENAME:-kafka.truststore.jks} KAFKA_SSL_TRUSTSTORE_CREDENTIALS: ${KAFKA_SSL_TRUSTSTORE_CREDENTIALS:-kafka_truststore_creds} + healthcheck: + test: ["CMD-SHELL", "kafka-topics --bootstrap-server localhost:9092 --command-config /etc/kafka/secrets/kafka_admin_client.properties --list > /dev/null 2>&1 || exit 1"] + interval: 30s + timeout: 10s + retries: 5 kafka-connect: environment: @@ -26,16 +33,19 @@ services: CONNECT_SASL_JAAS_CONFIG: org.apache.kafka.common.security.scram.ScramLoginModule required username="${KAFKA_CONNECT_USERNAME}" password="${KAFKA_CONNECT_PASSWORD}"; CONNECT_SSL_TRUSTSTORE_LOCATION: /etc/kafka/secrets/connect.truststore.jks CONNECT_SSL_TRUSTSTORE_PASSWORD: ${KAFKA_CONNECT_TRUSTSTORE_PASSWORD} + CONNECT_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM: CONNECT_PRODUCER_SECURITY_PROTOCOL: SASL_SSL CONNECT_PRODUCER_SASL_MECHANISM: SCRAM-SHA-512 CONNECT_PRODUCER_SASL_JAAS_CONFIG: org.apache.kafka.common.security.scram.ScramLoginModule required username="${KAFKA_CONNECT_USERNAME}" password="${KAFKA_CONNECT_PASSWORD}"; CONNECT_PRODUCER_SSL_TRUSTSTORE_LOCATION: /etc/kafka/secrets/connect.truststore.jks CONNECT_PRODUCER_SSL_TRUSTSTORE_PASSWORD: ${KAFKA_CONNECT_TRUSTSTORE_PASSWORD} + CONNECT_PRODUCER_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM: CONNECT_CONSUMER_SECURITY_PROTOCOL: SASL_SSL CONNECT_CONSUMER_SASL_MECHANISM: SCRAM-SHA-512 CONNECT_CONSUMER_SASL_JAAS_CONFIG: org.apache.kafka.common.security.scram.ScramLoginModule required username="${KAFKA_CONNECT_USERNAME}" password="${KAFKA_CONNECT_PASSWORD}"; CONNECT_CONSUMER_SSL_TRUSTSTORE_LOCATION: /etc/kafka/secrets/connect.truststore.jks CONNECT_CONSUMER_SSL_TRUSTSTORE_PASSWORD: ${KAFKA_CONNECT_TRUSTSTORE_PASSWORD} + CONNECT_CONSUMER_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM: volumes: - ./docker/kafka/secrets:/etc/kafka/secrets:ro @@ -47,5 +57,6 @@ services: KAFKA_SASL_JAAS_CONFIG: org.apache.kafka.common.security.scram.ScramLoginModule required username="${KAFKA_BACKEND_USERNAME}" password="${KAFKA_BACKEND_PASSWORD}"; KAFKA_SSL_TRUSTSTORE_LOCATION: /etc/kafka/secrets/backend.truststore.jks KAFKA_SSL_TRUSTSTORE_PASSWORD: ${KAFKA_BACKEND_TRUSTSTORE_PASSWORD} + KAFKA_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM: volumes: - ./docker/kafka/secrets:/etc/kafka/secrets:ro diff --git a/scripts/kafka-bootstrap-scram-users.sh b/scripts/kafka-bootstrap-scram-users.sh index 0c33674..b206052 100755 --- a/scripts/kafka-bootstrap-scram-users.sh +++ b/scripts/kafka-bootstrap-scram-users.sh @@ -5,13 +5,17 @@ BACKEND_USER="${KAFKA_BACKEND_USERNAME:-backend-user}" BACKEND_PASS="${KAFKA_BACKEND_PASSWORD:-backend-password}" CONNECT_USER="${KAFKA_CONNECT_USERNAME:-connect-user}" CONNECT_PASS="${KAFKA_CONNECT_PASSWORD:-connect-password}" +BROKER_USER="${KAFKA_BROKER_USERNAME:-broker-user}" +BROKER_PASS="${KAFKA_BROKER_PASSWORD:-broker-password}" # This script intentionally bootstraps SCRAM credentials against ZooKeeper while # the stack is still in PLAINTEXT mode to avoid circular auth bootstrapping. -for USERNAME in "$BACKEND_USER" "$CONNECT_USER"; do +for USERNAME in "$BACKEND_USER" "$CONNECT_USER" "$BROKER_USER"; do PASSWORD="$BACKEND_PASS" if [[ "$USERNAME" == "$CONNECT_USER" ]]; then PASSWORD="$CONNECT_PASS" + elif [[ "$USERNAME" == "$BROKER_USER" ]]; then + PASSWORD="$BROKER_PASS" fi echo "Creating SCRAM credentials for user: $USERNAME" diff --git a/scripts/kafka-generate-secrets.sh b/scripts/kafka-generate-secrets.sh index 0f7d113..2d5a674 100755 --- a/scripts/kafka-generate-secrets.sh +++ b/scripts/kafka-generate-secrets.sh @@ -3,14 +3,17 @@ set -euo pipefail SECRETS_DIR="docker/kafka/secrets" DNAME="CN=monitor-kafka, OU=Monitor, O=Monitor, L=Local, ST=Local, C=US" +SUBJECT_ALT_NAMES="SAN=dns:kafka,dns:localhost,dns:monitor-kafka,ip:127.0.0.1" STOREPASS="${KAFKA_KEYSTORE_PASSWORD:-changeit}" TRUSTPASS="${KAFKA_TRUSTSTORE_PASSWORD:-changeit}" VALIDITY_DAYS="${KAFKA_CERT_VALIDITY_DAYS:-365}" +BROKER_USER="${KAFKA_BROKER_USERNAME:-broker-user}" +BROKER_PASS="${KAFKA_BROKER_PASSWORD:-broker-password}" mkdir -p "$SECRETS_DIR" # Clean previous artifacts to avoid stale/corrupt stores in repeated local runs. -rm -f "$SECRETS_DIR"/*.jks "$SECRETS_DIR"/*.crt "$SECRETS_DIR"/*.csr || true +rm -f "$SECRETS_DIR"/*.jks "$SECRETS_DIR"/*.crt "$SECRETS_DIR"/*.csr "$SECRETS_DIR"/*.properties "$SECRETS_DIR"/*jaas.conf || true echo "[1/5] Generating broker keystore" keytool -genkeypair \ @@ -19,6 +22,7 @@ keytool -genkeypair \ -keysize 2048 \ -validity "$VALIDITY_DAYS" \ -dname "$DNAME" \ + -ext "$SUBJECT_ALT_NAMES" \ -keystore "$SECRETS_DIR/kafka.keystore.jks" \ -storepass "$STOREPASS" \ -keypass "$STOREPASS" \ @@ -54,5 +58,24 @@ chmod 600 \ "$SECRETS_DIR/kafka_sslkey_creds" \ "$SECRETS_DIR/kafka_truststore_creds" +cat > "$SECRETS_DIR/kafka_server_jaas.conf" < "$SECRETS_DIR/kafka_admin_client.properties" <