9.1 애플리케이션 네트워크 보안의 필요성
- 애플리케이션 보안은 인가되지 않은 사용자가 중요한 데이터를 훼손, 탈취, 무단 접근하지 못하도록 보호하는 모든 활동을 의미한다.
- 이를 위해 사용자 인증·인가 절차와, 네트워크를 오가는 데이터의 암호화가 필수적이다.
- 인증은 사용자의 정체를 확인하는 과정이고, 인가는 인증된 사용자의 리소스 접근 권한을 판단하는 절차다.
서비스 간 인증 Service-to-service authentication - SPIFFE 프레임워크
- SPIFFE는 서비스 간 신뢰를 위해 제3자가 발급한 짧은 수명의 암호화된 ID 문서(SVID)를 자동으로 제공하고, 이를 통해 서비스들이 서로를 인증한다.
- 이 ID는 TLS 연결이나 JWT 서명 검증 등에 사용되어, 서비스 간 안전한 상호 인증을 구현한다.
최종 사용자 인증 End-user authentication - JWT 등 자격증명
최종 사용자 인증은 사용자가 인증 서버에서 받은 자격 증명을 서비스에 제시하고, 서비스는 이를 인증 서버에 검증하여 접근을 허용하는 절차다.
인가 Authorization - 작업 수행 승인/거부
인가는 인증된 사용자의 ID를 바탕으로 수행 가능한 작업을 확인하여 승인 또는 거부하는 절차이며, 이스티오는 이를 세분화하여 제공한다.
MSA에서의 인증/인가
마이크로서비스 환경에서는 서비스가 동적으로 생성·소멸되고 여러 네트워크와 클라우드에 분산되기 때문에, IP 주소 기반 식별 방식은 신뢰할 수 없다.
이스티오는 SPIFFE 오픈소스 표준을 도입해, 다양한 환경에서 각 워크로드에 고유하고 검증 가능한 ID를 자동으로 부여함으로써 안전한 서비스 식별과 인증을 지원한다.
Istio에서의 인증/인가
이스티오의 보안은 PeerAuthentication, RequestAuthentication, AuthorizationPolicy 세 가지 커스텀 리소스 기반으로 구성된다.
PeerAuthentication은 서비스 간 트래픽에 대해 mTLS 인증을 적용하고, 상대 서비스의 인증서 정보를 추출한다.
RequestAuthentication은 최종 사용자의 자격 증명(JWT 등)을 검증하며, 인증된 사용자 정보를 추출한다.
AuthorizationPolicy는 앞서 추출된 서비스 또는 사용자 정보를 바탕으로 요청을 허용하거나 거부하는 인가 정책을 설정한다.
이 세 리소스는 프록시가 요청의 ID 정보를 필터 메타데이터로 저장하고, 인가 정책에 따라 세분화된 접근 제어를 실현하게 한다.
9.2 자동 상호 TLS (Auto mTLS)
- 이스티오는 프록시가 주입된 서비스 간 트래픽을 자동으로 암호화하고 상호 인증하며, 인증서 발급·갱신을 자동화해 휴먼 에러와 서비스 중단을 예방한다.
- mTLS와 최소 권한 정책을 적용하면 기본적으로 안전한 상태를 유지할 수 있지만, 메시 전체를 더 안전하게 만들기 위해서는 추가적인 보안 작업이 필요하다.
실습환경
#mTLS 실습 환경
# catalog와 webapp 배포
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
# webapp과 catalog의 gateway, virtualservice 설정
kubectl apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml -n istioinaction
# default 네임스페이스에 sleep 앱 배포
cat ch9/sleep.yaml
...
spec:
serviceAccountName: sleep
containers:
- name: sleep
image: governmentpaas/curl-ssl
command: ["/bin/sleep", "3650d"]
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /etc/sleep/tls
name: secret-volume
volumes:
- name: secret-volume
secret:
secretName: sleep-secret
optional: true
kubectl apply -f ch9/sleep.yaml -n default
# 확인
kubectl get deploy,pod,sa,svc,ep
kubectl get deploy,svc -n istioinaction
kubectl get gw,vs -n istioinaction
이스티오의 PeerAuthentication 리소스 이해하기
PeerAuthentication 리소스는 mTLS 인증 모드를 서비스 메시 전체, 네임스페이스, 또는 특정 워크로드 단위로 엄격(STRICT) 또는 허용(PERMISSIVE)하게 설정할 수 있다.
#
cat ch9/meshwide-strict-peer-authn.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default" # Mesh-wide policies must be named "default"
namespace: "istio-system" # Istio installation namespace
spec:
mtls:
mode: STRICT # mutual TLS mode
# 적용
kubectl apply -f ch9/meshwide-strict-peer-authn.yaml -n istio-system
# 요청 실행
kubectl exec deploy/sleep -c sleep -- curl -s http://webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"
000
command terminated with exit code 56
# 확인
kubectl get PeerAuthentication -n istio-system
kubectl logs -n istioinaction -l app=webapp -c webapp -f
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-05-01T08:32:08.511Z] "- - -" 0 NR filter_chain_not_found - "-" 0 0 0 - "-" "-" "-" "-" "-" - - 10.10.0.17:8080 10.10.0.16:51930 - -
[2025-05-01T08:32:10.629Z] "- - -" 0 NR filter_chain_not_found - "-" 0 0 0 - "-" "-" "-" "-" "-" - - 10.10.0.17:8080 10.10.0.16:53366 - -
# NR → Non-Route. Envoy에서 라우팅까지 가지 못한 단계에서 발생한 에러라는 의미입니다.
# filter_chain_not_found → 해당 Listener에서 제공된 SNI(Server Name Indication), IP, 포트, ALPN 등의 조건에 맞는 filter_chain이 설정에 없다는 뜻입니다.
상호 인증하기 않은 트래픽 허용하기
네임스페이스 범위 PeerAuthentication 정책은 메시 전체 정책을 덮어쓸 수 있어, 특정 네임스페이스의 워크로드에 맞는 인증 요구사항을 유연하게 적용할 수 있다.
cat << EOF | kubectl apply -f -
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default" # Uses the "default" naming convention so that only one namespace-wide resource exists
namespace: "istioinaction" # Specifies the namespace to apply the policy
spec:
mtls:
mode: PERMISSIVE # PERMISSIVE allows HTTP traffic.
EOF
# 요청 실행
kubectl exec deploy/sleep -c sleep -- curl -s http://webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"
# 확인
kubectl get PeerAuthentication -A
NAMESPACE NAME MODE AGE
istio-system default STRICT 2m51s
istioinaction default PERMISSIVE 7s
kubectl logs -n istioinaction -l app=webapp -c webapp -f
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
# 다음 실습을 위해 삭제 : PeerAuthentication 단축어 pa
kubectl delete pa default -n istioinaction

워크로드 셀렉터를 지정해 PeerAuthentication 정책을 webapp에만 적용하고, 네임스페이스 전체가 아닌 경우 정책 이름을 'webapp'으로 변경하는 것이 권장된다.

# istiod 는 PeerAuthentication 리소스 생성을 수신하고, 이 리소스를 엔보이용 설정으로 변환하며,
# LDS(Listener Discovery Service)를 사용해 서비스 프록시에 적용
docker exec -it myk8s-control-plane istioctl proxy-status
kubectl logs -n istio-system -l app=istiod -f
...
2025-05-01T09:48:32.854911Z info ads LDS: PUSH for node:catalog-6cf4b97d-2r9bn.istioinaction resources:23 size:85.4kB
2025-05-01T09:48:32.855510Z info ads LDS: PUSH for node:webapp-7685bcb84-jcg7d.istioinaction resources:23 size:94.0kB
...
#
cat ch9/workload-permissive-peer-authn.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "webapp"
namespace: "istioinaction"
spec:
selector:
matchLabels:
app: "webapp" # 레이블이 일치하는 워크로드만 PERMISSIVE로 동작
mtls:
mode: PERMISSIVE
kubectl apply -f ch9/workload-permissive-peer-authn.yaml
kubectl get pa -A
# 요청 실행
kubectl logs -n istioinaction -l app=webapp -c webapp -f
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
kubectl exec deploy/sleep -c sleep -- curl -s http://webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"
#
kubectl logs -n istioinaction -l app=catalog -c catalog -f
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
kubectl exec deploy/sleep -c sleep -- curl -s http://catalog.istioinaction/api/items -o /dev/null -w "%{http_code}\n"
2025-05-01T09:32:00.197Z] "- - -" 0 NR filter_chain_not_found - "-" 0 0 0 - "-" "-" "-" "-" "-" - - 10.10.0.18:3000 10.10.0.16:33192 - -
...
tcpdump로 서비스 간 트래픽 스니핑하기
# 패킷 모니터링 실행 해두기
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy \
-- sudo tcpdump -l --immediate-mode -vv -s 0 '(((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0) and not (port 53)'
# -l : 표준 출력(stdout)을 라인 버퍼 모드로 설정. 터미널에서 실시간으로 결과를 보기 좋게 함 (pipe로 넘길 때도 유용).
# --immediate-mode : 커널 버퍼에서 패킷을 모아서 내보내지 않고, 캡처 즉시 사용자 공간으로 넘김 → 딜레이 최소화.
# -vv : verbose 출력. 패킷에 대한 최대한의 상세 정보를 보여줌.
# -s 0 : snap length를 0으로 설정 → 패킷 전체 내용을 캡처. (기본값은 262144 bytes, 예전 버전에서는 68 bytes로 잘렸음)
# '(((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0) and not (port 53)' : DNS패킷 제외하고 TCP payload 길이가 0이 아닌 패킷만 캡처
# 즉, SYN/ACK/FIN 같은 handshake 패킷(데이터 없는 패킷) 무시, 실제 데이터 있는 패킷만 캡처
# 결론 : 지연 없이, 전체 패킷 내용을, 매우 자세히 출력하고, DNS패킷 제외하고 TCP 데이터(payload)가 1 byte 이상 있는 패킷만 캡처
# 요청 실행
kubectl exec deploy/sleep -c sleep -- curl -s webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"
...
## (1) sleep -> webapp 호출 HTTP
14:07:24.926390 IP (tos 0x0, ttl 63, id 63531, offset 0, flags [DF], proto TCP (6), length 146)
10-10-0-16.sleep.default.svc.cluster.local.32828 > webapp-7685bcb84-hp2kl.http-alt: Flags [P.], cksum 0x14bc (incorrect -> 0xa83b), seq 2741788650:2741788744, ack 3116297176, win 512, options [nop,nop,TS val 490217013 ecr 2804101520], length 94: HTTP, length: 94
GET /api/catalog HTTP/1.1
Host: webapp.istioinaction
User-Agent: curl/8.5.0
Accept: */*
## (2) webapp -> catalog 호출 HTTPS
14:07:24.931647 IP (tos 0x0, ttl 64, id 18925, offset 0, flags [DF], proto TCP (6), length 1304)
webapp-7685bcb84-hp2kl.37882 > 10-10-0-19.catalog.istioinaction.svc.cluster.local.3000: Flags [P.], cksum 0x1945 (incorrect -> 0x9667), seq 2146266072:2146267324, ack 260381029, win 871, options [nop,nop,TS val 1103915113 ecr 4058175976], length 1252
## (3) catalog -> webapp 응답 HTTPS
14:07:24.944769 IP (tos 0x0, ttl 63, id 7029, offset 0, flags [DF], proto TCP (6), length 1789)
10-10-0-19.catalog.istioinaction.svc.cluster.local.3000 > webapp-7685bcb84-hp2kl.37882: Flags [P.], cksum 0x1b2a (incorrect -> 0x2b6f), seq 1:1738, ack 1252, win 729, options [nop,nop,TS val 4058610491 ecr 1103915113], length 1737
## (4) webapp -> sleep 응답 HTTP
14:07:24.946168 IP (tos 0x0, ttl 64, id 13699, offset 0, flags [DF], proto TCP (6), length 663)
webapp-7685bcb84-hp2kl.http-alt > 10-10-0-16.sleep.default.svc.cluster.local.32828: Flags [P.], cksum 0x16c1 (incorrect -> 0x37d1), seq 1:612, ack 94, win 512, options [nop,nop,TS val 2804101540 ecr 490217013], length 611: HTTP, length: 611
HTTP/1.1 200 OK
content-length: 357
content-type: application/json; charset=utf-8
date: Thu, 01 May 2025 14:07:24 GMT
x-envoy-upstream-service-time: 18
server: istio-envoy
x-envoy-decorator-operation: webapp.istioinaction.svc.cluster.local:80/*
[{"id":1,"color":"amber","department":"Eyewear","name":"Elinor Glasses","price":"282.00"},{"id":2,"color":"cyan","department":"Clothing","name":"Atlas Shirt","price":"127.00"},{"id":3,"color":"teal","department":"Clothing","name":"Small Metal Shoes","price":"232.00"},{"id":4,"color":"red","department":"Watches","name":"Red Dragon Watch","price":"232.00"}] [|http]
...
워크로드 ID가 워크로드 서비스 어카운트에 연결돼 있는지 확인하기
openssl 명령어로 catalog 워크로드의 X.509 인증서를 확인하면, SVID 문서의 유효성, SPIFFE ID가 SAN(Subject Alternative Name) 필드에 URI로 인코딩되어 있는지, 그리고 해당 ID가 워크로드 서비스 어카운트와 일치하는지 직접 검증할 수 있다.
# (참고) 패킷 모니터링 : 아래 openssl 실행 시 동작 확인
kubectl exec -it -n istioinaction deploy/catalog -c istio-proxy \
-- sudo tcpdump -l --immediate-mode -vv -s 0 'tcp port 3000'
# catalog 의 X.509 인증서 내용 확인
kubectl -n istioinaction exec deploy/webapp -c istio-proxy -- ls -l /var/run/secrets/istio/root-cert.pem
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- openssl x509 -in /var/run/secrets/istio/root-cert.pem -text -noout
...
kubectl -n istioinaction exec deploy/webapp -c istio-proxy -- openssl -h
kubectl -n istioinaction exec deploy/webapp -c istio-proxy -- openssl s_client -h
# openssl s_client → TLS 서버에 연결해 handshake와 인증서 체인을 보여줌
# -showcerts → 서버가 보낸 전체 인증서 체인 출력
# -connect catalog.istioinaction.svc.cluster.local:80 → Istio 서비스 catalog로 TCP 80 연결
# -CAfile /var/run/secrets/istio/root-cert.pem → Istio의 root CA로 서버 인증서 검증
# 결론 : Envoy proxy에서 catalog 서비스로 연결하여 TLS handshake 및 인증서 체인 출력 후 사람이 읽을 수 있는 형식으로 해석
kubectl -n istioinaction exec deploy/webapp -c istio-proxy \
-- openssl s_client -showcerts \
-connect catalog.istioinaction.svc.cluster.local:80 \
-CAfile /var/run/secrets/istio/root-cert.pem | \
openssl x509 -in /dev/stdin -text -noout
...
Validity
Not Before: May 1 09:55:10 2025 GMT # 유효기간 1일 2분
Not After : May 2 09:57:10 2025 GMT
...
X509v3 extensions:
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication # 사용처 : 웹서버, 웹클라이언트
...
X509v3 Subject Alternative Name: critical
URI:spiffe://cluster.local/ns/istioinaction/sa/catalog # SPIFFE ID 확인
# catalog 파드의 서비스 어카운트 확인
kubectl describe pod -n istioinaction -l app=catalog | grep 'Service Account'
Service Account: catalog
이스티오 보안: SPIFFE
- PKI(공개 키 인프라)는 서버와 클라이언트가 안전하게 통신할 수 있도록 디지털 인증서(X.509)를 발급·검증하는 표준화된 프레임워크다.
- 인증서에는 공개 키와 소유자 신원 정보가 포함되며, 서버는 이를 통해 자신의 정체를 증명한다.
- TLS 프로토콜에서는 핸드셰이크 과정에서 서버가 X.509 인증서를 제시하고, 클라이언트가 신뢰 체인을 검증한다.
- 검증에 성공하면 클라이언트는 서버의 공개 키로 암호화한 세션 키(대칭 키)를 서버에 전달하고, 서버는 개인 키로 복호화한다.
- 이렇게 안전하게 교환된 대칭 키로 이후 트래픽을 암호화해 성능과 보안을 모두 확보한다.
- 최종 사용자 인증은 주로 세션 쿠키나 수명이 짧은 JWT를 발급해, 후속 요청에서 사용자를 인증하는 방식으로 구현된다.
- 이스티오 등 현대 서비스 메시 환경에서도 JWT 기반의 최종 사용자 인증과 PKI 기반 서비스 인증이 함께 활용된다.
SPIFFE: 모든 이를 위한 안전한 운영 환경 ID 프레임워크
- SPIFFE ID는 spiffe://trust-domain/path 형식의 RFC 3986 호환 URI로, trust-domain은 발급자를, path는 워크로드를 고유하게 식별한다.
- path의 구체적 구조는 SPIFFE 구현체가 결정하며, 예를 들어 이스티오는 쿠버네티스 서비스 어카운트를 path로 활용한다.
- Workload API는 워크로드가 SPIFFE ID가 포함된 SVID(인증서)를 요청·발급받을 수 있도록 표준 gRPC 엔드포인트를 제공한다.
- 이 API는 워크로드가 자체 비밀을 보유하지 않도록 설계되었으며, 안전하게 SVID를 수령하고, 상호 인증이나 메시지 서명 등에 활용할 수 있게 한다.
- 워크로드 엔드포인트는 SPIFFE 데이터플레인 구성요소로, 워크로드의 무결성을 attestation(증명)하고 SPIFFE ID가 포함된 CSR을 생성해 워크로드 API에 제출한다.
- 워크로드 API(예: Istio의 istiod)는 CSR을 검증·서명해 SPIFFE ID가 포함된 SVID(X.509 인증서)를 발급하며, 이 인증서는 SAN 필드에 SPIFFE ID가 URI로 인코딩된다.
- 이스티오에서는 워크로드 엔드포인트를 프록시(pilot-agent)가, 워크로드 API를 Istio CA(istiod)가 구현하며, 프록시가 ID 부트스트랩과 인증서 수령을 자동화한다.
- 결과적으로 모든 워크로드는 자동으로 고유한 SVID를 받아 상호 인증과 자동 mTLS 기반 암호화 통신이 가능해진다.
워크로드 ID의 단계별 부트스트랩
- 쿠버네티스에서 초기화된 모든 파드에는 /var/run/secrets/kubernetes.io/serviceaccount/ 경로에 서비스 어카운트 토큰, 클러스터 CA 인증서, 네임스페이스 정보가 포함된 시크릿이 기본적으로 마운트된다.
- 이 중 서비스 어카운트 토큰은 파드의 ID와 권한 정보를 담고 있으며, 서명되어 있어 페이로드를 임의로 수정할 수 없고, 쿠버네티스 API와의 안전한 통신 및 워크로드 ID 부트스트랩에 핵심적으로 사용된다.
#
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ls -l /var/run/secrets/kubernetes.io/serviceaccount/
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
TOKEN=$(kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# 헤더 디코딩
echo $TOKEN | cut -d '.' -f1 | base64 --decode | sed 's/$/}/' | jq
{
"alg": "RS256",
"kid": "nKgUYnbjH9BmgEXYbu56GFoBxwDF_jF9Q6obIWvinAM"
}
# 페이로드 디코딩
echo $TOKEN | cut -d '.' -f2 | base64 --decode | sed 's/$/}/' | jq
{
"aud": [
"https://kubernetes.default.svc.cluster.local"
],
"exp": 1777689454,
"iat": 1746153454,
"iss": "https://kubernetes.default.svc.cluster.local",
"kubernetes.io": {
"namespace": "istioinaction",
"pod": {
"name": "webapp-7685bcb84-hp2kl",
"uid": "98444761-1f47-45ad-b739-da1b7b22013a"
},
"serviceaccount": {
"name": "webapp",
"uid": "5a27b23e-9ed6-46f7-bde0-a4e4684949c2"
},
"warnafter": 1746157061
},
"nbf": 1746153454,
"sub": "system:serviceaccount:istioinaction:webapp"
}
# (옵션) brew install jwt-cli # Linux 툴 추천 부탁드립니다.
jwt decode $TOKEN
Token header
------------
{
"alg": "RS256",
"kid": "nKgUYnbjH9BmgEXYbu56GFoBxwDF_jF9Q6obIWvinAM"
}
Token claims
------------
{
"aud": [ # 이 토큰의 대상(Audience) : 토큰이 어떤 API나 서비스에서 사용될 수 있는지 정의 -> k8s api가 aud 가 일치하는지 검사하여 올바른 토큰인지 판단.
"https://kubernetes.default.svc.cluster.local"
],
"exp": 1777689454, # 토큰 만료 시간 Expiration Time (Unix timestamp, 초 단위) , date -r 1777689454 => (1년) Sat May 2 11:37:34 KST 2026
"iat": 1746153454, # 토큰 발급 시간 Issued At (Unix timestamp), date -r 1746153454 => Fri May 2 11:37:34 KST 2025
"iss": "https://kubernetes.default.svc.cluster.local", # Issuer, 토큰을 발급한 주체, k8s api가 발급
"kubernetes.io": {
"namespace": "istioinaction",
"pod": {
"name": "webapp-7685bcb84-hp2kl",
"uid": "98444761-1f47-45ad-b739-da1b7b22013a" # 파드 고유 식별자
},
"serviceaccount": {
"name": "webapp",
"uid": "5a27b23e-9ed6-46f7-bde0-a4e4684949c2" # 서비스 어카운트 고유 식별자
},
"warnafter": 1746157061 # 이 시간 이후에는 새로운 토큰을 요청하라는 Kubernetes의 신호 (토큰 자동 갱신용) date -r 1746157061 (1시간) => Fri May 2 12:37:41 KST 2025
},
"nbf": 1746153454, # Not Before, 이 시간 이전에는 토큰이 유효하지 않음. 보통 iat와 동일하게 설정됩니다.
"sub": "system:serviceaccount:istioinaction:webapp" # 토큰의 주체(Subject)
}
# sa 에 토큰 유효 시간 3600초 = 1시간 + 7초
kubectl get pod -n istioinaction -l app=webapp -o yaml
...
- name: kube-api-access-nt4qb
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
...
파일럿 에이전트는 쿠버네티스 서비스 어카운트 토큰을 디코딩해 SPIFFE ID를 생성하고, 이 ID가 포함된 CSR과 토큰을 이스티오 CA에 전송한다.
이스티오 CA는 TokenReview API로 토큰의 유효성을 검증한 뒤, 검증에 성공하면 CSR에 서명해 인증서를 파일럿 에이전트에 반환한다.
파일럿 에이전트는 SDS Secrets Discovery Service 를 통해 인증서와 키를 엔보이 프록시로 전달하고, 이로써 ID 부트스트랩 과정이 마무리된다.
# 유닉스 도메인 소켓 listen 정보 확인
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ss -xpl
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
u_str LISTEN 0 4096 etc/istio/proxy/XDS 13207 * 0 users:(("pilot-agent",pid=1,fd=11))
u_str LISTEN 0 4096 ./var/run/secrets/workload-spiffe-uds/socket 13206 * 0 users:(("pilot-agent",pid=1,fd=10))
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ss -xp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
u_str ESTAB 0 0 ./var/run/secrets/workload-spiffe-uds/socket 21902 * 23737 users:(("pilot-agent",pid=1,fd=16))
u_str ESTAB 0 0 etc/istio/proxy/XDS 1079087 * 1080955 users:(("pilot-agent",pid=1,fd=8))
...
# 유닉스 도메인 소켓 정보 확인
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- lsof -U
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
pilot-age 1 istio-proxy 8u unix 0x00000000bda7185a 0t0 1079087 etc/istio/proxy/XDS type=STREAM # 소켓 경로 및 스트림 타입
pilot-age 1 istio-proxy 10u unix 0x0000000009112f4b 0t0 13206 ./var/run/secrets/workload-spiffe-uds/socket type=STREAM # SPIFFE UDS (SPIFFE SVID 인증용)
# TYPE 파일 유형 (unix → Unix Domain Socket)
## 8u → 8번 디스크립터, u = 읽기/쓰기
## 10u → 10번 디스크립터, u = 읽기/쓰기
# 유닉스 도메인 소켓 파일 정보 확인
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ls -l /var/run/secrets/workload-spiffe-uds/socket
srw-rw-rw- 1 istio-proxy istio-proxy 0 May 1 23:23 /var/run/secrets/workload-spiffe-uds/socket
# istio 인증서 확인 :
docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/webapp.istioinaction
RESOURCE NAME TYPE STATUS VALID CERT SERIAL NUMBER NOT AFTER NOT BEFORE
default Cert Chain ACTIVE true 45287494908809645664587660443172732423 2025-05-03T16:13:14Z 2025-05-02T16:11:14Z
ROOTCA CA ACTIVE true 338398148201570714444101720095268162852 2035-04-29T07:46:14Z 2025-05-01T07:46:14Z
docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/webapp.istioinaction -o json
...
echo "." | base64 -d | openssl x509 -in /dev/stdin -text -noout
# istio ca 관련
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ls -l /var/run/secrets/istio
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- openssl x509 -in /var/run/secrets/istio/root-cert.pem -text -noout
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ls -l /var/run/secrets/tokens
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- cat /var/run/secrets/tokens/istio-token
TOKEN=$(kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- cat /var/run/secrets/tokens/istio-token)
# 헤더 디코딩
echo $TOKEN | cut -d '.' -f1 | base64 --decode | sed 's/$/}/' | jq
# 페이로드 디코딩
echo $TOKEN | cut -d '.' -f2 | base64 --decode | sed 's/$/"}/' | jq
# (옵션) brew install jwt-cli
jwt decode $TOKEN
# (참고) k8s ca 관련
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ls -l /var/run/secrets
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ls -l /var/run/secrets/kubernetes.io/serviceaccount
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- openssl x509 -in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -text -noout
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
TOKEN=$(kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# 헤더 디코딩
echo $TOKEN | cut -d '.' -f1 | base64 --decode | sed 's/$/}/' | jq
# 페이로드 디코딩
echo $TOKEN | cut -d '.' -f2 | base64 --decode | sed 's/$/}/' | jq
# (옵션) brew install jwt-cli
jwt decode $TOKEN
# (참고)
kubectl port-forward deploy/webapp -n istioinaction 15000:15000
open http://localhost:15000
curl http://localhost:15000/certs
요청 ID 이해하기
- 요청 ID는 요청의 필터 메타데이터에 저장되며, 이 메타데이터에는 JWT나 피어 인증서에서 추출한 신뢰할 수 있는 정보(사실, 클레임)가 포함된다.
- PeerAuthentication 리소스는 워크로드 간 상호 인증(mTLS)을 강제하고, RequestAuthentication 리소스는 JWT를 검증해 최종 사용자 정보를 추출한다.
- 필터 메타데이터에는 워크로드 ID(Principal), 네임스페이스, 최종 사용자 주체, 사용자 클레임 등 다양한 인증·인가 관련 정보가 저장된다.
- 이렇게 수집된 메타데이터는 서비스 프록시가 표준 출력 등으로 기록해 관찰할 수 있으며, 인가 정책의 근거로 활용된다.
RequestAuthentication 리소스로 수집한 메타데이터
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/istio-ingressgateway -n istio-system --level rbac:debug
#초기화
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
kubectl apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml -n istioinaction
kubectl apply -f ch9/enduser/jwt-token-request-authn.yaml
kubectl apply -f ch9/enduser/allow-all-with-jwt-to-webapp.yaml # :30000 포트 추가 필요, 아래 실습 설정 참고.
kubectl get requestauthentication,authorizationpolicy -A
# 로깅
kubectl logs -n istio-system -l app=istio-ingressgateway -f
# admin 토큰을 사용하는 요청 : 필터 메타데이터 확인
ADMIN_TOKEN=$(< ch9/enduser/admin.jwt)
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
-sSl -o /dev/null -w "%{http_code}\n" webapp.istioinaction.io:30000/api/catalog
...
dynamicMetadata: filter_metadata {
key: "envoy.filters.http.jwt_authn"
value {
fields {
key: "auth@istioinaction.io"
value {
struct_value {
fields {
key: "exp"
value {
number_value: 4745145071
}
}
fields {
key: "group"
value {
string_value: "admin"
}
}
fields {
key: "iat"
value {
number_value: 1591545071
}
}
fields {
key: "iss"
value {
string_value: "auth@istioinaction.io"
}
}
fields {
key: "sub"
value {
string_value: "218d3fb9-4628-4d20-943c-124281c80e7b"
}
}
}
}
}
}
}
filter_metadata {
key: "istio_authn"
value {
fields {
key: "request.auth.claims"
value {
struct_value {
fields {
key: "group"
value {
list_value {
values {
string_value: "admin"
}
}
}
}
fields {
key: "iss"
value {
list_value {
values {
string_value: "auth@istioinaction.io"
}
}
}
}
fields {
key: "sub"
value {
list_value {
values {
string_value: "218d3fb9-4628-4d20-943c-124281c80e7b"
}
}
}
}
}
}
}
fields {
key: "request.auth.principal"
value {
string_value: "auth@istioinaction.io/218d3fb9-4628-4d20-943c-124281c80e7b"
}
}
fields {
key: "request.auth.raw_claims"
value {
string_value: "{\"iat\":1591545071,\"sub\":\"218d3fb9-4628-4d20-943c-124281c80e7b\",\"group\":\"admin\",\"exp\":4745145071,\"iss\":\"auth@istioinaction.io\"}"
}
}
}
}
...
정리
아래는 첨부한 이미지를 기반으로, Istio에서 서비스 메쉬 워크로드에 인증서가 할당되는 과정을 단계별로 상세히 설명한 내용입니다.
Istio 인증서 발급 및 적용 과정 상세 설명
- 서비스 어카운트 토큰 주입 (Inject token)
- 쿠버네티스는 파드가 생성될 때, 서비스 어카운트 토큰을 /var/run/secrets/kubernetes.io/serviceaccount/ 경로에 자동으로 마운트한다.
- 이 토큰은 해당 파드(워크로드)의 신원을 증명하는 데 사용된다.
- 이미지에서 파드 내부의 Istio proxy(Envoy)와 Pilot agent가 이 토큰에 접근할 수 있다.
- CSR과 토큰 전송 (CSR + token)
- Istio 프록시 내부의 Pilot agent는 서비스 어카운트 토큰을 디코딩하여 파드의 신원 정보를 추출한다.
- 이 정보를 바탕으로 SPIFFE ID가 포함된 CSR(Certificate Signing Request, 인증서 서명 요청)을 생성한다.
- Pilot agent는 CSR과 서비스 어카운트 토큰을 함께 Istio CA(istiod)로 전송한다.
- 토큰 유효성 검증 (Validate token)
- Istio CA(istiod)는 쿠버네티스 API의 TokenReview 기능을 이용해, 전달받은 토큰이 실제로 쿠버네티스에서 발급한 유효한 토큰인지 확인한다.
- 이 단계에서 토큰이 위조되었거나 만료된 경우, 인증서 발급이 거부된다.
- 인증서 발급 (Certificate)
- 토큰 검증에 성공하면, Istio CA는 CSR에 서명하여 SPIFFE ID가 포함된 X.509 인증서를 생성한다.
- 이 인증서는 다시 Pilot agent로 반환된다.
- Envoy에 인증서 및 키 적용 (Configure with certificate and private key)
- Pilot agent는 전달받은 인증서와 개인 키를 Envoy 프록시에 동적으로 전달(SDS: Secret Discovery Service)한다.
- Envoy는 이 인증서를 사용해 서비스 간 mTLS(상호 TLS) 통신 시 자신의 신원을 증명하고, 암호화된 트래픽을 주고받을 수 있게 된다.
