Socket.IO 와 Kafka 설정을 마치고 배포 하여 테스트를 진행하였다. 해당 과정에서 TLS 설정 문제로 인해 Socket.IO 연결이 계속해서 끊기는 현상이 발견하였다. 따라서 이번 글에서는 원인 규명과 해결 방법에 대한 과정을 적는다.
1. 배포환경에서 난 오류
1.1 Kafka 연결 오류
이전에 KafkaConsumer.js 에서 Consumer 연결을 시도하는 코드가 있다. 리소스 누수를 방지하기 위하여 Consumer 가 최대 다섯번 동안 연결을 시도하고 안되었을 경우 연결을 취소하고 로그를 띄우게 구현하였다. 해당 로그를 보게 되면 현재 다섯번 시도하였고, 연결 상태가 끊어졌다는 오류를 배출하고 있다. 로컬환경에서는 정상적으로 작동하지만 배포환경에서 작동하지 않는 것을 보니 TLS/SSL 인증이 문제라고 판단하였다.
1.2 Server 연결 오류
해당 로그를 보게되면 예상대로 Server 에서 SSL 인증에 대한 오류가 난 것을 확인할 수 있다. 배포 환경에서는 TLS 인증을 받게 설정되어 있는데 필자의 경우 TLS 설정을 따로 설정하지 않았기 때문에 발생한 오류이다.
2. TLS 란 ?
TLS (Transport Layer Security)는 인터넷 통신에서 데이터를 암호화하여 안전하게 전송하기 위한 프로토콜이다. 이전 버전인 SSL (Secure Sockets Layer)의 후속 기술로, 데이터의 기밀성, 무결성, 인증을 보장한다. 현재 대부분의 인터넷 보안 표준에서 TLS를 사용하고 있으며, HTTPS를 포함한 많은 프로토콜에서 활용된다. 즉, TLS 는 SSL 의 업데이트 버전이며 명칭만 다르다고 생각하면 된다.
2.1 SSL/TLS 작동 방식
- HandeShake (핸드셰이크)
클라이언트가 서버에 접속하면, 클라이언트와 서버는 핸드셰이크 과정을 시작한다. 클라이언트는 서버에 연결 요청을 보냄과 동시에 지원하는 암호화 알고리즘을 서버에 전송한다. 이에 서버는 클라이언트의 요청에 응답하고, 사용할 암호화 알고리즘을 선택하고 인증서를 제공한다. 서버는 자신의 인증서를 클라이언트에 제공하여 신원을 증명하고, 클라이언트와 서버는 공개키 암호화를 사용해 대칭키를 생성한다. - Data Transfer (데이터 전송)
핸드셰이크가 완료되면 클라이언트와 서버는 생성된 대칭 키를 통해 암호화 된 데이터를 안전하게 전송한다. - Connection Close (연결 종료)
통신이 끝나면, 클라이언트나 서버중 한쪽에서 연결 종료 요청을 보낸다. 연결 종료 단계에서는 각 측에서 사용한 키 및 세션 정보를 정리하고 연결을 종료한다.
2.2 TLS 의 핸드셰이크 주요 단계
TCP 프로토콜은 신뢰할 수 있는 데이터 전송을 위해 클라이언트와 서버가 세 가지 단계를 거쳐 연결을 설정한다. 파랑색으로 이루어진 부분은 데이터를 전송하기 전 통신 준비를 확인하는 단계이다.
- SYN (Synchronize)
클라이언트가 서버와 연결을 요청하는 첫번째 메시지이다. 클라이언트는 자신이 사용할 초기 시퀀스 번호를 포함한 SYN 패킷을 서버로 보낸다. - SYN ACK (Synchronize-Acknowledge)
서버가 클라이언트의 요청을 수락하고, 자신의 초기 시퀀스 번호를 포함한 SYN 과 클라이언트의 SYN 에 대한 응답으로 ACK 를 함께 전송한다. 이 단계에서 서버는 클라이언트와 통신을 시작할 준비가 되었다는 뜻이다. - ACK (Acknowledge)
클라이언트가 서버의 SYN ACK 에 대한 확인 메시지(ACK)를 보낸다. 이 단계가 완료되면 클라이언트와 서버간의 TCP 연결 설정이 된다.
노랑색으로 이루어진 부분 (TLS Handshake) 은 클라이언트와 서버 간의 암호화된 통신을 설정하기 위한 과정이다. 이 단계는 데이터를 안전하게 전송할 수 있도록 인증과 키 교환을 수행한다.
- ClientHello (Client -> Server)
클라이언트는 TLS 연결을 시작하며, 사용할 TLS 프로토콜의 버전, 클라이언트가 지원하는 암호화 알고리즘 리스트, 세션 키 생성에 사용될 난수를 서버에 전송한다. - ServerHello (Server -> Client)
서버는 클라이언트 요청을 수락하며, 클라이언트와 합의한 TLS 프로토콜의 버전, 클라이언트가 제안한 암호화 알고리즘 중 하나를 선택, 세션 키 생성에 사용될 난수, 서버의 신원을 증명하는 증명서를 클라이언트에게 응답한다. - ClientKeyExchange 및 ChangeCipherSpec (Client -> Server)
클라이언트는 서버와 공유한 정보를 바탕으로 세션 키를 생성한다. 이후 ChangeCipherSpec 메시지를 전송하여 앞으로의 통신은 암호화된 형태로 진행됨을 알린다. Finished 메시지를 보내 TLS 핸드셰이크가 완료되었음을 알린다. - ChangeCipherSpec 및 Finished (Server -> Client)
서버도 동일한 세션 키를 생성하여 암호화 준비를 완료한다. 클라이언트에게 ChangeCipherSpec 및 Finished 메시지를 전송한다.
결과적으로 TLS 통신을 하기 전 TCP Handshake 가 먼저 이루어지고, 이후에 암호화된 통신을 위하여 TLS Handshake 를 통해 인증과 키 교환을 한다는 것을 알 수 있다.
3. TLS 설정
3.1 Kafka Cluster Config 확인
먼저 TLS 를 설정하기 위해 배포된 yaml 파일을 확인해야 한다. 필자의 경우 Redpanda 에서 지원하는 Kafka 를 사용하였기 때문에 관련된 설정을 확인하였다. 필자의 Kafka Cluster 설정은 다음과 같다.
tls:
trustedCertificates:
- pattern: '*.crt'
secretName: redpanda-default-cert
해당 설정은 redpanda-default-cert 라는 이름의 Kubernetes Secret 에 저장된 .crt 인증서를 TLS 로 사용하겠다는 뜻이다.
3.2 KafkaConfig 설정
Cluster 설정을 확인했으니 이제는 KafkaConfig 설정을 바꿔줘야 한다. 필자의 경우 개발환경과 배포환경이 다르기 때문에 TLS 가 없는 환경에서도, TLS 가 필요한 환경에서도 동작해야 하기 때문에 삼항연산자를 활용하여 코드를 작성하였고, 민감한 정보를 가지고 있는 환경변수의 경우 .env 를 활용하였다.
const useTls = fs.existsSync(process.env.KAFKA_SSL_CLIENT_CERT) && fs.existsSync(process.env.KAFKA_SSL_CLIENT_KEY);
const sslOptions = useTls
? {
rejectUnauthorized: true,
ca: fs.readFileSync(process.env.KAFKA_SSL_CA_CERT, 'utf-8'),
cert: fs.readFileSync(process.env.KAFKA_SSL_CLIENT_CERT, 'utf-8'),
key: fs.readFileSync(process.env.KAFKA_SSL_CLIENT_KEY, 'utf-8'),
}
: null;
return new Kafka({
clientId: clientId, // 역할별 clientId 지정
brokers: kafkaBrokers,
ssl: sslOptions, // TLS 옵션 추가
});
먼저 useTls 에서는 해당 경로에 파일이 존재하는지 확인한다. 환경 변수에서 클라이언트 인증서 경로와 클라이언트 키 경로를 가지고 온다. 두 파일이 모두 존재할 경우 True 를 반환한다. 후에 sslOptions 에서 TLS 가 활성화 된 경우 인증을 진행하고, TLS 가 비활성화된 경우 TLS 설정을 하지 않는다. 다음 Kafka 인스턴스 생성시 반환된 sslOptions 를 통해 TLS 인증을 시도한다.
4. 마치며
TLS 설정 후에 배포환경에서의 .env 를 재설정 하고 배포하니 정상적으로 작동되어졌다. 하지만 Hoppscotch 를 통해 웹소켓을 연결하는 순간 비정상적으로 연결이 끊기는 문제가 발생했다. 해당 문제는 다음 글에서 작성하도록 한다.
'Project > ST00CK' 카테고리의 다른 글
배포환경에서 Socket.IO 의 연결 끊김 이슈 및 해결방법 (1) | 2025.01.16 |
---|---|
node.js 에서 Kafka 설정하기 (0) | 2025.01.12 |
node.js 에서 gRPC 설정하기 (0) | 2025.01.08 |
ScyllaDB 연결 및 채팅방 API 구현 (1) | 2025.01.01 |
ScyllaDB ERD 작성, API 명세서 작성 (1차) (0) | 2024.12.31 |