Split One Port With Two Certificates
한국어 원문으로 표시합니다.
목표
한 리스너에 필터 체인을 여러 개 두고 SNI · 전송 프로토콜 · ALPN 으로 갈라 받는다. 조건이 겹칠 때 무엇이 이기는지도 직접 확인한다.
왜 중요한가
인그레스 게이트웨이 한 대가 도메인 수십 개를 받는 구성에서는 '어느 체인이 이 연결을 받는가' 가 곧 '어떤 인증서를 내보내는가' 이다. 이 규칙을 순서로 오해하면 체인을 위아래로 옮기며 하루를 보내게 된다. 실제 판정은 여덟 단계의 고정된 순서로 구체성을 비교해 이뤄지고, 맞는 체인이 없으면 오류 응답이 아니라 연결이 그냥 끊긴다 — 이 두 가지를 손으로 겪어 두면 '이 도메인만 안 됩니다' 신고를 몇 분 만에 끝낼 수 있다.
단계
/root/envd-listener/listener.yaml에 리스너 두 개를 두세요 —edge-a는127.0.0.1:10021에서listener=a를,edge-b는127.0.0.1:10022에서listener=b를direct_response로 돌려줍니다(관리 포트는9921). 띄운 뒤/listeners?format=json을/root/envd-listener/01-listeners.json에 저장하세요./root/envd-listener/tls/에 자체 서명 인증서 두 장을 만드세요 —a.crt/a.key는 CN 과 SAN 이a.envd.test,b.crt/b.key는b.envd.test입니다. 두 인증서의 subject 와 SAN 을/root/envd-listener/02-certs.txt에 적으세요./root/envd-listener/listener.yaml에 리스너mixed(127.0.0.1:10443)를 더하세요.tls_inspector리스너 필터를 켜고, TLS 체인 두 개를server_names로 가릅니다 —a.envd.test는chain=sni-a,b.envd.test는chain=sni-b를 돌려줍니다. 두 이름으로 각각 요청한 결과를/root/envd-listener/03-sni.txt에a=와b=두 줄로 적으세요.openssl s_client로10443에 두 번 붙되-servername만 바꿔, 각각 어떤 인증서가 나오는지/root/envd-listener/04-cert.txt에a=와b=두 줄로 적으세요(값은 인증서의 CN).mixed리스너에transport_protocol: "raw_buffer"로 맞는 체인plain을 맨 앞에 더해chain=plain을 돌려주게 하세요. 같은 포트10443로 평문 HTTP 와 TLS 요청을 각각 보내 결과를/root/envd-listener/05-raw.txt에plain=와tls=두 줄로 적으세요.mixed에 네 번째 체인alpn-h2를 더하세요 —transport_protocol: "tls"와application_protocols: ["h2"]로만 맞고,chain=alpn-h2를 HTTP/2 로 돌려줍니다. 그다음 SNI 를a.envd.test로 두고 HTTP/2 로 요청해 어느 체인이 받는지, SNI 를z.envd.test로 두고 HTTP/2 로 요청하면 어느 체인이 받는지 확인해/root/envd-listener/06-alpn.txt에sni_and_h2=와h2_only=두 줄로 적으세요.z.envd.test로 HTTP/1.1 TLS 요청을 보내 보세요 — 어느 체인에도 맞지 않습니다./root/envd-listener/07-nomatch.txt에nomatch_rc=(그 요청의 curl 종료 코드)와match_rc=(a.envd.test로 보낸 같은 요청의 종료 코드) 두 줄을 적으세요./root/envd-listener/08-report.md에chains=(mixed 리스너의 체인 개수),same_port_plain_and_tls=(yes 또는 no),sni_beats_alpn=(6단계에서 SNI 와 ALPN 이 함께 맞았을 때 이긴 체인의 이름),nomatch_rc=네 줄을 적고, 그 아래 배운 것을 네 줄 이상 적으세요.
참고
- Envoy 를 띄울 때는
setsid --fork nohup envoy -c <파일> --log-level warn > <로그> 2>&1 </dev/null를 쓰고, 다시 띄우기 전에는pkill -x envoy로 정리하세요. - 기동을 기다릴 때는 고정
sleep대신/ready가 LIVE 를 돌려줄 때까지 도는 루프를 쓰세요. - 자체 서명 인증서라
curl에는-k가 필요합니다. 이름을 붙여 보내려면--resolve 이름:포트:127.0.0.1을 함께 씁니다. - 흔한 실수 —
tls_inspector를 켜지 않으면server_names와application_protocols조건이 아무에게도 맞지 않습니다. 설정은 통과하는데 동작만 안 합니다. - 흔한 실수 —
-addext "subjectAltName=DNS:..."를 빠뜨리면 CN 만 있는 인증서가 나옵니다.
한 프로세스가 포트 두 개를 듣는다
/root/envd-listener/listener.yaml 에 리스너 두 개를 두세요 — edge-a 는 127.0.0.1:10021 에서 listener=a 를, edge-b 는 127.0.0.1:10022 에서 listener=b 를 direct_response 로 돌려줍니다(관리 포트는 9921). 띄운 뒤 /listeners?format=json 을 /root/envd-listener/01-listeners.json 에 저장하세요.
리스너는 '주소와 포트 하나' 단위입니다. 프로세스 하나가 여러 개를 들 수 있고, 각 리스너는 자기만의 필터 체인 목록을 가집니다. direct_response 를 쓰면 업스트림 없이도 응답이 나가므로 이 실습에서는 백엔드를 띄울 필요가 없습니다. 관리 포트의 /listeners 는 기본이 텍스트라 ?format=json 을 붙여야 구조가 나옵니다.
이름이 다른 인증서 두 장을 굽는다
/root/envd-listener/tls/ 에 자체 서명 인증서 두 장을 만드세요 — a.crt/a.key 는 CN 과 SAN 이 a.envd.test, b.crt/b.key 는 b.envd.test 입니다. 두 인증서의 subject 와 SAN 을 /root/envd-listener/02-certs.txt 에 적으세요.
SNI 로 체인을 가르려면 이름이 다른 인증서가 최소 두 장 필요합니다. openssl req -x509 -newkey rsa:2048 -nodes 한 줄이면 키와 인증서가 함께 나옵니다. -addext "subjectAltName=DNS:..." 를 빠뜨리지 마세요 — 요즘 클라이언트는 CN 을 보지 않고 SAN 만 봅니다. 확인은 openssl x509 -noout -subject -ext subjectAltName 로 합니다.
같은 포트인데 이름에 따라 다른 체인이 받는다
/root/envd-listener/listener.yaml 에 리스너 mixed(127.0.0.1:10443)를 더하세요. tls_inspector 리스너 필터를 켜고, TLS 체인 두 개를 server_names 로 가릅니다 — a.envd.test 는 chain=sni-a, b.envd.test 는 chain=sni-b 를 돌려줍니다. 두 이름으로 각각 요청한 결과를 /root/envd-listener/03-sni.txt 에 a= 와 b= 두 줄로 적으세요.
SNI 는 TLS 핸드셰이크의 첫 메시지에 평문으로 들어 있습니다. 그래서 복호화 전에도 읽을 수 있고, 그 일을 하는 것이 tls_inspector 리스너 필터입니다. 이 필터를 켜지 않으면 server_names 조건은 아무 연결에도 맞지 않습니다. 요청은 curl -k --resolve a.envd.test:포트:127.0.0.1 https://... 로 보냅니다.
체인이 갈리면 내보내는 인증서도 갈린다
openssl s_client 로 10443 에 두 번 붙되 -servername 만 바꿔, 각각 어떤 인증서가 나오는지 /root/envd-listener/04-cert.txt 에 a= 와 b= 두 줄로 적으세요(값은 인증서의 CN).
체인마다 transport_socket 이 따로 있으므로, 어느 체인이 뽑히느냐가 곧 어떤 인증서를 내보내느냐입니다. 이것이 인그레스 게이트웨이 한 대가 도메인 수십 개의 인증서를 들고 있을 수 있는 이유입니다. echo | openssl s_client -connect 127.0.0.1:포트 -servername 이름 2>/dev/null | openssl x509 -noout -subject 로 확인하세요.
한 포트에서 평문과 TLS 를 함께 받는다
mixed 리스너에 transport_protocol: "raw_buffer" 로 맞는 체인 plain 을 맨 앞에 더해 chain=plain 을 돌려주게 하세요. 같은 포트 10443 로 평문 HTTP 와 TLS 요청을 각각 보내 결과를 /root/envd-listener/05-raw.txt 에 plain= 와 tls= 두 줄로 적으세요.
tls_inspector 는 첫 바이트를 들여다보고 이 연결이 TLS 인지 아닌지를 판정해 transport_protocol 을 tls 또는 raw_buffer 로 세워 줍니다. 그래서 같은 포트에서 두 가지를 다 받을 수 있습니다. 평문 요청은 curl http://127.0.0.1:포트/, TLS 요청은 앞 단계와 같은 방식입니다. 체인 순서는 매칭에 영향을 주지 않습니다 — 조건의 구체성이 정합니다.
조건이 둘 다 맞으면 무엇이 이기는가
mixed 에 네 번째 체인 alpn-h2 를 더하세요 — transport_protocol: "tls" 와 application_protocols: ["h2"] 로만 맞고, chain=alpn-h2 를 HTTP/2 로 돌려줍니다. 그다음 SNI 를 a.envd.test 로 두고 HTTP/2 로 요청해 어느 체인이 받는지, SNI 를 z.envd.test 로 두고 HTTP/2 로 요청하면 어느 체인이 받는지 확인해 /root/envd-listener/06-alpn.txt 에 sni_and_h2= 와 h2_only= 두 줄로 적으세요.
필터 체인 매칭은 여덟 단계를 정해진 순서로 좁힙니다 — 목적지 포트, 목적지 IP, 서버 이름(SNI), 전송 프로토콜, 애플리케이션 프로토콜(ALPN), 그다음 출발지 조건들입니다. SNI 가 ALPN 보다 앞이라는 것이 이 단계의 핵심입니다. 두 조건이 동시에 맞을 때 무엇이 남는지 직접 보세요. curl --http2 -k --resolve ... 로 ALPN 을 h2 로 만듭니다.
어느 체인에도 맞지 않으면 응답이 아니라 침묵이다
z.envd.test 로 HTTP/1.1 TLS 요청을 보내 보세요 — 어느 체인에도 맞지 않습니다. /root/envd-listener/07-nomatch.txt 에 nomatch_rc=(그 요청의 curl 종료 코드)와 match_rc=(a.envd.test 로 보낸 같은 요청의 종료 코드) 두 줄을 적으세요.
맞는 체인이 없으면 Envoy 는 404 를 주지 않습니다 — HTTP 계층까지 가지도 못했기 때문입니다. 연결을 그냥 닫습니다. 클라이언트 쪽에서는 TLS 핸드셰이크 실패로 보이고 curl 은 0 이 아닌 종료 코드를 냅니다. 운영에서 '인증서를 넣었는데 연결이 그냥 끊긴다' 로 오는 신고의 큰 몫이 이것입니다. curl 의 종료 코드는 $? 로 받습니다.
체인 설계 메모를 남긴다
/root/envd-listener/08-report.md 에 chains=(mixed 리스너의 체인 개수), same_port_plain_and_tls=(yes 또는 no), sni_beats_alpn=(6단계에서 SNI 와 ALPN 이 함께 맞았을 때 이긴 체인의 이름), nomatch_rc= 네 줄을 적고, 그 아래 배운 것을 네 줄 이상 적으세요.
이 메모는 다음에 게이트웨이를 설계할 때 자기가 읽을 글입니다. 값만 적지 말고 '그래서 무엇을 조심하겠다' 를 적으세요. 체인 개수는 눈으로 세지 말고 yq 로 filter_chains 의 길이를 뽑는 편이 정확합니다.