0. 들어가며
서비스 메시가 왜 필요한가? 기존 마이크로서비스 환경에서 발생할 수 있는 인증/인가/암호화 문제들을 소개하고, Istio가 이를 어떻게 해결하는지 개요를 다룬다.
애플리케이션 네트워크 보안의 필요성
애플리케이션 보안은 인증되지 않은 사용자가 애플리케이션 데이터를 훔치거나 오염시키거나 무단 접근하지 못하도록 방지하는 모든 활동을 포함한다. 즉, 민감한 데이터를 안전하게 보호하려면 다음 요소가 반드시 필요하다.
- 리소스 접근 이전에 사용자 인증과 인가 수행
- 클라이언트와 서버 간 요청이 오가는 동안 전송 중 데이터 암호화로 도청 방지
인증(Authentication)은 클라이언트 또는 서버가 자신의 신원을 증명하는 과정을 의미한다. 일반적으로 비밀번호(아는 것), 디바이스나 인증서(가지고 있는 것), 지문이나 얼굴 인식 같은 생체 정보(자기 자신)를 사용한다.
인가(Authorization)는 인증을 마친 사용자가 리소스를 조회하거나 수정, 삭제하는 행위를 허용 또는 차단하는 절차다. “누가 무엇을 할 수 있는가”를 결정하는 정책이다
Istio 보안 개요
마이크로서비스 아키텍처는 서비스 간 통신이 빈번하고 동적으로 변경되는 환경에서 운영되기 때문에, 전통적인 IP 기반 보안 모델로는 한계가 있다. 기존 모놀리스 시스템은 고정된 인프라 환경에서 IP 주소를 기반으로 인증 및 인가 정책을 구성할 수 있었지만, 마이크로서비스는 수많은 서비스 인스턴스가 다양한 위치에서 짧은 수명으로 실행되기 때문에 신뢰 기반의 ID가 필요하다.
Istio는 이러한 요구사항을 해결하기 위해 SPIFFE라는 오픈 표준을 활용하여 각 워크로드에 고유한 ID를 부여한다. 이 ID는 서비스 계정, 네임스페이스, 클러스터 도메인을 조합해 생성되며, 동적 환경에서도 안정적인 인증과 인가를 가능하게 한다.
결과적으로 Istio는 네트워크 환경의 복잡성과 변화 속에서도 서비스 간 신뢰 기반 통신을 보장할 수 있는 제로 트러스트 보안 모델을 실현한다.
1. Istio에서 서비스 아이덴티티를 관리하는 방식(SPIFFE)
SPIFFE란?
SPIFFE(Secure Production Identity Framework For Everyone)는 분산 환경의 워크로드에 고유한 ID(SPIFFE ID)를 부여하기 위한 오픈소스 표준이다. 쿠버네티스나 VM처럼 이기종의 인프라에서도 워크로드 간 안전한 인증을 가능하게 해 준다.
핵심 구성은 다음과 같다:
• SPIFFE ID: spiffe://<trust-domain>/<path>
형태의 고유 ID
• SVID: SPIFFE Verifiable Identity Document (보통 X.509 인증서)
• 워크로드 API: 인증서를 발급받기 위한 통신 인터페이스
Istio에서의 SPIFFE 활용 방식
Istio는 워크로드에 자동으로 SPIFFE ID를 부여하고, 해당 ID를 포함한 X.509 인증서(SVID)를 발급하여 mTLS 기반의 상호 인증에 사용한다.
인증 흐름 요약
- Istiod가 사이드카 프록시(Envoy)에 SVID를 발급한다.
- SVID의 SAN 필드에 SPIFFE ID가 URI: 형식으로 포함된다.
- 프록시는 이를 이용해 다른 서비스와 mTLS 연결을 수립한다.
- 연결이 성립되면 상대의 SPIFFE ID를 추출하여 인가 정책 판단에 활용할 수 있다.
예시) SPIFFE ID
cluster.local
: Istio trust domainns/istioinaction
: 네임스페이스sa/catalog
: 서비스 어카운트명
spiffe://cluster.local/ns/istioinaction/sa/catalog
이를 통해 Istio는 동적이고 분산된 환경에서 안정적인 아이덴티티 보장 및 보안 제어를 가능하게 한다.
Istio에서 SPIFFE ID 및 인증서 실습 확인하기
X.509 인증서에서 SPIFFE ID 확인하기
Subject Alternative Name 필드에서 spiffe://... URI 확인이 가능하다.
# catalog 서비스의 X.509 인증서 확인
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
JWT 토큰 디코딩을 통한 SPIFFE ID 생성 원리 이해
Istio는 serviceAccount의 JWT 토큰을 기반으로 SPIFFE ID를 생성한다.
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 # Header
echo $TOKEN | cut -d '.' -f2 | base64 --decode | sed 's/$/}/' | jq # Payload
페이로드에는 namespace, pod name, serviceaccount 정보가 포함되며, 다음과 같은 형식의 SPIFFE ID가 생성된다:
spiffe://cluster.local/ns/istioinaction/sa/webapp
CSR 발급부터 인증서 전달까지 흐름 요약
- Envoy가 JWT와 함께 CSR 전달
- Istio-agent가 Istio CA에 요청
- Istio CA는 TokenReview API를 통해 JWT 유효성 검증
- 검증되면 SPIFFE ID가 삽입된 SVID 인증서 반환
- SDS를 통해 Envoy로 인증서 전달 → 메모리 내 관리, 자동 갱신
webapp istio-proxy 컨테이너의 projected volume이 설정되어 kube-api를 요청할 때, API 토큰으로 사용한다.
이때 사용되는 쿠버네티스 API는 tokenReview API로 토큰의 유효성을 검사
kubectl get pod -n istioinaction -l app=webapp -o yaml | grep -i 3607 -C 5
- name: kube-api-access-d9bgp
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
부가 확인: 소켓, 인증서, 인증 흐름 검증
유닉스 도메인 소켓
SDS 통신을 위한 소켓 확인
# 파일 정보 확인
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 8 08:48 /var/run/secrets/workload-spiffe-uds/socket
# 인증서 정보 확인
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 76043070127363586253772400579281246839 2025-05-10T22:25:16Z 2025-05-09T22:23:16Z
ROOTCA CA ACTIVE true 218624780416204804322835122825266397848 2035-05-02T10:44:53Z 2025-05-04T10:44:53Z
워크로드 ID가 워크로드 서비스 어카운트에 연결돼 있는지 확인하기
mTLS 통신에 사용된 인증서가 실제 유효한 **SVID(SPIFFE Verifiable Identity Document)**인지, 그리고 SPIFFE ID가 워크로드의 서비스 어카운트와 일치하는지 확인할 필요가 있다.
- 루트 인증서 서명 확인
openssl verify
로 인증 기관 CA 루트 인증서에 대해 서명을 확인함으로써 X.509 SVID의 내용물이 유효한지 살펴보자.- 루트 인증서는 istio-proxy 컨테이너에서
/var/run/secrets/istio/root-cert.pem
경로에 마운트 돼 있다.
# webapp.istio-proxy 쉘 접속
kubectl -n istioinaction exec -it deploy/webapp -c istio-proxy -- /bin/bash
-----------------------------------------------
# 인증서 검증
openssl verify -CAfile /var/run/secrets/istio/root-cert.pem \
<(openssl s_client -connect \
catalog.istioinaction.svc.cluster.local:80 -showcerts 2>/dev/null)
/dev/fd/63: OK
# 검증에 성공 시 OK 메시지 출력: 이스티오 CA가 인증서에 서명했으며, 내부 데이터가 믿을 수 있다는 것임을 알려줌.
exit
--------------
수많은 인증서를 어떻게 자동 관리할 수 있을까?
서비스 메시 내 모든 워크로드에 대해 인증서를 수동으로 관리하는 것은 불가능하다.
Istio는 이를 자동화하기 위해 다음과 같은 구조를 사용한다:
• Istiod가 각 워크로드에 대한 SVID(X.509 인증서, 개인키, 루트 체인) 자동 발급
• 해당 인증서는 SDS(Secret Discovery Service)를 통해 Envoy(istio-proxy)로 전달
• Envoy는 인증서를 메모리 내에서만 보관하며, 자동 갱신됨
즉, 파일로 인증서를 노출하지 않고도 신뢰 기반의 통신을 자동으로 유지할 수 있다.
istioctl로 SDS 상태와 인증서 정보 확인하기
openssl 명령어로 직접 확인하는 것 외에도, istioctl 명령어를 통해 각 워크로드가 제대로 mTLS 인증서(SVID)를 주고받고 있는지 확인할 수 있다.
istioctl ps
NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION
catalog-6cf4b97d-l87j9.istioinaction Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-8d74787f-j22k2
istio-egressgateway-85df6b84b7-q8jbc.istio-system Kubernetes SYNCED SYNCED SYNCED NOT SENT NOT SENT istiod-8d74787f-j22k2
istio-ingressgateway-6bb8fb6549-k7mmw.istio-system Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-8d74787f-j22k2
webapp-7685bcb84-85gtc.istioinaction Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-8d74787f-j22k2
# SDS 확인
istioctl proxy-config secret catalog-6cf4b97d-l87j9.istioinaction
RESOURCE NAME TYPE STATUS VALID CERT SERIAL NUMBER NOT AFTER NOT BEFORE
default Cert Chain ACTIVE true 97ae8325ba3df62266c757c49db03ec3 2025-05-09T08:48:56Z 2025-05-08T08:46:56Z
ROOTCA CA ACTIVE true a47999e2ed0b756ffb367540f61b0298 2035-05-02T10:44:53Z 2025-05-04T10:44:53Z
이처럼, SDS를 통해 관리되는 인증서에는 SPIFFE ID가 포함되어 있어 서비스 메시 내에서 신원 기반 보안 정책을 쉽게 적용할 수 있다.
2. mTLS로 서비스 간 통신 암호화하기(PeerAuthentication)
서비스 메시에서 기본적인 보안을 책임지는 첫 번째 단계는 서비스 간 통신을 암호화하고, 상대방의 신원을 확인하는 것이다. Istio는 이러한 요구사항을 만족시키기 위해 Mutual TLS(mTLS)를 기본적으로 제공한다. 이 챕터에서는 mTLS의 개념부터 Istio에서 이를 적용하는 실습까지 차근히 살펴본다.
mTLS란?
TLS 개요
TLS는 네트워크 상에서 통신을 암호화하기 위한 표준 프로토콜이다. 클라이언트와 서버가 통신할 때 데이터를 암호화함으로써 도청, 위조, 변조를 방지한다.
• TLS의 3단계 기본 절차는 다음과 같다:
1. 통신 가능한 암호 알고리즘을 협상
2. 인증서 교환 및 공개키 기반의 키 교환
3. 대칭키를 통해 암호화된 통신 및 메시지 무결성 검증
참고: 암호화방식, 인증서 개념, TLS 핸드셰이크
TLS vs mTLS
일반적인 TLS는 클라이언트가 서버를 인증하지만, mTLS(Mutual TLS)는 서버도 클라이언트를 인증한다. 즉, 양쪽이 서로의 신원을 증명하는 구조다.
Istio에서의 mTLS 적용
Istio는 기본적으로 프록시(Sidecar)를 통해 서비스 간 트래픽을 가로채고, 자동으로 암호화 및 인증 처리를 수행한다. 여기서 사용되는 인증서는 컨트롤 플레인(Istiod)에서 자동으로 발급되고 주기적으로 로테이션된다.
수동으로 인증서를 관리할 경우 만료 누락이나 오배포 등의 실수가 서비스 전체 장애로 이어질 있다. Istio는 이를 방지하기 위해 자동 인증서 발급 및 갱신 체계를 제공한다.
실습 환경 설정
mTLS 기능을 테스트하기 위해 총 세 가지 서비스를 준비한다:
catalog
,webapp
: Istio mesh에 속한 서비스들로, 기본적으로 mTLS가 적용됨sleep
: 사이드카 프록시가 주입되지 않은 레거시 워크로드로, Istio mesh 외부에 있는 것으로 간주됨
sleep → webapp 구간은 HTTP 평문 통신으로 동작하며, mTLS 인증이 수행되지 않는다. 이처럼 응답이 성공하면 webapp이 sleep의 평문 요청을 정상적으로 허용하고 있다는 의미다.
기본적으로 Istio는 평문 요청도 허용한다. 이는 전체 워크로드를 점진적으로 메시로 이전하는 동안 서비스 중단을 방지하기 위한 설계다. 하지만 PeerAuthentication 리소스를 통해 평문 트래픽을 명시적으로 차단할 수 있다.
리포지토리 클론 및 리소스 배포
git clone https://github.com/AcornPublishing/istio-in-action
cd istio-in-action/book-source-code-master
서비스 및 설정 리소스 배포
# catalog, webapp 서비스 배포
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
# gateway 및 virtual service 설정
kubectl apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml -n istioinaction
레거시 워크로드 (sleep) 배포
injection 라벨이 설정되지 않은 default 네임스페이스에 Sleep 앱을 배포하여 사이드카 없이 실행되도록 한다.
# 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
기본 통신 테스트
sleep에서 webapp으로 HTTP 요청을 보내고, 정상적인 통신이 되는지 확인한다.
kubectl exec deploy/sleep -c sleep -- curl -s webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"
# 반복 요청 시 사용
watch 'kubectl exec deploy/sleep -c sleep -- curl -s webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"'
Istio의 PeerAuthentication 리소스 이해하기
PeerAuthentication
은 Istio의 mTLS 인증 정책을 제어하는 리소스로, mesh-wide, namespace, workload 단위로 설정이 가능하다.
기본 구조 예시
PeerAuthentication의 기본 구조는 다음과 같다:
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
인증 모드 정리
PeerAuthentication은 다음 세 가지 인증 모드를 지원한다:
PERMISSIVE
: 기본값으로, 암호화된 mTLS 트래픽과 일반 평문 트래픽을 모두 허용한다.STRICT
: 암호화된 mTLS 트래픽만 허용한다. 모든 통신이 암호화되므로 보안성이 높아지지만, 메시에 속하지 않은 서비스와의 통신은 차단된다.DISABLE
: mTLS를 비활성화한다. 모든 트래픽이 평문으로 전송된다.
실습 : PeerAuthentication 케이스별 설정
[ CASE 1 : 메시 범위 정책으로 모든 미인증 트래픽 거부하기 ]
서비스 메시 전반에 걸쳐 보안을 강화하려면, mTLS를 강제 적용하는 메시 범위(mesh-wide) PeerAuthentication 정책을 설정해야 한다. 이를 통해 평문 트래픽을 일괄 차단할 수 있다.
Istio에서 메시 범위 정책을 만들기 위해서는 다음 두 가지 조건을 만족해야 한다:
- PeerAuthentication 리소스의 네임스페이스는 반드시 Istio가 설치된 네임스페이스여야 한다 (istio-system).
- 리소스 이름은 default여야 한다
참고: 이름을 default로 지정하는 것은 일종의 컨벤션이며, 메시 범위 정책은 단 하나만 존재해야 하기 때문에 이렇게 지정한다.
메시 범위 정책 YAML 예시
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default # 메시 범위 정책은 반드시 이 이름
namespace: istio-system
spec:
mtls:
mode: STRICT # 모든 통신에 대해 mTLS 강제
# 적용
kubectl apply -f ch9/meshwide-strict-peer-authn.yaml -n istio-system
적용 결과 확인
mTLS가 강제된 상태에서 사이드카가 없는 sleep 워크로드에서 webapp으로 요청을 보내면 실패하게 된다. 이는 mTLS 핸드셰이크가 성립되지 않기 때문이다.
로그 해석
실패 로그를 보면 다음과 같은 메시지를 확인할 수 있다:
- NR (Non-Route): Envoy 프록시가 라우팅까지 가지 못하고 요청을 차단했음을 의미
- filter_chain_not_found: Envoy Listener 설정 내에 해당 요청 조건(SNI, 포트, ALPN 등)에 대응하는 filter_chain이 존재하지 않음
[ CASE 2 : 상호 인증하기 않은 트래픽 허용하기 ]
서비스 메시 전체에 mTLS를 강제했더라도, 특정 네임스페이스에 한해 예외를 둘 수 있다. 이때 사용하는 것이 네임스페이스 범위 PeerAuthentication 정책이다. 이는 메시 범위 설정을 오버라이드(덮어쓰기) 하며, 해당 네임스페이스의 워크로드에 더 적합한 인증 요구사항을 정의할 수 있게 해 준다.
PERMISSIVE 모드 설정 적용
PERMISSIVE 모드는 mTLS 트래픽과 평문 트래픽을 모두 허용하는 유연한 설정으로, 점진적인 메시 도입 단계에서 자주 사용된다.
아래의 예시는 istioinaction 네임스페이스의 워크로드가 레거시 워크로드인 sleep 서비스로부터의 평문 트래픽을 허용하도록 설정한다.
cat << EOF | kubectl apply -f -
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default" # 네임스페이스 범위 정책은 기본적으로 default 사용
namespace: "istioinaction" # Specifies the namespace to apply the policy
spec:
mtls:
mode: PERMISSIVE # 평문 트래픽과 mTLS 트래픽을 모두 허용
EOF
설정 결과 확인
PERMISSIVE 정책 적용 이후, sleep에서 webapp으로 HTTP 요청을 보내면 응답이 성공적으로 돌아온다. 이는 webapp 서비스가 평문 트래픽을 허용하고 있다는 증거다.
kubectl exec deploy/sleep -c sleep -- curl -s http://webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"
# -> 200 응답
로그에서도 성공적인 요청이 수신되었음을 확인할 수 있다
kubectl logs -n istioinaction -l app=webapp -c istio-proxy --tail=1
# 예시 로그
[2025-05-08T11:24:47.131Z] "GET /api/catalog HTTP/1.1" 200 - via_upstream ...
[ CASE 3 : 워크로드별 PeerAuthentication 정책 적용하기 ]
이제는 네임스페이스 전체가 아닌, 특정 워크로드에만 PERMISSIVE 정책을 적용해 보자. 이를 위해 selector를 지정한 PeerAuthentication 리소스를 정의하면 된다.
이번 예시에서는 webapp 워크로드만을 대상으로 mTLS와 평문 트래픽을 모두 허용하도록 설정한다.
이렇게 하면 sleep → webapp → catalog
통신 흐름 중 webapp만 PERMISSIVE 모드로 동작하고, 나머지는 기존과 동일하게 STRICT 모드를 유지할 수 있다.
또한 리소스 이름도 default에서 webapp으로 변경했다. 이는 동작에 영향을 주지는 않지만, 네임스페이스 전체에 적용되는 정책만 default라는 이름을 쓰는 컨벤션을 따르기 위한 것이다.
PeerAuthentication 설정 적용(PERMISSIVE + Selector)
cat << EOF > kubectl apply -f -
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "webapp"
namespace: "istioinaction"
spec:
selector:
matchLabels:
app: "webapp" # 레이블이 일치하는 워크로드만 PERMISSIVE로 동작
mtls:
mode: PERMISSIVE
EOF
요청 테스트 및 결과 확인
1️⃣ sleep → webapp → catalog 통신 (성공)
webapp이 PERMISSIVE 정책을 적용받고 있어, sleep의 평문 요청 시, 성공 응답이 반환된다.
kubectl exec deploy/sleep -c sleep -- curl -s http://webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"
# → 200
로그에서도 성공 응답을 확인할 수 있다:
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
# → HTTP 200 응답 로그 출력
2️⃣ sleep → catalog 직접 요청 (실패)
catalog은 여전히 STRICT 모드로 설정되어 있기 때문에, sleep에서 직접 접근 시 mTLS 인증이 실패하고 오류가 발생한다.
kubectl exec deploy/sleep -c sleep -- curl -s http://catalog.istioinaction/api/items -o /dev/null -w "%{http_code}\n"
# → 404 (실제 연결 실패 또는 인증 실패 처리)
로그를 보면 요청이 처리되지 않았음을 알 수 있다:
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
# → HTTP 404 또는 mTLS 인증 실패 로그
이처럼 selector 기반 정책을 활용하면, 특정 워크로드만 예외적으로 트래픽을 허용하면서도 전체 메시 보안을 유지할 수 있다.
3. 서비스 간 트래픽 인가하기(AuthorizationPolicy)
서비스 메시 환경에서 인증만으로는 충분하지 않다. 인가(Authorization)는 “누가 어떤 리소스에 어떤 작업을 수행할 수 있는가? “를 판단하는 절차로, Istio에서는 이를 위해 AuthorizationPolicy 리소스를 제공한다. 이 섹션에서는 Istio에서 인가 정책을 정의하고 적용하는 방법을 실습과 함께 설명한다.
들어가며(인가란 무엇인가?)
인가(Authorization)는 이미 인증된 사용자 또는 서비스가 특정 리소스나 작업에 접근할 수 있는지를 결정하는 과정이다.
- “이 사용자가 이 작업을 수행할 권한이 있는가? “를 확인
- 인증은 누구인지 확인, 인가는 무엇을 할 수 있는지 판단
인가 정책의 구성 요소
- 주체(Who): 인증된 사용자 또는 서비스
- 작업(What): 허용된 동작 (예: 읽기, 쓰기, 삭제)
- 정책(Policy): 누가 무엇을 할 수 있는지에 대한 규칙 정의
AuthorizationPolicy 리소스 이해하기
Istio는 AuthorizationPolicy
라는 리소스를 통해 인가 정책을 정의한다. 이를 사용하면 특정 서비스 또는 네임스페이스 단위로 허용할 트래픽을 세밀하게 제어할 수 있다.
# ch9/allow-catalog-requests-in-web-app.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-catalog-requests-in-web-app
namespace: istioinaction
spec:
selector:
matchLabels:
app: webapp
rules:
- to:
- operation:
paths: ["/api/catalog*"]
action: ALLOW
이 예시는 webapp 워크로드에 대해 /api/catalog* 경로로의 요청만 허용하는 인가 정책이다.
AuthorizationPolicy 구성 요소
- selector: 인가 정책을 적용할 워크로드를 지정 (예: app: webapp)
- action: 정책의 동작을 지정 (ALLOW, DENY, CUSTOM)
- rules: 요청을 허용하거나 거부할 조건들을 정의하는 규칙 목록
인가 규칙 구조 이해하기
인가 규칙(rules)은 요청의 출처(source)와 작업(operation) 조건을 기반으로 정책을 집행한다.
주요 필드 설명:
- from:
- 요청의 출처를 정의
- 사용 가능한 타입:
- principals: mTLS 인증서의 SPIFFE ID 기반
- namespaces: 요청 주체의 네임스페이스 (SVID 기반 추출)
- ipBlocks: 요청자의 IP 또는 CIDR 범위
- to:
- 허용할 작업 정의 (예: 경로, 메서드, 포트 등)
- when:
- 규칙이 일치한 후 추가적으로 만족해야 하는 조건 지정
출처를 정확히 지정하려면 mTLS가 활성화된 상태여야 한다. 인증된 주체에 한해 세분화된 인가 제어가 가능하기 때문이다.
실습: AuthorizationPolicy 케이스별 설정
[ CASE 1: 워크로드에 정책 적용 시 동작 확인 ]
AuthorizationPolicy를 적용 전, 주의할 점은 특정 워크로드에 하나 이상의 ALLOW 정책이 적용되면, 기본적으로 모든 트래픽이 차단된다. 허용하려는 요청은 반드시 ALLOW 정책의 규칙과 일치해야만 통과된다.
적용 전 상태 확인
초기에는 AuthorizationPolicy가 적용되지 않은 상태이므로, webapp으로 향하는 모든 요청이 기본적으로 수락된다.
# 정상 응답 (200)
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
# 존재하지 않는 경로 (404)
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world
AuthroriziationPolicy 적용(특정 경로만 허용)
다음 AuthorizationPolicy는 webapp 워크로드에 대해 /api/catalog*
경로만 허용하도록 설정한다.
# cat ch9/allow-catalog-requests-in-web-app.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "allow-catalog-requests-in-web-app"
namespace: istioinaction
spec:
selector:
matchLabels:
app: webapp # 워크로드용 셀렉터 Selector for workloads
rules:
- to:
- operation:
paths: ["/api/catalog*"] # 요청을 경로 /api/catalog 와 비교한다 Matches requests with the path /api/catalog
action: ALLOW # 일치하면 허용한다 If a match, ALLOW
kubectl apply -f ch9/allow-catalog-requests-in-web-app.yaml
적용 후 동작 확인
이제 인가 정책이 활성화되어, /api/catalog 요청만 허용되고 그 외 모든 요청은 거부된다.(에러코드 변경: 404 -> 403)
# 허용된 경로 요청 (200)
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
# 차단된 경로 요청 (403)
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world
# → RBAC: access denied
로그 예시:
GET /api/catalog HTTP/1.1" 200 ...
GET /hello/world HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] ...
catch-all DENY 정책의 중요성
인가 정책이 여러 개 적용되면, 어떤 요청이 왜 허용됐는지 직관적으로 판단하기 어려울 수 있다.
이를 방지하기 위해 다음과 같은 명시적 거부(catch-all DENY) 정책을 추가하는 것이 좋다.
- 별도의 ALLOW 정책이 부합하지 않는 모든 요청은 자동으로 거부
- 결과적으로, “허용하고 싶은 요청만 허용”하는 방향으로 정책 구성이 단순해짐(화이트리스트 방식)
[ CASE 2: 전체 정책으로 기본적으로 모든 요청 거부하기 ]
서비스 메시 전체의 보안성을 강화하고 인가 정책의 적용 대상을 명확히 하기 위해, 기본적으로 모든 요청을 거부하는 catch-all DENY 정책을 메시 범위에 적용할 수 있다. 이렇게 하면 명시적으로 허용하지 않은 트래픽은 모두 차단되므로, 보안 사고를 예방하고 정책 설계도 단순화된다.
AuthorizationPolicy 설정(메시 범위 DENY 정책)
AuthorizationPolicy는 Istio를 설치한 네임스페이스(istio-system)에 정의되며, 메시 전체의 모든 워크로드에 영향을 준다.
# cat ch9/policy-deny-all-mesh.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: istio-system # 이스티오를 설치한 네임스페이스의 정책은 메시의 모든 워크로드에 적용된다
spec: {} # spec 이 비어있는 정책은 모든 요청을 거부한다
적용 전 요청 테스트
기존에는 별도의 정책이 없기 때문에 모든 요청이 허용된다:
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
# → 200 OK
"GET /api/catalog HTTP/1.1" 200 ...
정책 적용 및 확인
# 정책 적용
kubectl apply -f ch9/policy-deny-all-mesh.yaml
# 정책 확인
kubectl get authorizationpolicy -A
NAMESPACE NAME AGE
istio-system deny-all 6s
istioinaction allow-catalog-requests-in-web-app 13m
이제 메시 수준에서 모든 요청이 차단되기 때문에, 기존에 허용되던 요청도 정책에 명시되지 않으면 실패하게 된다:
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
# → error calling Catalog service
curl -s http://webapp.istioinaction.io:30000/api/catalog
# → RBAC: access denied
참고: 모든 요청 허용하는 정책 만들기 (반대 정책)
반대로, 메시 전체에 모든 요청을 허용하고 싶다면, 다음과 같이 설정하면 된다.
# ch9/policy-allow-all-mesh.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-all
namespace: istio-system
spec:
rules:
- {} # 빈 rule = 모든 요청 허용
이제 기본은 “모든 요청 거부” 상태이므로, 허용하고 싶은 트래픽만 개별적으로 ALLOW 정책으로 추가하면 된다.
정책 적용 범위와 구성을 명확히 하면, 관리도 쉬워지고 보안도 강화된다.
[ CASE 3: 특정 네임스페이스에서 온 요청 허용하기 ]
운영 환경에서는 특정 네임스페이스에서 시작된 요청만 허용하고 싶은 경우가 종종 있다.
예를 들어, default 네임스페이스의 서비스가 istioinaction 네임스페이스의 워크로드에 접근할 수 있도록 설정하고 싶을 수 있다.
이런 요구사항은 AuthorizationPolicy에서 source.namespaces
조건을 사용하면 구현할 수 있다.
네임스페이스 기반 인가 정책 설정
다음은 default 네임스페이스에서 발생한 HTTP GET 요청만 허용하는 정책이다.
cat << EOF | kubectl apply -f -
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "webapp-allow-view-default-ns"
namespace: istioinaction # istioinaction의 워크로드
spec:
rules:
- from: # default 네임스페이스에서 시작한
- source:
namespaces: ["default"]
to: # HTTP GET 요청에만 적용
- operation:
methods: ["GET"]
EOF
예상 문제: sleep 서비스는 레거시 워크로드
• 현재 sleep 서비스는 사이드카 프록시가 없는 레거시 워크로드이다.
• 따라서 mTLS 인증서가 없으며, webapp 프록시는 이 요청의 출처가 실제로 default 네임스페이스인지 확인할 수 없다.
• 그 결과, 아래와 같이 요청이 403 (RBAC: access denied)로 차단된다.
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
# → RBAC: access denied
# 로그 예시:
403 - rbac_access_denied_matched_policy[none]
default 네임스페이스의 사이드카 인젝션 설정을 주입하여, sleep 서비스의 요청을 허용하도록 한다.
# 인젝션 섲렁
kubectl label ns default istio-injection-
kubectl rollout restart deploy/sleep
# 사이드카 주입 확인
docker exec -it myk8s-control-plane istioctl proxy-status | grep sleep
sleep-6f8cfb8c8f-jql6b.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-8d74787f-j22k2 1.17.8
# 정상적으로 주입되면 sleep Pod가 2/2 상태가 된다
k get pod | grep sleep
sleep-6f8cfb8c8f-jql6b 2/2 Running 0 60s
요청 흐름 결과 확인
사이드카가 주입된 후 다음 요청 흐름을 테스트한다:
✅ sleep → webapp (GET 요청): 허용됨
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
# → 200 OK
❌ webapp → catalog: 여전히 차단됨
이는 메시 전체에 적용된 deny-all 정책이 존재하기 때문이다.
kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items
# → 403 Forbidden
# 로그 예시
"GET /items HTTP/1.1" 403 - ...
source.namespaces는 사이드카 프록시가 존재하는 워크로드에서만 정상적으로 작동한다. 왜냐하면 레거시 워크로드는 출처 확인이 불가능하므로, 신뢰 기반 인가 정책을 적용할 수 없다. 따라서, 전체 메시 보안을 유지하려면 최대한 모든 서비스에 사이드카를 주입하는 것이 권장된다
[ CASE 4: 미인증 레거시 워크로드에서 온 요청 허용하기 ]
서비스 메시 환경에서 간혹 사이드카가 없는 레거시 워크로드의 요청을 허용해야 할 상황이 있다. 하지만 이런 워크로드는 인증 정보(SPIFFE ID)를 가지지 않기 때문에 일반적인 from.source 기반 인가 정책이 작동하지 않는다.
이 경우에는 AuthorizationPolicy에서 from 필드를 제거해 요청의 출처 검증 없이 허용하는 방식을 사용할 수 있다.
Authorization 정책 설정(인증되지 않은 요청 허용)
아래 예시는 webapp 워크로드에만 적용되며, GET 요청을 인증 여부와 관계없이 허용한다.
# cat ch9/allow-unauthenticated-view-default-ns.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "webapp-allow-unauthenticated-view-default-ns"
namespace: istioinaction
spec:
selector:
matchLabels:
app: webapp
rules:
# - from: # default 네임스페이스에서 시작한
# - source:
# namespaces: ["default"]
- to:
- operation:
methods: ["GET"]
kubectl apply -f ch9/allow-unauthenticated-view-default-ns.yaml
from 필드를 생략하면 모든 출처로부터의 요청을 허용하게 된다. 또한, selector로 webapp에만 적용되므로, catalog는 여전히 mTLS 인증이 요구된다
정책 우선순위 확인
AuthorizationPolicy는 명시적인 우선순위 필드가 없기 때문에, 동일 워크로드에 여러 정책이 적용되면 Istio가 이를 병합하여 처리한다.
kubectl get authorizationpolicy -A
NAMESPACE NAME AGE
istio-system deny-all 21m
istioinaction webapp-allow-unauthenticated-view-default-ns 3m32s
istioinaction webapp-allow-view-default-ns 20
정확한 적용 상태는 Envoy 프록시 설정에서 확인할 수 있다:
istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json | jq
✅ sleep → webapp (GET 요청): 허용됨
# default → webapp 요청 (허용됨)
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
# → 200 OK
# 로그에서도 정상 응답 확인 가능
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
❌ webapp → catalog: (GET 요청): 여전히 차단됨
요청은 webapp까지만 도달하고, webapp → catalog로 이어지는 후속 요청은 메시 범위의 deny-all 정책에 의해 차단된다:
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
# → error calling Catalog service
catalog 로그에서 403 Forbidden 혹은 500 오류 응답 확인 가능:
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
이렇게 하면 인증이 불가능한 레거시 워크로드에서 최소한의 트래픽만 허용할 수 있다. 하지만 권장 방식은 항상 서비스에 사이드카를 주입하여 출처 인증이 가능한 구조를 유지하는 것이다.
[ CASE 5: 특정 서비스 어카운트에서 온 요청 허용하기 ]
Istio에서는 트래픽의 출처를 인증하는 방법 중 하나로 서비스 어카운트(SA)를 활용할 수 있다.
서비스 어카운트 정보는 SPIFFE ID (SVID)에 인코딩 되며, 상호 인증 과정 중 해당 정보를 확인하고 필터 메타데이터에 저장된다.
이를 기반으로 AuthorizationPolicy의source.principals
필드를 사용하면, 특정 서비스 어카운트로부터 온 요청만 허용하는 세밀한 인가 정책을 설정할 수 있다.
AuthorizationPolicy 설정
아래 정책은 catalog 서비스에 적용되며, 서비스 어카운트가 webapp인 워크로드에서 온 GET 요청만 허용한다.
- principals에는 인증된 서비스의 SVID 경로를 지정
- 형식:
cluster.local/ns/<namespace>/sa/<serviceaccount>
# cat ch9/catalog-viewer-policy.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "catalog-viewer"
namespace: istioinaction
spec:
selector:
matchLabels:
app: catalog
rules:
- from:
- source:
principals: ["cluster.local/ns/istioinaction/sa/webapp"] # Allows requests with the identity of webapp
to:
- operation:
methods: ["GET"]
실습 흐름 요약
- sleep → webapp 요청
- 인증되지 않은 레거시 워크로드지만, webapp은 이를 허용하도록 구성됨
- webapp → catalog 요청
- webapp의 서비스 어카운트가 명시적으로 허용된 상태라 요청 통과
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
# → 200 OK
로그 확인
webapp, catalog 양쪽에서 정상적으로 요청이 통과된 로그를 확인할 수 있다:
# webapp → catalog
"GET /items HTTP/1.1" 200 ...
# sleep → webapp
"GET /api/catalog HTTP/1.1" 200 ...
서비스 어카운트 기반 인가는 워크로드 간 권한을 명확하게 분리할 수 있어, 특정 워크로드만 민감한 API나 서비스에 접근 가능하게 제어할 수 있다.
또한, 서비스 어카운트(SA)가 유출되거나 도난당하더라도, 피해를 해당 ID가 가진 최소 권한 범위로 제한 가능하다.
인가 정책이 평가되는 순서 이해하기
평가 순서
1. CUSTOM 정책
- 외부 인가 서버(ExtAuthz 등)와 연동된 정책이 가장 먼저 평가됨
- 요청이 거부되면 즉시 중단됨
2. DENY 정책
- CUSTOM 정책에서 허용된 요청 중 DENY 정책에 하나라도 일치하면 요청은 즉시 거부됨
- DENY 정책이 없다면 다음 단계로 넘어감
3. ALLOW 정책
- 위에서 거부되지 않은 요청이 ALLOW 정책을 평가
- ALLOW 정책 중 하나라도 일치하면 요청 허용
- ALLOW 정책이 존재하지만 일치하는 항목이 없다면 요청은 거부됨
4. Catch-all 정책 존재 여부 확인
- catch-all 정책은 일종의 **기본 허용 또는 거부 정책
- 이 정책이 존재할 경우, 그 정책이 최종 판단을 내림
- 예: 모든 요청을 명시적으로 거부하는 deny-all 정책
- 없다면 다음 기준 적용:
- ALLOW 정책이 아예 없으면 요청 허용
- ALLOW 정책이 있고 어떤 항목도 일치하지 않으면 요청 거부
적용 시 유의사항
- ALLOW 정책이 하나라도 적용되면, 기본적으로 모든 요청은 거부된 상태로 시작됨
- 따라서 “정책이 없으면 허용”되는 기본 보안 모델이 아닌, 정책이 있으면 명시적으로 허용해야 하는 모델이 됨
- 의도하지 않은 접근 차단 또는 허용을 막기 위해, 정확하고 구체적인 정책 작성이 중요
4. JWT 기반 사용자 인증 및 인가 구현하기(RequestAuthentication) -
서비스 간 인증뿐만 아니라, 최종 사용자(User)가 요청한 API에 대해서도 인증 및 인가 처리를 해야 할 때가 많다. Istio에서는 이를 위해 RequestAuthentication
리소스를 통해 JWT(Json Web Token)를 검증하고, AuthorizationPolicy를 조합해 정교한 인가 정책을 구현할 수 있다.
들어가며
사전 지식
- Service Account Token Volume Projection
- Admission Control
- JWT(JSON Web Token)
- OIDC
인그레스 게이트웨이에서 최종 사용자 인증 및 인가
Istio는 최종 사용자 인증을 워크로드 수준에서 수행할 수 있지만, 일반적으로는 인그레스 게이트웨이 수준에서 처리하는 것이 효율적이다.
그 이유는 다음과 같다:
- 잘못된 요청을 서비스로 전달하기 전에 조기에 차단해 불필요한 리소스 낭비 방지
- 요청에 포함된 JWT 토큰을 중간에 제거하여 토큰 유출 및 재전송 공격(replay attack)을 방지
- 이스티오 워크로드가 JWT로 최종 사용자 요청을 인증하고 인가하도록 설정할 수 있다.
실습 환경 초기화 및 재배포
기존 리소스를 정리하고 실습 환경을 새로 구성한다
# 기본 서비스 배포
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
# JWT 인증 테스트용 게이트웨이 설정 확인 및 적용
cat ch9/enduser/ingress-gw-for-webapp.yaml
kubectl apply -f ch9/enduser/ingress-gw-for-webapp.yaml -n istioinaction
# 기본 서비스 배포
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
# JWT 인증 테스트용 게이트웨이 설정 확인 및 적용
cat ch9/enduser/ingress-gw-for-webapp.yaml
kubectl apply -f ch9/enduser/ingress-gw-for-webapp.yaml -n istioinaction
RequestAuthentication 리소스 이해하기
RequestAuthentication은 Istio에서 최종 사용자 요청에 포함된 JWT(Json Web Token)를 검증하는 리소스다. 이 리소스는 토큰의 유효성을 확인하고, JWT 클레임을 추출하여 Envoy 필터 메타데이터에 저장한다. 이 메타데이터는 이후 AuthorizationPolicy에서 조건 판단의 근거로 활용된다.
RequestAuthentication은 인가를 직접 수행하지 않는다.
역할은 다음과 같다:
- JWT의 서명과 유효성 검사
- 클레임(예: email, group) 추출
- Envoy 메타데이터에 클레임을 저장 → 이후 인가 판단에 활용됨
예: group=admin 클레임을 가진 요청은 해당 값이 메타데이터에 저장되고, AuthorizationPolicy에서 이 값에 따라 ALLOW/DENY 결정 가능
다음은 Istio 인그레스 게이트웨이에 JWT 검증을 적용하는 예시다. 발급자는 auth@istioinaction.io
이며, 공개키는 인라인 JWKS로 제공된다
cat ch9/enduser/jwt-token-request-authn.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
name: "jwt-token-request-authn"
namespace: istio-system # 적용할 네임스페이스
spec:
selector:
matchLabels:
app: istio-ingressgateway
jwtRules:
- issuer: "auth@istioinaction.io" # 발급자 Expected issuer
jwks: | # 특정 JWKS로 검증
{ "keys":[ {"e":"AQAB","kid":"CU-ADJJEbH9bXl0tpsQWYuo4EwlkxFUHbeJ4ckkakCM","kty":"RSA","n":"zl9VRDbmVvyXNdyoGJ5uhuTSRA2653KHEi3XqITfJISvedYHVNGoZZxUCoiSEumxqrPY_Du7IMKzmT4bAuPnEalbY8rafuJNXnxVmqjTrQovPIerkGW5h59iUXIz6vCznO7F61RvJsUEyw5X291-3Z3r-9RcQD9sYy7-8fTNmcXcdG_nNgYCnduZUJ3vFVhmQCwHFG1idwni8PJo9NH6aTZ3mN730S6Y1g_lJfObju7lwYWT8j2Sjrwt6EES55oGimkZHzktKjDYjRx1rN4dJ5PR5zhlQ4kORWg1PtllWy1s5TSpOUv84OPjEohEoOWH0-g238zIOYA83gozgbJfmQ"}]}
#
kubectl apply -f ch9/enduser/jwt-token-request-authn.yaml
kubectl get requestauthentication -A
NAMESPACE NAME AGE
istio-system jwt-token-request-authn 8s
실습 : RequestAuthentication으로 JWT 검증하기
[ CASE 1: 유효한 발행자의 JWT 토큰으로 통신하는 경우 - 허용 ]
JWT의 발급자(issuer)가 RequestAuthentication 리소스에 등록된 값과 일치하면, 해당 토큰은 검증을 통과하고 요청은 클러스터로 전달된다.
유효한지 통신 확인
curl -H "Authorization: Bearer $USER_TOKEN" \
-sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
200%
# Ingress Gateway 로그:
kubectl logs -n istio-system -l app=istio-ingressgateway -f
[2025-05-10T08:14:38.413Z] "GET /api/catalog HTTP/1.1" 200 - via_upstream - "-" 0 357 24 11 "192.168.107.1" "curl/8.7.1" "9f158a13-ef8c-9b8e-8cf9-4b29eb98f2d5" "webapp.istioinaction.io:30000" "10.10.0.39:8080" outbound|80||webapp.istioinaction.svc.cluster.local 10.10.0.6:40598 10.10.0.6:8080 192.168.107.1:23287 - -
[ CASE 2: 유효하지 않은 발행자의 JWT 토큰으로 통신하는 경우 - 거부 ]
등록되지 않은 issuer에서 발급된 JWT는 즉시 거부된다. 이는 토큰의 서명 여부와 무관하게, 발급자가 신뢰되지 않기 때문이다.
유효한지 통신 확인
인증 자체가 실패했기 때문에, 이 요청은 메타데이터도 생성되지 않고 거부된다.
curl -H "Authorization: Bearer $WRONG_ISSUER" \
-sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
# Ingress Gateway 로그
[2025-05-10T08:20:29.991Z] "GET /api/catalog HTTP/1.1" 401 - jwt_authn_access_denied{Jwt_issuer_is_not_configured} - "-" 0 28 0 - "192.168.107.1" "curl/8.7.1" "9b6ecc88-e180-959a-b6d2-8fd9edf0de0b" "webapp.istioinaction.io:30000" "-" outbound|80||webapp.istioinaction.svc.cluster.local - 10.10.0.6:8080 192.168.107.1:52273 - -
[ CASE 3 : JWT 토큰 없이 통신하는 경우 - 허용]
토큰이 아예 없는 경우에는 기본적으로 요청이 허용된다.
RequestAuthentication은 토큰이 존재할 경우에만 검증을 수행하고, 없을 경우는 무시하고 통과시킨다.
# 호출
curl -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
200%
# Ingress Gateway 로그:
200 - via_upstream
큰 없는 요청은 혼란을 줄 수 있다. 보통 인증이 필요한 요청은 무조건 거부되기를 기대하기 때문이다. 하지만 Istio는 기본적으로 토큰이 없으면 ‘인증 정보 없음’ 상태로 간주하고 요청 자체를 차단하지 않는다.
왜 토큰 없는 요청을 허용할까? 많은 웹 애플리케이션은 공개 API, 로그인 전 요청, 정적 리소스 요청 등 인증이 불필요한 경로도 존재한다.
따라서 Istio는 인증을 강제하지 않고, 필요한 경우 AuthorizationPolicy를 통해 정책적으로 거부할 수 있게 설계
[ CASE 4 : JWT 토큰 없이 통신하는 경우 - 거부]
RequestAuthentication 리소스는 JWT가 없는 요청을 기본적으로 허용하기 때문에, 이를 명시적으로 거부하고 싶다면 AuthorizationPolicy를 별도로 구성해야 한다.
이러한 정책은 requestPrincipals가 존재하지 않는 요청(즉, JWT가 없는 요청)을 대상으로 작동하며, DENY 액션을 통해 요청을 차단한다.
AuthorizationPolicy 설정(JWT 없는 요청 거부 정책)
다음 정책은 istio-ingressgateway 워크로드에 적용되며, requestPrincipals가 비어 있는 요청을 포트 30000에서 차단한다.
# cat ch9/enduser/app-gw-requires-jwt.yaml # vi/vim, vscode 에서 포트 30000 추가
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: app-gw-requires-jwt
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: DENY
rules:
- from:
- source:
notRequestPrincipals: ["*"] # 요청 주체에 값이 없는 source는 모두 해당된다
to:
- operation:
hosts: ["webapp.istioinaction.io:30000"] # 이 규칙은 이 특정 호스트에만 적용된다
ports: ["30000"]
#
kubectl apply -f ch9/enduser/app-gw-requires-jwt.yaml
#
kubectl get AuthorizationPolicy -A
NAMESPACE NAME AGE
istio-system app-gw-requires-jwt 15s
테스트 결과
✅ JWT 포함 요청 (정상 통과)
curl -H "Authorization: Bearer $USER_TOKEN" \
-sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
# → 200
❌ JWT 없는 요청 (거부됨)
curl -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
# → 403
# Ingress Gateway 로그
403 - rbac_access_denied_matched_policy[app-gw-requires-jwt]
이로써, 유효한 토큰만 허용, 잘못된 토큰은 차단, 토큰이 없으면 거부하는 JWT 기반 인증 체계가 완성되었다.
[ CASE 5 : JWT 클레임을 활용하여 다양한 접근제어 ]
Istio에서는 JWT에 포함된 클레임 값을 활용해 요청 주체의 역할을 식별하고, 이에 따라 세분화된 접근 제어**를 구현할 수 있다.
이번 실습에서는 다음과 같은 시나리오를 다룬다:
- 일반 사용자(user): 데이터를 읽는(GET) 요청은 허용, 수정(POST) 요청은 거부
- 관리자(admin): 모든 요청(GET, POST 등) 허용\
각 사용자에게 할당된 토큰에는 다음과 같은 group 클레임이 포함되어 있다:
# jwt-cli 사용
# 일반 사용자 토큰 : 'group: user' 클레임
jwt decode $(cat ch9/enduser/user.jwt)
...
{
"exp": 4745145038,
"group": "user",
"iat": 1591545038,
"iss": "auth@istioinaction.io",
"sub": "9b792b56-7dfa-4e4b-a83f-e20679115d79"
}
# 관리자 토큰 : 'group: admin' 클레임
jwt decode $(cat ch9/enduser/admin.jwt)
...
{
"exp": 4745145071,
"group": "admin",
"iat": 1591545071,
"iss": "auth@istioinaction.io",
"sub": "218d3fb9-4628-4d20-943c-124281c80e7b"
}
AuthorizationPolicy 리소스 설정(일반 사용자 허용 정책: GET만 허용)
# cat ch9/enduser/allow-all-with-jwt-to-webapp.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-all-with-jwt-to-webapp
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["auth@istioinaction.io/*"] # 최종 사용자 요청 주체를 표현 Represents the end-user request principal
to:
- operation:
hosts: ["webapp.istioinaction.io:30000"]
methods: ["GET"]
AuthorizationPolicy 리소스 설정(관리자 허용 정책: 모든 메서드 허용)
# cat ch9/enduser/allow-mesh-all-ops-admin.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "allow-mesh-all-ops-admin"
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["auth@istioinaction.io/*"]
when:
- key: request.auth.claims[group]
values: ["admin"] # 이 클레임을 포함한 요청만 허용.
정책 적용 및 확인
kubectl apply -f ch9/enduser/allow-all-with-jwt-to-webapp.yaml
kubectl apply -f ch9/enduser/allow-mesh-all-ops-admin.yaml
#
kubectl get authorizationpolicy -A
NAMESPACE NAME AGE
istio-system allow-all-with-jwt-to-webapp 8s
istio-system allow-mesh-all-ops-admin 8s
istio-system app-gw-requires-jwt 10m
실습 결과: 일반 사용자
USER_TOKEN=$(< ch9/enduser/user.jwt)
# GET 요청 → 허용
curl -H "Authorization: Bearer $USER_TOKEN" \
-sSl -o /dev/null -w "%{http_code}\n" webapp.istioinaction.io:30000/api/catalog
# → 200
# POST 요청 → 거부
curl -H "Authorization: Bearer $USER_TOKEN" \
-XPOST webapp.istioinaction.io:30000/api/catalog \
--data '{"id": 2, "name": "Shoes", "price": "84.00"}'
# → RBAC: access denied (403)
# RBAC 로그 확인:
403 - rbac_access_denied_matched_policy[none]
실습 결과:관리자
관리자는 group=admin 클레임을 포함하고 있기 때문에 모든 메서드 요청이 허용됨을 확인할 수 있다.
ADMIN_TOKEN=$(< ch9/enduser/admin.jwt)
# GET 요청 → 허용
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
-sSl -o /dev/null -w "%{http_code}\n" webapp.istioinaction.io:30000/api/catalog
# → 200
# POST 요청 → 허용
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
-XPOST webapp.istioinaction.io:30000/api/catalog \
--data '{"id": 2, "name": "Shoes", "price": "84.00"}'
# → 응답 내용으로 POST 반영됨
Istio는 서비스 계층뿐 아니라, 최종 사용자 수준의 접근 제어도 가능하게 해 준다. 이를 통해 인증된 사용자의 역할에 따른 차별화된 보안 정책을 실현할 수 있다.
5. 커스텀 외부 인가 서비스 연동하기(authZ)
Istio는 기본적으로 Envoy 프록시 내의 RBAC 필터를 사용해 인가 정책을 처리하지만, 더 복잡한 조건이나 비즈니스 로직이 필요한 경우 외부 인가 서비스(External Authorization)를 연동할 수 있다. 이를 통해 Istio는 인가 결정을 외부 시스템에 위임하고, 유연한 정책 집행이 가능해진다
외부 인가 서비스 배포
샘플 외부 인가 서비스 설명
- ext-authz 서비스는 헤더 기반 조건만 검사하는 간단한 구조다.
- 요청에
x-ext-authz: allow
헤더가 포함되어 있으면 요청을 허용, 그렇지 않으면 거부한다.
실습환경 초기화
# 기존 인증/인가 정책 모두 삭제
kubectl delete authorizationpolicy,peerauthentication,requestauthentication --all -n istio-system
# 실습 애플리케이션 배포
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/sleep.yaml -n default
외부 인가 서비스 배포
cat istio-$ISTIOV/samples/extauthz/ext-authz.yaml
apiVersion: v1
kind: Service
metadata:
name: ext-authz
labels:
app: ext-authz
spec:
ports:
- name: http
port: 8000
targetPort: 8000
- name: grpc
port: 9000
targetPort: 9000
selector:
app: ext-authz
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ext-authz
spec:
replicas: 1
selector:
matchLabels:
app: ext-authz
template:
metadata:
labels:
app: ext-authz
spec:
containers:
- image: gcr.io/istio-testing/ext-authz:latest
imagePullPolicy: IfNotPresent
name: ext-authz
ports:
- containerPort: 8000
- containerPort: 9000
kubectl apply -f istio-$ISTIOV/samples/extauthz/ext-authz.yaml -n istioinaction
설치 확인
# 설치 확인 : ext-authz
kubectl get deploy,svc ext-authz -n istioinaction
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/ext-authz 1/1 1 1 30s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ext-authz ClusterIP 10.200.1.172 <none> 8000/TCP,9000/TCP 30s
# 로그
kubectl logs -n istioinaction -l app=ext-authz -c ext-authz -f
- 배포한
ext-authz
서비스는 아주 간단해서 들어온 요청에x-ext-authz
헤더가 있고 그 값이allow
인지만 검사한다. - 이 헤더가 요청에 들어 있으면 요청은 허용되고, 들어 있지 않으면 요청은 거부된다.
- 요청의 다른 속성을 평가하도록 외부 인가 서비스를 직접 작성하거나, 상술한 기존 서비스 중 하나를 사용할 수 있다.
이스티오에 외부 인가 설정하기
외부 인가 서비스를 실제로 사용하려면, Istio가 해당 서비스를 인가 제공자(Authorization Provider)로 인식하도록 설정해야 한다. 이를 위해 MeshConfig의 extensionProviders 필드에 외부 인가 관련 설정을 추가해야 한다.
Istio ConfigMap 수정
Istio의 설정은 istio-system 네임스페이스에 위치한 ConfigMap(istio)을 통해 관리된다.
여기에 외부 인가 서비스 설정을 추가한다:
• envoyExtAuthzHttp
: HTTP 기반 외부 인가 서비스
• includeRequestHeadersInCheck
: 인가 요청 시 프록시가 함께 전달할 HTTP 헤더 목록
kubectl edit -n istio-system cm istio
extensionProviders:
- name: "sample-ext-authz-http"
envoyExtAuthzHttp:
service: "ext-authz.istioinaction.svc.cluster.local"
port: "8000"
includeRequestHeadersInCheck: ["x-ext-authz"]
동작 방식
• Envoy 프록시는 클라이언트 요청을 수신하면 먼저 외부 인가 서비스에 해당 요청을 위임한다.
• 이때 x-ext-authz 헤더가 포함되어 전달되고, 외부 인가 서비스는 이 값이 "allow"일 경우 요청을 허용한다.
• 이외의 경우, 응답으로 403 Forbidden을 반환하여 요청을 차단한다
커스텀 AuthorizationPolicy 리소스 사용하기
앞서 AuthorizationPolicy의 action을 ALLOW, DENY로 설정해 인가 정책을 구성했다.
이번에는 CUSTOM 액션을 사용해, 인가 로직을 외부 인가 서비스로 위임하는 방식을 적용해 본다.
CUSTOM 액션을 통한 외부 인가 연동
다음 정책은 istioinaction 네임스페이스의 webapp 워크로드에 적용되며,
앞에서 설정한 extensionProvider 중 sample-ext-authz-http를 참조해 해당 외부 인가 서비스에 인가 결정을 위임한다.
# 아래 AuthorizationPolicy 는 istioinaction 네임스페이스에 webapp 워크로드에 적용되며,
# sample-ext-authz-http 이라는 외부 인가 서비스에 위임한다.
cat << EOF | kubectl apply -f -
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: ext-authz
namespace: istioinaction
spec:
selector:
matchLabels:
app: webapp
action: CUSTOM
provider:
name: sample-ext-authz-http # meshConfig에 등록한 이름과 일치해야 함
rules:
- to:
- operation:
paths: ["/*"] # 모든 경로에 적용
EOF
#
kubectl get AuthorizationPolicy -A
NAMESPACE NAME AGE
istioinaction ext-authz 11s
인가 테스트: 헤더가 없는 요청
헤더가 없기 때문에 외부 인가 서비스가 요청을 거부
kubectl -n default exec -it deploy/sleep -- curl webapp.istioinaction/api/catalog
# → denied by ext_authz: not found header `x-ext-authz: allow`
# 외부 인가 서비스 로그 확인
[HTTP][denied]: GET webapp.istioinaction/api/catalog
인가 테스트: 허용 헤더 추가
요청에 x-ext-authz: allow 헤더가 포함되어 있어 인가 허용됨
kubectl -n default exec -it deploy/sleep -- \
curl -H "x-ext-authz: allow" webapp.istioinaction/api/catalog
# 응답
[{"id":1,"name":"Elinor Glasses",...}]
# 외부 인가 로그:
[HTTP][allowed]: GET webapp.istioinaction/api/catalog
'Kubernetes > Istio' 카테고리의 다른 글
Istio 시리즈 # 9 – Istio 성능 튜닝 가이드(Istio Tuning) (0) | 2025.05.18 |
---|---|
Istio 시리즈 # 8 - Istio 트러블 슈팅 가이드(Istio Troubleshooting) (1) | 2025.05.17 |
Istio 시리즈 # 6 – 메트릭과 트레이싱으로 살펴보는 Istio Observability (1) | 2025.05.04 |
Istio 시리즈 # 5 – 네트워크 복원력 강화하기(Resilience) (0) | 2025.04.27 |
Istio 시리즈 # 4 – 세밀하게 트래픽 제어하기(Traffic Control) (0) | 2025.04.27 |