CCAAK · 섹션 2
Kafka Security
운영자가 가장 많이 틀리는 영역입니다. 개념이 어려운 게 아니라
listeners · advertised.listeners ·
listener.security.protocol.map 세 설정의 조합이 서로를 참조하기 때문입니다.
하나만 어긋나도 "브로커는 떴는데 클라이언트가 못 붙는" 상태가 되고, 로그만 봐서는 원인이 잘 안 보입니다.
이 페이지는 그 조합을 판독하는 법과 인증·인가 실패를 오류 이름으로 되짚는 법에 집중합니다.
학습 목표
- 보안 프로토콜 4종을 암호화·인증 여부로 구분하고,
SASL_PLAINTEXT의 위험을 설명할 수 있습니다. - SASL 메커니즘 5종의 용도와 자격증명 저장 위치를 구분할 수 있습니다.
- 리스너 3종 설정 조합을 보고 어떤 클라이언트가 어디로 붙는지 읽어 낼 수 있습니다.
- ACL이 없는 리소스의 기본 동작과
super.users의 역할을 말할 수 있습니다. - 인증 실패와 인가 실패를 오류 이름으로 구분하고 조치 순서를 정할 수 있습니다.
이 도메인이 묻는 것
문항은 대개 설정 조각을 주고 결과를 예측하게 하거나, 실패 증상을 주고 원인을 고르게 합니다. "무엇이 안전한가"를 묻는 개념 문항보다 "이 구성에서 클라이언트가 붙는가"를 묻는 판독 문항이 많습니다.
| 질문 형태 | 실제로 확인하는 것 |
|---|---|
"이 server.properties에서 외부 클라이언트가 붙을 포트는?" |
리스너 3종 조합 판독 |
| "자격증명이 네트워크에 평문으로 흐르는 구성은?" | SASL_PLAINTEXT와 SASL_SSL의 차이 |
| "ACL이 하나도 없는 토픽에 접근하면?" | 기본 거부 동작과 super.users, allow.everyone.if.no.acl.found |
"컨슈머가 GROUP_AUTHORIZATION_FAILED를 받는다" |
토픽 ACL만 주고 그룹 ACL을 빼먹은 실수 |
| "각 SASL 메커니즘을 자격증명 저장 위치와 연결하시오" (matching) | GSSAPI/PLAIN/SCRAM/OAUTHBEARER 구분 |
핵심 개념 요약 — 운영 관점
Kafka 보안은 네 겹입니다. 암호화(전송 구간 보호), 인증(누구인지 확인), 인가(무엇을 허용할지), 그리고 감사(누가 무엇을 했는지)입니다. 운영 사고는 거의 전부 앞의 세 겹 사이의 불일치에서 생깁니다 — 예를 들어 인증은 켰는데 인가를 안 켠 상태, 혹은 그 반대입니다.
보안 프로토콜 4종 — SASL_PLAINTEXT를 주의하세요
리스너마다 보안 프로토콜 하나가 붙습니다. 선택지는 네 개뿐이고 (대소문자 구분 없음), 이름이 두 축의 조합이라는 점을 알면 외울 것이 없습니다: 앞부분은 인증 방식, 뒷부분은 전송 암호화입니다.
| 프로토콜 | 전송 암호화 | 클라이언트 인증 | 자격증명이 평문으로 흐르는가 | 용도 |
|---|---|---|---|---|
PLAINTEXT |
아니오 | 없음 | 해당 없음 | 격리된 개발 환경. 추가 설정이 전혀 필요 없습니다. |
SSL |
예 (TLS) | mTLS (선택) | 해당 없음 | 인증서 기반. ssl.client.auth=required로 켜야 클라이언트 인증이 됩니다. |
SASL_PLAINTEXT |
아니오 | SASL | 예 — 위험 | 신뢰된 사설망 내부 한정. 프로덕션 클라이언트 노출 금지. |
SASL_SSL |
예 (TLS) | SASL | 아니오 | 프로덕션 표준. SASL을 쓴다면 사실상 이것을 씁니다. |
SASL 메커니즘 5종
보안 프로토콜이 SASL_*이면 그 위에서 메커니즘을 고릅니다.
Kafka가 지원하는 메커니즘은 다음 5개입니다. 브로커는
sasl.enabled.mechanisms로 허용 목록을 정하고(기본값은 GSSAPI 하나),
클라이언트는 sasl.mechanism으로 하나를 고릅니다.
| 메커니즘 | 자격증명 | 저장 위치 | 운영 포인트 |
|---|---|---|---|
GSSAPI |
Kerberos 프린시펄 + keytab | KDC (Active Directory 등) | 모든 호스트가 FQDN으로 해석되어야 합니다. sasl.kerberos.service.name이 브로커 프린시펄과 일치해야 합니다. |
PLAIN |
사용자명 · 비밀번호 | JAAS 설정 파일 또는 sasl.jaas.config |
사용자를 추가하려면 브로커 재시작이 필요합니다(기본 구현 기준). 반드시 TLS와 함께. |
SCRAM-SHA-256 |
salted challenge-response | 메타데이터 로그 | 사용자를 동적으로 추가·변경할 수 있습니다. 기본 반복 횟수 4096, 최소도 4096입니다. |
SCRAM-SHA-512 |
동일 (해시 강도만 다름) | 메타데이터 로그 | SHA-256과 별개 자격증명입니다. 두 메커니즘을 다 쓰려면 각각 등록해야 합니다. |
OAUTHBEARER |
OAuth 2.0 토큰(JWT) | 외부 인가 서버 | 토큰 검증기·재시도 설정이 별도로 있습니다. 프로덕션 사용은 비프로덕션 예제 설정과 구분됩니다. |
리스너 3종 조합 판독 — 운영자 최다 실수
세 설정의 역할은 다음과 같습니다. 이 세 문장만 정확히 구분하면 대부분의 문항이 풀립니다.
| 설정 | 무엇을 정하는가 | 기본값 |
|---|---|---|
listeners |
브로커가 실제로 바인딩할 주소·포트와 리스너 이름 | PLAINTEXT://:9092 |
advertised.listeners |
클라이언트·다른 브로커에게 알려 줄 주소. 설정하지 않으면 listeners 값이 쓰입니다 |
null |
listener.security.protocol.map |
리스너 이름 → 보안 프로토콜 매핑 | SASL_SSL:SASL_SSL,PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT |
advertised.listeners가 왜 필요한지
핵심 규칙은 네 개입니다.
- 리스너 이름이 보안 프로토콜 이름이 아니면
listener.security.protocol.map을 반드시 정의해야 합니다. - 같은 보안 프로토콜을 두 개 이상의 포트에서 쓰려면 map으로 이름을 갈라 줘야 합니다.
advertised.listeners에는0.0.0.0을 쓸 수 없습니다.listeners에서는 가능합니다.- 브로커 간 리스너는
inter.broker.listener.name으로 지정합니다.security.inter.broker.protocol과 동시에 설정하면 오류입니다.
process.roles=broker
node.id=1
# 실제 바인딩: 내부용 9092, 외부용 9094, 컨트롤러 통신 수신은 없음(브로커 전용 노드)
listeners=INTERNAL://0.0.0.0:9092,EXTERNAL://0.0.0.0:9094
# 알려 줄 주소: 내부는 사설 DNS, 외부는 공개 DNS
advertised.listeners=INTERNAL://kafka-1.internal:9092,EXTERNAL://kafka-1.example.com:9094
# 이름 → 프로토콜. 이름이 프로토콜명이 아니므로 반드시 필요
listener.security.protocol.map=INTERNAL:SASL_SSL,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
authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer
인가 — ACL 모델
KRaft 클러스터의 기본 authorizer는
org.apache.kafka.metadata.authorizer.StandardAuthorizer이고,
ACL은 클러스터 메타데이터에 저장됩니다.
이 설정은 브로커·컨트롤러·결합 노드 전부에 넣어야 합니다.
ACL 한 줄은 이렇게 읽습니다 — "프린시펄 P는 호스트 H에서, 리소스 패턴 RP에 매칭되는 리소스 R에 대해 작업 O를 허용/거부한다."
| 리소스 | 무엇을 보호하는가 | 실패 오류 |
|---|---|---|
| Topic | 토픽 읽기·쓰기·생성·삭제 | TOPIC_AUTHORIZATION_FAILED (29) |
| Group | 컨슈머 그룹 참여·오프셋 커밋 | GROUP_AUTHORIZATION_FAILED (30) |
| Cluster | 클러스터 전역 작업, 멱등 프로듀서의 IdempotentWrite | CLUSTER_AUTHORIZATION_FAILED (31) |
| TransactionalId | 트랜잭션 프로듀서 | TRANSACTIONAL_ID_AUTHORIZATION_FAILED (53) |
| DelegationToken | 위임 토큰 조회·갱신 | 토큰 관련 인가 오류 |
| User | 다른 사용자의 토큰 생성·조회 권한 | 동일 |
작업(operation) 목록은 다음과 같습니다:
Read, Write, Create, Delete,
Alter, Describe, ClusterAction,
DescribeConfigs, AlterConfigs, IdempotentWrite,
CreateTokens, DescribeTokens, All.
필수 CLI 명령어
# [기동 전] 브로커 간 통신용 초기 자격증명을 포맷 단계에서 심는다
bin/kafka-storage.sh format -t $(bin/kafka-storage.sh random-uuid) \
-c config/server.properties \
--add-scram 'SCRAM-SHA-256=[name="admin",password="admin-secret"]'
# [기동 후] 클라이언트 사용자 추가 (반복 횟수 지정 가능, 미지정 시 4096)
bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter \
--add-config 'SCRAM-SHA-256=[iterations=8192,password=alice-secret]' \
--entity-type users --entity-name alice --command-config client.properties
# 등록된 자격증명 확인
bin/kafka-configs.sh --bootstrap-server localhost:9092 --describe \
--entity-type users --entity-name alice --command-config client.properties
# 삭제 (메커니즘 단위)
bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter \
--delete-config 'SCRAM-SHA-256' \
--entity-type users --entity-name alice --command-config client.properties
--producer / --consumer 편의 옵션을 쓰세요# 프로듀서 권한 한 번에 (Write + Describe + 필요한 Create)
bin/kafka-acls.sh --bootstrap-server localhost:9092 --add \
--allow-principal User:Bob --producer --topic orders
# 컨슈머 권한 — 그룹을 반드시 함께 지정해야 합니다
bin/kafka-acls.sh --bootstrap-server localhost:9092 --add \
--allow-principal User:Alice --consumer --topic orders --group order-workers
# 프리픽스 패턴: orders- 로 시작하는 모든 토픽
bin/kafka-acls.sh --bootstrap-server localhost:9092 --add \
--allow-principal User:Jane --producer --topic orders- --resource-pattern-type prefixed
# 특정 토픽에 영향을 주는 ACL 전부 보기 (literal + wildcard + prefixed)
bin/kafka-acls.sh --bootstrap-server localhost:9092 --list \
--topic orders --resource-pattern-type match
# 삭제 — --add 를 --remove 로 바꾸고 나머지는 동일하게
bin/kafka-acls.sh --bootstrap-server localhost:9092 --remove \
--allow-principal User:Bob --producer --topic orders
# 브로커에 접근할 수 없으면 컨트롤러로
bin/kafka-acls.sh --bootstrap-controller localhost:9093 --list
반드시 외워야 할 설정값
| 설정 | 기본값 | 의미 | 운영 포인트 |
|---|---|---|---|
listeners |
PLAINTEXT://:9092 |
바인딩할 리스너 목록 | 기본값이 암호화·인증 없음입니다. 아무것도 안 하면 열린 클러스터입니다. |
advertised.listeners |
null |
클라이언트에 알릴 주소 | 미설정 시 listeners가 그대로 광고됩니다 — 컨테이너/클라우드에서 사고의 원인. |
listener.security.protocol.map |
4종 프로토콜의 자기 매핑 | 리스너 이름 → 프로토콜 | 커스텀 이름을 쓰는 순간 직접 정의해야 합니다. |
security.inter.broker.protocol |
PLAINTEXT |
브로커 간 통신 프로토콜 | inter.broker.listener.name과 동시 설정 시 오류입니다. |
inter.broker.listener.name |
null |
브로커 간 통신에 쓸 리스너 이름 | 컨트롤러 리스너와 같은 값을 줄 수 없습니다. |
controller.listener.names |
비어 있음 | 컨트롤러 통신용 리스너 이름 목록 | KRaft에서 필수. 브로커 전용 노드도 선언해야 합니다. 나가는 요청은 첫 번째를 씁니다. |
sasl.enabled.mechanisms |
GSSAPI |
브로커가 허용하는 SASL 메커니즘 | SCRAM을 쓰려면 명시해야 합니다. 기본값은 Kerberos만입니다. |
sasl.mechanism.inter.broker.protocol |
GSSAPI |
브로커 간 통신에 쓸 메커니즘 | sasl.enabled.mechanisms에 포함되어 있어야 합니다. |
ssl.client.auth |
none |
mTLS 클라이언트 인증 요구 수준 | 기본값은 인증하지 않음. required로 바꿔야 mTLS가 인증으로 동작합니다. requested는 선택 제출입니다. |
ssl.endpoint.identification.algorithm |
https |
서버 인증서의 호스트명 검증 방식 | 비우면 검증이 꺼져 MITM에 노출됩니다. 인증서 SAN에 실제 advertised 호스트명이 들어 있어야 합니다. |
authorizer.class.name |
빈 문자열 | 인가 구현체 | 기본은 인가 없음 — 인증만 켜면 인증된 누구나 전부 할 수 있습니다. |
allow.everyone.if.no.acl.found |
false |
ACL 없는 리소스의 기본 동작 | 기본은 거부(super user만 허용). 마이그레이션 중에만 한시적으로 켭니다. |
super.users |
미설정 | ACL을 우회하는 프린시펄 목록 | 구분자는 세미콜론. User는 대소문자 구분. |
장애 시나리오와 대응
시나리오 1 — 브로커는 떴는데 외부 클라이언트가 붙지 못한다
가장 흔한 보안·네트워크 사고입니다. 증상은 "부트스트랩은 성공했는데 이후 요청이 타임아웃"
또는 "내부 호스트명으로 연결을 시도한다"는 로그입니다.
원인은 거의 항상 advertised.listeners입니다.
advertised.listeners가 없으면 listeners가 그대로 광고됩니다.
클라이언트는 메타데이터로 0.0.0.0:9092 또는 컨테이너 내부 호스트명을 받아 연결에 실패합니다.
listeners=EXTERNAL://0.0.0.0:9094
listener.security.protocol.map=EXTERNAL:SASL_SSL
# advertised.listeners 없음
외부에서 실제로 도달 가능한 주소를 명시합니다.
advertised.listeners에는 0.0.0.0을 쓸 수 없습니다.
listeners=EXTERNAL://0.0.0.0:9094
advertised.listeners=EXTERNAL://kafka-1.example.com:9094
listener.security.protocol.map=EXTERNAL:SASL_SSL
- 클라이언트가 받은 메타데이터를 먼저 확인합니다. 부트스트랩 성공 후 실패라면 광고 주소 문제입니다.
- 브로커에서
advertised.listeners가 실제 도달 가능한 주소인지 확인합니다. - TLS를 쓰면 인증서 SAN에 그 광고 호스트명이 포함되어야 합니다. 없으면 호스트명 검증에서 끊깁니다.
- 포트가 각각 열려 있는지, 리스너 이름이 map에 정의되어 있는지 확인합니다.
- 수정 후에는 롤링 재시작이 필요합니다(
advertised.listeners는 read-only 설정).
시나리오 2 — 클라이언트가 인증에 실패한다
인증(authentication) 실패와 인가(authorization) 실패는 다른 문제입니다. 먼저 어느 쪽인지 가른 뒤 조치해야 합니다.
| 단계 | 확인 | 전형적 원인 |
|---|---|---|
| 1 | 클라이언트의 security.protocol이 그 리스너의 프로토콜과 같은가 |
SASL_SSL 리스너에 SASL_PLAINTEXT로 접속 |
| 2 | 클라이언트 sasl.mechanism이 브로커 sasl.enabled.mechanisms에 있는가 |
브로커가 GSSAPI만 열어 둔 상태에서 SCRAM-SHA-512 시도 |
| 3 | 해당 메커니즘의 자격증명이 등록되어 있는가 | SHA-256만 등록하고 SHA-512로 접속 (별개 자격증명) |
| 4 | sasl.jaas.config의 로그인 모듈이 메커니즘과 맞는가 |
PlainLoginModule로 SCRAM 시도 |
| 5 | TLS 쪽 문제인가 | truststore에 CA 없음, 인증서 만료, 호스트명 검증 실패 |
| 6 | Kerberos라면 FQDN·keytab·시계 | 역방향 DNS가 안 되어 SASL 핸드셰이크가 느려지는 증상도 여기서 나옵니다 |
시나리오 3 — 프로듀서는 되는데 컨슈머만 실패한다
- 오류 이름을 확인합니다.
GROUP_AUTHORIZATION_FAILED면 그룹 ACL 문제입니다. kafka-acls.sh --add --allow-principal ... --consumer --topic X --group Y로 토픽과 그룹을 함께 부여합니다.--consumer는 그룹 지정이 필수입니다.TOPIC_AUTHORIZATION_FAILED인데 ACL이 있다고 보인다면--resource-pattern-type match로 실제 적용 ACL을 다시 확인합니다.- 멱등 프로듀서가
CLUSTER_AUTHORIZATION_FAILED를 받으면 Cluster 리소스의IdempotentWrite가 없는 경우입니다.enable.idempotence기본값이true이므로 의도하지 않아도 걸립니다. - 트랜잭션 프로듀서라면 TransactionalId 리소스에
Write가 필요합니다.
시나리오 4 — 보안을 켠 뒤 브로커가 기동하지 않는다
- 리스너 이름이 보안 프로토콜명이 아닌데
listener.security.protocol.map에 없는지 확인합니다. inter.broker.listener.name과security.inter.broker.protocol을 둘 다 설정하지 않았는지 확인합니다. 동시 설정은 오류입니다.controller.listener.names가inter.broker.listener.name과 같은 값이 아닌지 확인합니다.- 브로커 간 통신용 SCRAM 자격증명이 포맷 단계에서 심겼는지 확인합니다. 없으면 브로커끼리 인증하지 못해 복제가 시작되지 않습니다.
- keystore/truststore 경로와 비밀번호, 파일 권한(브로커 실행 사용자가 읽을 수 있는가)을 확인합니다.
자주 나오는 함정
관련 케이스 스터디
- 케이스 9 · 스키마 배포 후 전체 컨슈머가 죽었다 — 인증·인가 오류와 직렬화 오류를 구분하는 연습
- 케이스 2 · 컨슈머가 무한 리밸런스 루프에 빠졌다 — 그룹 권한 누락이 리밸런스로 보이는 경우
설정 문법과 전체 목록은 보안 설정 치트시트, 운영 배경은 11장 운영 기초에 있습니다. 같은 오류를 인가 관점이 아니라 클러스터 관점에서 보는 방법은 Troubleshooting에서 다룹니다.
미니 퀴즈
리스너 조합 판독과 오류 이름 매칭이 중심입니다.
공식 문서 출처
- Listener Configuration — 보안 프로토콜 4종,
listener.security.protocol.map, 컨트롤러 리스너 규칙 - Authentication using SASL — 메커니즘 5종, JAAS 우선순위, SCRAM 자격증명 생성과 기본 반복 횟수 4096
- Authorization and ACLs —
StandardAuthorizer, ACL 없는 리소스의 기본 거부,super.users, 리소스·작업 목록, 오류 코드 - Encryption and Authentication using SSL — 키스토어·트러스트스토어,
ssl.client.auth - Incorporating Security Features in a Running Cluster — 무중단 보안 적용 순서
- Broker Configs —
listeners,advertised.listeners,sasl.enabled.mechanisms,ssl.client.auth,authorizer.class.name기본값