성능이나 신뢰성과 마찬가지로, 보안은 컴포넌트 별로 따로따로 다뤄지기보다 전체 시스템에 대해 다뤄져야 할 부분이다. 시스템 보안은 가장 약한 고리만큼만 강한 법이고, 보안 절차와 정책 역시 시스템 전체에 걸쳐 적용되어야 한다.
카프카의 보안 기능, 각각의 기능이 보안의 서로 다른 측면을 어떻게 담당하는지, 모범 사례, 잠재적인 위협에 대한 대응에 대해 알아보자.
기본적인 보안 관련 용어 살펴보면 다음과 같다.
카프카 클러스터에서 데이터가 어떻게 흐르는지 다음과 같다.
이 과정에서 안전한 카프카 클러스터는 다음과 같은 특정을 지닌다.
앞으로 위 특징들을 보장하기 위해 카프카에서 어떤 기능을 사용할 수 있는지 살펴보자.
카프카는 2개의 표준 기술, TLS 와 SASL 을 이용하여 4개의 보안 프로토콜을 지원한다. TLS(이전에는 SSL)은 서버와 클라이언트 간 인증 뿐만 아니라 암호화를 지원하고, SASL 은 연결 지향 프로토콜에서 서로 다른 매커니즘을 사용한 인증을 지원하는 프레임워크다. 각 카프카 보안 프로토콜은 전송 계층(PLAINTEXT or SSL)과 인증 계층(SSL 혹은 SASL)을 조합해서 정의된다.
SSL 을 쓸 것인가? SASL 을 쓸 것인가에 따라 총 4가지로 나눠지는 것이다.
SSL(TLS)를 사용해서 암호화 할 것인지? SASL 을 사용해서 인증 기능을 사용할 것인지 결정하는 것이다.
SSL vs SASL
SASL은 응용/세션 계층의 인증 메커니즘 집합, SSL/TLS는 전송 계층의 보안 프로토콜.
즉, SASL 은 인증 방식 협의 프레임워크로서 서로 합의하는 방법과 절차를 정의하여 인증을 돕는거고, SSL/TLS 는 통신 과정에서 서로를 확인하고(핸드셰이크) 이후 실제 데이터를 주고받을 때는 협상된 대칭키를 이용해 데이터를 빠르게 암호화하고 복호화하여 처리하는 프로토콜이다.
- TLS: 통신 경로를 안전하게 (암호화 + 서버 신원 확인)
- SASL: 누가 접속했는지 증명 (인증 메커니즘)
→ TLS는 “파이프를 잠그는 것”,
→ SASL은 “신분증을 보여주는 것”
각 보안 설정은 각각에 맞춰 설정할 수 있다.
listener=EXTERNAL://9092,INTERNAL://10.0.0.2:9094,BROKER://10.0.0.2:9094
advertised.listeners=EXTERNAL://broker1.example.com:9092,INTERNAL://broker1.local:9093,BROKER://broker1.local:9094
listener.security.protocol.map=EXTERNAL:SASL_SSL,INTERNAL:SSL,BROKER:SSL
inter.broker.listener.name=BROKER
인증은 서버와 클라이언트 사이에서 서로의 신원을 확인하는 과정이다. 서버 인증 기능은 클라이언트가 실제 브로커와 연결을 설정할 수 있도록한다. 클라이언트 인증은 요청자의 신원(패스워드 또는 인증서)을 확인함으로써 검증한다. 클라이언트의 신원을 나타내기 위해 KafkaPrincipal 객체를 사용하다(인가, 쿼터도 요 객체를 사용함).
SSL, SASL_SSL 을 사용하는 경우 TLS 보안 전송 계층을 사용한다. TLS 핸드셰이크 과정에서 인증, 암호화 매개변수 교환, 암호화를 위한 공유 키 생성 등이 처리된다. 이후 클라이언트는 서버의 신원 확인을 위해 서버의 디지털 인증서를 검증한다. 만약 SSL 를 사용한 클라이언트 인증 기능이 활성화되어 있다면 서버 역시 클라이언트의 디지털 인증서를 검증해서 신원을 확인한다.
SSL 채널을 통해 암호화된 데이터를 통신하여 안전하지만, 이로 인해 CPU 사용 증가 및 제로 카피 전송이 안되는 단점이 있다.
브로커 리스너에 SSL, SASL_SSL 를 사용해서 TLS 기능을 활성화시킬 경우, 브로커에는 비밀키와 인증서를 포함하고 있는 키스토어가 설정되어야하며, 클라이언트는 브로커의 인증서 혹은 인증서에 서명한 인증기관의 인증서를 포함하는 트러스트스토어를 등록해야한다. 브로커 인증서는 SAN(subject alternative name), CN(common name) 항목에 브로커 호스트명이 설정되어 있어서 클라이언트가 검증할 수 있도록 되어야 한다. (기본적으로 클라이언트는 서버 인증서에 저장되어있는 서버 이름과 클라이언트가 접속을 시도할 호스트명이 일치하는지 확인한다)
브로커 역시 ssl.client.auth 옵션을 통해 클라이언트를 인증하도록 설정할 수 있다. required 또는 requested 를 통해 필수 또는 선택 사항으로 설정할 수 있다.
TLS 는 https 를 포함한 많은 프로토콜의 전송계층 보안을 위해 널리사용된다. 그러나 오래된 프로토콜은 발견된 보안 취약점이 있기 때문에 카프카는 TLSv1.2, TLSv1.3 이후의 프로토콜만 지원한다. 또한 TLS 핸드셰이크는 자원을 많이 잡아먹고 브로커의 네트워크 스레드에 대해 상당한 시간을 사용하기 때문에 연결에 쿼터나 제한을 두어야할 수 있다. (connection.failed.authentication.delay.ms 옵션을 통해 재연결 속도를 늦춤으로써 인증 실패 시 재시도 횟수를 완화시킬 수 있다)
SASL 인증은 서버가 챌린지를 내놓으면 클라이언트가 여기에 응답을 보내는 과정을 일정한 순서로 수행한다. 카프카 브로커는 다음과 같은 SASL 메커니즘을 지원한다.
프카 브로커는 클라이언트가 새로운 연결을 맺는 시점에서 클라이언트 인증을 수행한다. 케르베로스나 OAuth와 같은 보안 메커니즘은 유효기간이 있는 자격증명을 사용하는데, 기존 증명이 만료되기 전에 자격증명을 얻어오기 위해 재인증이 사용된다. 또는 PLAIN 이나 SCRAM 는 주기적인 로그인을 설정함으로써 암호 순환 기능을 지원할 수 있다.
카프카에서 connections.max.reauth.ms 설정 옵션을 사용해서 유효기간이 없는 자격 증명을 포함한 모든 SASL 메커니즘에 대해 재인증을 요구한다. 이때 자격 증명의 잔여 수명과 비교해 더 작은 값을 클라이언트에 알려준다. 이 기간 동안 재인증을 하지 않은 연결은 브로커가 강제로 종료한다.
비밀번호를 순환시키거나, 보안 패치를 적용하거나, 최신 보안 프로토콜을 업데이트하거나 하기 위해서는 정기적으로 카프카를 정비해주어야 한다. 이때 업데이트는 롤링 방식으로 이루어진다. (오래된 리스너 -> 최신 리스너)
PLAINTEXT -> SASL_SSL 로 넘어가는 예시를 들어보자.
1. 카프카 설정 툴을 사용해서 각 브로커에 새로운 포트를 사용하는 새로운 리스터를 추가한다. listeners, advertised.listeners 설정이 예전 리스너와 최신 리스너를 모두 받게한다.
2. 모든 클라이언트 애플리케이션이 SASL_SSL리스너를 사용하도록 수정한다.
3. 브로커 간의 커뮤니케이션도 SASL_SSL 리스너를 사용한다면, 새로운 inter.broker.listener.name 설정과 함께 브로커를 롤링 업데이트한다.
4. 설정 툴을 사용해 예전에 사용하던 리스너를 제거한다.
암호화는 데이터의 기밀성과 무결성을 보장하기 위해 사용된다. SSL 과 SASL_SSL 보안 프로토콜을 사용하는 카프카 리스너는 전송 계층으로 TLS 를 사용함으로써 암호화된 채널을 사용한다.
그리고 디스크 탈취 등에도 대응하려면 디스크에도 암호화가 필수이다.
내부 운영자가 메모리에 올라간 데이터 또는 직접 데이터 접근을 막으려면 추가 보안이 필수이다. 전체 데이터 흐름이 암호화되는 종단 암호화(end-to-end encrytion)를 구현하기 위해 커스텀 암호화 제공자를 카프카 클라이언트에 플러그인 형태로 설정할 수도 있다.
프로듀서, 컨슈머에서는 각각 시리얼라이저, 디시리얼라이저를 이용하여 바이트 배열로 변환 또는 다시 메시지로 변환하는 것을 보았다. 이때 직렬화 도중에 메시지를 암호화하거나 역직렬화할 때 복호화하여 암호화 라이브러리에 통합될 수 있다. 보통은 KMS(key management system)에 공유 키를 저장하여 암호화, 복호화를 진행한다.
이때 키는 주기적으로 교체하는 것이 좋은데, 키가 변경된 후에도 이전 키로 암호화된 메시지는 처리할 수 있어야하기 때문에 이를 지원하는 KMS 시스템이 있거나, 메시지 압착 과정에서 새로운 메시지와 섞이는 것을 방지하기 위해 바뀌는 동안 메시지 처리를 하지 않도록 조심해야된다.
메시지를 암호화 후 압축하는 것은 암호화 전에 압축하는 것에 비해 저장 공간 측면에서 아무런 이점이 없다.
즉, 암호화를 적용한 경우(메시지 압축하기 전 암호화하든, 압축 후 암호화하든) 오버헤드가 발생하기 때문에 압축 옵션을 끄는 것이 좋다.
그리고 안전하지 않은 전송 계층을 통해 데이터를 보내는 경우 암호화된 메시지를 압축할 때 발생하는 보안 취약점도 고려해야한다.
보통 많은 환경에서(TLS 를 전송 계층으로 사용하는 경우), 메시지 키는 암호화를 필요로 하지 않는다. 보통은 value 에 민감한 정보를 담지 않기 때문이다. 그러나 키 암호화가 필요한 경우에는 키는 그대로 두고 키와 관련된 암호화된 키 값은 value 또는 header 에 담는다. 이러한 이유는 키가 단순히 value 와 같이 데이터 전달에 그치지 않고, 파티셔닝 및 압착할 때 사용되기 때문에 해시 동등성을 유지해야 되기 때문이다. 따라서 키는 그대로 두고 이와 관련된 암호화 값을 헤더나 value 에 둔 뒤, 인터셉터 등에서 키와 별개로 value 에 해당 값을 변환하도록 작업하는 것이 나을 수 있다.
인가는 사용자가 자원에 대해 어떠한 작동을 수행할 수 있는지를 결정하는 절차다. 카프카 브로커는 커스터마이즈가 가능한 권한 부여자를 사용해서 접근 제어를 관리한다. 카프카는 다음과 같은 설정으로 켤 수 있는 AclAuthorizer 권한 부여자를 기본적으로 제공한다.
authorizer.class.name=kafka.security.AclAuthorizer # 주키퍼
authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer # Kraft
지금은 주키퍼 대신 Kraft 를 권장하므로 책과 다르게 StandardAuthorizer 에 대해 살펴보자.
StandardAuthorizer는 카프카의 기본 ACL 인가 엔진으로, 브로커가 요청을 처리하기 전에 클라이언트의 Principal(예: User:alice), 요청 발신 호스트, 대상 리소스(Topic, Group, Cluster, TransactionalId, DelegationToken)와 요청 작업(Operation)을 종합해 접근을 허용/거부한다.
ACL은 리소스 이름의 일치 방식(정확 매칭, 접두사 매칭)과 허용/거부 규칙을 갖고, 평가 순서는 일반적으로 명시적 DENY가 우선하고 그 다음 ALLOW가 적용된다. 클러스터 메타데이터(__cluster_metadata)에 저장된 ACL을 각각의 브로커가 캐시해 빠르게 처리(평가)하며, 감사 로그를 통해 승인/거절 이벤트를 남길 수 있다.
카프카 ACL과 부여되는 접근권한은 confluent 공식 문서 에서 표로 확인 가능하다.
감사와 디버깅을 목적으로 상세한 log4j 로그를 생성하도록 카프카 브로커를 설정할 수 있다. log4j.properties 에는 로깅 레벨 뿐만 아니라 로깅에 사용되는 어펜더(appender) 각각에 대한 설정까지도 잡아 줄 수 있다. 감사 목적의 로그를 남기기 위해서는 인가를 로깅하는 kafka.authorizer.logger 에 설정하고, 요청을 로깅할 때는 kafka.request.logger 에 설정한다. 프로덕션 환경에서는 이러한 로그를 분석하기 위해 엘라스틱 스택과 같은 프레임워크를 사용할 수 있다.
예를 들어 권한 관리자는 거부된 접근은 INFO 로, 성공한 접근은 DEBUG 레벨로 로그를 남기고 DEBUG 레벨의 로그에는 사용자의 보안 주체 및 클라이언트 호스트를 포함하여(또는 TRACE 레벨로 올려 요청의 전체 세부사항까지 출력) 로깅되도록하고 이를 토대로 감사에 사용할 수 있다.
앞에서는 카프카 클러스터를 안전하게 하기 위해 카프카에 대한 접근을 제어하는 옵션에 대해서 알아보았다. 프로덕션 환경에 대해 보안을 설계할 때는 개별 요소에 가해지는 보안 위협에 대해서 뿐만 아니라 전체 시스템에 대한 위협 모델을 고려해야 한다.
단순히 안전한 인증, 인가 그리고 암호화를 사용해서 카프카에 저장된 데이터 및 메타데이터를 보호하는 것 뿐만 아니라, 전체 플랫폼에 대한 안전을 위해 네트워크 방화벽이나 물리적 저장소 암호화 등이 필요할 수 있다. 예를 들어, 설정 파일에 암호화되지 않은 비밀번호는 설령 접근이 제한되어있다 한들 안전하지 않다. 이에 따라 카프카는 외부의 안전한 저장소(외부 안전한 서드파티 비밀번호 저장소)에 비밀번호를 저장할 수 있도록 한다.