기본 TLS 인증서

기본 TLS 인증서 (Default TLS Certificate)

기본 TLS 인증서는 SNI 헤더에 지정된 호스트 이름과 일치하는 다른 인증서가 없거나 SNI가 없을 때 폴백(fallback)으로 동작해요. Gateway API 스펙은 기본 TLS 인증서를 기본적으로 지원하지 않지만, spec.listeners[].hostname 필드가 비어 있는 리스너가 모든 호스트 이름과 일치하면서 해당 프로토콜과 포트에 대한 폴백 역할을 해요.

출처: Default TLS Certificate

본문

기본 TLS 인증서는 SNI 헤더에 지정된 호스트 이름과 일치하는 다른 인증서가 없거나 SNI가 없을 때 폴백으로 동작해요. Gateway API 스펙은 기본 TLS 인증서를 기본적으로 지원하지 않아요. 대신 spec.listeners[].hostname 필드가 빈 리스너가 모든 호스트 이름과 일치하면서 해당 프로토콜과 포트에 대한 폴백 역할을 해요.

이 예제에서는 간단한 HTTP 서비스를 배포하고 Cilium Gateway API를 통해 노출해볼게요.

Istio 프로젝트의 bookinfo 샘플 애플리케이션을 사용할 거예요.

데모 앱 배포하기 (Deploy the Demo App)

$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.11/samples/bookinfo/platform/kube/bookinfo.yaml

이것은 데모 앱을 배포하는 것뿐이며, Istio 컴포넌트를 추가하는 게 아니에요. Cilium Service Mesh에서는 데모 앱 각 마이크로서비스와 함께 Envoy 사이드카가 생성되지 않는 것을 확인할 수 있어요.

$ kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
details-v1-5498c86cf5-kjzkj       1/1     Running   0          2m39s
productpage-v1-65b75f6885-ff59g   1/1     Running   0          2m39s
ratings-v1-b477cf6cf-kv7bh        1/1     Running   0          2m39s
reviews-v1-79d546878f-r5bjz       1/1     Running   0          2m39s
reviews-v2-548c57f459-pld2f       1/1     Running   0          2m39s
reviews-v3-6dd79655b9-nhrnh       1/1     Running   0          2m39s

참고

사이드카 구현이었다면 출력이 2/2 READY로 보였을 거예요. 하나는 마이크로서비스, 하나는 Envoy 사이드카 때문이에요.

TLS 인증서와 개인 키 생성하기 (Create TLS Certificate and Private Key)

데모 목적으로는 만들어낸 셀프 사이닝 CA(인증 기관)가 서명한 TLS 인증서를 사용해볼게요. 손쉬운 방법 중 하나는 mkcert를 사용하는 거예요. 이 예제에서 사용하는 호스트 이름인 bookinfo.cilium.rocks와 hipstershop.cilium.rocks를 검증할 인증서가 필요해요.

$ mkcert bookinfo.cilium.rocks hipstershop.cilium.rocks
Note: the local CA is not installed in the system trust store.
Run "mkcert -install" for certificates to be trusted automatically ⚠️

Created a new certificate valid for the following names 📜
 - "bookinfo.cilium.rocks"
 - "hipstershop.cilium.rocks"

The certificate is at "./bookinfo.cilium.rocks+1.pem" and the key at "./bookinfo.cilium.rocks+1-key.pem" ✅

It will expire on 29 November 2026 🗓

이 데모 키와 인증서로 Kubernetes 시크릿을 만들어볼게요:

$ kubectl create secret tls demo-cert --key=bookinfo.cilium.rocks+1-key.pem --cert=bookinfo.cilium.rocks+1.pem

(대안) cert-manager를 설치해볼게요:

$ helm repo add jetstack https://charts.jetstack.io
$ helm install cert-manager jetstack/cert-manager --version v1.16.2 \
    --namespace cert-manager \
    --set crds.enabled=true \
    --create-namespace \
    --set config.apiVersion="controller.config.cert-manager.io/v1alpha1" \
    --set config.kind="ControllerConfiguration" \
    --set config.enableGatewayAPI=true

이제 CA Issuer를 만들어볼게요:

$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/kubernetes/servicemesh/ca-issuer.yaml

Gateway와 HTTPRoutes 배포하기 (Deploy the Gateway and HTTPRoutes)

이 예제에서는 단일 인증서가 모든 인입 TLS 연결에 제공되고, 이후 HTTPRoute에 정의된 호스트 이름에 따라 적절한 백엔드로 라우팅돼요.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: tls-gateway
spec:
  gatewayClassName: cilium
  listeners:
  - name: default
    protocol: HTTPS
    port: 443
    tls:
      certificateRefs:
      - kind: Secret
        name: demo-cert
      mode: Terminate
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: bookinfo
spec:
  parentRefs:
  - name: tls-gateway
    sectionName: default
  hostnames:
  - "bookinfo.cilium.rocks"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: details
      port: 9080
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: hipstershop
spec:
  parentRefs:
  - name: tls-gateway
    sectionName: default
  hostnames:
  - "hipstershop.cilium.rocks"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: productpage
      port: 9080

구성을 적용해볼게요:

$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/kubernetes/gateway/https-default-tls-certificate.yaml

Gateway와 HTTPRoutes가 배포되면 Gateway에서 외부 IP 주소를 확인하고, HTTPRoute의 호스트 이름이 유효한지 확인할 수 있어요.

$ kubectl get gateway tls-gateway
NAME          CLASS    ADDRESS          PROGRAMMED   AGE
tls-gateway   cilium   172.18.255.200   True         27m

$ kubectl get httproute bookinfo hipstershop
NAME          HOSTNAMES                      AGE
bookinfo      ["bookinfo.cilium.rocks"]      27m
hipstershop   ["hipstershop.cilium.rocks"]   27m

HTTPS 요청 보내기 (Make HTTPS Requests)

curl 요청에 CA의 인증서를 지정하면 해당 CA가 서명한 인증서를 신뢰한다는 뜻이 돼요.

$ curl --cacert minica.pem -v https://bookinfo.cilium.rocks/details/1 --resolve bookinfo.cilium.rocks:443:172.18.255.200
$ curl --cacert minica.pem -v https://hipstershop.cilium.rocks/ --resolve hipstershop.cilium.rocks:443:172.18.255.200
...
*   subjectAltName: "bookinfo.cilium.rocks" matches cert's "bookinfo.cilium.rocks"
* SSL certificate verified via OpenSSL.

TLS 인증서가 요청한 호스트 이름으로 발급됐으므로 경고 메시지가 보이지 않아야 해요.

다음 요청은 IP 주소로 서비스에 접근할 때 클라이언트가 SNI 헤더를 제공하지 않는다는 점을 보여줘요. 이는 보통 Cilium이 IP 주소로 연결을 맺는 로드 밸런서나 프록시의 백엔드로 동작할 때 발생해요. SNI가 없어도 기본 인증서로 TLS 연결은 여전히 수립돼요. 인증서의 호스트 이름이 IP 주소와 일치하지 않으므로, 이 예제에서는 검증을 우회하기 위해 curl에서 -k 플래그를 사용한다는 점을 주목해주세요.

이후 올바른 백엔드로의 라우팅은 Host HTTP 헤더로 결정되는데, 이 예제에서는 이를 명시적으로 제공해요.

$ curl -H "Host: hipstershop.cilium.rocks" -k -v https://172.18.255.200/
...
*  SSL certificate verification failed, continuing anyway!
* Established connection to 172.18.255.200 (172.18.255.200 port 443) from 10.244.0.52 port 42458

curl 요청에서 -v를 지정하면 TLS 핸드셰이크가 성공한 것을 볼 수 있어요.

더 알아보기 (Learn more)