CloudNet@ 가시다님이 진행하는 Istio 스터디 1기 - 5주차 정리 내용 입니다.
앞서 진행했던 주차를 보고 오시면 이해가 빠릅니다.
이번 글에서는, 아래 주제에 대해 알아보겠습니다.
- 서비스 간 트래픽 인가
- 최종 사용자 인증 및 인가
- 커스텀 외부 인가 서비스와 통합하기
📌 서비스 간 트래픽 인가하기

✅ 인가 란?
사용자가 특정 리소스에 대해 어떤 작업을 수행할 수 있는 권한이 있는지를 판단하는 과정 입니다.
즉, 사용자가 로그인했더라도, 모든 기능이나 데이터에 접근할 수 있는 건 아니며, 접근 가능한 범위를 제한하고 제어하는 것이 인가입니다.
이런 인가 작업을 관리하는 것이 Istio 에서는 AuthorizationPolicy 리소스 입니다.
✅ AuthorizationPolicy 리소스 란?
Istio에서 인증(Authentication) 이후에 수행되는 인가(Authorization) 를 처리하기 위한 리소스입니다.
이 리소스는 서비스 메시에 메시 범위, 네임스페이스 범위, 워크로드별 접근 정책을 정의하는 선언적 API 입니다.
이제 Istio에서 인가를 어떻게 구현하는지 알아보겠습니다.
Istio에서는 요청이 서비스로 도달하기 전에 Envoy 프록시 레벨에서 인증 -> 인가 단계를 거쳐 요청을 처리합니다.

위 그림 처럼
1. 사용자가 요청을 하면 사용자 정보나 권한 등이 JWT (JSON Web Token) 에 담겨져 애플리케이션 쪽으로 요청을 합니다.
2. 애플리케이션의 사이드카 프록시(Envoy)가 요청을 먼저 받아, JWT 토큰의 서명을 검증하고 상호 TLS 인증을 통해 SVID를 추출합니다.
3. JWT 클레임, SPIFFE ID, 기타 인증 정보 등 프록시 내부의 Filter metadata로 저장
4. 앞에서 생성된 Filter medata를 기반으로 AuthorizationPolicy 에 의해 AuthZfilters가 요청을 허용할지 거부할지를 판단합니다.
5. 요청을 허용하면 최종적으로 Application으로 요청이 전달됩니다.
이제 AuthorizationPolicy 리소스의 YAML 파일을 살펴보면,
$ 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
rules:
- to:
- operation:
paths: ["/api/catalog*"]
action: ALLOW
위와 같이 워크로드에 허용할 트래픽 규칙을 정해주면 됩니다.
YAML 파일의 내용을 해석해보면,
app=webapp 라벨을 가진 파드(워크로드)에 /api/catalog/* 경로로 오는 요청을 허용 해줍니다.
이런 식으로 Policy를 정해주면 간단하게 정책을 적용해줄 수 있습니다.
AuthorizationPolicy 리소스에 대해 좀 더 자세히 알아보면,
$ 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
rules:
- to:
- operation:
paths: ["/api/catalog*"]
action: ALLOW
app=webapp 라벨을 가진 파드(워크로드)에 /api/catalog/* 경로로 오는 요청을 허용 해줍니다.
이런 식으로 Policy를 정해주면 간단하게 정책을 적용해줄 수 있습니다.
AuthorizationPolicy 리소스의 구성 요소들에 대해 좀 더 자세히 알아보겠습니다.
📌 인가 정책의 속성
- AuthorizationPolicy 리소스 사양에서 정책을 설정하고 정의하는 필드는 세 가지 입니다.
- selector 필드는 정책을 적용할 워크로드 정의. [ex) label 명]
- action 필드는 이 정책이 허용(ALLOW)인지, 거부(DENY)인지, 커스텀(CUSTOM)인지 지정
- action은 rules에 정의한 어떤 조건과도 일치하지 않으면, 그 정책은 무시되고 action은 실행되지 않습니다.
- rules 필드는 정책을 활성화할 요청을 식별하는 규칙 목록을 정의
📌 인가 정책 규칙 이해하기
- 하나의 rule 안에 from, to, when 이 있다면 모두 만족 해야 해당 rule 이 적용 됩니다.
- 전체 AuthorizationPolicy 에는 여러 rule 이 있을 수 있으며, 그 중 하나라도 만족하면 action 이 실행 됩니다.
rules:
- from:
- source:
principals:
- "cluster.local/ns/istioinaction/sa/frontend"
namespaces:
- "istioinaction"
ipBlocks:
- "192.168.0.0/24"
- 위 예시 처럼 단일 규칙 필드로 3가지가 있습니다.
- principals: 서비스 계정(SPIFFE ID)을 기준으로 요청의 주체를 식별
- namespaces: 요청을 보낸 주체의 네임스페이스 기준으로 제한- ipBlocks: 요청을 보낸 IP 주소나 CIDR 범위를 기준으로 제한
- 또한 principal, namespace를 조건으로 사용하려면 mTLS가 필수 입니다.
이제 실습을 통해 좀 더 자세히 알아보겠습니다.
📌 실습 환경 요약

- 실습 환경 요약
- sleep 워크로드는 default 네임스페이스에 배포했고, 평문 HTTP 요청을 만드는 데 사용합니다.
- webapp 워크로드는 istioinaction 네임스페이스에 배포했고, default 네임스페이스에 있는 워크로드에서 미인증 요청을 받아들이고 있습니다.
- catalog 워크로드는 istioinaction 네임스페이스에 배포했고, 같은 네임스페이스의 인증된 워크로드로부터만 요청을 받아들이고 있습니다.
🔧 PeerAuthentication 설정 : 앞에서 이미 설정함
$ cat ch9/meshwide-strict-peer-authn.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default"
namespace: "istio-system"
spec:
mtls:
mode: STRICT
---
$ cat ch9/workload-permissive-peer-authn.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "webapp"
namespace: "istioinaction"
spec:
selector:
matchLabels:
app: webapp
mtls:
mode: PERMISSIVE
🔧 앞서 설정 해둔 PeerAuthentication 리소스 확인
$ kubectl get peerauthentications.security.istio.io -A
결과
NAMESPACE NAME MODE AGE
istio-system default STRICT 7h50m
istioinaction webapp PERMISSIVE 6h52m
이제 워크로드에 정책 적용 시 동작 확인해보겠습니다.
일단 워크로드에 하나 이상의 ALLOW 인가 정책이 적용되면, 모든 트래픽에서 해당 워크로드의 접근은 기본적으로 거부 됩니다.
트래픽을 받아들이려면, ALLOW 정책이 최소 하나는 부합해야 합니다.
예를 들어,
$ 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
위 AuthorizationPolicy 리소스를 보면, app=webapp 라벨이 붙은 파드로 요청 중 HTTP 경로에 /api/catalog* 가 포함된 것을 허용 하도록 설정을 했습니다.
해당 리소스를 적용 시켜주면, webapp 라벨을 가진 워크로드(즉, webapp 파드)에 대해서
/api/catalog* 경로로 요청이 들어온 경우에만 트래픽을 허용(ALLOW) 합니다.
그 외의 요청 (예: /api/users, /, /healthz 등)은 명시적으로 허용되지 않았기 때문에 차단(DENY) 됩니다.
실제로 다른 경로로 요청을 주면 DENY 되는지 확인해보겠습니다.
📌 워크로드에 정책 적용 시 동작 확인
🔧 로그 켜두기
$ 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
🔧 AuthorizationPolicy 리소스 적용 전 호출 테스트
$ kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog1
결과
[{"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":"Wa
$ kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world
결과
...
<body>
<div id="wrapper">
<div id="container">
<div class="navtop">
<h1>Not Found</h1>
</div>
<div id="content">
<br>The page you have requested has flown the coop.<br>Perhaps you are here because:<br><br><ul><br>The page has moved<br>The page no longer exists<br>You were looking for your puppy and got lost<br>You like 404 pages</ul>
<a href="/" title="Home" class="button">Go Home</a><br />
<br>Powered by beego 2.0.0
</div>
</div>
</div>
</body>
</html>
🔧 AuthorizationPolicy 리소스 적용
$ kubectl apply -f ch9/allow-catalog-requests-in-web-app.yaml
🔧 적용 후 호출 테스트
# 해당 결과는 정상적으로 나옴
$ kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
# 해당 결과는 403 에러를 리턴 합니다.
$ kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world
명령어 실행 결과
RBAC: access denied
로그 결과
[2025-05-09T06:43:35.194Z] "GET /hello/world HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] - "-" 0 19 0 - "-" "curl/8.5.0" "3395543e-f5d2-9923-bfff-c212cd95dedd" "webapp.istioinaction" "-" inbound|8080|| - 10.10.0.14:8080 10.10.0.10:41078 - -
🔧 다음 실습을 위해 정책 삭제
$ kubectl delete -f ch9/allow-catalog-requests-in-web-app.yaml
그리고 kiail에 접속해서 webapp 워크로드에 Istio Config를 살펴보면 AuthorizationPolicy가 적용 된 것을 확인할 수 있습니다.


이렇게 원하는 규칙만 정해놓고 그 외 다른 규칙은 DENY 되도록 하는 것이 좋습니다.
따라서 전체 정책을 기본적으로 "모든 요청 거부" 상태로 해두겠습니다.
📌 전체 정책을 기본적으로 모든 요청 거부하기
🔧 기본 거부 정책 리소스 확인
$ 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
$ curl -s http://webapp.istioinaction.io:30000/api/catalog
결과
[{"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"}]
🔧 정책 적용
$ kubectl apply -f ch9/policy-deny-all-mesh.yaml
🔧 적용 후 테스트 1
$ kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
결과
error calling Catalog service
🔧 적용 후 테스트 2
$ curl -s http://webapp.istioinaction.io:30000/api/catalog
결과
RBAC: access denied
로그에서도 403 에러가 나오는 것을 확인 할 수 있습니다.
[2025-05-09T07:03:08.030Z] "GET /items HTTP/1.1" 403 - via_upstream - "-" 0 19 1 1 "-" "beegoServer" "16e5568c-a5fd-9ebb-8fcd-34d22d613a75" "catalog.istioinaction:80" "10.10.0.13:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.14:49452 10.200.1.64:80 10.10.0.14:32802 - default
그리고 이제 특정 네임스페이스에서 온 요청만 허용 해보도록 하겠습니다.
📌 특정 네임스페이스에서 온 요청 허용하기
🔧 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
🔧 리소스 생성 확인
$ kubectl get AuthorizationPolicy -A
결과
NAMESPACE NAME AGE
istio-system deny-all 20m
istioinaction webapp-allow-view-default-ns 9s
🔧 호출 테스트
$ kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
결과
# 왜냐하면 sleep 파드에는 사이드카 프록시(Envoy)가 존재 하지 않기 때문에 정책 적용이 되지 않아서 access denied 가 나오게 됩니다.
RBAC: access denied
🔧 sleep 파드에 사이드카 프록시 주입
$ kubectl label ns default istio-injection=enabled
$ kubectl delete pods -l app=sleep
🔧 sleep 파드에 사이드카 프록시 생겼는지 확인
$ docker exec -it myk8s-control-plane istioctl proxy-status
결과
NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION
sleep-6f8cfb8c8f-sn959.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-8d74787f-wx8zm 1.17.8
🔧 다시 호출 테스트 진행 (default 네임스페이스 -> webapp 파드까지는 성공)
$ kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction
로그 결과
[2025-05-09T07:50:40.286Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 4527 82 80 "-" "curl/8.5.0" "df93ee84-e6c6-9c0a-870d-249cd0bd0265" "webapp.istioinaction" "10.10.0.14:8080" inbound|8080|| 127.0.0.6:51033 10.10.0.14:8080 10.10.0.15:45152 outbound_.80_._.webapp.istioinaction.svc.cluster.local default
🔧 webapp -> catalog 까지 호출
$ kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
로그 결과
# webapp에서 catalog 까지의 호출은 실패한 것을 확인 할 수 있습니다.
[2025-05-09T07:56:02.196Z] "GET /items HTTP/1.1" 403 - via_upstream - "-" 0 19 10 9 "-" "beegoServer" "b91c1b8a-dfe1-9660-be29-b9c0a2b9de61" "catalog.istioinaction:80" "10.10.0.13:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.14:59366 10.200.1.64:80 10.10.0.14:33860 - default
[2025-05-09T07:56:02.191Z] "GET /api/catalog HTTP/1.1" 500 - via_upstream - "-" 0 29 17 16 "-" "curl/8.5.0" "b91c1b8a-dfe1-9660-be29-b9c0a2b9de61" "webapp.istioinaction" "10.10.0.14:8080" inbound|8080|| 127.0.0.6:51033 10.10.0.14:8080 10.10.0.15:45152 outbound_.80_._.webapp.istioinaction.svc.cluster.local default
🔧 default 네임스페이스에서 catalog로 호출 테스트
# 로그 활성화
$ kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
$ kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items
로그 결과
# 성공한 것을 확인할 수 있습니다.
[2025-05-09T08:00:01.105Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 43 42 "-" "curl/8.5.0" "2f745bbc-712d-9005-94a0-8a1bb66b3f03" "catalog.istioinaction" "10.10.0.13:3000" inbound|3000|| 127.0.0.6:58667 10.10.0.13:3000 10.10.0.15:60956 outbound_.80_._.catalog.istioinaction.svc.cluster.local default
🔧 다음 실습을 위해 원복
$ kubectl label ns default istio-injection-
$ kubectl rollout restart deploy/sleep
이제 미 인증 워크로드에서 온 요청을 허용 해보도록 하겠습니다.
📌 워크로드에 정책 적용 시 동작 확인
미 인증 워크로드에서 온 요청을 허용하려면 from 필드를 삭제 해야 합니다.
실습을 통해 좀 더 자세히 알아보겠습니다.
🔧 webapp 워크로드 쪽 요청은 인증 없이 허용하는 리소스 생성
$ 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:
- to:
- operation:
methods: ["GET"]
$ kubectl apply -f ch9/allow-unauthenticated-view-default-ns.yaml
🔧 호출 테스트 (default 네임스페이스 -> webapp)
$ kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction
로그 결과
[2025-05-09T08:09:21.424Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 4527 25 24 "-" "curl/8.5.0" "2181af65-4436-97db-a415-4cfe3ec2cd65" "webapp.istioinaction" "10.10.0.14:8080" inbound|8080|| 127.0.0.6:51033 10.10.0.14:8080 10.10.0.16:47930 - default
🔧 catalog 쪽으로도 호출 테스트 (default 네임스페이스 -> catalog)
$ kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
로그 결과
# webapp -> catalog 요청은 허용하지 않아서 에러가 나옵니다.
[2025-05-09T08:12:39.303Z] "GET /items HTTP/1.1" 403 - via_upstream - "-" 0 19 1 0 "-" "beegoServer" "6f91d0e5-68a0-9b6d-af80-dce969e1dc71" "catalog.istioinaction:80" "10.10.0.13:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.14:59366 10.200.1.64:80 10.10.0.14:59856 - default
[2025-05-09T08:12:39.297Z] "GET /api/catalog HTTP/1.1" 500 - via_upstream - "-" 0 29 7 7 "-" "curl/8.5.0" "6f91d0e5-68a0-9b6d-af80-dce969e1dc71" "webapp.istioinaction" "10.10.0.14:8080" inbound|8080|| 127.0.0.6:51033 10.10.0.14:8080 10.10.0.16:53974 - default
이번엔 특정 서비스 어카운트에서 온 요청을 허용해보겠습니다.
📌 특정 서비스 어카운트에서 온 요청 허용
트래픽이 특정 워크로드에서 왔는지 인증할 수 있는 간단한 방법은 트래픽에 주입된 서비스 어카운트를 사용하는 것 입니다.
서비스 어카운트 정보는 SVID에 인코딩 되어 있으며, 상호 인증 중에 해당 정보를 검증하고 필터 메타데이터에 저장합니다.
이제 실습을 통해 알아보겠습니다.
🔧 서비스 어카운트가 webapp인 워크로드에서 온 트래픽만 허용하도록 설정
$ 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"]
$ kubectl apply -f ch9/catalog-viewer-policy.yaml
🔧 로그 켜두기
$ kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
$ kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
🔧 호출테스트 sleep --(미인증 레거시 허용)--> webapp --(principals webapp 허용)--> catalog
$ kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction
$ kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
따라서 호출 테스트 결과를 보면 모두 성공으로 나오게 됩니다.
webapp 프록시 로그
[2025-05-10T01:36:10.820Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 3 3 "-" "beegoServer" "b67e3b7b-9ab2-99b9-a147-54ee1e6bff30" "catalog.istioinaction:80" "10.10.0.6:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.12:59912 10.200.1.64:80 10.10.0.12:53090 - default
[2025-05-10T01:36:10.818Z] "GET /api/catalog HTTP/1.1" 200 - via_upstream - "-" 0 357 12 12 "-" "curl/8.5.0" "b67e3b7b-9ab2-99b9-a147-54ee1e6bff30" "webapp.istioinaction" "10.10.0.12:8080" inbound|8080|| 127.0.0.6:35993 10.10.0.12:8080 10.10.0.7:47404 - default
catalog 프록시 로그
[2025-05-10T01:36:03.815Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 24 23 "-" "beegoServer" "4fe877a7-455b-982a-9a27-d64de9413666" "catalog.istioinaction:80" "10.10.0.6:3000" inbound|3000|| 127.0.0.6:59521 10.10.0.6:3000 10.10.0.12:59900 outbound_.80_._.catalog.istioinaction.svc.cluster.local default
[2025-05-10T01:36:10.821Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 2 2 "-" "beegoServer" "b67e3b7b-9ab2-99b9-a147-54ee1e6bff30" "catalog.istioinaction:80" "10.10.0.6:3000" inbound|3000|| 127.0.0.6:43847 10.10.0.6:3000 10.10.0.12:59912 outbound_.80_._.catalog.istioinaction.svc.cluster.local default
📌 정책의 조건부 적용
이제 특정 조건이 충족되는 경우에만 요청이 허용되도록 설정해보도록 하겠습니다.
설정할 조건은 2가지 입니다.
- 토큰은 요청 주체 auth@istioninaction.io/* 가 발급한 것이야 한다.
- JWT에 값이 'admin'인 group 클레임이 포함돼 있어야 한다.
이 두 정책이 모두 충족되는 경우에만 요청이 허용이 됩니다.
🔧 두 조건을 만족할 때 요청을 허용하는 AuthorizationPolicy 리소스
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"] # 반드시 일치해야 하는 값의 목록을 지정한다
📌 값 비교 표현식 이해하기
Istio는 규칙을 더 다야앟게 만들 수 있도록 간단한 비교 표현식을 지원합니다.
- 일치: 예를 들어 GET은 값이 정확히 일치해야합니다.
- 접두사 비교: 예를 들어 /api/catalog* 는 /api/catalog/1과 같이 이 접두사로 시작하는 모든 값에 부합합니다.
- 접미사 비교: 예를 들어 *.istioinaction.io 는 login.istioninaction.io 와 같이 모든 서브도메인에 부합합니다.
- 존재성 비교: 모든 값에 부합하며, *로 표기합니다. 이는 필드가 존재해야 하지만, 값은 중요하지 않아 어떤 값이 들어가 된다는 의미 입니다.
이제 AuthorizationPolicy 리소스를 살펴보면서 좀 더 자세히 알아보겠습니다.
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"]
위 리소스에서는 규칙이 2개가 있습니다.
rules 아래에 있는 규칙들은 서로 독립적인 규칙 입니다.
-> 즉 , 하나라도 조건을 만족하면 ALLOW 또는 DENY 같은 action 이 적용됩니다.
다만, 하나의 규칙 안에서는 from, to, when 이 모두 AND 조건 입니다.
-> 즉, 하나의 rule 내부에서는 from and to and when 로서 모두 만족해야 rule 이 적용됩니다.
📌 인가 정책이 평가되는 순서 이해하기
한 워크로드에 많은 정책이 적용되고 순서를 이해하기 어려울 때 정책의 복잡성이 커집니다.
많은 솔루션이 priority(우선순위) 필드를 사용하여 순서를 정의 합니다.
Istio는 정책 평가에 따른 접근법을 사용합니다.
정책 평가 순서는 아래와 같습니다.

요청이 들어오면,
- CUSTOM 정책 평가
- 이 정책에서 DENY 반환 시, 요청 거부
- 그렇지 않으면 평가 진행
- DENY 정책 평가
- 일치하는 DENY 규칙이 있으면 요청 거부
- 그렇지 않으면 평가 진행
- ALLOW 정책 존재 여부 평가
- ALLOW 정책이 존재하는 지 여부 확인, 존재 시 다음 단계로 넘어가 ALLOW 정책 평가
- 그렇지 않으면, 일반 정책이 존재하는지 평가
- ALLOW 정책 평가
- 여러 개의 ALLOW 정책 중 하나라도 매치되면 요청 허용
- 어떤 것도 매치 되지 않으면, 일반 정책으로 평가 진행
이제 Istio에서 최종 사용자 인증 및 인가에 대해 알아보겠습니다.
📌 최종 사용자 인증 및 인가
먼저 JWT 토큰에 대해 알아보겠습니다.
✅ JWT란 ?
JWT는 클라이언트를 서버에 인증하는 데 사용하는 간단한 클레임 표현 입니다.
JWT는 3가지 부분으로 이루어져 있습니다,
- 헤더: 유형 및 해싱 알고리즘으로 구성
- 페이로드: 사용자 클레임 포함
- 서명: JWT의 진위 여부를 파악하는 데 사용
위 3가지가 점(.)으로 구분되고 Base64 URL로 인코딩되기 때문에, JWT는 HTTP 요청에서 사용하기 좋습니다.
실제 jwt 토큰을 보고 디코딩 해보겠습니다.
🔧 jwt 토큰 내용 확인
$ cat ./ch9/enduser/user.jwt
결과
eyJhbGciOiJSUzI1NiIsImtpZCI6IkNVLUFESkpFYkg5YlhsMHRwc1FXWXVvNEV3bGt4RlVIYmVKNGNra2FrQ00iLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ3NDUxNDUwMzgsImdyb3VwIjoidXNlciIsImlhdCI6MTU5MTU0NTAzOCwiaXNzIjoiYXV0aEBpc3Rpb2luYWN0aW9uLmlvIiwic3ViIjoiOWI3OTJiNTYtN2RmYS00ZTRiLWE4M2YtZTIwNjc5MTE1ZDc5In0.jNDoRx7SNm8b1xMmPaOEMVgwdnTmXJwD5jjCH9wcGsLisbZGcR6chkirWy1BVzYEQDTf8pDJpY2C3H-aXN3IlAcQ1UqVe5lShIjCMIFTthat3OuNgu-a91csGz6qtQITxsOpMcBinlTYRsUOICcD7UZcLugxK4bpOECohHoEhuASHzlH-FYESDB-JYrxmwXj4xoZ_jIsdpuqz_VYhWp8e0phDNJbB6AHOI3m7OHCsGNcw9Z0cks1cJrgB8JNjRApr9XTNBoEC564PX2ZdzciI9BHoOFAKx4mWWEqW08LDMSZIN5Ui9ppwReSV2ncQOazdStS65T43bZJwgJiIocSCg
🔧 jwt 토큰 디코딩 방법
$ jwt -show ch9/enduser/user.jwt
결과
Header:
{
"alg": "RS256",
"kid": "CU-ADJJEbH9bXl0tpsQWYuo4EwlkxFUHbeJ4ckkakCM",
"typ": "JWT"
}
Claims:
{
"exp": 4745145038, # 만료시간
"group": "user", # group 클레임
"iat": 1591545038, # 발행시각
"iss": "auth@istioinaction.io", # 토큰 발행자
"sub": "9b792b56-7dfa-4e4b-a83f-e20679115d79" # 토큰의 주체
}
여기서 나오는 클레인 덕분에 서비스는 클라이언트의 ID 및 인가를 판단할 수 있습니다.
이제 JWT는 어떻게 발행이되고 검증이 되는지 알아보겠습니다.
JWT는 인증 서버에서 발급이 되는데,
인증 서버는 토큰을 서명하는 비밀 키와 검증하기 위한 공개 키를 가지고 있습니다.
공개 키는 JWKS(JSON Web Key Set, JSON 웹 키셋)라고 하며, well-known HTTP 엔드포인트에 노출됩니다.
서비스는 해당 엔드포인트에서 공개 키를 가져와 인증 서버가 발급한 토큰을 검증할 수 있습니다.
- 인증 서버는 여러 솔루션으로 준비할 수 있다.
- 애플리케이션 백엔드 프레임워크에서 구현할 수 있다.
- OpenIAM 혹은 Keycloak 등의 서비스로, 자체적으로 구현할 수 있다.
- Auth0, Okta 등의 서비스형 ID Identity-as-a-Service 솔루션으로 구현할 수 있다.
이제 서버가 토큰을 검증하는 데 JWKS를 어떻게 사용하는지 알아보겠습니다.

- 클라이언트가 인증 서버에 로그인
→ JWT(토큰)를 발급받음 - 클라이언트가 서버에 요청 보냄
→ JWT를 Authorization 헤더에 담아 전송 - 서버가 JWT의 서명을 검증해야 함
→ 어떤 키로 서명됐는지 확인 (kid 확인) - 서버가 인증 서버에서 JWKS를 다운로드
→ https://[인증서버]/.well-known/jwks.json 주소로 요청 - JWKS에서 kid에 해당하는 공개 키 찾기
→ JWT의 서명 검증에 사용 - 서명이 유효하면 요청 처리, 아니면 거부 (401)
이제 Ingress Gateway에서 최종 사용자 인증 및 인가에 대해 알아보겠습니다.
✅ 최종 사용자란?
ID 제공자에게 인증받고 ID와 클레임을 나타내는 토큰을 발급받은 사용자를 뜻합니다.
최종 사용자 인가는 모든 워크로드 수준에서 수행할 수 있지만, 보통 Ingress Gateway에서 수행합니다.
이렇게 하면, 불필요한 트래픽 낭비를 막고, 보안을 강화할 수 있습니다.
- 외부 요청을 가장 먼저 받는 곳이 Ingress Gateway
→ Gateway에서 인가를 걸면, 내부 서비스까지 트래픽이 들어오지 않도록 차단 가능 - 리소스를 절약하고 성능 최적화
→ 불필요한 요청이 내부 서비스까지 전달되지 않으므로 리소스 낭비 방지 - 보안 제어를 중앙에서 일괄 적용 가능
→ 사용자 권한 체크를 Gateway에 집중시켜 인가 정책을 관리하기 쉬움
이제 실습을 하기 위해 환경을 준비해보겠습니다.
🔧 앞서 생성한 istioincation 네임스페이스에 존재하는 리소스들 삭제
$ kubectl delete virtualservice,deployment,service,\
destinationrule,gateway,peerauthentication,authorizationpolicy --all -n istioinaction
🔧 앞서 생성한 peerauthentication,authorizationpolicy 리소스 삭제
$ kubectl delete peerauthentication,authorizationpolicy -n istio-system --all
🔧 실습 환경 배포
$ 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
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: webapp-gateway
namespace: istioinaction
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "webapp.istioinaction.io"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: webapp-virtualservice
namespace: istioinaction
spec:
hosts:
- "webapp.istioinaction.io"
gateways:
- webapp-gateway
http:
- route:
- destination:
host: webapp
port:
number: 80
$ kubectl apply -f ch9/enduser/ingress-gw-for-webapp.yaml -n istioinaction
이제 RequestAuthentication 리소스를 탐색할 준비가 되었습니다.
✅ RequestAuthentication 리소스의 주 목적은 JWT를 검증하고, 유효한 토큰의 클레임을 추출하고,
이 클레임을 필터 메타데이터에 저장하는 것 입니다.
위에서 언급한 필터 메타데이터는 인가 정책이 조치를 취하는 근거로 사용됩니다.
✅ 필터 메타데이터란 ?
서비스 프록시에서 필터 간 요청을 처리하는 동안 사용할 수 있는 키-값 쌍의 모음을 말합니다.
예를 들어, 클레임 group=admin 이 있는 요청이 검증되면 이 값은 필터 메타 데이터로 저장되며, 필터 메타 데이터는 인가 정책이 요청을 허용하거나 거부하는 데 사용 됩니다.
결국 최종 사용자 요청에 따라 결과는 셋 중 하나가 됩니다.
- 유효한 토큰을 갖고 있는 요청은 클러스터로 받아들여지며, 이들의 클레임은 필터 메타데이터 형태로 정책에 전달된다.
- 유효하지 않은 토큰을 갖고 있는 요청은 거부된다.
- 토큰이 없는 요청은 클러스터로 받아들여지지만 요청 ID가 없다. 즉, 어떤 클레임도 필터 메타데이터에 저장되지 않는다.
이제 RequestAuthentication 리소스를 만들어 실습 해보도록 하겠습니다.
🔧 RequestAuthentication 리소스 생성
$ 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
🔧 유효한 JWT를 변수로 등록
$ cat ch9/enduser/user.jwt
$ USER_TOKEN=$(< ch9/enduser/user.jwt)
$ echo "$USER_TOKEN" | jwt -show -
결과
Header:
{
"alg": "RS256",
"kid": "CU-ADJJEbH9bXl0tpsQWYuo4EwlkxFUHbeJ4ckkakCM",
"typ": "JWT"
}
Claims:
{
"exp": 4745145038,
"group": "user",
"iat": 1591545038,
"iss": "auth@istioinaction.io",
"sub": "9b792b56-7dfa-4e4b-a83f-e20679115d79"
}
🔧 jwt 토큰을 가지고 catalog 워크로드까지 호출
$ curl -H "Authorization: Bearer $USER_TOKEN" \
-sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
결과
200
🔧 유효하지 않은 JWT를 변수로 등록
$ cat ch9/enduser/not-configured-issuer.jwt
$ WRONG_ISSUER=$(< ch9/enduser/not-configured-issuer.jwt)
$ echo "$WRONG_ISSUER" | jwt -show -
결과
Header:
{
"alg": "RS256",
"kid": "CU-ADJJEbH9bXl0tpsQWYuo4EwlkxFUHbeJ4ckkakCM",
"typ": "JWT"
}
Claims:
{
"exp": 4745151548,
"group": "user",
"iat": 1591551548,
"iss": "old-auth@istioinaction.io",
"sub": "79d7506c-b617-46d1-bc1f-f511b5d30ab0"
}
🔧 유효하지 않은 JWT 토큰을 가지고 catalog 워크로드까지 호출
$ curl -H "Authorization: Bearer $WRONG_ISSUER" \
-sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
결과
401
로그 결과
[2025-05-10T13:47:11.744Z] "GET /api/catalog HTTP/1.1" 401 - jwt_authn_access_denied{Jwt_issuer_is_not_configured} - "-" 0 28 0 - "172.18.0.1" "curl/8.5.0" "e2126bf2-687a-9487-8bcd-b0207a3af51d" "webapp.istioinaction.io:30000" "-" outbound|80||webapp.istioinaction.svc.cluster.local - 10.10.0.10:8080 172.18.0.1:50376 - -
🔧 JWT 토큰 없이 요청 실행
$ curl -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
결과
200
🔧 JWT 토큰 없은 요청 거부하기 위한 AuthorizationPolicy 리소스 적용
$ 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
🔧 AuthorizationPolicy 리소스 적용 후 JWT 토큰 없이 호출
$ curl -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
결과
403
🔧 유효한 JWT 토큰을 가지고 호출
$ curl -H "Authorization: Bearer $USER_TOKEN" \
-sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
결과
200
이번엔 JWT 토큰 마다 권한을 다르게 주어서 실습을 해보도록 하겠습니다.
🔧 user.jwt 토큰 -> 'group: user'
$ jwt -show ch9/enduser/user.jwt
결과
Header:
{
"alg": "RS256",
"kid": "CU-ADJJEbH9bXl0tpsQWYuo4EwlkxFUHbeJ4ckkakCM",
"typ": "JWT"
}
Claims:
{
"exp": 4745145038,
"group": "user",
"iat": 1591545038,
"iss": "auth@istioinaction.io",
"sub": "9b792b56-7dfa-4e4b-a83f-e20679115d79"
}
🔧 admin.jwt 토큰 -> 'group: admin'
$ jwt -show ch9/enduser/admin.jwt
결과
Header:
{
"alg": "RS256",
"kid": "CU-ADJJEbH9bXl0tpsQWYuo4EwlkxFUHbeJ4ckkakCM",
"typ": "JWT"
}
Claims:
{
"exp": 4745145071,
"group": "admin",
"iat": 1591545071,
"iss": "auth@istioinaction.io",
"sub": "218d3fb9-4628-4d20-943c-124281c80e7b"
}
🔧 일반 유저(user)가 webapp에서 데이터를 읽을 수 있도록 AuthorizationPolicy 리소스 설정
$ vim ch9/enduser/allow-all-with-jwt-to-webapp.yaml # vi/vim, vscode 에서 포트 30000 추가
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"]
🔧 관리자(admin) 에게 모든 작업을 허용하는 AuthorizationPolicy 리소스 설정
$ vim 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"] # 이 클레임을 포함한 요청만 허용.
🔧 AuthorizationPolicy 리소스 적용
$ kubectl apply -f ch9/enduser/allow-all-with-jwt-to-webapp.yaml
$ kubectl apply -f ch9/enduser/allow-mesh-all-ops-admin.yaml
🔧 일반 유저(user) 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
결과 (get)
200
$ curl -H "Authorization: Bearer $USER_TOKEN" \
-XPOST webapp.istioinaction.io:30000/api/catalog \
--data '{"id": 2, "name": "Shoes", "price": "84.00"}'
결과 (post)
403
🔧 관리자(admin) get, post 호출
$ 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
결과 (get)
200
$ curl -H "Authorization: Bearer $ADMIN_TOKEN" \
-XPOST webapp.istioinaction.io:30000/api/catalog \
--data '{"id": 2, "name": "Shoes", "price": "84.00"}'
결과 (post)
[{"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"}]
📌 커스텀 외부 인가 서비스와 통합하기
지금까지 SPIFFE를 기반으로 구축된 Istio의 인증 메커니즘이 서비스 인가를 구축할 수 있는 기반을 제공하는 방법을 살펴보았습니다.
이번엔 인가에 좀 더 정교한 커스텀 메커니즘을 추가하는 방법을 알아보겠습니다.
인가에 커스텀 설정을 추가하는 방법은 외부 인가 서비스를 호출 하면 됩니다.
구성을 하면 아래 그림처럼 동작을 하게 됩니다.

✅ 요청 흐름 순서
1. 클라이언트 요청(Request)
사용자의 요청이 Envoy 프록시에 도착합니다.
2. CUSTOM 정책 적용
Envoy는 우선 CUSTOM 정책이 설정되어 있는 경우, 외부의 Authorization 서버에 판단을 위임합니다.
→ 이 Authorization 서버가 "허용(Allow)" 또는 "거부(Reject)" 응답을 반환합니다.
3. ALLOW/DENY 정책 확인
외부 서버가 허용하더라도, Envoy 내에 설정된 DENY 정책이 있으면 여전히 요청을 차단할 수 있습니다.
4. 최종적으로 App 전달
모든 정책이 통과되면 요청이 애플리케이션(App)으로 전달됩니다.
이제 실습을 통해 좀 더 자세히 알아보도록 하겠습니다.
🔧 앞선 설정 초기화
$ 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 샘플에서 샘플 외부 인가 서비스 배포
$ docker exec -it myk8s-control-plane bash
---
🔧 외부 인가 서버 적용
$ 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 istio
🔧 빠져나오기
$ exit
---
🔧 외부 인가 서비스 설치 확인
$ kubectl get deploy,svc ext-authz -n istioinaction
결과
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/ext-authz 1/1 1 1 36s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ext-authz ClusterIP 10.200.1.47 <none> 8000/TCP,9000/TCP 36s
🔧 Istio가 새로운 외부 인가 서비스를 인식하도록 설정
# 이를 위해서 Istio meshconfig 설정에서 extensionProviders 를 설정해야 한다.
$ KUBE_EDITOR="vim" 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
이렇게 설정을 해주면, envoyExtAuthz 서비스의 HTTP 구현체인 sample-ext-authz-http를 인식 하게 됩니다.
이제 커스텀 AuthorizationPolicy 리소스를 사용하여 동작이 되는지 확인해보겠습니다.
🔧 AuthorizationPolicy 생성
$ 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
🔧 호출 전 로그 활성화
$ 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
🔧 헤더 없이 호출
$ kubectl -n default exec -it deploy/sleep -- curl webapp.istioinaction/api/catalog
로그 결과
(webapp Envoy 프록시)
[2025-05-10T15:01:25.642Z] "GET /api/catalog HTTP/1.1" 403 UAEX ext_authz_denied - "-" 0 76 18 16 "-" "curl/8.5.0" "804a3344-6ef5-90a2-8c00-e7f02a610db2" "webapp.istioinaction" "-" inbound|8080|| - 10.10.0.14:8080 10.10.0.4:41290 - -
(ext-authz Envoy 프록시)
2025/05/10 15:01:25 [HTTP][denied]: GET webapp.istioinaction/api/catalog, headers: map[Content-Length:[0] X-B3-Parentspanid:[3db4f6b89f472cef] X-B3-Sampled:[1] X-B3-Spanid:[32186977617a9220] X-B3-Traceid:[080ecf1d24a0fb00d79e3954ac14c507] X-Envoy-Expected-Rq-Timeout-Ms:[600000] X-Envoy-Internal:[true] X-Forwarded-Client-Cert:[By=spiffe://cluster.local/ns/istioinaction/sa/default;Hash=c0676bf16d242383ca31ce2e1975b107f0d75fb70e1c4fbeaec85e9b61475a24;Subject="";URI=spiffe://cluster.local/ns/istioinaction/sa/webapp] X-Forwarded-For:[10.10.0.14] X-Forwarded-Proto:[https] X-Request-Id:[548028d9-c235-986a-a632-2cc42a5ca381]], body: []
🔧 헤더 적용 후 호출
$ kubectl -n default exec -it deploy/sleep -- curl -H "x-ext-authz: allow" webapp.istioinaction/api/catalog
로그 결과
(webapp Envoy 프록시)
[2025-05-10T15:03:48.747Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 37 36 "-" "beegoServer" "0da22895-ed4d-9788-8866-02f25cb0deeb" "catalog.istioinaction:80" "10.10.0.13:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.14:47656 10.200.1.117:80 10.10.0.14:36328 - default
[2025-05-10T15:03:48.728Z] "GET /api/catalog HTTP/1.1" 200 - via_upstream - "-" 0 357 58 49 "-" "curl/8.5.0" "0da22895-ed4d-9788-8866-02f25cb0deeb" "webapp.istioinaction" "10.10.0.14:8080" inbound|8080|| 127.0.0.6:57785 10.10.0.14:8080 10.10.0.4:34342 - default
(extAuth Envoy 프록시)
2025/05/10 15:03:48 [HTTP][allowed]: GET webapp.istioinaction/api/catalog, headers: map[Content-Length:[0] X-B3-Parentspanid:[62329a86b4e18a5a] X-B3-Sampled:[1] X-B3-Spanid:[04d43a12530650bf] X-B3-Traceid:[1bccc54cc3adaf3522127297d2bad053] X-Envoy-Expected-Rq-Timeout-Ms:[600000] X-Envoy-Internal:[true] X-Ext-Authz:[allow] X-Forwarded-Client-Cert:[By=spiffe://cluster.local/ns/istioinaction/sa/default;Hash=c0676bf16d242383ca31ce2e1975b107f0d75fb70e1c4fbeaec85e9b61475a24;Subject="";URI=spiffe://cluster.local/ns/istioinaction/sa/webapp] X-Forwarded-For:[10.10.0.14] X-Forwarded-Proto:[https] X-Request-Id:[0092d3f0-27e0-95d1-ace1-2da21077f03e]], body: []
이렇게 커스텀 정책을 사용하는 방법까지 알아보았습니다.
Istio 스터디 5주차의 내용을 요약 하면 아래와 같습니다.
✅ 요약
- PeerAuthentication 은 피어 간 인증을 정의하는 데 사용하며, 엄격한 인증 요구 사항을 적용하면 트래픽이 암호화돼 도청할 수 없다.
- PERMISSIVE 정책은 이스티오 워크로드가 암호화된 트래픽과 평문 트래픽을 모두 수용할 수 있게 해서 다운타임 없이 천천히 마이그레이션할 수 있도록 해준다.
- AuthorizationPolicy 는 워크로드 ID 인증서나 최종 사용자 JWT에서 추출한 검증 가능한 메타데이터를 근거로 서비스 사이의 요청이나 최종 사용자의 요청을 인가(허용, 차단)하는 데 사용한다.
- RequestAuthentication 은 JWT가 포함된 최종 사용자 요청을 인증하는 데 사용한다.
- AuthorizationPolicy 에서 CUSTOM action을 사용하면 외부 인가 서비스를 통합할 수 있다.
이상 입니다.
감사합니다.
'스터디 > Istio' 카테고리의 다른 글
Istio 스터디 5주차 - <마이크로서비스 통신 보호 1부> (0) | 2025.05.08 |
---|---|
Istio 스터디 4주차 - <Observability 시각화> (1) | 2025.05.03 |
Istio 스터디 4주차 - <Observability> (0) | 2025.04.30 |
Istio 스터디 3주차 - <Resilience> (0) | 2025.04.25 |
Istio 스터디 3주차 - <Traffic Control> (0) | 2025.04.23 |