서비스 간 통신이 많은 마이크로서비스 환경에서, 각 서비스는 자신이 통신하는 상대 서비스가 “정말 그 서비스가 맞는지” 확인할 필요가 있습니다. 이때 사용하는 것이 SPIFFE (Secure Production Identity Framework For Everyone) 프레임워크입니다.
사용자 인증은 사용자 데이터 보호의 핵심입니다. 일반적인 인증 흐름은 다음과 같습니다
Istio 환경에서도 JWT 기반 인증이 쉽게 통합되며, Istio의 Policy 설정을 통해 인증 흐름을 자동화할 수 있습니다.
서비스가 사용자의 신원을 확인했다면, 다음 단계는 “이 사용자가 이 작업을 할 수 있는가?”입니다. 이를 인가(Authorization)라고 하며, 다음과 같은 방식으로 동작합니다
예시) 특정 사용자만 /admin API에 접근 가능하도록 제한


이런 문제를 해결해 고도로 동적이고 이질적인 환경에서 ID를 제공하고자 이스티오는 SPIFFE 사양을 사용한다.
SPIFFE는 고도로 동적이고 이질적인 환경에서 워크로드에 ID를 제공하기 위한 일렬의 오픈소스 표준이다.
Istio는 각 워크로드(서비스)마다 SPIFFE ID를 발급합니다. 이 ID는 다음 형식의 URI로 표현됩니다:
spiffe://<trust-domain>/<path>
Istio는 이 SPIFFE ID를 X.509 인증서 형태로 인코딩한 SVID(Spiffe Verifiable Identity Document)를 생성해 제공합니다. 이 인증서는 mTLS 통신에서 사용되며, 서비스 간 트래픽의 암호화 및 인증 기반이 됩니다.
istio에서는 보안 정책을 아래 세가지 커스텀 리소스를 통해 선언적으로 관리합니다.

서비스간 트래픽을 인증(mTLS) 하기 위한 설정입니다.
→ 인증 성공 시, 상대 서비스의 SPIFFE ID 정보를 추출해 인가 판단에 활용할 수 있습니다.
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
spec:
mtls:
mode: STRICT
최종 사용자 인증(JWT 등)을 위한 설정입니다.
→ 사용자 자격증명에서 사용자 정보를 추출하여 인가에 활용합니다.
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
spec:
jwtRules:
- issuer: "https://secure.bocopile.com"
jwksUri: "https://secure.bocopile.com/.well-known/jwks.json"
인가 판단을 담당하는 정책입니다.
위 두 리소스에서 추출된 정보 (SPIFFE ID , JWT claims 등)를 기반으로 접근 허용 여부를 결정합니다.
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
spec:
rules:
- from:
- source:
principals: ["spiffe://test.bocopile.io/ns/default/sa/frontend"]
→ 인증된 신원 정보는 Envoy 필터 메타데이터에 저장되어, 이후 인가 판단에 참조됩니다.
아래 아키텍처는 Istio 보안의 흐름을 한눈에 보여줍니다

서비스 메시 환경에서는 프록시가 주입된 서비스들 간의 트래픽이 기본적으로 암호화되고, 상호 인증이 이루어집니다. 하지만 여기서 중요한 점은, 이 모든 과정의 핵심인 인증서 발급과 로테이션(갱신)을 자동화하는 것입니다.
과거에는 인증서를 수동으로 관리하면서 실수로 인한 서비스 중단이 빈번하게 발생했으며, 이는 운영 비용 상승으로 이어졌습니다. 하지만 Istio와 같은 서비스 메시 프레임워크는 이러한 인증서 관리를 자동화하여 오류를 최소화하고 서비스 중단을 예방할 수 있도록 지원합니다.
아래 그림은 Istio의 컨트롤 플레인에서 발급한 인증서를 사용해 서비스 간 인증과 트래픽 암호화를 수행하는 구조를 보여줍니다.

서비스 간 인증은 SVID(Spiffe Verifiable Identity Document) 인증서를 통해 이루어지며, 서비스 메시의 보안 기반을 형성합니다.
Istio를 설치하더라도, 서비스 간 트래픽이 자동으로 상호 인증되지는 않습니다. 이는 모든 트래픽을 상호 인증으로 제한하면, 점진적인 메시 도입이 어려운 대규모 조직에서 문제가 생길 수 있기 때문입니다. 다양한 팀이 독립적으로 운영하는 상황에서는 메시 전체 도입까지 긴 시간이 필요하고, 이를 고려해 설치 시 기본값은 보수적으로 설정되어 있습니다.
서비스 간 인증이 가능해지면, 다음 단계는 정책 기반 인가(Authorization) 입니다. 즉, 각 서비스가 필요한 리소스에만 접근하도록 최소 권한을 설정하는 것입니다.
이는 보안상 매우 중요한 개념입니다. 만약 인증서가 외부로 유출되더라도, 그 인증서가 접근 가능한 서비스 범위만큼으로 피해를 제한할 수 있기 때문입니다. ID 기반 정책 제어를 통해 더 견고한 보안 아키텍처를 구축할 수 있습니다.
mTLS 기능 실습을 위해 아래와 같이 3가지 서비스를 준비합니다.
sleep 서비스를 추가 : 레거시 워크로드로, 사이드카 프록시가 없어서 상호 인증을 할 수 없습니다.

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
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 워크로드로 평문 요청 실행
watch 'kubectl exec deploy/sleep -c sleep -- curl -s webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"'
키알리 확인 : unknown → webapp 구간은 평문 통신

응답이 성공했다는 것은 서비스들이 올바르게 준비됐으며 webapp 서비스가 sleep 서비스의 평문 요청을 받아들였다는 사실을 보여줍니다.
그러나 PerAuthentication 리로스로 평문 트래픽을 금지할수 있습니다.
Istio의 PeerAuthentication 리소스는 워크로드 간 통신에서 mTLS를 어떻게 적용할지를 제어합니다. 주요 모드는 다음과 같습니다:
STRICT: 오직 암호화된(mTLS) 트래픽만 허용PERMISSIVE: 암호화된 트래픽과 평문 트래픽을 모두 허용DISABLE: 암호화 없이 평문만 허용UNSET: 상위 정책을 따름정책 적용 범위는 세 가지입니다:
| 범위 | 설명 |
|---|---|
| Mesh-wide | 메시 전체에 적용 (istio-system/default) |
| Namespace-wide | 네임스페이스에 적용 |
| Workload-specific | 레이블로 지정한 워크로드에만 적용 |
서비스 메시 전반에 mTLS를 강제하려면 istio-system 네임스페이스에 default 이름의 정책을 STRICT 모드로 적용합니다.
설정 파일 확인
cat ch9/meshwide-strict-peer-authn.yaml

적용
kubectl apply -f ch9/meshwide-strict-peer-authn.yaml -n istio-system
적용 확인
kubectl get PeerAuthentication -n istio-system

요청 실행 - 에러 발생
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=webapp -c webapp -f
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

레거시 워크로드 등 메시 외부에서 오는 트래픽도 있기 때문에 PERMISSIVE 모드를 네임스페이스에 적용해 예외를 줄 수 있습니다.
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 get PeerAuthentication -A

요청 실행 - 200 OK
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=webapp -c webapp -f
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

다음 실습을 위한 PeerAuthentication 삭제
kubectl delete pa default -n istioinaction
네임스페이스 전체가 아닌 특정 워크로드만 PERMISSIVE로 설정하려면 selector를 사용합니다.
myk8s-control-plane 서비스 프록시 상태 확인
docker exec -it myk8s-control-plane istioctl proxy-status

webapp PeerAuthentication 적용
cat ch9/workload-permissive-peer-authn.yaml
kubectl apply -f ch9/workload-permissive-peer-authn.yaml

적용 확인
kubectl get pa -A

webapp 요청 실행 - 200 OK
kubectl exec deploy/sleep -c sleep -- curl -s http://webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"

webapp 로그 확인
kubectl logs -n istioinaction -l app=webapp -c webapp -f
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f


catalog 요청 실행 - 502 에러 발생
kubectl exec deploy/sleep -c sleep -- curl -s http://catalog.istioinaction/api/items -o /dev/null -w "%{http_code}\n"

catalog 로그 확인
kubectl logs -n istioinaction -l app=catalog -c catalog -f
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f

istio Proxy에는 기본적으로 tcpdump가 설치되어 있습니다.
해당 도구는 네트워크 인터페이스를 통과하는 네트워크 트래픽을 포착하고 분석합니다.
tcpdump 는 보안 때문에 권한 privileged permission 이 필요하나 기본적으로 이 권한은 꺼져 있습니다.
해당 권한을 부여하기 위해선 values.global.proxy.privileged=true 로 설정해 이스티오 설치를 업데이트를 진행해야 합니다.
해당 실습에선 이미 해당 권한을 부여 하였으므로 해당 작업을 스킵하고 진행합니다.
확인
kubectl get istiooperator -n istio-system installed-state -o yaml
kubectl get pod -n istioinaction -l app=webapp -o json


기타 확인
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- whoami
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- id
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- sudo whoami
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- sudo tcpdump -h

패킷 모니터링 실행
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)'

요청 실행
kubectl exec deploy/sleep -c sleep -- curl -s webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"

openssl 명령어를 사용해 catalog 워크로드의 X.509 인증서 내용물을 확인 합니다.
패킷 모니터링 실행
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와 인증서 체인 확인
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

catalog 파드의 Service Account 확인
kubectl describe pod -n istioinaction -l app=catalog | grep 'Service Account'

루트 인증서 서명 확인 - 검증에 성공하여 OK 메세지 출력
# 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)

istio는 AuthorizationPolicy라는 리소스를 통해 인가 정책을 정의합니다.
서비스 프록시(Envoy) 가 정책의 집행자 역할을 합니다.
각 서비스 옆에 배포된 프록시는 요청을 허용하거나 거부할지 로컬에서 직접 판단합니다.
해당 작업으로 인해 정책 평가 속도가 빠르고 효율적입니다.

예시 - webapp 워크로드에서 /api/catalog* 경로로 들어오는 요청만 허용하도록 설정
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
AuthorizationPolicy는 세 가지 주요 필드로 구성됩니다.
matchLabels 로 지정rules 안에는 세가지 세부 필드가 존재합니다.
spiffe://cluster.local/ns/default/sa/webapp)9.2 에서 이미 배포가 완료됨

워크로드에 하나 이상의 ALLOW 인가 정책이 적용되면,
모든 트래픽에서 해당 워크로드의 접근은 기본적으로 거부된다.
트래픽을 받아들이려면, ALLOW 정책이 최소 하나는 부합해야 합니다.
예를 들면 다음 AuthorizationPolicy 리소스는 webapp 으로의 요청 중 HTTP 경로에 /api/catalog* 가 포함된 것을 허용합니다.
설정 파일 확인
cat ch9/allow-catalog-requests-in-web-app.yaml

적용전 요청 테스트 - /hello/world 404 에러 발생
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world

AuthrizationPolicy 리소스 적용
kubectl apply -f ch9/allow-catalog-requests-in-web-app.yaml
리소스 적용 확인
kubectl get authorizationpolicy -n istioinaction

listener 확인
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json > webapp-listener.json
cat webapp-listener.json

요청 테스트 진행 - 403 에러 발생
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world


kiali 확인

위의 예시 처럼 /api/catalog* ALLOW 정책을 워크로드에 적용했을 때 /hello/world 기본적으로 거부 되는 것을 확인 할수 있습니다.
적용 리소스 삭제
kubectl delete -f ch9/allow-catalog-requests-in-web-app.yaml
보안성을 증가시키고, 과정을 단순화하기 위하여,
ALLOW 정책을 명시적으로 지정하지 않는 모든 요청을 거부하도록 정의해야 합니다.
신규 적용 할 정책 확인
cat ch9/policy-deny-all-mesh.yaml

정책 적용 전 요청 테스트 - 200 OK
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
curl -s http://webapp.istioinaction.io:30000/api/catalog

정책 적용
kubectl apply -f ch9/policy-deny-all-mesh.yaml
정책 적용 확인
kubectl get authorizationpolicy -A

정책 적용 후 요청 테스트 - 403 에러 발생
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog

로그 확인
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

정책 적용 후 요청 테스트2 - 403 에러 발생
curl -s http://webapp.istioinaction.io:30000/api/catalog

로그 확인
kubectl logs -n istio-system -l app=istio-ingressgateway -f

이번에는 네임스페이스에서 시작한, 모든 서비스에 대한 트래픽을 허용하고자 합니다.
해당 설정은 source.namespace 속성으로 진행할수 있습니다.
정책 적용
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
정책 적용 확인
kubectl get AuthorizationPolicy -A

listener 확인
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json

현재 적용한 sleep은 레거시 워크로드입니다.
따라서 사이드카가 존재하지 않으며, ID도 없습니다.
그러므로 webapp 프록시는 요청이 default 네임이스페이스의 워크로드에서 온 것인지 확인할 수 없습니다.
이를 해결하기 위해선 다음 중 하나를 선택해야 합니다.
1번 방식으로 하는 것을 권장합니다.
그러나 실습을 위해 첫번째 방법이 불가능하단 가정하에 두번째 방법으로 진행하고자 합니다.
Envoy가 자동으로 주입(auto-injection) 되도록 설정
kubectl label ns default istio-injection=enabled
sleep pod 제거
kubectl delete pod -l app=sleep
파드 상태 확인 - istiod 연결 확인
docker exec -it myk8s-control-plane istioctl proxy-status

default → webapp 호출 테스트
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction

webapp → catalog 요청 - 500 에러 발생
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog

로그 확인
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

default → catalog 호출 테스트 : 200 OK
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items


다음 실습을 위해 default 네임스페이스 원복
kubectl label ns default istio-injection-
kubectl rollout restart deploy/sleep
원복 확인
docker exec -it myk8s-control-plane istioctl proxy-status
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction

이번엔 미인증 워크로드에서 온 요청을 허용하고자 합니다.
해당 정책을 적용하기 위해서 from 필드를 삭제하고 반영해야 합니다.
아래 정책을 webapp에만 적용하기 위해 app:webapp 샐럭터를 추가합니다.
이렇게 하면 catalog 서비스에는 여전히 상호 인증이 필요합니다.
신규 적용 할 정책 확인
cat ch9/allow-unauthenticated-view-default-ns.yaml

정책 적용
kubectl apply -f ch9/allow-unauthenticated-view-default-ns.yaml
정책 확인
kubectl get AuthorizationPolicy -A

listener 확인
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json | jq



호출 테스트 - webapp : 200 OK
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction # default -> webapp 은 성공
로그 확인
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

호출 테스트 - catalog : 500 에러 발생
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog

webapp 은 sleep 서비스에서 요청을 허용했지만, 메시 범위 전체 거부 정책이 catalog 서비스로의 후속 요청을 거부 하였습니다.
트래픽이 webapp 서비스에 왔는지 인증할 수 있는 가장 간단한 방법은 트래픽에 주입된 서비스 어카운트를 사용하는 것입니다.
서비스 어카운트 정노는 SVID에 인코딩돼 있으며, 상호 인증 중에 그 정보를 검증하고 필터 메타데이터에 저장합니다.
해당 실습에서는 catalog 서비스가 필터 메타데이터를 사용해 서비스 어카운트가 webapp인 워크로드에서 온 트래픽만 허용하도록 설정 합니다.
신규 적용할 정책 확인
cat ch9/catalog-viewer-policy.yaml

정책 적용
kubectl apply -f ch9/catalog-viewer-policy.yaml
정책 확인
kubectl get AuthorizationPolicy -A

listener 확인
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction --port 15006 -o json | jq

호출 테스트 - 200OK
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog


로그 확인
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f


정책 중 일부는 특정 조건이 충족 되는 경우에만 적용이 되기도 합니다.
예를 들면 사용자가 관리자일 때는 모든 작업이 허용되도록 적용할 수 있습니다.
이는 인가 정책의 when 속성을 사용하여 구현할 수 있습니다.
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "allow-mesh-all-ops-admin"
namespace: istio-system
spec:
rules:
- from:
- source:
requestPrincipals: ["auth@istioinaction.io/*"]
when:
- key: request.auth.claims[groups] # 이스티오 속성을 지정한다
values: ["admin"] # 반드시 일치해야 하는 값의 목록을 지정한다
이 정책은 다음 두 조건이 모두 충족될 때만 요청을 허용합니다.
auth@istioinaction.io/* 가 발급한 것또는 notValues 속성을 사용하여 이 정책을 적용하지 않아야 하는 값들을 정의할 수도 있습니다.
Istio에서는 인가 정책의 조건을 정할 때, 단순한 문자열 비교 외에도 여러 비교 방식이 가능합니다
이를 통해 보다 유연하고 정확한 정책 정의가 가능합니다.
method: GET)/api/catalog*는 /api/catalog/1 등과 매치).istioinaction.io는 login.istioinaction.io 등과 매치)istio에서는 하나의 정책 안에서도 여러 규칙을 정의할 수 있으며, 각 규칙은 다중조건을 포함합니다.
이를 통해 다층적인 제어가 가능합니다.
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "allow-mesh-all-ops-admin"
namespace: istio-system
spec:
rules:
- from:
- source:
principals: ["cluster.local/ns/istioinaction/sa/webapp"]
- source:
namespace: ["default"]
to:
- operation:
methods: ["GET"]
paths: ["/users*"]
- operation:
methods: ["POST"]
paths: ["/data"]
when:
- key: request.auth.claims[group]
values: ["beta-tester", "admin", "developer"]
- to:
- operation:
paths: ["*.html", "*.js", "*.png"]
from, to, when은 AND 조건으로 모두 만족해야 적용됩니다.to 안에 여러 operation이 있으면 OR 조건으로 평가됩니다.from도 여러 소스가 있을 수 있으며 OR로 평가되지만, 전체 조건은 AND로 묶입니다.when 조건은 모두 일치해야 적용됩니다.Istio는 priority 필드를 따로 두지 않고, 정해진 순서대로 정책을 평가합니다.

요청이 허용/거부 될지 시각적으로 표현한 흐름도는 다음과 같습니다.

서비스 간 인증 이외에도 최종 사용자의 인증과 인가도 중요합니다.
Istio는 JWT를 사용한 최종 사용자 인증을 지원합니다.
JWT(JSON Web Token)는 클라이언트-서버 간 인증을 위한 대표적인 방식으로, 토큰 기반 인증 시스템에서 널리 사용됩니다.

# 기존 실습환경 삭제
kubectl delete virtualservice,deployment,service,\
destinationrule,gateway,peerauthentication,authorizationpolicy --all -n istioinaction
#
kubectl delete peerauthentication,authorizationpolicy -n istio-system --all
# 삭제 확인
kubectl get gw,vs,dr,peerauthentication,authorizationpolicy -A
# 실습 환경 배포
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
cat ch9/enduser/ingress-gw-for-webapp.yaml
kubectl apply -f ch9/enduser/ingress-gw-for-webapp.yaml -n istioinaction

RequestAuthentication 리소스
필터 메타데이터
요청 처리 흐름

1. 유효한 JWT가 있는 경우
- 요청이 클러스터로 전달되고, 추출된 클레임이 메타데이터에 저장되어 인가 정책에 활용됩니다.
2. 유효하지 않은 JWT가 있는 경우
- 요청이 즉시 거부됩니다.
3. JWT가 없는 경우
- 요청은 클러스터로 전달되지만, 메타데이터에 클레임이 없어 인가 정책에서 활용되지 않습니다.
다음 RequestAuthentication 리소스는 Istio의 IngressGateway에 적용됩니다.
이는 IngressGateway가 uth@istioinaction.io 에서 발급한 토큰을 검증하도록 설정합니다.
적용할 정책 확인
cat ch9/enduser/jwt-token-request-authn.yaml

RequestAuthentication 적용
kubectl apply -f ch9/enduser/jwt-token-request-authn.yaml
적용 확인
kubectl get requestauthentication -A

Listener 확인
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json

JWT 확인
cat ch9/enduser/user.jwt
USER_TOKEN=$(< ch9/enduser/user.jwt)

JWT 디코딩 - https://jwt.io/ : webapp.istioinaction.io 도메인이 /etc/hosts에만 설정 되어있어 해당 사이트에서 디코딩시 유효하지 않는 도메인으로 인식

catalog 호출 - 200 OK
curl -H "Authorization: Bearer $USER_TOKEN" \
-sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog

로그 확인 - 워크로드에 적용된 AuthorizationPolicy가 없으므로 기본적으로 ALLOW가 됩니다.
kubectl logs -n istio-system -l app=istio-ingressgateway -f

이번엔 인가되지 않는 JWT로 요청 테스트를 진행합니다.
JWT 확인 - 현재 설정한 정책의 발급자와 다름 issuer: "auth@istioinaction.io"
cat ch9/enduser/not-configured-issuer.jwt
WRONG_ISSUER=$(< ch9/enduser/not-configured-issuer.jwt)

호출 - 401 에러 발생
curl -H "Authorization: Bearer $WRONG_ISSUER" \
-sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog

로그 확인
kubectl logs -n istio-system -l app=istio-ingressgateway -f

토큰 없이 curl 요청 실행 - 200 OK
# 호출
curl -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog

로그 확인 - 응답코드 요청이 클러스터로 받아들여짐
kubectl logs -n istio-system -l app=istio-ingressgateway -f

위와 같은 결과로 인해 토큰이 없는 요청을 거부하려면, 다음에 설명할 약간의 추가 작업이 필요합니다.
JWT가 없는 요청을 거부하려면 명시적으로 거부하는 AuthorizationPolicy 리소스를 만들어야 합니다.
해당 정책은 RequestPrincipals 속성이 없는 source에서 온 모든 요청에 적용되며, action 속성에 지정되는대로 요청을 거부합니다.
RequestPrincipals의 초기화 방식은 JWT의 발행자 issuer와 주체 subject 클래임을 ‘iss/sub’ 형태로 결합한 것입니다.
클레임은 RequestPrincipals 리소스로 인증되고, AuthorizationPolicy 필터 등 다른 필터가 사용할 . 수있도록 커넥션 메타데이터로 가공됩니다.
적용할 정책 확인
vi ch9/enduser/app-gw-requires-jwt.yaml -> port 30000 추가
cat ch9/enduser/app-gw-requires-jwt.yaml

해당 정책 적용
kubectl apply -f ch9/enduser/app-gw-requires-jwt.yaml
정책 적용 확인
kubectl get AuthorizationPolicy -A

listener 확인
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json

호출 1 테스트 (JWT 미인증) - 403 에러 발생
curl -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog

호출 2 테스트 (JWT 인증) - 200 OK
curl -H "Authorization: Bearer $USER_TOKEN" \
-sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog

로그 확인 - 403, 200 로그 확인
kubectl logs -n istio-system -l app=istio-ingressgateway -f

이번엔 유저별로 다른 정책을 설정하고자 합니다.
해당 예제에서는 일반 사용자가 API에서 데이터를 읽는 것은 허용하지만 새 데이터를 쓰거나 기존 데이터를 금지합니다.
물론 관리자에겐 모든 권한을 부여합니다.
일반 사용자 - group : user
cat ch9/enduser/user.jwt

관리자 - group : admin
cat ch9/enduser/admin.jwt

일반 사용자가 webapp 에서 데이터를 읽을 수 있게 허용하도록 AuthorizationPolicy 리소스 설정
vi ch9/enduser/allow-all-with-jwt-to-webapp.yaml # 포트 30000 추가
cat ch9/enduser/allow-all-with-jwt-to-webapp.yaml

관리자는 모든 작업을 허용하도록 AuthorizationPolicy 리소스 설정
cat ch9/enduser/allow-mesh-all-ops-admin.yaml

정책 적용
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

listener 확인
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json
사용자

관리자

envoy rbac 로거는 메타데이터를 로그에 출력하지 않으므로 로깅 수준을 debug으로 설정이 필요
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/istio-ingressgateway -n istio-system --level rbac:debug

일반 유저 - Get / Post 호출
USER_TOKEN=$(< ch9/enduser/user.jwt)
curl -H "Authorization: Bearer $USER_TOKEN" \
-sSl -o /dev/null -w "%{http_code}\n" webapp.istioinaction.io:30000/api/catalog
curl -H "Authorization: Bearer $USER_TOKEN" \
-XPOST webapp.istioinaction.io:30000/api/catalog \
--data '{"id": 2, "name": "Shoes", "price": "84.00"}'
GET 요청 - 200 OK / POST 요청 - RBAC access denied

로그 확인
kubectl logs -n istio-system -l app=istio-ingressgateway -f

관리자 - GET / POST 요청 - 두개 요청 모두 200OK
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
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
-XPOST webapp.istioinaction.io:30000/api/catalog \
--data '{"id": 2, "name": "Shoes", "price": "84.00"}'

로그 확인
kubectl logs -n istio-system -l app=istio-ingressgateway -f

istio Envoy의 기본 RBAC 기능을 사용해 인가를 구현합니다.
그런데 Authentication를 좀 더 정교한 커스텀 메커니즘이 필요한 경우에는 어떻게 해야 할까?
요청을 허용할지 여부를 결정할 때 외부 Authentication 서비스를 호출하도록 istio 서비스 Proxy를 설정할 수 있습니다.

외부 Authentication 적용을 위해선 엔보이의 CheckRequest API를 구현해야 합니다.
API를 구현하는 외부 서비스에는 다음과 같습니다.
외부 인가 서비스는 프록시가 인가를 집행하는 데 사용하는 ‘허용’이나 ‘거부’ 메시지를 반환합니다.
실습 환경 초기화
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
istio 샘플에서 샘플 외부 Authentication 배포
docker exec -it myk8s-control-plane bash
-----------------------------------
cat istio-$ISTIOV/samples/extauthz/ext-authz.yaml
kubectl apply -f istio-$ISTIOV/samples/extauthz/ext-authz.yaml -n istioinaction
# 빠져나오기
exit
-----------------------------------

배포 확인
kubectl get deploy,svc ext-authz -n istioinaction

로그 모니터링 설정
kubectl logs -n istioinaction -l app=ext-authz -c ext-authz -f
배포한 ext-authz 서비스는 아주 간단해서 들어온 요청에 x-ext-authz 헤더가 있고 그 값이 allow 인지만 검사합니다.
해당 헤더가 요청에 들어 있으면 요청은 허용되고, 들어 있지 않으면 요청은 거부됩니다.
요청의 다른 속성을 평가하도록 외부 인가 서비스를 직접 작성하거나, 상술한 기존 서비스 중 하나를 사용할 수 있습니다.
istio가 새로운 외부 Authentication 서비스를 인식하도록 설정해야 합니다.
이를 위해서는 istio meshconfig 설정에서 extensionProviders 를 설정해야 합니다.
해당 설정은 istio-system 네임스페이스의 istio configmap에 존재합니다.
이 configmap을 수정하여 Authentication 서비스에 대한 적절한 설정을 추가한다.
includeHeadersInCheck 적용
# includeHeadersInCheck (DEPRECATED)
KUBE_EDITOR="vi" 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"]*
...
--------------------------------------------------------
# 확인
kubectl describe -n istio-system cm istio

istio envoyExtAuthz 서비스의 HTTP 구현체인 새 확장 sample-ext-authz-http 를 인식하도록 설정했습니다.
해당 서비스는 ext-authz.istioinaction.svc.cluster.local 위치하는 것으로 정의했는데, 앞 절에서 봤던 쿠버네티스 서비스에 맞추었습니다.
외부 Authentication 서비스에 전달할 헤더를 구성할 수 있는데, 이 설정에서는 x-ext-authz 헤더를 전달 합니다.
예제 외부 Authentication 서비스에서는 이 헤더를 인가 결과를 결정하는 데 사용 히며, 외부 Authentication 기능을 사용하기 위한 마지막 단계는 이 기능을 사용하도록 AuthorizationPolicy 리소스를 설정하는 것입니다.
action 이 CUSTOM 인 AuthorizationPolicy 를 만들고 정확히 어떤 외부 인가 서비스를 사용할지 지정해봅니다.
아래 AuthorizationPolicy 는 istioinaction 네임스페이스에 webapp 워크로드에 적용되며, sample-ext-authz-http 이라는 외부 인가 서비스에 위임합니다.
custom action 적용
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 # custom action 사용
provider:
name: sample-ext-authz-http # meshconfig 이름과 동일해야 한다
rules:
- to:
- operation:
paths: ["/*"] # 인가 정책을 적용할 경로
EOF
# 적용 확인
kubectl get AuthorizationPolicy -A

rbac log level debug 변경
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level rbac:debug
로그 모니터링
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
kubectl logs -n istioinaction -l app=ext-authz -c ext-authz -f
헤더 없이 호출 - denied 발생
kubectl -n default exec -it deploy/sleep -- curl webapp.istioinaction/api/catalog

로그 확인


헤더 적용 호출 - 200 OK
kubectl -n default exec -it deploy/sleep -- curl -H "x-ext-authz: allow" webapp.istioinaction/api/catalog

로그 확인

