Envoy Gateway TLS와 보안: HTTPS를 설정하고 API를 벌거벗은 채 두지 말자
HTTP를 동작시키는 건 워밍업에 불과합니다. 여러분의 서비스가 석기시대에 살고 있는 게 아니라면, 운영 환경에서는 거의 확실히 HTTPS가 필요합니다. 이 글에서는 Envoy Gateway에서 TLS의 기본기를 정리해, API가 평문으로 돌아다니지 않게 만드는 방법을 다룹니다.
Gateway에서 TLS는 어떻게 동작하나
가장 흔한 접근 방식은 Gateway listener에서 TLS를 종료하는 것입니다.
뜻은 다음과 같습니다:
- 클라이언트는 HTTPS로 접속한다
- Gateway가 핸드셰이크와 인증서를 처리한다
- 그 뒤 트래픽은 내부에서 암호화 없이 Service로 전달된다
기본적인 HTTPS listener는 이렇게 생겼습니다:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: eg
spec:
gatewayClassName: eg
listeners:
- name: http
protocol: HTTP
port: 80
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- kind: Secret
group: ""
name: example-cert여기서 중요한 것은 두 가지입니다:
protocol: HTTPStls.mode: Terminate
즉, TLS는 여기서 끝나고, Gateway가 인증서를 처리한다는 뜻입니다.
HTTPS + HTTPRoute와 TLSRoute: 뭐가 다른가
이건 중요합니다. 많은 사람이 TLSRoute를 보면 곧바로 이렇게 묻습니다:
"HTTPS listener만 있으면 충분한 거 아닌가요? 왜 굳이 별도 TLSRoute가 필요한가요?"
차이는 Gateway가 TLS를 종료하는지 여부입니다:
| 시나리오 | 흔한 조합 | 설명 |
|---|---|---|
| Gateway가 TLS를 종료한 뒤 HTTP 규칙 적용 | HTTPS listener + HTTPRoute |
가장 흔한 웹사이트/API 시나리오 |
| Gateway가 복호화하지 않고 SNI만 보고 암호화된 트래픽 라우팅 | TLS listener + TLSRoute |
TLS passthrough 시나리오 |
즉:
- Gateway에서
path,header,method를 보고 싶다면 먼저 TLS를 종료한 뒤HTTPRoute를 써야 합니다 - end-to-end 암호화를 유지하고 Gateway에서 복호화하지 않으려면
TLSRoute가 맞습니다
어느 쪽이 "더 고급"인 것은 아닙니다. 핵심은 여러분이 원하는 것이 무엇인지 아는 것입니다. "TLS를 종료하고 L7 라우팅을 할 것인가", 아니면 "암호화를 유지한 채 SNI 기반 라우팅을 할 것인가"입니다.
인증서 Secret 준비하기
테스트 환경에서는 self-signed 인증서로도 충분합니다. 공식 예제도 정확히 이 방식을 사용합니다:
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 \
-subj '/O=example Inc./CN=example.com' \
-keyout example.com.key \
-out example.com.crt
openssl req -out www.example.com.csr -newkey rsa:2048 -nodes \
-keyout www.example.com.key \
-subj "/CN=www.example.com/O=example organization"
openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key \
-set_serial 0 -in www.example.com.csr -out www.example.com.crt인증서와 키를 Kubernetes Secret으로 저장합니다:
kubectl create secret tls example-cert \
--key=www.example.com.key \
--cert=www.example.com.crt그러면 Gateway가 certificateRefs를 통해 이 Secret을 참조할 수 있습니다.
⚠️ Self-signed 인증서는 테스트 전용입니다. 운영 환경에서는 cert-manager나 기존 인증서 관리 워크플로를 사용하세요. 데모 구성을 운영으로 그대로 들고 가지 마세요. 가끔 컵라면으로 버티는 건 괜찮지만, 그걸 장기 식단으로 삼을 수는 없는 것과 같습니다.
기존 Gateway에 HTTPS Listener 추가하기
이전 글에서 만든 eg Gateway가 이미 있다면, 바로 patch할 수 있습니다:
kubectl patch gateway eg --type=json --patch '
- op: add
path: /spec/listeners/-
value:
name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- kind: Secret
group: ""
name: example-cert
'상태를 확인합니다:
kubectl get gateway/eg -o yamllistener 상태가 건강해 보인다면 HTTPS를 테스트합니다:
curl -v -HHost:www.example.com \
--resolve "www.example.com:443:${GATEWAY_HOST}" \
--cacert example.com.crt \
https://www.example.com/get이 명령은 로컬 검증에 특히 좋습니다. DNS가 아직 연결되지 않았을 때도 유용하죠.
하나의 Gateway, 여러 HTTPS 도메인
도메인이 여러 개라면 같은 Gateway에 HTTPS listener를 여러 개 추가하면 됩니다.
예를 들어 foo.example.com을 추가한다면:
listeners:
- name: https-main
protocol: HTTPS
port: 443
hostname: www.example.com
tls:
mode: Terminate
certificateRefs:
- name: example-cert
- name: https-foo
protocol: HTTPS
port: 443
hostname: foo.example.com
tls:
mode: Terminate
certificateRefs:
- name: foo-cert해당 HTTPRoute의 hostname도 함께 맞춰 업데이트하는 것을 잊지 마세요. 그렇지 않으면 listener는 듣고 있어도, route는 트래픽을 어디로 보내야 할지 모르게 됩니다.
TLSRoute를 위한 사고 모델
TLS passthrough에서 TLSRoute는 이렇게 생각하면 됩니다:
💡
TLSRoute의apiVersion은 설치된 Gateway API CRD 버전에 따라 달라질 수 있습니다. 아래 예시는 개념 설명용이므로, 적용 전에는 클러스터에 설치된 버전을 확인하세요.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: eg-tls
spec:
gatewayClassName: eg
listeners:
- name: tls
protocol: TLS
port: 443
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
name: passthrough-route
spec:
parentRefs:
- name: eg-tls
hostnames:
- "db.example.com"
rules:
- backendRefs:
- name: db-service
port: 5432이 패턴의 흔한 사용 사례:
- SNI hostname만 기준으로 라우팅
- 암호화를 백엔드까지 그대로 유지
- Gateway에서 HTTP path/header를 해석하지 않음
즉 TLSRoute와 HTTPRoute의 가장 큰 차이는 이름이 아니라, 서로 완전히 다른 트래픽 계층에서 동작한다는 점입니다.
네임스페이스 간 인증서 참조
공식 문서는 유용한 기능 하나를 강조합니다:
Gateway는 다른 네임스페이스의 certificate Secret을 참조할 수 있지만, ReferenceGrant가 필요합니다.
예를 들어 플랫폼 팀이 인증서를 envoy-gateway-system에 중앙 관리하고, 애플리케이션 Gateway는 default에 있다고 해 봅시다. 명시적인 승인 없이 네임스페이스 경계를 넘어서 참조할 수는 없습니다.
예시:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-default-to-read-cert
namespace: envoy-gateway-system
spec:
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: default
to:
- group: ""
kind: Secret뜻은 다음과 같습니다:
default네임스페이스의Gateway가envoy-gateway-system의Secret리소스를 참조할 권한을 가진다
ReferenceGrant가 없으면 이런 네임스페이스 간 참조는 invalid로 처리됩니다. 괜히 빡빡한 게 아니라 보호 장치인 셈입니다.
한 줄 요약
Envoy Gateway의 TLS는 생각보다 그렇게 신비롭지 않습니다. 인증서 Secret을 준비하고, HTTPS listener를 구성하고, route와 hostname이 서로 맞물리는지 확인하면 거의 끝입니다.
💡 초보자가 가장 자주 저지르는 실수는 TLS 자체가 어려워서가 아니라,
hostname,certificateRefs,HTTPRoute hostnames이 세 가지 정렬이 안 맞기 때문입니다. 이 중 하나만 어긋나도 트래픽이 이상하게 움직이기 시작합니다.
다음 단계
이제 설치, 개념, 라우팅, TLS를 다뤘으니, 다음 글에서는 운영에서 덜 터지게 해 주는 실전 습관들을 정리합니다: 👉 모범 사례