이번 실습에서는 Istio 공식 문서의 시나리오 중 하나인 Ingress Sidecar TLS Termination을 그대로 따라가면서, 사이드카(Envoy)가 TLS 종료를 수행하도록 설정하고 흐름을 검증해보았다. 기존의 Istio Ingress Gateway가 TLS를 종료하는 구조와는 달리, 이번 실습은 사이드카가 직접 TLS 통신을 종료하고 내부 컨테이너로는 HTTP로 전달하는 흐름을 구성하는 데 초점을 둔다.
이번 실습은 다음과 같은 통신 구조를 구성하고 검증하는 것이다:

먼저, Istio를 설치할 때 사이드카가 TLS 종료 기능을 사용할 수 있도록 다음과 같이 experimental 기능인 ENABLE_TLS_ON_SIDECAR_INGRESS를 활성화한다.
istioctl install \
--set profile=default \
--set values.pilot.env.ENABLE_TLS_ON_SIDECAR_INGRESS=true
이 설정은 Istio가 사이드카에서 TLS 종료를 처리할 수 있도록 내부 기능을 활성화하는 중요한 단계다.
kubectl create ns test
kubectl label namespace test istio-injection=enabled
kubectl -n test apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
spec:
mtls:
mode: STRICT
EOF
사이드카가 외부에서 들어오는 TLS 요청을 직접 처리할 수 있도록 하기 위해, 외부와 통신할 포트(9080)에 대해서만 mTLS를 끈다.
kubectl -n test apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: disable-peer-auth-for-external-mtls-port
spec:
selector:
matchLabels:
app: httpbin
mtls:
mode: STRICT
portLevelMtls:
9080:
mode: DISABLE
EOF
# CA 인증서
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
# 서버 인증서 (httpbin)
openssl req -out httpbin.test.svc.cluster.local.csr \
-newkey rsa:2048 -nodes \
-keyout httpbin.test.svc.cluster.local.key \
-subj "/CN=httpbin.test.svc.cluster.local/O=httpbin organization"
openssl x509 -req -days 365 \
-CA example.com.crt -CAkey example.com.key -set_serial 1 \
-in httpbin.test.svc.cluster.local.csr \
-out httpbin.test.svc.cluster.local.crt
# 클라이언트 인증서
openssl req -out client.test.svc.cluster.local.csr \
-newkey rsa:2048 -nodes \
-keyout client.test.svc.cluster.local.key \
-subj "/CN=client.test.svc.cluster.local/O=client organization"
openssl x509 -req -days 365 \
-CA example.com.crt -CAkey example.com.key -set_serial 1 \
-in client.test.svc.cluster.local.csr \
-out client.test.svc.cluster.local.crt
kubectl -n test create secret generic httpbin-mtls-termination-cacert --from-file=ca.crt=./example.com.crt
kubectl -n test create secret tls httpbin-mtls-termination \
--cert=httpbin.test.svc.cluster.local.crt \
--key=httpbin.test.svc.cluster.local.key
kubectl -n test apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: httpbin
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
labels:
app: httpbin
spec:
ports:
- port: 8443
name: https
targetPort: 9080
- port: 8080
name: http
targetPort: 9081
selector:
app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v1
template:
metadata:
labels:
app: httpbin
version: v1
annotations:
sidecar.istio.io/userVolume: '{"tls-secret":{"secret":{"secretName":"httpbin-mtls-termination","optional":true}},"tls-ca-secret":{"secret":{"secretName":"httpbin-mtls-termination-cacert"}}}'
sidecar.istio.io/userVolumeMount: '{"tls-secret":{"mountPath":"/etc/istio/tls-certs/","readOnly":true},"tls-ca-secret":{"mountPath":"/etc/istio/tls-ca-certs/","readOnly":true}}'
spec:
serviceAccountName: httpbin
containers:
- image: docker.io/kennethreitz/httpbin
name: httpbin
ports:
- containerPort: 80
EOF
kubectl -n test apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
name: ingress-sidecar
spec:
workloadSelector:
labels:
app: httpbin
version: v1
ingress:
- port:
number: 9080
protocol: HTTPS
name: external
defaultEndpoint: 0.0.0.0:80
tls:
mode: MUTUAL
privateKey: "/etc/istio/tls-certs/tls.key"
serverCertificate: "/etc/istio/tls-certs/tls.crt"
caCertificates: "/etc/istio/tls-ca-certs/ca.crt"
- port:
number: 9081
protocol: HTTP
name: internal
defaultEndpoint: 0.0.0.0:80
EOF
kubectl -n test apply -f https://raw.githubusercontent.com/istio/istio/release-1.17/samples/sleep/sleep.yaml
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.17/samples/sleep/sleep.yaml
INTERNAL_CLIENT=$(kubectl -n test get pod -l app=sleep -o jsonpath='{.items[0].metadata.name}')
kubectl -n test exec "${INTERNAL_CLIENT}" -c sleep -- \
curl -IsS "http://httpbin:8080/status/200"
→ HTTP/1.1 200 OK 반환되면 사이드카 뒤쪽으로 HTTP 통신이 잘 이루어짐을 의미함
EXTERNAL_CLIENT=$(kubectl get pod -l app=sleep -n default -o jsonpath='{.items[0].metadata.name}')
kubectl cp client.test.svc.cluster.local.key default/${EXTERNAL_CLIENT}:/tmp/
kubectl cp client.test.svc.cluster.local.crt default/${EXTERNAL_CLIENT}:/tmp/
kubectl cp example.com.crt default/${EXTERNAL_CLIENT}:/tmp/ca.crt
kubectl exec -n default "${EXTERNAL_CLIENT}" -c sleep -- \
curl -IsS \
--cacert /tmp/ca.crt \
--key /tmp/client.test.svc.cluster.local.key \
--cert /tmp/client.test.svc.cluster.local.crt \
-HHost:httpbin.test.svc.cluster.local \
"https://httpbin.test.svc.cluster.local:8443/status/200"
→ HTTP/2 200 OK 응답이 오면 mTLS 종료가 사이드카에서 성공한 것.
kubectl exec -n default "${EXTERNAL_CLIENT}" -c sleep -- \
curl -IsS \
--cacert /tmp/ca.crt \
--key /tmp/client.test.svc.cluster.local.key \
--cert /tmp/client.test.svc.cluster.local.crt \
-HHost:httpbin.test.svc.cluster.local \
"http://httpbin.test.svc.cluster.local:8080/status/200"
→ Connection reset by peer 에러가 발생하면 정상적으로 HTTPS 포트 외의 요청은 거부됨
이번 실습을 통해, Istio에서 Ingress Gateway를 거치지 않고도 사이드카가 직접 TLS를 종료하는 구조를 성공적으로 구성해보았다. 이 구조는 내부에서 HTTP로 처리되지만 외부 요청에 대해서는 mTLS로 보호되고 있으며, Gateway가 없는 대신 Sidecar 리소스의 ingress 설정을 통해 직접 TLS 종료 처리를 맡는 구조이다. 이는 내부 서비스가 API Gateway처럼 외부에 직접 노출되는 상황에서 유용하게 활용할 수 있다.