4계층으로 나눠 생각하세요

보안 프로토콜 4종

listener.security.protocol.map에 쓸 수 있는 값은 아래 4개뿐입니다 (대소문자 구분 없음). 브로커의 기본 listener.security.protocol.map 값은 SASL_SSL:SASL_SSL,PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT이므로, 리스너 이름을 프로토콜 이름과 같게 지으면 이 설정을 생략할 수 있습니다.

보안 프로토콜 비교
프로토콜 암호화 인증 주체 Principal 결정 언제 쓰는가
PLAINTEXT 없음 없음 — 누구나 접속 ANONYMOUS 로컬 개발 전용. 운영에서 절대 노출하지 마세요
SSL TLS 클라이언트 인증서 (mTLS). ssl.client.authrequired일 때 인증서 DN → ssl.principal.mapping.rules 인증서 관리 체계가 이미 있는 조직
SASL_PLAINTEXT 없음 SASL (SCRAM · GSSAPI 등) SASL 사용자명 사설망 내부 구간에서만. PLAIN 메커니즘과 조합하면 비밀번호가 평문으로 흐릅니다
SASL_SSL TLS SASL SASL 사용자명 운영 기본 선택. 암호화 + 사용자 단위 인증
SASL 메커니즘 5종 — Apache Kafka 4.3이 지원하는 전부
메커니즘 자격 증명 저장 위치 TLS 필요성 선택 기준
PLAIN 브로커 JAAS 설정 (또는 커스텀 콜백 핸들러) 필수. 공식 문서가 "SSL과만 함께 쓸 것"을 명시합니다 가장 단순하지만 사용자 추가에 브로커 재시작이 필요합니다
SCRAM-SHA-256 메타데이터 로그 (KRaft) 필수 (교환 가로채기 방지) 사용자를 동적으로 추가·변경할 수 있습니다. 외부 IdP가 없을 때의 기본 선택
SCRAM-SHA-512 메타데이터 로그 필수 SHA-256과 동일. 더 강한 해시
GSSAPI (Kerberos) KDC · keytab 권장 이미 AD/Kerberos를 쓰는 조직. sasl.mechanism의 기본값이 이것이라 실수로 남는 경우가 많습니다
OAUTHBEARER 외부 OAuth 2.0 IdP 필수 기본 구현은 서명 없는 JWT라 비운영 전용입니다. 운영에는 IdP 연동 구현을 씁니다

TLS (SSL) 설정

단방향 TLS — 암호화만, 클라이언트 인증 없음

server.properties — 브로커
process.roles=broker,controller
node.id=1

listeners=CLIENT://:9092,CONTROLLER://:9093
advertised.listeners=CLIENT://kafka-1.example.com:9092
listener.security.protocol.map=CLIENT:SSL,CONTROLLER:SSL
inter.broker.listener.name=CLIENT
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=kafka-1.example.com:9093

# 브로커 자신의 인증서
ssl.keystore.location=/var/private/ssl/server.keystore.jks
ssl.keystore.password=changeit
ssl.key.password=changeit

# 클라이언트 인증서를 요구하지 않습니다 (기본값이 none 입니다)
ssl.client.auth=none

# 브로커 간 통신은 서로의 인증서를 검증하므로 truststore 가 필요합니다
ssl.truststore.location=/var/private/ssl/server.truststore.jks
ssl.truststore.password=changeit
client.properties — 클라이언트
security.protocol=SSL
ssl.truststore.location=/var/private/ssl/client.truststore.jks
ssl.truststore.password=changeit

# 기본값이 https 입니다. 호스트명 검증을 켜 두세요.
# advertised.listeners 의 호스트명이 인증서 SAN 과 일치해야 합니다.
ssl.endpoint.identification.algorithm=https

양방향 TLS (mTLS) — 인증서로 인증까지

server.properties — 위 설정에서 이 부분만 변경
# required: 인증서 없는 클라이언트는 거부됩니다.
# requested 는 공식 문서가 "false sense of security" 라며 권장하지 않습니다
# (잘못 설정된 클라이언트가 그냥 접속됩니다).
ssl.client.auth=required

# 클라이언트 인증서를 서명한 CA 를 truststore 에 넣어야 합니다
ssl.truststore.location=/var/private/ssl/server.truststore.jks
ssl.truststore.password=changeit

# 인증서 DN 을 어떤 Principal 로 매핑할지.
# 기본값 DEFAULT 는 전체 DN 을 그대로 Principal 로 씁니다:
#   User:CN=alice,OU=eng,O=example,L=Seoul,ST=Seoul,C=KR
# CN 만 쓰고 싶으면 규칙을 지정합니다.
ssl.principal.mapping.rules=RULE:^CN=(.*?),.*$/$1/,DEFAULT
client.properties — 클라이언트 인증서 추가
security.protocol=SSL
ssl.truststore.location=/var/private/ssl/client.truststore.jks
ssl.truststore.password=changeit

# 클라이언트 자신의 인증서 (mTLS 에서만 필요)
ssl.keystore.location=/var/private/ssl/client.keystore.jks
ssl.keystore.password=changeit
ssl.key.password=changeit

SASL 설정

SASL_SSL + SCRAM-SHA-512 (운영 권장)

1단계 — 포맷 시점에 브로커 간 인증용 자격 증명 심기
# 브로커 간 통신에 쓰는 자격 증명은 브로커가 기동하기 "전에" 존재해야 합니다.
# KRaft 에서는 kafka-storage.sh 로 메타데이터 로그에 직접 심습니다.
KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"

bin/kafka-storage.sh format -t "$KAFKA_CLUSTER_ID" -c config/server.properties \
  --add-scram 'SCRAM-SHA-512=[name="admin",password="admin-secret"]'
2단계 — server.properties
process.roles=broker,controller
node.id=1

listeners=CLIENT://:9092,CONTROLLER://:9093
advertised.listeners=CLIENT://kafka-1.example.com:9092
listener.security.protocol.map=CLIENT:SASL_SSL,CONTROLLER:SASL_SSL
inter.broker.listener.name=CLIENT
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=kafka-1.example.com:9093

# 기본값이 GSSAPI 이므로 반드시 명시해야 합니다
sasl.enabled.mechanisms=SCRAM-SHA-512
sasl.mechanism.inter.broker.protocol=SCRAM-SHA-512
sasl.mechanism.controller.protocol=SCRAM-SHA-512

# 브로커가 다른 브로커에 접속할 때 쓰는 자격 증명
listener.name.client.scram-sha-512.sasl.jaas.config=\
  org.apache.kafka.common.security.scram.ScramLoginModule required \
  username="admin" \
  password="admin-secret";
listener.name.controller.scram-sha-512.sasl.jaas.config=\
  org.apache.kafka.common.security.scram.ScramLoginModule required \
  username="admin" \
  password="admin-secret";

# TLS
ssl.keystore.location=/var/private/ssl/server.keystore.jks
ssl.keystore.password=changeit
ssl.key.password=changeit
ssl.truststore.location=/var/private/ssl/server.truststore.jks
ssl.truststore.password=changeit

# 인가
authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer
super.users=User:admin
3단계 — 기동 후 애플리케이션 사용자 동적 생성
# 기본 iteration count 는 4096 입니다. 지정하지 않으면 이 값이 쓰입니다.
bin/kafka-configs.sh --bootstrap-server kafka-1.example.com:9092 \
  --command-config admin.properties --alter \
  --add-config 'SCRAM-SHA-512=[iterations=8192,password=alice-secret]' \
  --entity-type users --entity-name alice

# 확인 (salt · iterations · StoredKey · ServerKey 가 메타데이터 로그에 저장됩니다)
bin/kafka-configs.sh --bootstrap-server kafka-1.example.com:9092 \
  --command-config admin.properties --describe \
  --entity-type users --entity-name alice

# 삭제
bin/kafka-configs.sh --bootstrap-server kafka-1.example.com:9092 \
  --command-config admin.properties --alter \
  --delete-config 'SCRAM-SHA-512' \
  --entity-type users --entity-name alice
4단계 — client.properties
security.protocol=SASL_SSL
sasl.mechanism=SCRAM-SHA-512
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
  username="alice" \
  password="alice-secret";
ssl.truststore.location=/var/private/ssl/client.truststore.jks
ssl.truststore.password=changeit

SASL_PLAINTEXT + SCRAM (내부망 전용)

server.properties — TLS 없이 SASL만
listeners=INTERNAL://:9092,CONTROLLER://:9093
advertised.listeners=INTERNAL://kafka-1.internal:9092
listener.security.protocol.map=INTERNAL:SASL_PLAINTEXT,CONTROLLER:SASL_PLAINTEXT
inter.broker.listener.name=INTERNAL
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=kafka-1.internal:9093

sasl.enabled.mechanisms=SCRAM-SHA-512
sasl.mechanism.inter.broker.protocol=SCRAM-SHA-512
sasl.mechanism.controller.protocol=SCRAM-SHA-512

listener.name.internal.scram-sha-512.sasl.jaas.config=\
  org.apache.kafka.common.security.scram.ScramLoginModule required \
  username="admin" password="admin-secret";
listener.name.controller.scram-sha-512.sasl.jaas.config=\
  org.apache.kafka.common.security.scram.ScramLoginModule required \
  username="admin" password="admin-secret";
client.properties
security.protocol=SASL_PLAINTEXT
sasl.mechanism=SCRAM-SHA-512
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
  username="alice" password="alice-secret";

SASL_SSL + PLAIN

server.properties — 사용자를 브로커 설정에 나열
listeners=CLIENT://:9092,CONTROLLER://:9093
listener.security.protocol.map=CLIENT:SASL_SSL,CONTROLLER:SASL_SSL
inter.broker.listener.name=CLIENT
controller.listener.names=CONTROLLER

sasl.enabled.mechanisms=PLAIN
sasl.mechanism.inter.broker.protocol=PLAIN

# username/password 는 브로커가 "다른 브로커에 접속할 때" 쓰는 자격 증명
# user_{이름}={비밀번호} 는 이 브로커가 "받아들일" 사용자 목록
listener.name.client.plain.sasl.jaas.config=\
  org.apache.kafka.common.security.plain.PlainLoginModule required \
  username="admin" \
  password="admin-secret" \
  user_admin="admin-secret" \
  user_alice="alice-secret";

리스너 조합 패턴 5가지

운영자가 가장 많이 틀리는 부분입니다. 세 설정의 역할을 먼저 분리하세요.

세 설정의 역할
설정누가 쓰는가무엇을 정하는가
listeners 브로커 자신 어디에 바인딩할지. 이름://호스트:포트. 호스트를 비우면 모든 인터페이스
advertised.listeners 클라이언트 메타데이터 응답으로 클라이언트에게 알릴 주소. 클라이언트가 실제로 접속할 주소여야 합니다
listener.security.protocol.map 브로커 리스너 이름 → 보안 프로토콜 매핑. 이름을 프로토콜과 다르게 지었으면 필수

패턴 1 — 단일 리스너 (로컬 개발)

server.properties
process.roles=broker,controller
node.id=1

# 리스너 이름을 프로토콜 이름과 같게 지으면
# listener.security.protocol.map 을 생략할 수 있습니다.
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
listener.security.protocol.map=PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT
inter.broker.listener.name=PLAINTEXT
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=localhost:9093

# 생략하면 listeners 값이 그대로 광고됩니다.
# localhost 로 접속하는 로컬 개발에서는 이대로도 동작합니다.
advertised.listeners=PLAINTEXT://localhost:9092

패턴 2 — 내부 / 외부 분리 (사내망 + 외부 클라이언트)

server.properties — 두 개의 클라이언트 리스너
process.roles=broker
node.id=1

# INTERNAL: 브로커 간 복제 + 사내 클라이언트 (사설 IP)
# EXTERNAL: 외부 클라이언트 (공인 도메인, 인증 강제)
# CONTROLLER: 컨트롤러 통신 — listeners 에 없어도 반드시 "정의"해야 합니다
listeners=INTERNAL://:9092,EXTERNAL://:9094
advertised.listeners=INTERNAL://kafka-1.internal:9092,EXTERNAL://kafka-1.example.com:9094
listener.security.protocol.map=INTERNAL:SASL_PLAINTEXT,EXTERNAL:SASL_SSL,CONTROLLER:SASL_SSL

inter.broker.listener.name=INTERNAL
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=controller-1.internal:9093,controller-2.internal:9093,controller-3.internal:9093

sasl.enabled.mechanisms=SCRAM-SHA-512
sasl.mechanism.inter.broker.protocol=SCRAM-SHA-512
sasl.mechanism.controller.protocol=SCRAM-SHA-512

패턴 3 — 브로커 전용 노드 (역할 분리)

server.properties — process.roles=broker
process.roles=broker
node.id=11

# 컨트롤러 역할이 없으므로 CONTROLLER 를 listeners 에 넣지 않습니다.
# 브로커가 컨트롤러 리스너를 "노출"하지는 않기 때문입니다.
listeners=BROKER://:9092
advertised.listeners=BROKER://kafka-1.internal:9092
inter.broker.listener.name=BROKER

# 하지만 컨트롤러에 "접속"해야 하므로 리스너 이름과 보안 설정은 정의해야 합니다.
# 포트는 controller.quorum.bootstrap.servers 에서 옵니다.
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=controller-1.internal:9093,controller-2.internal:9093,controller-3.internal:9093
listener.security.protocol.map=BROKER:SASL_SSL,CONTROLLER:SASL_SSL
controller.properties — process.roles=controller
process.roles=controller
node.id=1

# 컨트롤러는 컨트롤러 리스너를 실제로 노출합니다.
listeners=CONTROLLER://:9093
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=controller-1.internal:9093,controller-2.internal:9093,controller-3.internal:9093
listener.security.protocol.map=CONTROLLER:SASL_SSL

# 컨트롤러 전용 노드는 advertised.listeners 를 쓰지 않습니다
# (클라이언트가 붙는 대상이 아닙니다).

패턴 4 — Docker Compose

docker-compose.yml — 컨테이너 내부와 호스트 양쪽에서 접속
services:
  kafka-1:
    image: apache/kafka:4.3.1
    hostname: kafka-1
    ports:
      - "19092:19092"          # 호스트에서 접속할 포트
    environment:
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_NODE_ID: 1

      # DOCKER  : 컨테이너 네트워크 내부에서 서비스명으로 접속 (다른 컨테이너용)
      # HOST    : 호스트 머신에서 localhost 로 접속 (개발자 노트북용)
      # CONTROLLER: 컨트롤러 쿼럼
      KAFKA_LISTENERS: DOCKER://:9092,HOST://:19092,CONTROLLER://:9093
      KAFKA_ADVERTISED_LISTENERS: DOCKER://kafka-1:9092,HOST://localhost:19092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: DOCKER:PLAINTEXT,HOST:PLAINTEXT,CONTROLLER:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_CONTROLLER_QUORUM_BOOTSTRAP_SERVERS: kafka-1:9093

      # 단일 노드에서는 내부 토픽 RF 를 1 로 낮춰야 기동합니다
      # (기본값 3 이라 브로커 1대에서는 InvalidReplicationFactorException)
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1

패턴 5 — Kubernetes (StatefulSet + 파드별 외부 노출)

브로커 컨테이너 진입 스크립트 — 파드 서수로 주소를 계산
#!/usr/bin/env bash
set -euo pipefail

# StatefulSet 의 파드 이름은 kafka-0, kafka-1, … 형태로 안정적입니다.
ORDINAL="${HOSTNAME##*-}"
NODE_ID=$((ORDINAL + 1))

# INTERNAL : 클러스터 내부 — headless Service 의 안정적 DNS 이름
# EXTERNAL : 클러스터 외부 — 파드별 LoadBalancer/NodePort 도메인
#            파드마다 값이 달라야 하므로 여기서 계산합니다.
INTERNAL_HOST="${HOSTNAME}.kafka-headless.${POD_NAMESPACE}.svc.cluster.local"
EXTERNAL_HOST="kafka-${ORDINAL}.${EXTERNAL_DOMAIN}"

exec /opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/server.properties \
  --override node.id="${NODE_ID}" \
  --override listeners="INTERNAL://:9092,EXTERNAL://:9094" \
  --override advertised.listeners="INTERNAL://${INTERNAL_HOST}:9092,EXTERNAL://${EXTERNAL_HOST}:9094" \
  --override listener.security.protocol.map="INTERNAL:SASL_SSL,EXTERNAL:SASL_SSL,CONTROLLER:SASL_SSL" \
  --override inter.broker.listener.name="INTERNAL" \
  --override controller.listener.names="CONTROLLER" \
  --override controller.quorum.bootstrap.servers="kafka-controller-0.kafka-controller-headless.${POD_NAMESPACE}.svc.cluster.local:9093"

ACL

Authorizer 켜기

server.properties — 모든 노드(브로커·컨트롤러·combined)에 적용
# KRaft 는 ACL 을 클러스터 메타데이터(KRaft 메타데이터 로그)에 저장합니다
authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer

# 구분자는 세미콜론입니다 (SSL DN 에 콤마가 들어갈 수 있으므로).
# PrincipalType 문자열 "User" 는 대소문자를 구분합니다.
super.users=User:admin;User:kafka-operator

# ACL 이 없는 리소스는 기본적으로 거부됩니다 (super user 만 접근 가능).
# 아래를 true 로 두면 ACL 이 "하나도 없는" 리소스만 모두에게 열립니다.
# ACL 이 하나라도 있으면 이 설정과 무관하게 ACL 이 적용됩니다.
#allow.everyone.if.no.acl.found=true

자주 쓰는 ACL 명령

편의 옵션 — 이것부터 쓰세요
export BS=localhost:9092
CC="--command-config admin.properties"

# 프로듀서 역할: 토픽에 WRITE, DESCRIBE, CREATE 를 한 번에 부여
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
  --allow-principal User:Bob --producer --topic orders

# 멱등 프로듀서라면 --idempotent 를 함께 (Cluster IdempotentWrite)
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
  --allow-principal User:Bob --producer --idempotent --topic orders

# 컨슈머 역할: 토픽에 READ·DESCRIBE + 그룹에 READ
# 그룹 권한을 빼먹는 것이 가장 흔한 실수입니다
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
  --allow-principal User:Bob --consumer --topic orders --group order-processor

# 접두어 기반 — 토픽이 늘어나도 재부여가 필요 없습니다
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
  --allow-principal User:Jane --producer \
  --topic "order-" --resource-pattern-type prefixed

# 조회
bin/kafka-acls.sh --bootstrap-server $BS $CC --list --topic orders

# 특정 토픽에 매칭되는 모든 패턴(literal + prefixed + wildcard)을 함께 보기
bin/kafka-acls.sh --bootstrap-server $BS $CC --list \
  --topic orders --resource-pattern-type match

# 제거 — 부여할 때와 같은 인자로
bin/kafka-acls.sh --bootstrap-server $BS $CC --remove \
  --allow-principal User:Bob --producer --topic orders --force
세밀한 제어 — 오퍼레이션을 직접 지정
# 여러 principal · 여러 host · 여러 operation 을 한 번에
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
  --allow-principal User:Bob --allow-principal User:Alice \
  --allow-host 198.51.100.0 --allow-host 198.51.100.1 \
  --operation Read --operation Write \
  --topic orders

# Deny 는 Allow 를 이깁니다. 특정 principal 만 차단할 때 씁니다.
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
  --allow-principal User:'*' --allow-host '*' \
  --deny-principal User:BadBob --deny-host 198.51.100.3 \
  --operation Read --topic orders

# 트랜잭션 프로듀서
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
  --allow-principal User:Bob \
  --operation Write --operation Describe \
  --transactional-id order-tx-

# 클러스터 레벨 (재할당·브로커 설정 변경 등)
bin/kafka-acls.sh --bootstrap-server $BS $CC --add \
  --allow-principal User:kafka-operator \
  --operation Alter --operation ClusterAction --cluster

오퍼레이션 13종 · 리소스 타입 6종

ACL 하나는 (principal, host, operation, resource, permission) 조합입니다. --operation에 쓸 수 있는 값과 리소스 타입은 아래가 전부입니다.

--operation 유효값 13종 — 공식 kafka-acls.sh 문서 기준
Operation대표 용도
Readfetch, 오프셋 커밋, 그룹 조인·하트비트
Writeproduce, 트랜잭션 API
Create토픽 생성 (자동 생성 포함)
Delete토픽 삭제, 레코드 삭제
Alter파티션 추가, 재할당, ACL 변경
Describe메타데이터·오프셋·그룹 조회. 없으면 리소스가 존재하지 않는 것처럼 보입니다
ClusterAction브로커 간 동작(복제 fetch 등). 브로커 principal에 필요
DescribeConfigskafka-configs --describe
AlterConfigskafka-configs --alter
IdempotentWrite멱등 프로듀서 (Cluster 리소스에 부여)
CreateTokens위임 토큰 생성
DescribeTokens위임 토큰 조회
All위 전부. 편하지만 최소 권한 원칙에 반합니다
리소스 타입 6종 — ResourceType.java 확인 (UNKNOWN·ANY 제외)
리소스kafka-acls.sh 옵션메모
TOPIC--topic가장 흔합니다
GROUP--group컨슈머 그룹. 토픽 권한과 별개로 반드시 필요
CLUSTER--cluster단일 리소스. 클러스터 전역 동작
TRANSACTIONAL_ID--transactional-id*는 모든 트랜잭션 ID
DELEGATION_TOKEN--delegation-token*는 모든 토큰
USER--user-principal현재는 위임 토큰과 연계해 쓰입니다
--resource-pattern-type — 패턴 매칭 방식
의미언제 쓰는가
literal이름이 정확히 일치. *는 전체를 의미하는 특수값기본. 개별 리소스
prefixed주어진 문자열로 시작하는 모든 리소스팀·서비스별 네임스페이스. 토픽이 늘어나도 재부여 불필요
match주어진 이름에 매칭되는 모든 패턴을 찾습니다 (조회 전용)--list로 "이 토픽에 걸린 ACL 전부"를 볼 때
any모든 패턴 타입 (조회·삭제 필터)--list · --remove

역할별 필요 권한

역할별 필요 ACL — 공식 문서의 오퍼레이션·리소스 매핑 기준
하려는 일 Operation Resource 메모
일반 produceWriteTopic
멱등 produceIdempotentWriteCluster4.x는 멱등성이 기본 true
트랜잭션 produceWriteTransactionalIdtransactional.id를 설정한 프로듀서
일반 consume (fetch)ReadTopic파티션마다 필요합니다
오프셋 커밋ReadGroup + Topic둘 다 필요합니다. 그룹 권한 누락이 가장 흔한 실수
오프셋 조회DescribeGroup + Topic
그룹 조인 · 하트비트 · 리브 · 동기화ReadGroupJOIN_GROUP / HEARTBEAT / LEAVE_GROUP / SYNC_GROUP
그룹 상세 조회DescribeGroup
그룹 목록 조회DescribeCluster 또는 Group클러스터 권한이 없으면 권한 있는 그룹만 반환됩니다
코디네이터 찾기 (컨슈머)DescribeGroupFIND_COORDINATOR
코디네이터 찾기 (트랜잭션)DescribeTransactionalId
메타데이터 조회DescribeTopic이것이 없으면 토픽이 존재하지 않는 것처럼 보입니다
토픽 자동 생성CreateCluster 또는 Topic클러스터 권한을 먼저 확인하고, 없으면 토픽 레벨로 폴백합니다
토픽 생성 (명시)CreateCluster 또는 Topic클러스터 권한이 없으면 CLUSTER_AUTHORIZATION_FAILED가 아니라 토픽 권한으로 폴백합니다
토픽 삭제DeleteTopic
레코드 삭제DeleteTopickafka-delete-records.sh
오프셋 조회 (LIST_OFFSETS)DescribeTopickafka-get-offsets.sh
producer ID 초기화Write / IdempotentWriteTransactionalId / ClusterINIT_PRODUCER_ID
트랜잭션 오프셋 커밋Write + ReadTransactionalId + GroupTXN_OFFSET_COMMIT
팔로워 복제 (브로커 간)ClusterActionCluster브로커 principal에 필요합니다

인증 실패 로그 읽는 법

증상별 원인과 확인 방법
증상가장 흔한 원인확인
SaslAuthenticationException: Authentication failed 사용자명·비밀번호 오류, 또는 SCRAM 자격 증명이 아직 생성되지 않음 kafka-configs.sh --describe --entity-type users --entity-name alice
UnsupportedSaslMechanismException 클라이언트 sasl.mechanism이 브로커 sasl.enabled.mechanisms에 없음 양쪽 설정 비교. 클라이언트 기본값은 GSSAPI입니다
SslAuthenticationException: certificate_unknown 브로커 인증서를 서명한 CA가 클라이언트 truststore에 없음 keytool -list -v -keystore client.truststore.jks
SslAuthenticationException: bad_certificate mTLS인데 클라이언트 인증서가 없거나 브로커 truststore가 서명자를 모름 브로커 ssl.client.auth 값과 브로커 truststore 확인
호스트명 검증 실패 (No subject alternative names) advertised.listeners의 호스트명이 인증서 SAN에 없음 openssl x509 -in server.crt -noout -text | grep -A1 "Subject Alternative Name"
CLI가 아무 메시지 없이 타임아웃 --command-config 누락. 인증 실패가 타임아웃으로 보입니다 프로퍼티 파일을 넘기고 다시 실행
부트스트랩은 되는데 produce/fetch만 타임아웃 advertised.listeners가 클라이언트가 도달할 수 없는 주소 kafka-broker-api-versions.sh --bootstrap-server … 출력의 호스트명 확인
인증은 되는데 특정 작업만 거부 ACL 누락 kafka-authorizer.log에 거부 이유가 남습니다
TLS 핸드셰이크를 클라이언트에서 직접 보기
# 1) JVM TLS 디버그 — 가장 정확합니다
KAFKA_OPTS="-Djavax.net.debug=ssl:handshake" \
  bin/kafka-topics.sh --bootstrap-server kafka-1.example.com:9093 \
  --command-config client.properties --list 2>&1 | head -80

# 2) 브로커가 실제로 제시하는 인증서 체인 확인
openssl s_client -connect kafka-1.example.com:9093 -showcerts </dev/null

# 3) SAN 확인 — advertised.listeners 의 호스트명이 여기 있어야 합니다
openssl s_client -connect kafka-1.example.com:9093 </dev/null 2>/dev/null \
  | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"

# 4) truststore 내용 확인
keytool -list -v -keystore client.truststore.jks -storepass changeit | grep -E "Alias|Owner|Valid"
ACL 거부 이유를 로그에서 찾기 (재시작 없이)
# Authorizer 로거를 DEBUG 로
bin/kafka-configs.sh --bootstrap-server $BS --command-config admin.properties --alter \
  --entity-type broker-loggers --entity-name 1 \
  --add-config org.apache.kafka.metadata.authorizer.StandardAuthorizer=DEBUG

# 문제를 재현한 뒤 로그 확인
tail -f /var/log/kafka/kafka-authorizer.log

# 조사가 끝나면 반드시 되돌립니다 (DEBUG 를 켜 두면 디스크가 빠르게 찹니다)
bin/kafka-configs.sh --bootstrap-server $BS --command-config admin.properties --alter \
  --entity-type broker-loggers --entity-name 1 \
  --add-config org.apache.kafka.metadata.authorizer.StandardAuthorizer=INFO

운영 전환 체크리스트

PLAINTEXT 개발 클러스터를 운영으로 올릴 때
항목확인 방법
PLAINTEXT 리스너가 외부에 노출되어 있지 않은가advertised.listeners와 방화벽 규칙 확인
authorizer.class.name이 설정되어 있는가미설정이면 인가를 전혀 하지 않습니다
super.users가 최소 인원인가super user는 ACL을 우회합니다
allow.everyone.if.no.acl.found가 켜져 있지 않은가기본은 거부입니다. 켜면 ACL 없는 리소스가 전부 열립니다
ssl.endpoint.identification.algorithm이 비어 있지 않은가빈 값이면 호스트명 검증이 꺼집니다
ssl.client.authrequested가 아닌가공식 문서가 권장하지 않는 값입니다. required 또는 none
JMX 원격 포트에 인증이 걸려 있는가Kafka는 JMX 인증을 기본적으로 켜지 않습니다
인증서 만료일을 모니터링하는가만료 시 전면 장애가 됩니다
auto.create.topics.enable이 꺼져 있는가켜져 있으면 Topic Create 권한 설계가 무의미해집니다
브로커 간 통신도 암호화·인증되는가inter.broker.listener.name의 프로토콜 확인
컨트롤러 리스너도 보호되는가controller.listener.names의 프로토콜 확인. 여기에 ACL이 없으면 메타데이터가 노출됩니다
평문 비밀번호가 설정 파일에 남아 있지 않은가Configuration Provider 또는 시크릿 관리 도구 사용

공식 문서 출처

설정명·기본값·명령 형태는 Apache Kafka 4.3.1 공식 문서에서 확인했습니다. super.usersallow.everyone.if.no.acl.found의 기본 동작은 설정 표에 없어 StandardAuthorizer 소스로 확인했습니다. 리스너 패턴 4·5(Docker · Kubernetes)의 구성은 공식 문서의 설정 규칙을 적용한 이 가이드의 구성이며, 공식 문서가 제시한 예시가 아닙니다.