원문 캡처 · getkloak.io
원문 캡처 · getkloak.io

kloak은 OpenSSL의 암호화 트래픽에서 비밀 값을 바꿀 때 필요한 해시 부분키 H를 CPU에 따라 다른 위치에서 읽어야 한다는 점을 반영했다. 이 수정은 일부 머신에서만 간헐적으로 나타나던 재작성 실패를 해결했으며, 해당 경로를 확인하는 자동화도 추가했다.

kloak은 애플리케이션에 실제 비밀 값을 노출하지 않는다. 변이 웹훅이 비밀을 같은 길이의 무작위 kl::… 자리표시자가 든 그림자 복사본으로 바꾼다. 애플리케이션이 TLS 연결에 데이터를 쓰면 eBPF uprobe가 자리표시자를 찾아 목적지가 정책상 허용되는지 확인한다. 허용된 경우 tc 프로그램이 전송 중인 암호화 레코드에 실제 값을 넣는다. 따라서 서버는 실제 비밀을 받지만 애플리케이션과 파드 안의 다른 구성 요소는 자리표시자만 본다.

암호문을 바꾸면 GCM 인증 태그도 다시 계산해야 한다. 커널에서 태그를 계산하려면 연결의 해시 부분키 H가 필요하다. kloak은 TLS를 종료하는 프록시 대신 각 TLS 스택의 키와 평문이 있는 지점에 연결한다. 그 대가로 릴리스마다 달라지는 내부 구조의 오프셋을 읽는다.

kloak은 지원하는 스택별로 오프셋을 찾는 도구를 운영한다. 매일 지원 대상 릴리스와 아키텍처에서 오프셋을 찾아 전체 E2E 테스트를 실행하며, 새 릴리스가 나오면 봇이 지원을 추가하는 풀 리퀘스트를 연다. 하지만 2026년 6월까지 E2E 테스트는 데모 이미지에 포함된 OpenSSL 버전만 대상으로 했다. 5월에는 OpenSSL 3.2 이상에서만 맞는 하드코딩 오프셋 때문에 3.0과 3.1에서 kloak이 작동하지 않는 문제가 있었고, Ubuntu 24.04에서 시험한 뒤에야 발견했다(PR #222).

6월에 OpenSSL 각 릴리스를 소스에서 빌드해 오프셋과 E2E 동작을 확인하는 야간 작업을 추가했다. 그 뒤 10월까지 여러 버전에서 간헐적 실패가 이어졌다. 서버에는 실제 비밀 대신 kl:: 자리표시자가 도착했고, 처음에는 경쟁 상태를 의심했다. 실패는 같은 버전에서도 어떤 밤에는 나타나고 다른 밤에는 사라졌다.

100회 실행 결과 OpenSSL 3.0은 98회 통과했고 실패한 적이 없었다. 3.1부터 3.6까지는 각 버전이 약 26%의 비율로 실패했다. 차이를 추적한 결과, OpenSSL 3.1부터 일부 CPU에서 사용하는 x86 vaes_gcm 경로가 원인이었다. 이 경로는 AVX-512를 지원하는 Intel Ice Lake 이상 또는 AMD Zen 4 이상에서 작동한다. GitHub 호스팅 러너는 사용 가능한 Azure 머신에 배정되므로 작업마다 CPU가 달랐다.

이 경로는 GCM 문맥을 0으로 초기화한 뒤 H의 사전 계산 테이블만 채웠고, GCM128_CONTEXT.H에는 값을 쓰지 않았다. 따라서 이 CPU에서는 해당 필드가 늘 0이었다. 실패는 특정 CPU 계열에서 일관되게 발생했지만, 러너 배정이 무작위여서 간헐적으로 보였다.

H는 메모리에 다른 형태로 저장돼 있었다. 사전 계산 테이블의 15번 슬롯에는 HashKey_1이 들어 있으며, 이는 H에 다항식 곱셈을 적용한 값이다. Go의 gcmAesInit도 같은 형태를 사용한다. kloak은 기존에 Go 경로에서 이 변환을 되돌리는 방법을 알고 있었다.

수정 사항은 OpenSSL에서 H를 읽는 경로를 하나로 모았다. 먼저 GCM 문맥의 구조를 확인하고, 원래 H가 있으면 이를 읽는다. 값이 없으면 x86에서만 HashKey_1을 역산한다. arm64에서 H가 0이면 kloak이 모르는 구현으로 판단해 처리를 거부한다. 오프셋 검색 도구도 OpenSSL 소스의 분기 방식과 사전 계산 순서·상수가 BPF 코드의 가정과 맞는지 검사한다. 다르면 오프셋을 내보내지 않아 CI가 실패한다. 잘못 계산한 태그를 배포하는 것보다 빌드에서 멈추는 편을 택했다.

H를 AES 라운드 키에서 다시 계산하려던 초기 수정안(PR #276)은 BPF 검증기에서 호출 스택 크기 제한에 걸렸다. 이후 테스트에서 같은 버전이 통과와 실패를 오가는 점도 그 수정안의 가정과 맞지 않아 닫았다.

수정 후 CI는 특정 AVX-512 러너를 고정하는 대신 각 러너의 CPU 플래그를 기록한다. 수정 브랜치의 OpenSSL 야간 작업은 두 종류의 CPU에서 실행됐고, 표에 든 OpenSSL 버전이 모두 통과했다. AVX-512와 VAES가 있는 Intel Xeon Platinum 8573C에서는 3.3.7, 3.5.9, 3.6.5가 통과했다. AMD EPYC 9V45에서는 3.0.22가 통과했으며, 이 버전에는 VAES 경로가 없다. AVX-512가 없는 AMD EPYC 7763에서는 3.1.8, 3.2.6, 3.4.8이 통과했다. 앞의 세 OpenSSL 버전은 원래 H 필드가 항상 0이므로, 수정 전에는 실패했던 재작성 작업이 HashKey_1을 이용해 통과했다.