(KO) PostgreSQL 전송 보안 — TLS(OpenSSL)와 GSSAPI 암호화
목차
- 이론적 배경
- DBMS 공통 설계 패턴
- PostgreSQL의 접근 방식
- 소스 워크스루
- 소스 검증 (2026-06-05 기준)
- PostgreSQL 너머 — 비교 설계와 연구 전선
- 출처
이론적 배경
섹션 제목: “이론적 배경”데이터베이스 서버는 자격증명, 질의, 결과 데이터를 주고받는 네트워크 엔드포인트를 노출한다. 클라이언트와 서버 사이 경로에 있는 공격자는 이 모든 데이터를 읽거나 변조하려 한다. **전송 보안(transport security)**은 TCP 연결의 평문 바이트 스트림을 세 가지 보장을 갖춘 채널로 바꾸는 계층이다. be-secure.c 헤더 주석이 세 가지를 명시한다: 기밀성(도청자가 내용을 알 수 없다), 메시지 무결성(변조가 탐지된다), 엔드포인트 인증(클라이언트가 실제 서버와 통신하고 있음을 검증할 수 있다, 선택적으로 반대 방향도). TLS와 GSSAPI/Kerberos 프로토콜 패밀리는 이 세 가지 보안 목표를 제공하도록 설계됐다.
설계 공간을 정의하는 아키텍처 질문은 세 가지다.
첫째, 보안 계층은 애플리케이션 프로토콜 기준 어디에 위치하는가? 깔끔한 답, 그리고 PostgreSQL이 택한 답은 그 아래다. FE/BE 메시지 프레이밍(타입 바이트 + 길이 워드, postgres-wire-protocol.md 참조)은 바이트가 암호화됐는지 여부와 무관하게 동일하다. 암호화는 메시지 계층이 바이트를 생성한 이후 바이트 스트림에 적용된다. 애플리케이션 코드는 추상 채널로 read/write를 호출하며 아래에 암호화 계층이 있는지 알 필요가 없다.
둘째, 보안 채널은 어떻게 협상하는가? 클라이언트는 서버가 TLS를 지원한다고 단정할 수 없다. 서버가 SSL을 비활성화했거나 TLS가 무의미한 Unix 소켓으로 연결됐을 수 있다. 따라서 프로토콜에는 인밴드 협상이 필요하다. 클라이언트가 “TLS를 시작해도 되는가?”라고 묻고 서버가 예/아니오로 답한 뒤 “예”일 때만 암호화 핸드셰이크가 시작된다. 대안인 직접/묵시적 TLS(HTTPS처럼 암호화 핸드셰이크가 와이어에서 맨 처음 나타나는 형식)는 협상 왕복을 없애는 대신 하나의 포트에서 평문과 암호화 클라이언트를 동시에 수용할 수 없게 된다. PostgreSQL은 역사적으로 협상 형식만 사용했고, PG17에서 ALPN으로 판별하는 직접 TLS 경로를 선택적으로 추가했다.
셋째, 어떤 암호화 스택을 사용하는가? TLS(OpenSSL 경유)가 주류다. X.509 인증서, 비대칭 키 교환, 협상된 대칭 암호를 사용한다. GSSAPI/Kerberos는 기업 SSO 환경에서 선호하는 대안이다. 사용자를 인증한 Kerberos 티켓이 세션 키도 수립하므로 암호화와 인증이 단일 메커니즘을 공유한다. PostgreSQL은 두 가지를 모두 지원하되, 같은 리스닝 소켓에서 상호 배타적으로 동작한다.
Database System Concepts(Silberschatz et al.)는 애플리케이션 보안 장에서 전송 중 데이터 암호화를 접근 제어와 직교하는 관심사로 정리한다. 인증은 당신이 누구인지, 권한 부여는 무엇을 할 수 있는지, 전송 암호화는 다른 사람이 이것을 읽을 수 있는지를 답한다. Database Internals(Petrov)는 노드 통신 논의에서, 프로덕션 시스템이 암호화 관심사를 메시지 코덱 아래 얇은 심(shim)으로 밀어 넣어 상위 계층(질의 프로토콜, 복제 프로토콜)이 암호화 방식에 무관하게 유지될 수 있다고 설명한다. PostgreSQL의 secure_read/secure_write 쌍이 바로 그 심이다.
이 계층을 관통하는 미묘하지만 보안에 결정적인 주제가 있다. 평문과 암호문 사이 경계는 완전히 밀폐되어야 한다. 핸드셰이크 완료 이전에 도착한 바이트를 서버가 핸드셰이크 이후 애플리케이션 데이터인 것처럼 처리하면, 중간자(MitM) 공격자가 평문을 주입해 애플리케이션이 인증된 트래픽으로 오해하게 만들 수 있다. PostgreSQL은 이 경계를 명시적으로 지킨다(“received unencrypted data after SSL request” FATAL). 그 경비가 위협 모델에서 직접 유래했다는 점이 중요하다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”이 절은 클라이언트-서버 데이터베이스가 전송 보안을 위해 채택하는 반복적 설계 패턴을 정리한다. PostgreSQL의 선택이 공통 설계 공간 안의 선택으로 읽히게 하기 위함이다.
메시지 코덱 아래 디스패치 심
섹션 제목: “메시지 코덱 아래 디스패치 심”선택적 암호화를 지원하는 모든 엔진은 간접 계층을 도입한다. 메시지 프레이밍 코드는 recv(2)를 직접 호출하는 대신 channel_read(conn, buf, len)을 호출하고, channel_read는 런타임에 연결별로 원시 소켓 경로, TLS 경로, 또는 다른 보안 경로로 디스패치한다. 디스패치 비용은 연결별 플래그에 대한 몇 개의 분기로 작다. 메시지 파싱과 빌딩 코드 전체를 추상 채널 하나에 맞춰 한 번만 작성하면 된다. PostgreSQL의 secure_read가 전형적인 사례다. port->ssl_in_use를 먼저 확인하고, 그 다음 port->gss->enc를 확인하며, 원시 폴백으로 떨어진다.
단일 바이트 판정을 사용하는 인밴드 협상
섹션 제목: “단일 바이트 판정을 사용하는 인밴드 협상”협상 TLS 패턴은 거의 보편적이다. 클라이언트가 고정 센티넬(“StartTLS”, SSLRequest, MySQL의 SSL 기능 플래그)을 보내면 서버는 핸드셰이크 바이트가 흐르기 전에 최소한의 예/아니오 토큰을 평문으로 답한다. 그 시점에 양측 모두 아직 암호화 알고리즘이 없기 때문이다. PostgreSQL은 SSLRequest에 한 바이트 'S'(예) 또는 'N'(아니오)으로, GSSENCRequest에 'G' 또는 'N'으로 답한다.
추상화된 소켓에서의 핸드셰이크 (BIO 패턴)
섹션 제목: “추상화된 소켓에서의 핸드셰이크 (BIO 패턴)”TLS 라이브러리는 파일 디스크립터를 직접 소유한다고 가정하지 않는다. OpenSSL의 BIO 추상화는 애플리케이션이 자체 send/recv 함수를 삽입하게 해 인터럽트 처리, 논블로킹 시맨틱, 플랫폼 고유 문제(Windows 시그널 처리)가 라이브러리가 아닌 데이터베이스 통제 하에 있도록 한다. 데이터베이스는 읽기/쓰기 메서드가 자체 원시 소켓 기본 요소를 얇게 감싸는 BIO를 제공하고, OpenSSL은 그 BIO를 통해 핸드셰이크와 레코드 암호화를 수행한다. PostgreSQL의 port_bio_read/port_bio_write(secure_raw_read/secure_raw_write 호출)가 정확히 그 역할이다.
인증서 기반 상호 인증
섹션 제목: “인증서 기반 상호 인증”보안 계층이 TLS일 때 기밀성을 수립하는 동일한 핸드셰이크가 X.509 인증서로 양 끝점을 인증할 수도 있다. 서버는 항상 인증서를 제시하고, 클라이언트에게 인증서를 요청할 수 있다(CertificateRequest). 서버는 클라이언트 인증서에서 신원(Common Name, 전체 Distinguished Name)을 추출하며, 인증 방법(pg_hba.conf의 cert)이 이를 데이터베이스 역할로 매핑할 수 있다. 전송 보안과 인증을 연결하지만 그 연결은 하드코딩이 아니라 pg_hba.conf가 중재한다.
채널 바인딩으로 인증을 전송에 묶는다
섹션 제목: “채널 바인딩으로 인증을 전송에 묶는다”SCRAM-SHA-256 인증 교환은 그 아래 특정 TLS 채널에 바인딩될 수 있다(RFC 5929 tls-server-end-point). 서버 인증서의 해시를 인증 증명에 혼합하는 방식이다. TLS를 종료하고 재개시키는 중간자 공격자를 무력화한다. 릴레이된 인증 증명이 공격자의 인증서와 더 이상 일치하지 않기 때문이다. 전송 계층은 따라서 “RFC 5929가 규정하는 알고리즘으로 서버 인증서 해시를 달라”는 요청에 응답해야 한다. PostgreSQL의 be_tls_get_certificate_hash가 이를 제공한다.
비TLS 암호화를 위한 길이-접두사 프레이밍
섹션 제목: “비TLS 암호화를 위한 길이-접두사 프레이밍”보안 계층이 TLS가 아닌 경우 — 예를 들어 레코드 스트림을 직접 관리하지 않고 불투명한 “wrapped” 토큰을 애플리케이션에 전달하는 GSSAPI — 데이터베이스가 직접 토큰을 프레이밍해야 한다. 보편적 답은 토큰당 4바이트 길이 접두사다. 수신자는 완전한 토큰을 수집한 뒤 암호화 라이브러리에 전달할 수 있다. PostgreSQL의 GSSAPI 경로가 정확히 이 방식을 사용하며, 악의적인 피어가 무한 할당을 요구하지 못하도록 PQ_GSS_MAX_PACKET_SIZE 상한을 둔다.
이론 ↔ PostgreSQL 매핑
섹션 제목: “이론 ↔ PostgreSQL 매핑”| 이론 / 관례 | PostgreSQL 이름 |
|---|---|
| 코덱 아래 디스패치 심 | secure_read / secure_write in be-secure.c |
| 원시 소켓 기본 요소 | secure_raw_read / secure_raw_write (recv/send) |
| 인밴드 TLS 협상 요청 | NEGOTIATE_SSL_CODE (SSLRequest), 한 바이트 'S'/'N' 응답 |
| 인밴드 GSS 협상 요청 | NEGOTIATE_GSS_CODE (GSSENCRequest), 한 바이트 'G'/'N' 응답 |
| 직접/묵시적 TLS | ProcessSSLStartup이 0x16 TLS 레코드 바이트를 스니핑; ALPN postgresql |
| TLS 핸드셰이크 | be_tls_open_server → SSL_accept |
| 커스텀 소켓 BIO | port_bio_read / port_bio_write / ssl_set_port_bio |
| TLS 레코드 I/O | be_tls_read (SSL_read) / be_tls_write (SSL_write) |
| 서버 인증서 / 키 로드 | be_tls_init (SSL_CTX_use_certificate_chain_file, …) |
| 클라이언트 인증서 검증 콜백 | verify_cb, SSL_CTX_set_verify 경유로 설정 |
| 피어 신원 추출 | port->peer_cn, port->peer_dn in be_tls_open_server |
| 채널 바인딩 해시 | be_tls_get_certificate_hash (RFC 5929) |
| GSSAPI 전송 암호화 | be_gssapi_read / be_gssapi_write (gss_unwrap/gss_wrap) |
| GSSAPI 세션 수립 | secure_open_gssapi (gss_accept_sec_context) |
| GSS 패킷 상한 | PQ_GSS_MAX_PACKET_SIZE (16 kB), PQ_GSS_AUTH_BUFFER_SIZE (64 kB) |
PostgreSQL의 접근 방식
섹션 제목: “PostgreSQL의 접근 방식”PostgreSQL은 전송 보안을 세 소스 파일에 나눠 구현하며, 책임 경계가 명확하다.
-
be-secure.c— 암호화 무관 심(cipher-agnostic shim).secure_read/secure_write(디스패치),secure_raw_read/secure_raw_write(recv/send기본 요소), 그리고 얇은secure_open_server/secure_close래퍼를 소유한다. SSL도 GSS도 없는 빌드에서도 컴파일되며, 그 경우 디스패치는 단순히 원시 경로로 수렴한다. 와이어 프로토콜 계층(pqcomm.c)이 알고 있는 유일한 파일이다.pqcomm.c는 OpenSSL이나 GSSAPI 헤더를 포함하지 않는다. -
be-secure-openssl.c— TLS 전체. 컨텍스트 설정(be_tls_init), 연결별 핸드셰이크(be_tls_open_server), 커스텀 BIO, 레코드 I/O(be_tls_read/be_tls_write), 클라이언트 인증서 처리, ALPN, 채널 바인딩, 그리고pg_stat_ssl과ssl_*SQL 함수에 데이터를 공급하는 각종 접근자(be_tls_get_version,be_tls_get_cipher, …)를 담는다. -
be-secure-gssapi.c— GSSAPI 전송 암호화 전체. 길이-접두사 패킷 프레이밍(be_gssapi_read/be_gssapi_write), 세션 수립 루프(secure_open_gssapi), 그리고 접근자(be_gssapi_get_enc,be_gssapi_get_princ, …)를 담는다.
이들 가운데 선택하는 협상 로직은 한 계층 위인 backend_startup.c(ProcessStartupPacket, ProcessSSLStartup)에 있다. 스타트업 패킷 처리 전반은 postgres-wire-protocol.md에서 다루고, 여기서는 SSL/GSS 분기에 집중한다.
디스패치 심: secure_read / secure_write
섹션 제목: “디스패치 심: secure_read / secure_write”아키텍처 전체는 런타임 분기 하나에 달려 있다. secure_read는 두 연결별 플래그로 백엔드를 선택하고 원시 소켓으로 폴스루한다.
// secure_read — src/backend/libpq/be-secure.cretry:#ifdef USE_SSL waitfor = 0; if (port->ssl_in_use) n = be_tls_read(port, ptr, len, &waitfor); else#endif#ifdef ENABLE_GSS if (port->gss && port->gss->enc) { n = be_gssapi_read(port, ptr, len); waitfor = WL_SOCKET_READABLE; } else#endif { n = secure_raw_read(port, ptr, len); waitfor = WL_SOCKET_READABLE; }secure_write는 이것을 쓰기 방향으로 그대로 반영한다. 두 플래그는 상호 배타적이다. 연결은 TLS이거나, GSS 암호화이거나, 평문이지만, 동시에 두 가지일 수 없다. 협상 로직(아래)이 ssl_done/gss_done을 설정해 하나를 수락하면 다른 것을 거부하도록 한다.
심은 블로킹 규율도 소유한다. 블로킹 모드 Port에서 선택된 백엔드가 EWOULDBLOCK/EAGAIN을 반환하면, secure_read는 소켓이 다시 읽기 가능해질 때까지 대기 이벤트 집합(FeBeWaitSet)에 파킹한다. postmaster가 종료된 이후 클라이언트 백엔드가 남아 있지 않도록 postmaster 사망도 재확인한다.
// secure_read — src/backend/libpq/be-secure.cif (n < 0 && !port->noblock && (errno == EWOULDBLOCK || errno == EAGAIN)){ WaitEvent event; ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL); WaitEventSetWait(FeBeWaitSet, -1, &event, 1, WAIT_EVENT_CLIENT_READ); if (event.events & WL_POSTMASTER_DEATH) ereport(FATAL, (errcode(ERRCODE_ADMIN_SHUTDOWN), errmsg("terminating connection due to unexpected postmaster exit"))); if (event.events & WL_LATCH_SET) { ResetLatch(MyLatch); ProcessClientReadInterrupt(true); } goto retry;}waitfor 값에 주목할 필요가 있다. TLS의 경우 be_tls_read가 WL_SOCKET_READABLE 또는 WL_SOCKET_WRITEABLE로 설정한다. SSL_read가 TLS 재협상/포스트-핸드셰이크 메시지를 위해 쓰기를 해야 할 수 있기 때문이다. 원시 경로와 GSS 경로는 항상 읽기 가능성을 기다린다. waitfor가 be_tls_read의 출력 파라미터인 이유가 여기에 있다.
secure_raw_read / secure_raw_write: 바닥층
섹션 제목: “secure_raw_read / secure_raw_write: 바닥층”모든 경로 맨 아래에는 평범한 recv/send가 있다. 한 가지 미묘한 점이 있다. secure_raw_read는 보안 계층이 설정되기 전에 버퍼링된 바이트를 먼저 소진한다. SSL 협상 중 서버는 SSLRequest 이후 몇 바이트를 이미 읽었을 수 있다. secure_open_server가 그것을 port->raw_buf에 저장하고, secure_raw_read는 소켓에 손대기 전에 그것을 반환한다. OpenSSL의 첫 핸드셰이크 읽기가 요청 다음에 오는 정확한 바이트를 보게 된다.
// secure_raw_read — src/backend/libpq/be-secure.cif (port->raw_buf_remaining > 0){ if (len > port->raw_buf_remaining) len = port->raw_buf_remaining; memcpy(ptr, port->raw_buf + port->raw_buf_consumed, len); port->raw_buf_consumed += len; port->raw_buf_remaining -= len; return len;}n = recv(port->sock, ptr, len, 0);return n;협상: SSLRequest / GSSENCRequest
섹션 제목: “협상: SSLRequest / GSSENCRequest”일반 스타트업 패킷 이전에, v3 클라이언트는 두 가지 특수 4바이트 요청 코드 중 하나를 보낼 수 있다. 이들은 이 목적으로 예약된 프로토콜 “버전”이다.
// special request codes — src/include/libpq/pqcomm.h#define CANCEL_REQUEST_CODE PG_PROTOCOL(1234,5678)#define NEGOTIATE_SSL_CODE PG_PROTOCOL(1234,5679)#define NEGOTIATE_GSS_CODE PG_PROTOCOL(1234,5680)ProcessStartupPacket이 이를 인식하고 단일 바이트로 답한다. 이 시점에 secure_write는 아직 원시 경로이므로 평문으로 쓴다. SSL의 경우:
// ProcessStartupPacket (SSL branch) — src/backend/tcop/backend_startup.cif (proto == NEGOTIATE_SSL_CODE && !ssl_done){ char SSLok;#ifdef USE_SSL if (!LoadedSSL || port->laddr.addr.ss_family == AF_UNIX || port->ssl_in_use) SSLok = 'N'; else SSLok = 'S'; /* Support for SSL */#else SSLok = 'N'; /* No support for SSL */#endif while (secure_write(port, &SSLok, 1) != 1) { ... }#ifdef USE_SSL if (SSLok == 'S' && secure_open_server(port) == -1) return STATUS_ERROR;#endif ... ssl_done = true; if (SSLok == 'S') gss_done = true; /* SSL chosen → GSS no longer offered */ goto retry;}GSS 분기는 구조적으로 동일하다. 'G' 또는 'N'으로 답하고 secure_open_gssapi를 호출한다. 클라이언트는 두 가지를 어느 순서로도 시도할 수 있다. ssl_done/gss_done 플래그가 하나를 수락하면 다른 것의 문을 닫고, 거부된 요청이 클라이언트로 하여금 다른 메커니즘으로 폴백하게 한다. 핸드셰이크 성공 후 클라이언트는 실제 스타트업 패킷을 암호화된 채널 위로 재전송하며, goto retry가 그것을 읽으러 돌아간다.
중간자 방어는 핸드셰이크 직후에 작동한다. 이미 버퍼링된 바이트가 있으면(암호화 수립 전에 도착했으므로 평문이다) 서버가 연결을 중단한다.
// ProcessStartupPacket — src/backend/tcop/backend_startup.cif (pq_buffer_remaining_data() > 0) ereport(FATAL, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("received unencrypted data after SSL request"), errdetail("This could be either a client-software bug or evidence of an attempted man-in-the-middle attack.")));직접 TLS (PG17+): 레코드 바이트 스니핑
섹션 제목: “직접 TLS (PG17+): 레코드 바이트 스니핑”PostgreSQL은 첫 번째 바이트가 SSLRequest가 아닌 TLS 핸드셰이크 레코드인 직접 TLS 연결도 수락한다. ProcessSSLStartup이 첫 번째 바이트를 들여다본다. TLS 핸드셰이크 레코드의 ContentType은 0x16이며, 이는 합법적인 PostgreSQL 스타트업 길이의 상위 바이트가 될 수 없다(수백 MB짜리 패킷을 의미하게 된다).
// ProcessSSLStartup — src/backend/tcop/backend_startup.cfirstbyte = pq_peekbyte();if (firstbyte != 0x16) return STATUS_OK; /* not a TLS handshake; normal startup */#ifdef USE_SSL if (!LoadedSSL || port->laddr.addr.ss_family == AF_UNIX) goto reject; if (secure_open_server(port) == -1) goto reject;직접 TLS 연결은 반드시 postgresql ALPN 프로토콜을 협상해야 한다(ProcessSSLStartup 끝의 확인). 서버가 의도적인 PostgreSQL 직접 TLS 클라이언트를 해당 포트에서 TLS를 여는 다른 프로토콜과 구별하는 방법이다.
TLS 핸드셰이크 구조
섹션 제목: “TLS 핸드셰이크 구조”flowchart TD
A["클라이언트 연결<br/>(TCP accept)"] --> B{"첫 바이트?"}
B -->|"SSLRequest 코드"| C["'S'/'N' 응답<br/>(평문, 1 바이트)"]
B -->|"0x16 TLS 레코드"| D["ProcessSSLStartup<br/>직접 TLS"]
B -->|"스타트업 패킷"| Z["암호화 없음<br/>→ 인증"]
C -->|"'S'"| E["secure_open_server"]
D --> E
E --> F["be_tls_open_server"]
F --> G["SSL_new + ssl_set_port_bio<br/>(recv/send 위 커스텀 BIO)"]
G --> H["SSL_accept 루프<br/>(WANT_READ/WANT_WRITE → 대기)"]
H --> I["ALPN 확인<br/>(SSL_get0_alpn_selected)"]
I --> J["피어 인증서 추출<br/>CN / DN / valid 플래그"]
J --> K["ssl_in_use = true"]
K --> L["암호화된 채널 위로<br/>스타트업 패킷 재읽기"]
L --> Z2["인증<br/>(pg_hba, channel binding)"]
be_tls_open_server는 연결별 SSL을 할당하고, 커스텀 BIO를 설치하고, SSL_accept를 루프 안에서 구동한다. OpenSSL이 WANT_READ/WANT_WRITE를 반환할 때마다 소켓에 파킹한다.
// be_tls_open_server — src/backend/libpq/be-secure-openssl.cSSL_CTX_set_alpn_select_cb(SSL_context, alpn_cb, port);if (!(port->ssl = SSL_new(SSL_context))) { ... }if (!ssl_set_port_bio(port)) { ... }port->ssl_in_use = true;aloop: ERR_clear_error(); r = SSL_accept(port->ssl); if (r <= 0) { err = SSL_get_error(port->ssl, r); switch (err) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: (void) WaitLatchOrSocket(NULL, waitfor, port->sock, 0, WAIT_EVENT_SSL_OPEN_SERVER); goto aloop; ... } return -1; }핸드셰이크의 SSL_ERROR_SSL 분기(위에서 생략)는 OpenSSL 에러 코드(SSL_R_UNSUPPORTED_PROTOCOL, SSL_R_WRONG_VERSION_NUMBER, …) 패밀리를 ssl_min_protocol_version/ssl_max_protocol_version 범위에 대한 힌트로 번역한다. 작지만 의미 있는 운용성 설계다.
커스텀 BIO: recv/send를 OpenSSL에 넘기는 방법
섹션 제목: “커스텀 BIO: recv/send를 OpenSSL에 넘기는 방법”OpenSSL은 소켓에 직접 접근하지 않는다. ssl_set_port_bio는 데이터 포인터가 Port이고 읽기/쓰기 메서드가 원시 기본 요소로 연결되는 BIO를 구성한다.
// port_bio_read — src/backend/libpq/be-secure-openssl.cstatic intport_bio_read(BIO *h, char *buf, int size){ int res = 0; Port *port = (Port *) BIO_get_data(h); if (buf != NULL) { res = secure_raw_read(port, buf, size); BIO_clear_retry_flags(h); port->last_read_was_eof = res == 0; if (res <= 0) if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) BIO_set_retry_read(h); } return res;}모든 바이트가 secure_raw_read를 경유하므로 TLS 레코드도 다른 모든 것과 같은 버퍼링-후-recv 경로를 탄다. 인터럽트 처리는 PostgreSQL 손에 남는다. (port_bio_ctrl의 BIO_CTRL_EOF 처리는 OpenSSL이 EOF를 감지하는 방식을 문서화되지 않게 변경했기 때문에 존재한다. 코드 주석이 업스트림 이슈를 링크한다.)
클라이언트 인증서: 신원 추출
섹션 제목: “클라이언트 인증서: 신원 추출”SSL_accept 성공 후 be_tls_open_server는 피어 인증서(클라이언트가 보냈다면)를 가져와 Common Name과 전체 RFC 2253 Distinguished Name을 Port에 추출한다. 내장 NUL을 거부한다(클래식 CVE-2009-4034 공격 방어. CN=admin\0.evil.com이 단순 접두사 매칭을 속일 수 있었다).
// be_tls_open_server — src/backend/libpq/be-secure-openssl.cport->peer = SSL_get_peer_certificate(port->ssl);if (port->peer != NULL){ X509_NAME *x509name = X509_get_subject_name(port->peer); len = X509_NAME_get_text_by_NID(x509name, NID_commonName, NULL, 0); ... /* Reject embedded NULLs in certificate common name (CVE-2009-4034). */ if (len != strlen(peer_cn)) { ereport(COMMERROR, ...); pfree(peer_cn); return -1; } port->peer_cn = peer_cn; ... X509_NAME_print_ex(bio, x509name, 0, XN_FLAG_RFC2253); /* DN */ ... port->peer_dn = peer_dn; port->peer_cert_valid = true;}클라이언트 인증서가 없거나 유효하지 않을 때 치명적인지 여부는 나중에 pg_hba.conf(clientcert, cert 인증 방법)가 결정한다. TLS 계층은 발견한 것을 기록할 뿐이다. 컨텍스트 수준의 SSL_CTX_set_verify는 SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE로 설정된다(be_tls_init에서). 서버가 인증서를 요청하지만 없어도 핸드셰이크를 실패시키지 않는다.
GSSAPI 전송 암호화
섹션 제목: “GSSAPI 전송 암호화”GSSAPI는 다른 보안 경로다. secure_open_gssapi가 Kerberos 컨텍스트 수립을 완료하면, 모든 애플리케이션 바이트는 4바이트 네트워크 순서 길이로 프레이밍된 암호화 토큰으로 래핑된다.
flowchart LR
subgraph write["be_gssapi_write"]
W1["app bytes"] --> W2["gss_wrap<br/>(per ≤PqGSSMaxPktSize chunk)"]
W2 --> W3["prepend uint32 length<br/>(pg_hton32)"]
W3 --> W4["secure_raw_write<br/>(loop on partial send)"]
end
subgraph read["be_gssapi_read"]
R1["secure_raw_read 4-byte len"] --> R2["read len bytes<br/>into PqGSSRecvBuffer"]
R2 --> R3["gss_unwrap<br/>→ PqGSSResultBuffer"]
R3 --> R4["dole out to caller"]
end
프레이밍 상수는 코드 자신이 인정하듯 프로토콜 스펙의 일부이므로 절대 변경할 수 없다. 양 끝이 동의해야 어느 쪽도 무한 버퍼를 할당하지 않는다.
// be-secure-gssapi.c — packet size caps#define PQ_GSS_MAX_PACKET_SIZE 16384 /* includes uint32 header word */#define PQ_GSS_AUTH_BUFFER_SIZE 65536 /* larger, only during auth handshake */be_gssapi_write는 루프를 돌며 호출자의 데이터를 PqGSSMaxPktSize보다 크지 않은 청크로 자르고, 각각을 gss_wrap으로 감싸고, 부분 진행을 주장하지 않는다(재시도 상태 머신을 단순하게 유지하기 위해 0 또는 전체를 반환한다. 커밋 d053a879b를 인용하는 주석에 따르면 힘들게 얻은 교훈이다).
// be_gssapi_write — src/backend/libpq/be-secure-gssapi.cmajor = gss_wrap(&minor, gctx, 1, GSS_C_QOP_DEFAULT, &input, &conf_state, &output);if (major != GSS_S_COMPLETE) { pg_GSS_error(...); errno = ECONNRESET; return -1; }if (conf_state == 0) /* refuse if confidentiality not actually applied */{ ereport(COMMERROR, (errmsg("outgoing GSSAPI message would not use confidentiality"))); errno = ECONNRESET; return -1; }netlen = pg_hton32(output.length);memcpy(PqGSSSendBuffer + PqGSSSendLength, &netlen, sizeof(uint32));PqGSSSendLength += sizeof(uint32);memcpy(PqGSSSendBuffer + PqGSSSendLength, output.value, output.length);PqGSSSendLength += output.length;conf_state == 0 확인이 중요하다. gss_wrap은 기밀성을 요청받지만(1 인자), 메커니즘이 무결성만 제공할 수도 있다. PostgreSQL은 “기밀성 없음”을 무음으로 무결성 전용 데이터를 보내는 대신 치명적 연결 오류로 처리한다.
be_gssapi_read는 반대 방향이다. 4바이트 길이를 수집하고, 정확히 그 바이트 수를 PqGSSRecvBuffer로 읽고, gss_unwrap으로 전체 토큰을 PqGSSResultBuffer로 풀고, 필요한 만큼 호출을 거쳐 평문을 호출자에게 나눠 준다. 동일한 길이 상한이 클라이언트로부터 오는 과대 패킷으로부터 수신 측을 보호한다.
// be_gssapi_read — src/backend/libpq/be-secure-gssapi.cinput.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer);if (input.length > PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32)){ ereport(COMMERROR, (errmsg("oversize GSSAPI packet sent by the client (%zu > %zu)", (size_t) input.length, PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32)))); errno = ECONNRESET; return -1;}...major = gss_unwrap(&minor, gctx, &input, &output, &conf_state, NULL);if (conf_state == 0) /* peer sent integrity-only data: reject */{ ereport(COMMERROR, (errmsg("incoming GSSAPI message did not use confidentiality"))); errno = ECONNRESET; return -1; }GSSAPI 세션 수립
섹션 제목: “GSSAPI 세션 수립”secure_open_gssapi는 위의 것들을 안전하게 호출하기 전에 Kerberos 컨텍스트 수립 루프를 수행한다. 더 큰 PQ_GSS_AUTH_BUFFER_SIZE 버퍼를 할당하고(인증 토큰은 64 kB에 달할 수 있다) 루프를 돈다. 클라이언트가 항상 먼저 말하므로, 길이-프레이밍된 패킷을 읽고, gss_accept_sec_context에 공급하고, GSSAPI 라이브러리가 생성하는 토큰을 돌려보낸다. 라이브러리가 컨텍스트 완료를 신호할 때까지 반복한다.
// secure_open_gssapi — src/backend/libpq/be-secure-gssapi.cmajor = gss_accept_sec_context(&minor, &port->gss->ctx, GSS_C_NO_CREDENTIAL, &input, GSS_C_NO_CHANNEL_BINDINGS, &port->gss->name, NULL, &output, NULL, NULL, pg_gss_accept_delegation ? &delegated_creds : NULL);if (GSS_ERROR(major)) { pg_GSS_error(...); return -1; }else if (!(major & GSS_S_CONTINUE_NEEDED)) complete_next = true; /* one more send, then done */루프가 완료되면 64 kB 인증 버퍼를 해제하고 16 kB 운용 버퍼를 재할당한다. gss_wrap_size_limit으로 PqGSSMaxPktSize를 계산해(be_gssapi_write가 버퍼보다 큰 토큰을 생성하지 않도록) port->gss->enc = true를 설정한다. 이 플래그가 이후 모든 secure_read/secure_write 호출이 확인하는 플래그다. 선택적으로 위임된 Kerberos 자격증명이 여기서 캡처된다(pg_store_delegated_credential). 그 덕분에 백엔드가 다른 Kerberized 서비스에서 클라이언트를 가장할 수 있다.
be_gssapi_read/be_gssapi_write와 달리 이 함수는 소켓에서 WaitLatchOrSocket으로 블로킹한다(헬퍼 read_or_wait 경유). 수립은 일회성이고 순서가 정해진 교환이므로 부분 진행 상태 관리가 의미 없다.
소스 워크스루
섹션 제목: “소스 워크스루”아래 심볼은 파일과 호출 흐름별로 그룹화됐다. 이름이 안정적인 앵커이고, 위치 힌트 표의 줄 번호는 updated: 날짜를 기준으로 한다.
be-secure.c — 암호화 무관 심
섹션 제목: “be-secure.c — 암호화 무관 심”secure_initialize/secure_destroy—be_tls_init/be_tls_destroy의 얇은 래퍼. 비SSL 빌드에서는 no-op.secure_open_server— 협상 경로에서 호출. 핸드셰이크 이전에 버퍼링된 바이트를port->raw_buf에 저장(OpenSSL의 첫 읽기가 올바른 바이트를 보도록)한 뒤be_tls_open_server에 위임. 피어 DN/CN을DEBUG2에 기록.secure_read— 디스패치:port->ssl_in_use면be_tls_read,port->gss->enc면be_gssapi_read, 아니면secure_raw_read.FeBeWaitSet에 대한 블로킹-대기 루프와 postmaster 사망 확인을 소유.secure_write—secure_read의 쓰기 방향 미러.secure_raw_read—port->raw_buf를 먼저 소진한 뒤recv(2).secure_raw_write— 직접send(2). 모든 암호화 경로의 최하위 계층.secure_close— TLS가 사용 중이면be_tls_close.
be-secure-openssl.c — OpenSSL 경유 TLS
섹션 제목: “be-secure-openssl.c — OpenSSL 경유 TLS”be_tls_init— 프로세스 전역SSL_CTX구성:SSLv23_method(최고 TLS 협상), 서버 인증서 체인과 개인 키 로드, 최소/최대 프로토콜 버전, 암호 목록, DH/ECDH 파라미터, 루트 CA + CRL, CA가 설정된 경우verify_cb와 함께SSL_CTX_set_verify. SIGHUP에서 재로드 가능.be_tls_open_server— 연결별 핸드셰이크:SSL_new,ssl_set_port_bio,SSL_accept루프, ALPN 확인(SSL_get0_alpn_selected), 피어 인증서 신원 추출.be_tls_read/be_tls_write—SSL_read/SSL_write를SSL_get_error스위치로 감싼다.WANT_READ/WANT_WRITE를*waitfor힌트로 매핑하고 에러를errno로 변환.ssl_set_port_bio,port_bio_method,port_bio_read,port_bio_write,port_bio_ctrl— OpenSSL을secure_raw_read/secure_raw_write로 라우팅하는 커스텀 소스/싱크 BIO.verify_cb— 인증서 검증 콜백. 실패 시 나중에 기록하기 위한 상세cert_errdetail(주체, 일련번호, 발급자)을 구성.alpn_cb— ALPN 선택.PG_ALPN_PROTOCOL("postgresql")만 수락. 다른 프로토콜에는 RFC 7301no_application_protocol치명 경보를 반환.info_cb— OpenSSL 핸드셰이크 상태 문자열을DEBUG4에 PG 로그로 복사.be_tls_get_certificate_hash— SCRAM-SHA-256-PLUS가 사용하는 채널 바인딩(RFC 5929tls-server-end-point) 해시. 인증서 서명이 MD5/SHA-1이면 SHA-256으로 대체.initialize_dh/initialize_ecdh/load_dh_file— 순방향 비밀성을 위한 임시 키 파라미터.be_tls_get_version,be_tls_get_cipher,be_tls_get_cipher_bits,be_tls_get_peer_subject_name,be_tls_get_peer_issuer_name—pg_stat_ssl에 데이터를 공급하는 접근자.
be-secure-gssapi.c — GSSAPI 전송 암호화
섹션 제목: “be-secure-gssapi.c — GSSAPI 전송 암호화”secure_open_gssapi— Kerberos 컨텍스트 수립(gss_accept_sec_context루프), 버퍼 재할당,gss_wrap_size_limit,port->gss->enc = true.read_or_wait으로 블로킹.be_gssapi_write— 청크 →gss_wrap→ 길이 접두사 →secure_raw_write. 전부 아니면 없음 진행.conf_state기밀성 확인.be_gssapi_read—secure_raw_read길이 + 본문 →gss_unwrap→ 결과 버퍼 → 나눠주기. 과대 패킷과conf_state확인.read_or_wait— 수립 중에만 사용하는 블로킹 헬퍼.be_gssapi_get_enc,be_gssapi_get_auth,be_gssapi_get_princ,be_gssapi_get_delegation— 접근자.
backend_startup.c — 협상 (postgres-wire-protocol.md 교차 참조)
섹션 제목: “backend_startup.c — 협상 (postgres-wire-protocol.md 교차 참조)”ProcessStartupPacket—NEGOTIATE_SSL_CODE/NEGOTIATE_GSS_CODE인식, 한 바이트 응답,secure_open_server/secure_open_gssapi호출,ssl_done/gss_done설정, 요청 이후 암호화되지 않은 데이터에 대한 MitM 방어 실행.ProcessSSLStartup— 직접 TLS 경로.0x16레코드 바이트 스니핑.
위치 힌트 (2026-06-05 기준, REL_18 273fe94)
섹션 제목: “위치 힌트 (2026-06-05 기준, REL_18 273fe94)”| 심볼 | 파일 | 줄 |
|---|---|---|
secure_initialize | src/backend/libpq/be-secure.c | 75 |
secure_open_server | src/backend/libpq/be-secure.c | 112 |
secure_read | src/backend/libpq/be-secure.c | 179 |
secure_raw_read | src/backend/libpq/be-secure.c | 268 |
secure_write | src/backend/libpq/be-secure.c | 305 |
secure_raw_write | src/backend/libpq/be-secure.c | 377 |
be_tls_init | src/backend/libpq/be-secure-openssl.c | 97 |
be_tls_open_server | src/backend/libpq/be-secure-openssl.c | 438 |
be_tls_close | src/backend/libpq/be-secure-openssl.c | 734 |
be_tls_read | src/backend/libpq/be-secure-openssl.c | 764 |
be_tls_write | src/backend/libpq/be-secure-openssl.c | 823 |
port_bio_read | src/backend/libpq/be-secure-openssl.c | 911 |
port_bio_write | src/backend/libpq/be-secure-openssl.c | 935 |
port_bio_ctrl | src/backend/libpq/be-secure-openssl.c | 954 |
ssl_set_port_bio | src/backend/libpq/be-secure-openssl.c | 1010 |
verify_cb | src/backend/libpq/be-secure-openssl.c | 1205 |
info_cb | src/backend/libpq/be-secure-openssl.c | 1283 |
alpn_cb | src/backend/libpq/be-secure-openssl.c | 1334 |
be_tls_get_cipher_bits | src/backend/libpq/be-secure-openssl.c | 1510 |
be_tls_get_version | src/backend/libpq/be-secure-openssl.c | 1524 |
be_tls_get_cipher | src/backend/libpq/be-secure-openssl.c | 1533 |
be_tls_get_certificate_hash | src/backend/libpq/be-secure-openssl.c | 1582 |
X509_NAME_to_cstring | src/backend/libpq/be-secure-openssl.c | 1644 |
PQ_GSS_MAX_PACKET_SIZE | src/backend/libpq/be-secure-gssapi.c | 52 |
PQ_GSS_AUTH_BUFFER_SIZE | src/backend/libpq/be-secure-gssapi.c | 60 |
be_gssapi_write | src/backend/libpq/be-secure-gssapi.c | 102 |
be_gssapi_read | src/backend/libpq/be-secure-gssapi.c | 269 |
read_or_wait | src/backend/libpq/be-secure-gssapi.c | 430 |
secure_open_gssapi | src/backend/libpq/be-secure-gssapi.c | 502 |
be_gssapi_get_enc | src/backend/libpq/be-secure-gssapi.c | 755 |
CANCEL_REQUEST_CODE / NEGOTIATE_SSL_CODE / NEGOTIATE_GSS_CODE | src/include/libpq/pqcomm.h | 137 / 172 / 173 |
PG_ALPN_PROTOCOL / _VECTOR | src/include/libpq/pqcomm.h | 165 / 166 |
ProcessSSLStartup | src/backend/tcop/backend_startup.c | 401 |
ProcessStartupPacket (SSL/GSS 분기) | src/backend/tcop/backend_startup.c | 575 / 647 |
소스 검증 (2026-06-05 기준)
섹션 제목: “소스 검증 (2026-06-05 기준)”/data/hgryoo/references/postgres의 REL_18(커밋 273fe94, PG 18.x)를 기준으로 검증했다.
- 디스패치 순서.
secure_read/secure_write는port->gss->enc보다port->ssl_in_use를 먼저 검사한다. 원시 경로는#else폴백이다.be-secure.c에서 확인. ✓ - 협상 응답 바이트.
SSLRequest는'S'/'N'으로,GSSENCRequest는'G'/'N'으로, 각각 한 바이트를secure_write로 평문 전송.ProcessStartupPacket에서 확인. ✓ - 요청 코드.
NEGOTIATE_SSL_CODE = PG_PROTOCOL(1234,5679),NEGOTIATE_GSS_CODE = PG_PROTOCOL(1234,5680),CANCEL_REQUEST_CODE = PG_PROTOCOL(1234,5678).pqcomm.h에서 확인. ✓ - MitM 방어. 핸드셰이크 이후
pq_buffer_remaining_data() > 0이면 FATAL “received unencrypted data after SSL/GSSAPI … request” 발생.ProcessStartupPacket양 분기에서 확인. ✓ - 직접 TLS 스니핑 바이트.
ProcessSSLStartup은 첫 번째 바이트가0x16(TLS 핸드셰이크ContentType)이 아닌 경우STATUS_OK를 반환한다. 확인. ✓ - ALPN 프로토콜 문자열.
PG_ALPN_PROTOCOL "postgresql",PG_ALPN_PROTOCOL_VECTOR { 10, 'p','o','s','t','g','r','e','s','q','l' }.alpn_cb는 다른 프로토콜에SSL_TLSEXT_ERR_ALERT_FATAL을 반환.pqcomm.h와be-secure-openssl.c에서 확인. ✓ - 커스텀 BIO.
port_bio_read/port_bio_write가secure_raw_read/secure_raw_write를 호출.port_bio_ctrl이BIO_CTRL_EOF를port->last_read_was_eof로 처리. 확인. ✓ - 내장 NUL 거부.
be_tls_open_server가 ASN.1 길이와strlen이 다른 피어 CN 또는 DN을 거부(CVE-2009-4034 방어). 확인. ✓ - 클라이언트 인증서 검증 모드.
be_tls_init이 CA 파일이 설정된 경우에만SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE(요청하되 요구하지 않음)를 설정. 확인. ✓ - 채널 바인딩 해시 알고리즘.
be_tls_get_certificate_hash가 인증서 서명 NID가NID_md5/NID_sha1이면 SHA-256으로 대체하고, 그 외에는 서명 자체 다이제스트 사용(RFC 5929 §4.1). 확인. ✓ - GSS 패킷 상한.
PQ_GSS_MAX_PACKET_SIZE 16384(운용),PQ_GSS_AUTH_BUFFER_SIZE 65536(핸드셰이크).be_gssapi_read와be_gssapi_write모두output.length/input.length ≤ MAX - sizeof(uint32)를 강제. 확인. ✓ - GSS 기밀성 강제. 양 방향 모두
conf_state == 0(무결성 전용)을ECONNRESET으로 처리. 확인. ✓ - GSS 위임.
secure_open_gssapi가pg_gss_accept_delegation이 설정된 경우에만gss_accept_sec_context에&delegated_creds를 전달하고pg_store_delegated_credential로 저장. 확인. ✓ - TLS 최소 기본값.
ssl_min_protocol_version은PG_TLS1_2_VERSION으로,ssl_max_protocol_version은PG_TLS_ANY로 기본 설정.be-secure.c전역(열거형은libpq.h)에서 확인. ✓
이 개정판의 범위 밖(교차 참조 문서에 위임): 빠진/유효하지 않은 클라이언트 인증서가 치명적인지 결정하는 pg_hba.conf 매칭(postgres-authentication.md, 계획됨), 채널 바인딩 해시를 소비하는 SCRAM-SHA-256 교환(동일), 협상 분기 주변의 스타트업 패킷 파싱 메커니즘(postgres-wire-protocol.md), 그리고 contrib/ 모듈(지시에 따라 범위 밖).
PostgreSQL 너머 — 비교 설계와 연구 전선
섹션 제목: “PostgreSQL 너머 — 비교 설계와 연구 전선”포인터이지 분석이 아니다. 각 항목은 후속 문서를 위한 출발점이다.
-
NSS / GnuTLS / Schannel 대안. PostgreSQL 백엔드는 현재 OpenSSL(및 OpenSSL 호환 API를 통한 LibreSSL)을 사용한다.
ssl_libraryGUC가 어떤 것이 사용 중인지 보고하지만 빌드는 한 번에 하나의 TLS 백엔드를 지원한다. 다른 데이터베이스(예: MySQL의 역사적 YaSSL, MariaDB의 선택적 GnuTLS)는 플러그형 TLS 백엔드를 제공했다.be-secure-openssl.c⇄be-secure.c경계가 두 번째 백엔드(be-secure-nss.c)가 업스트림에서 프로토타입으로 만들어졌다가 포기된 바로 그 이음새다. 추상be_tls_*인터페이스가 그 작업의 산물이다. NSS 포트가 멈춘 이유(콜백 모델 불일치, FIPS 배관)를 비교하면 “추상 TLS” 경계가 실제로 얼마나 누수가 있는지 알 수 있다. -
직접/묵시적 TLS 채택 (ALPN 게이트). PG17이
sslnegotiation=direct클라이언트 모드와0x16스니핑 서버 경로를 추가했지만, 직접 연결에서는postgresqlALPN 프로토콜을 요구한다. 일반 TLS 반사기가 되지 않기 위해서다. HTTPS 스타일 묵시적 TLS는 왕복 하나를 없애지만, 서버가 ALPN 판별자 없이 포트에서 평문과 TLS 클라이언트를 동시에 수용할 수 없게 된다. 직접 TLS의 지연 시간 이점을 TLS 자체를 종료하는 연결 풀러(PgBouncer)와 비교한 후속 연구가 가능하다. -
채널 바인딩과 MitM 위협 모델. SCRAM-SHA-256-PLUS는 TLS 종료 프록시가 자격증명을 릴레이할 수 없도록
be_tls_get_certificate_hash를 인증 증명에 혼합한다. RFC 5929tls-server-end-point는 인증서에 바인딩한다. 최신tls-exporter(RFC 9266)는 TLS 세션 키 자료에 바인딩하며 일부 프로파일에서 TLS 1.3 필수다. PostgreSQL은 엔드포인트 바인딩을 사용한다.tls-exporter로 이동해야 하는지에 대한 업스트림 논의가 진행 중이다. 계획된postgres-authentication.md와 연결된다. -
GSSAPI 위임과 혼동된 대리인 위험.
secure_open_gssapi는 위임된 Kerberos 자격증명을 수락할 수 있다(pg_gss_accept_delegation). 백엔드가 다운스트림 Kerberized 서비스(예: 원격 PG에 대한postgres_fdw, Kerberized HDFS)에서 클라이언트를 가장할 수 있게 한다. 강력하지만 위험하다. 전형적인 혼동된 대리인 표면이다. 설계 근거(기본 비활성화,pg_hba별 선택 가입)와 폭발 반경은postgres-fdw.md와 함께 더 깊이 살펴볼 가치가 있다. -
암호화 비용 대 버퍼 결합 계층. GSSAPI 프레이밍은 패킷을 16 kB로 제한하고 재버퍼링한다. TLS 레코드도 자체적인 MTU 크기가 있다. 두 가지 모두
pqcomm.c의 8 kB 전송 버퍼(postgres-wire-protocol.md)와 상호작용한다. GSS 16 kB 패킷 크기 또는 TLS 레코드 크기가COPY중심 대량 로드 워크로드에 맞는 결합 세분성인지는 실험적 질문이다.be-secure-gssapi.c의 주석은 버퍼 크기가 “여러 패킷을 만들어야 하는 횟수를 최소화하기 위해” 선택됐다고 인정하지만 측정치를 제공하지 않는다. -
양자 후 키 교환. OpenSSL 3.x가 하이브리드 PQ 키 교환(예:
X25519MLKEM768)을 지원하게 됐다. PostgreSQL이SSLv23_method와 암호/그룹 GUC(ssl_groups/SSLECDHCurve)로 전체 키 교환을 OpenSSL에 위임하기 때문에, PQ 그룹 활성화는 PostgreSQL 코드 변경보다는 OpenSSL 설정 문제다. 얇은 심 설계가 효과를 발휘하는 깔끔한 사례다.
PostgreSQL 소스 (/data/hgryoo/references/postgres, REL_18 273fe94)
섹션 제목: “PostgreSQL 소스 (/data/hgryoo/references/postgres, REL_18 273fe94)”src/backend/libpq/be-secure.c—secure_initialize,secure_open_server,secure_read,secure_raw_read,secure_write,secure_raw_write,secure_close. 암호화 무관 디스패치 심과recv/send바닥층.src/backend/libpq/be-secure-openssl.c—be_tls_init,be_tls_open_server,be_tls_read/be_tls_write, 커스텀 BIO(port_bio_read/port_bio_write/port_bio_ctrl/ssl_set_port_bio),verify_cb,alpn_cb,info_cb,be_tls_get_certificate_hash, 그리고be_tls_get_*접근자.src/backend/libpq/be-secure-gssapi.c—secure_open_gssapi,be_gssapi_read/be_gssapi_write,read_or_wait,PQ_GSS_*상한, 그리고be_gssapi_get_*접근자.src/backend/tcop/backend_startup.c—ProcessStartupPacket(SSL/GSS 협상 분기),ProcessSSLStartup(직접 TLS).src/include/libpq/pqcomm.h—NEGOTIATE_SSL_CODE,NEGOTIATE_GSS_CODE,CANCEL_REQUEST_CODE,PG_ALPN_PROTOCOL,PG_ALPN_PROTOCOL_VECTOR.src/include/libpq/libpq.h—PG_TLS_ANY,PG_TLS1_2_VERSION프로토콜 버전 열거형.
- RFC 5929 — TLS용 채널 바인딩(
tls-server-end-point).be_tls_get_certificate_hash의 기초. - RFC 7301 — 애플리케이션 레이어 프로토콜 협상(ALPN).
alpn_cb의no_application_protocol치명 경보. - RFC 2744 / GSS-API —
gss_accept_sec_context,gss_wrap/gss_unwrap시맨틱.secure_open_gssapi와 GSS I/O 함수가 참조. - RFC 2253 — X.509 Distinguished Name의 문자열 표현.
X509_NAME_print_ex(..., XN_FLAG_RFC2253)가port->peer_dn에 생성하는 형식.
교재 장 (knowledge/research/dbms-general/)
섹션 제목: “교재 장 (knowledge/research/dbms-general/)”- Database System Concepts (Silberschatz et al.) — 애플리케이션 수준 보안: 전송 중 암호화 대 인증 대 권한 부여.
- Database Internals (Petrov) — 노드 간 통신과 메시지 코덱 아래의 얇은 암호화 심.
교차 참조 (형제 모듈 문서)
섹션 제목: “교차 참조 (형제 모듈 문서)”postgres-wire-protocol.md— 이 계층 위에 탑재되는 FE/BE 프레이밍. 협상 분기 주변의 전체 스타트업 패킷 메커니즘.secure_raw_read/secure_raw_write가 공급하는pqcomm.c전송/수신 버퍼.postgres-authentication.md— (계획됨)auth.c,pg_hba.conf매칭,port->peer_cn을 소비하는cert인증 방법, 채널 바인딩 해시를 소비하는 SCRAM-SHA-256-PLUS 교환.postgres-backend-lifecycle.md—ProcessStartupPacket이postmaster→ 백엔드 시작 과정에서 어떻게 도달되는지,PostgresMain전에.postgres-fdw.md—secure_open_gssapi에서 캡처된 GSSAPI 자격증명 위임의 소비자.