[Istio] 보안 (Securing - 2)

xgro·2025년 5월 10일
0

Istio

목록 보기
10/17
post-thumbnail

📌 Notice

Istio Hands-on Study (=Istio)
직접 실습을 통해 Isito를 배포 및 설정하는 내용을 정리한 블로그입니다.

CloudNet@에서 스터디를 진행하고 있습니다.

Gasida님께 다시한번 🙇 감사드립니다.

EKS 관련 이전 스터디 내용은 아래 링크를 통해 확인할 수 있습니다.



📌 Overview

이 블로그는 앞선 [Istio] 보안 (Securing - 1)에 이어, Istio에서 외부 인가 시스템과 통합하는 방식을 다룹니다.
앞 단계에서는 JWT 기반으로 최종 사용자를 검증하고, 클레임에 따라 역할별 접근 제어를 구성했습니다.

이번 포스팅에서는 한 발 더 나아가, Istio가 요청을 수락할지 결정하기 위해 외부 인가 서비스(ExtAuthz)를 호출하는 구조를 실습합니다.

이를 통해 다음과 같은 실전 시나리오에 대비할 수 있습니다:

  • 내부 RBAC 정책만으로는 표현할 수 없는 복잡한 인가 로직 대응
  • 조직 외부에 위치한 정책 서버 또는 중앙 정책 엔진과의 연동
  • 헤더, 사용자 컨텍스트 등 다양한 정보를 바탕으로 인가 결정 위임

Istio의 extensionProvidersAuthorizationPolicyCUSTOM 액션을 조합해
프록시 레벨에서 인가 로직을 외부화하고 세밀하게 제어할 수 있는 실습을 진행합니다.

📌 마이크로서비스 통신 보안

👉 Step 04. 최종 사용자 인증 및 인가

최종 사용자 인증 및 인가 기능을 다루기 전, 다음과 같은 핵심 사전 개념을 이해할 필요가 있습니다.

Service Account Token Volume Projection

ServiceAccount의 자격 증명을 기존의 시크릿 기반 볼륨 대신 projected volume을 사용해 보다 정교하게 제어할 수 있습니다. 이 기능을 활용하면 토큰의 audience, expirationSeconds 등 다양한 속성을 지정할 수 있으며, 보안성과 유연성을 동시에 확보할 수 있습니다.

volumes:
- name: vault-token
  projected:
    sources:
    - serviceAccountToken:
        path: vault-token
        expirationSeconds: 7200
        audience: vault

또한 Kubernetes는 기본적으로 Bound Service Account Token Volume 기능을 통해 token, ca.crt, namespace를 포함한 projected volume을 자동으로 생성해 파드에 마운트합니다.


Configure a Pod to Use a Projected Volume for Storage

projected 볼륨 타입은 secret, configMap, downwardAPI, serviceAccountToken을 하나의 경로에 통합해 마운트할 수 있습니다. 아래는 username.txtpassword.txt를 각각 시크릿으로 만들어 projected 볼륨으로 마운트한 예입니다.

kubectl create secret generic user --from-file=./username.txt
kubectl create secret generic pass --from-file=./password.txt
kubectl apply -f projected-volume.yaml

이렇게 구성된 파드에서 파일을 직접 확인할 수 있으며, 시크릿 값이 잘 마운트되었는지 검증할 수 있습니다.


Kubernetes API 접근 흐름

Kubernetes API 서버 접근은 다음과 같은 흐름으로 진행됩니다:

  • AuthN → AuthZ → Admission Control
  • Admission 단계에서는 MutatingWebhook 또는 ValidatingWebhook을 통해 오브젝트를 수정하거나 검증할 수 있습니다.
kubectl get mutatingwebhookconfigurations
kubectl get validatingwebhookconfigurations

MutatingWebhook은 요청을 변형하고, ValidatingWebhook은 요청을 거부할 수 있습니다. 이를 통해 클러스터 내에서 정책을 동적으로 통제할 수 있습니다.


JWT (JSON Web Token)

JWT는 X.509 인증서를 경량화한 JSON 포맷의 토큰으로, 세 부분(Header, Payload, Signature)으로 구성됩니다.

  • Header: 알고리즘 및 토큰 타입 명시
  • Payload: 실제 전송 데이터 (ex: 사용자 ID, 권한)
  • Signature: 데이터 변조 방지를 위한 서명

JWT는 Bearer 타입으로 Authorization 헤더에 포함되어 전송되며, Istio의 최종 사용자 인증에서 주로 활용됩니다.


OIDC (OpenID Connect)

OIDC는 OAuth 2.0에 기반한 인증 프로토콜로, 최종 사용자 인증을 위해 사용됩니다. 사용자 ID 확인을 위해 IdP (Identity Provider, 예: Google, Kakao)가 인증 토큰을 발급하고, RP (Relying Party)는 이 토큰을 검증해 사용자의 신원을 파악합니다.

OIDC는 인증(ID Token)과 인가(Access Token)의 두 기능을 통합하고 있으며, 다음과 같은 정보를 포함합니다:

  • iss: 토큰 발급자
  • sub: 사용자 고유 식별자
  • email: 사용자 이메일
  • exp: 만료 시간
  • aud: 발급 대상 클라이언트

이러한 구조를 통해 최종 사용자 기반의 인증/인가를 Istio 환경에서도 확장 가능하게 구성할 수 있습니다.


✅ JSON 웹 토큰이란 무엇인가?

Istio는 최종 사용자 인증과 인가를 위해 JWT (JSON Web Token)를 지원합니다. 본 절에서는 JWT의 구조와 검증 방식, 그리고 Istio 환경에서의 활용에 앞서 기본 개념을 정리합니다.

JWT는 클라이언트를 서버에 인증하는 데 사용하는 간단한 클레임 표현으로, 다음과 같은 세 부분으로 구성됩니다:

  • Header: 토큰의 타입과 해싱 알고리즘 명시
  • Payload: 사용자 클레임을 포함
  • Signature: JWT의 진위를 확인하기 위한 서명

각 부분은 Base64 URL로 인코딩되며, 점(.)으로 구분되어 HTTP 요청에서 사용하기 적합합니다.

# JWT 토큰 확인
cat ./ch9/enduser/user.jwt

# 디코딩 방법 1
jwt decode $(cat ./ch9/enduser/user.jwt)

# 디코딩 방법 2
cat ./ch9/enduser/user.jwt | cut -d '.' -f2 | base64 --decode | jq

디코딩된 JWT 페이로드 예시는 다음과 같습니다:

{
  "exp": 4745145038,
  "group": "user",
  "iat": 1591545038,
  "iss": "auth@istioinaction.io",
  "sub": "9b792b56-7dfa-4e4b-a83f-e20679115d79"
}

이 페이로드는 사용자의 ID와 권한을 나타내는 클레임(claim)으로 구성되며, 이를 통해 서비스는 요청 주체의 신원을 식별하고 인가 여부를 판단할 수 있습니다.

JWT는 어떻게 발행되고 검증되는가?

JWT는 일반적으로 인증 서버에서 발급되며, 이 서버는 다음을 포함합니다:

  • Private key: JWT를 서명하는 데 사용
  • Public key: JWT를 검증하는 데 사용, 일반적으로 JWKS (JSON Web Key Set) 형태로 HTTP 엔드포인트에 노출

서버 측에서는 클라이언트가 제시한 JWT를 다음과 같은 순서로 검증합니다:

  1. JWT의 서명은 인증 서버의 private key로 생성됩니다.
  2. 서비스는 인증 서버가 제공하는 JWKS 엔드포인트에서 public key를 가져옵니다.
  3. public key로 서명을 복호화하여 페이로드의 해시값과 비교합니다.
  4. 값이 일치하면 토큰의 클레임을 신뢰할 수 있습니다.

인증 서버는 다음과 같은 방식으로 구현할 수 있습니다:

  1. 직접 구현한 애플리케이션 백엔드
  2. Keycloak, OpenIAM 등 자체 인증 솔루션
  3. Auth0, Okta 같은 SaaS 형태의 인증 서비스

이러한 구조를 통해 Istio는 최종 사용자 인증을 서비스 프록시 차원에서 처리하며, JWT 기반의 클레임을 바탕으로 세밀한 접근 제어를 가능하게 합니다.


✅ 인그레스 게이트웨이에서의 최종 사용자 인증 및 인가

Istio는 JWT(JSON Web Token)를 활용해 최종 사용자 인증과 인가를 수행할 수 있으며, 이 기능은 워크로드 단위뿐 아니라 인그레스 게이트웨이 수준에서도 적용할 수 있습니다.

최종 사용자는 일반적으로 ID 제공자(IdP)로부터 인증을 받고, 신원을 나타내는 토큰(JWT)을 발급받은 사용자입니다. 이 토큰은 클레임 기반의 정보를 담고 있으며, 이를 기반으로 인가 정책을 정의할 수 있습니다.

최종 사용자 인증과 인가를 인그레스 게이트웨이에서 수행하는 이유는 다음과 같습니다:

  • 유효하지 않은 요청을 게이트웨이에서 조기 차단하여 백엔드 워크로드의 부하를 줄이고 성능을 향상시킬 수 있습니다.
  • JWT를 게이트웨이에서 제거하면, 내부 마이크로서비스 간 통신에서의 토큰 유출 위험을 줄이고, 재전송 공격(replay attack)을 방지할 수 있습니다.

실습 환경 준비

기존 설정을 정리하고, 실습에 필요한 웹앱 및 카탈로그 서비스를 배포합니다.

# 기존 리소스 정리
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으로 JWT 검증하기

Istio에서 최종 사용자 인증을 다룰 때 가장 핵심이 되는 리소스 중 하나가 바로 RequestAuthentication입니다. 이 리소스는 JWT를 검증하고, 유효한 토큰이라면 클레임 정보를 추출하여 필터 메타데이터에 저장합니다.

이렇게 저장된 메타데이터는 후속 AuthorizationPolicy에서 인가를 위한 조건으로 활용됩니다.


JWT 검증의 흐름

요청이 들어오면 다음과 같은 세 가지 경우 중 하나로 처리됩니다:

  • 유효한 JWT가 있는 요청: 필터 메타데이터에 클레임이 저장되고, 후속 인가 정책에서 활용됩니다.
  • 유효하지 않은 JWT가 있는 요청: 요청은 즉시 거부됩니다.
  • JWT가 없는 요청: 요청 자체는 허용되지만, 어떠한 ID도 추론할 수 없어 인가 조건을 만족하지 못할 수 있음.

즉, RequestAuthentication 리소스는 인가를 강제하지 않고, JWT의 유효성을 검증하고 클레임 데이터를 추출하는 역할만 담당합니다.

인가를 수행하려면 반드시 별도의 AuthorizationPolicy 리소스가 필요합니다.


필터 메타데이터란?

  • Envoy 기반의 Istio 서비스 프록시에서 요청 흐름을 제어할 때 내부적으로 사용되는 key-value 형태의 구조입니다.
  • 예를 들어 group: admin 클레임이 포함된 JWT가 인증되면, 해당 값은 필터 메타데이터에 저장되며 AuthorizationPolicy에서 "request.auth.claims[groups]" == "admin" 와 같은 형태로 참조할 수 있게 됩니다.

이제 다음 단계에서는 실제 RequestAuthentication 리소스를 정의하고, 다양한 시나리오에서 JWT 유무에 따른 처리 결과를 실습을 통해 확인해보겠습니다.


✅ RequestAuthentication 리소스로 JWT 검증하기

이번 단계에서는 Istio 인그레스 게이트웨이에서 JWT 토큰을 검증하기 위한 RequestAuthentication 리소스를 구성합니다. 이 리소스를 통해 Istio는 유효한 토큰의 클레임을 추출하고, 이를 기반으로 후속 인가 정책이 동작할 수 있는 메타데이터를 생성합니다.

🔐 인증 흐름 정리

  • RequestAuthentication은 토큰이 유효한지 검증만 수행합니다.
  • 인가 여부 판단은 별도의 AuthorizationPolicy 리소스가 수행합니다.
  • 검증된 JWT의 클레임 정보는 Envoy 필터 메타데이터에 저장됩니다.
  • 이 메타데이터는 이후 인가 조건에서 활용됩니다.

실습: 인그레스 게이트웨이용 RequestAuthentication 생성

# 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"
    jwks: |
      { "keys":[ { "e":"AQAB", "kid":"CU-ADJJEbH9bXl0tpsQWYuo4EwlkxFUHbeJ4ckkakCM", "kty":"RSA", "n":"..." } ]}
# 리소스 적용
kubectl apply -f ch9/enduser/jwt-token-request-authn.yaml
kubectl get requestauthentication -A

Envoy 필터 설정 확인

# 인그레스 게이트웨이에 적용된 필터 확인
docker exec -it myk8s-control-plane \
  istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json

이 명령을 통해 envoy.filters.http.jwt_authn 필터가 정상적으로 구성되었는지, 지정된 issuer와 JWKS가 포함되어 있는지 확인할 수 있습니다.


이제 인그레스 게이트웨이는 auth@istioinaction.io에서 발급된 JWT가 첨부된 요청만 처리할 준비가 되었습니다.


✅ 유효한 발행자의 토큰이 있는 요청은 받아들여진다

이번 실습에서는 유효한 JWT를 포함한 요청이 Istio 인그레스 게이트웨이를 통해 정상적으로 처리되는지 확인합니다.

현재 RequestAuthentication 리소스만 적용된 상태이므로, 별도의 AuthorizationPolicy가 없다면 요청은 기본적으로 허용됩니다.

유효한 토큰으로 요청 테스트

# JWT 토큰 확인
cat ch9/enduser/user.jwt
USER_TOKEN=$(< ch9/enduser/user.jwt)
jwt decode $USER_TOKEN
# 인그레스를 통해 웹앱 호출
curl -H "Authorization: Bearer $USER_TOKEN" \
     -sSl -o /dev/null -w "%{http_code}" \
     webapp.istioinaction.io:30000/api/catalog
# 결과: 200
# 인그레스 게이트웨이 로그 확인
docker exec -it myk8s-control-plane \
  istioctl proxy-config log deploy/istio-ingressgateway -n istio-system --level rbac:debug

kubectl logs -n istio-system -l app=istio-ingressgateway -f

요청 헤더에 JWT를 포함하면, RequestAuthentication 리소스에 의해 해당 토큰이 검증됩니다.

현재는 AuthorizationPolicy가 설정되어 있지 않기 때문에 검증된 요청은 기본적으로 허용(Allow) 됩니다.

이후 단계에서는 클레임 정보 기반 인가 정책을 추가하여 보다 정교한 접근 제어를 수행할 수 있습니다.


✅ 유효하지 않은 발행자의 토큰이 있는 요청은 거부된다

이번 실습에서는 RequestAuthentication 리소스에 등록되지 않은 발급자(issuer)를 사용한 JWT를 포함해 요청을 보냈을 때, Istio가 해당 요청을 어떻게 처리하는지 확인합니다.

🧪 유효하지 않은 토큰으로 요청 테스트

# 잘못된 issuer를 가진 JWT 확인
cat ch9/enduser/not-configured-issuer.jwt
WRONG_ISSUER=$(< ch9/enduser/not-configured-issuer.jwt)

# 디코딩
jwt decode $WRONG_ISSUER
{
  "exp": 4745151548,
  "group": "user",
  "iat": 1591551548,
  "iss": "old-auth@istioinaction.io",  # 설정된 issuer와 다름
  "sub": "79d7506c-b617-46d1-bc1f-f511b5d30ab0"
}
# 호출
curl -H "Authorization: Bearer $WRONG_ISSUER" \
     -sSl -o /dev/null -w "%{http_code}" \
     webapp.istioinaction.io:30000/api/catalog
# 결과: 401 Unauthorized
# 인그레스 게이트웨이 로그 확인
kubectl logs -n istio-system -l app=istio-ingressgateway -f
[2025-05-04T06:36:22.089Z] "GET /api/catalog HTTP/1.1" 401 - jwt_authn_access_denied{Jwt_issuer_is_not_configured} ...

토큰의 iss(issuer)가 현재 RequestAuthentication 리소스에 명시된 값과 다르면, Istio는 해당 요청을 401 Unauthorized 상태로 거부합니다.

로그에서 jwt_authn_access_denied{Jwt_issuer_is_not_configured} 메시지를 통해 원인을 확인할 수 있습니다.

이 과정을 통해 잘못된 토큰이 서비스 메시 내부로 유입되는 것을 조기에 차단할 수 있습니다.


✅ 토큰이 없는 요청은 클러스터로 받아들여진다

이번 실습에서는 JWT 토큰 없이 인그레스 게이트웨이를 통해 서비스를 호출할 경우, Istio가 요청을 어떻게 처리하는지 확인합니다.

토큰 없이 호출

# Authorization 헤더 없이 요청
curl -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
# 예시 결과: 200
# 인그레스 게이트웨이 로그 확인
kubectl logs -n istio-system -l app=istio-ingressgateway -f

응답 코드 200 OK는 토큰 없이 보낸 요청이 클러스터로 정상 전달되었음을 의미합니다.

일반적인 보안 상식으로는 "토큰이 없으면 접근이 거부된다"고 생각할 수 있지만, 현재 RequestAuthentication 리소스는 토큰 검증만 수행할 뿐, 토큰이 없을 경우 이를 거부하지는 않습니다.

실제 웹 환경에서는 사용자 로그인 전의 요청처럼 토큰이 없는 요청이 존재하므로, Istio는 이를 유연하게 허용하도록 기본 동작을 구성하고 있습니다.

토큰이 없는 요청을 명시적으로 거부하려면, AuthorizationPolicy를 통해 정책을 추가로 정의해야 합니다.


✅ JWT가 없는 요청 거부하기

JWT가 없는 요청을 거부하려면 명시적으로 DENY 정책을 설정해야 합니다. 아래 AuthorizationPolicy는 JWT가 없는 요청(requestPrincipals가 없는 요청)을 거부합니다. notRequestPrincipals 속성은 해당 클레임이 없는 경우를 식별합니다.

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: ["*"]
    to:
    - operation:
        hosts: ["webapp.istioinaction.io:30000"]
kubectl apply -f ch9/enduser/app-gw-requires-jwt.yaml

#
kubectl get AuthorizationPolicy -A
NAMESPACE      NAME                  AGE
istio-system   app-gw-requires-jwt   2m14s

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

# 호출 1
curl -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
403

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

# 로그
kubectl logs -n istio-system -l app=istio-ingressgateway -f
[2025-05-04T07:04:01.791Z] "GET /api/catalog HTTP/1.1" 403 - rbac_access_denied_matched_policy[ns[istio-system]-policy[app-gw-requires-jwt]-rule[0]] - "-" 0 19 0 - "172.18.0.1" "curl/8.7.1" "41678cf6-6ef8-986e-beb4-4e5af46e7a26" "webapp.istioinaction.io:30000" "-" outbound|80||webapp.istioinaction.svc.cluster.local - 10.10.0.5:8080 172.18.0.1:65424 - -

이제 토큰이 없는 요청은 모두 403으로 거부됩니다. 이후, 필요한 요청만 명시적으로 허용하는 ALLOW 정책을 추가해 운영 정책을 구성합니다.


✅ JWT 클레임에 기반한 다양한 접근 수준

JWT 클레임을 기반으로 일반 사용자와 관리자에게 서로 다른 권한을 부여할 수 있습니다. 아래는 두 개의 JWT 예시입니다.

  • 일반 사용자 토큰:
    {
      "group": "user",
      "iss": "auth@istioinaction.io"
    }
  • 관리자 토큰:
    {
      "group": "admin",
      "iss": "auth@istioinaction.io"
    }

1. 일반 사용자에게 GET 요청만 허용

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/*"]
    to:
    - operation:
        hosts: ["webapp.istioinaction.io:30000"]
        methods: ["GET"]

2. 관리자에게 모든 작업 허용 (그룹 클레임 조건)

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"]

호출 결과 요약:

  • 일반 사용자:
    • GET /api/catalog: 200 OK
    • POST /api/catalog: 403 Forbidden
  • 관리자:
    • GET /api/catalog: 200 OK
    • POST /api/catalog: 200 OK

이처럼 JWT의 클레임을 활용하면 사용자 유형에 따라 정밀한 권한 분리가 가능합니다.



👉 Step 05. 커스텀 외부 인가 서비스와 통합하기

✅ 외부 인가 서비스 호출

Istio는 기본적으로 Envoy 프록시의 RBAC 필터를 통해 인증된 주체에 대한 인가 정책을 정의할 수 있습니다. 그러나 더 정교한 인가 로직이 필요할 경우, 서비스 프록시가 외부 인가 서비스(External Authorization, ExtAuthz)를 호출하도록 구성할 수 있습니다.

외부 인가 흐름은 다음과 같습니다:

  • Envoy 프록시로 요청이 유입되면, 프록시는 외부 인가 서비스에 CheckRequest API를 통해 요청 정보를 전달합니다.
  • 외부 인가 서비스는 이 정보를 기반으로 정책 판단을 수행하고, 요청을 허용(allow)하거나 거부(deny)하는 응답을 반환합니다.
  • 이 응답에 따라 프록시는 요청을 계속 전달하거나 차단합니다.

외부 인가 서비스는 메시 내부 또는 외부에 배치될 수 있으며, 다음과 같은 대표적인 구현체를 사용할 수 있습니다:

외부 인가 서비스를 구현하려면 Envoy의 CheckRequest API를 지원해야 합니다. 예시 구현은 go-control-plane 프로젝트를 참고하면 됩니다.

ExtAuthz의 성능 트레이드오프

외부 인가 방식은 요청이 프록시를 통과하기 전에 외부 인가 서비스를 반드시 거쳐야 하므로, 네트워크 호출에 따른 지연(latency)이 발생합니다.
Istio의 내장 인가 기능은 대부분의 상황에서 유연하고 성능적으로 우수하지만, 보안 또는 규정상 외부 판단 로직이 필요한 경우에만 ExtAuthz를 사용해야 하며, 이때는 성능 트레이드오프를 반드시 고려해야 합니다.
특히, 외부 인가 서비스를 애플리케이션 사이드카와 동일한 네임스페이스에 배치하면 네트워크 지연을 최소화할 수 있습니다.

관련 공식 문서: https://istio.io/latest/docs/tasks/security/authorization/authz-custom/


✅ 외부 인가 실습

Istio의 외부 인가(External Authorization) 기능을 실습을 통해 적용해보는 단계입니다. 이를 통해 Istio가 Envoy의 외부 인가 필터와 통합되어 요청을 허용 또는 거부하는 방식에 대해 명확히 이해할 수 있습니다.

실습 환경 초기화 및 구성

먼저 기존에 적용된 인증 및 인가 관련 리소스를 삭제하고, 필요한 워크로드와 Istio 샘플 외부 인가 서비스를 배포합니다.

# 기존 인증/인가 정책 제거
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에서 제공하는 샘플 외부 인가 서비스 ext-authz를 배포합니다. 이 서비스는 단순히 x-ext-authz: allow 헤더가 포함된 요청만 허용합니다.

# Docker 컨트롤 플레인에서 배포 파일 확인 및 적용
docker exec -it myk8s-control-plane bash

ls -l istio-$ISTIOV/samples/extauthz/

# 예시 파일 확인
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 서비스는 Envoy의 외부 인가 필터를 통해 연동되며, 요청 헤더에 x-ext-authz: allow가 포함되어 있으면 요청을 허용합니다. 해당 헤더가 없으면 요청은 거부됩니다.

이 샘플은 단순한 구조이지만, 실무에서는 Open Policy Agent(OPA), Keycloak, Auth0 등의 외부 인가 서버와 연동해 더욱 복잡한 정책을 집행할 수 있습니다.


### ✅ 이스티오에 외부 인가 설정하기

Istio가 외부 인가 서비스(ExtAuthz)를 인식하고 연동할 수 있도록 구성하기 위해서는 meshconfig 설정의 extensionProviders 항목에 해당 서비스를 명시해야 합니다.

이 설정은 istio-system 네임스페이스의 ConfigMap 리소스인 istio에 존재합니다.

외부 인가 서비스 연동 설정

먼저, Istio가 HTTP 기반 외부 인가 서비스를 사용할 수 있도록 ConfigMap을 수정합니다. 이 설정은 샘플 서비스 ext-authz.istioinaction.svc.cluster.local을 대상으로 구성되며, 요청 헤더 중 x-ext-authz를 전달하도록 지정합니다.

# ConfigMap 수정 (에디터 사용)
KUBE_EDITOR="nano" kubectl edit -n istio-system cm istio

아래와 같이 extensionProviders 항목을 추가합니다:

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

  • sample-ext-authz-http는 Envoy의 HTTP 기반 외부 인가 필터를 사용합니다.
  • ext-authz.istioinaction.svc.cluster.local:8000 서비스로 요청을 보내 인가 여부를 판단합니다.
  • includeRequestHeadersInCheck 옵션을 통해, 요청 시 x-ext-authz 헤더만 외부 인가 서버에 전달되도록 지정했습니다.
  • 샘플 ext-authz 서비스는 이 헤더의 값이 "allow"일 경우 요청을 허용하고, 그 외에는 거부합니다.

✅ 커스텀 AuthorizationPolicy 리소스 사용하기

앞서 Istio 설정에 등록한 sample-ext-authz-http 확장(Envoy HTTP 기반 외부 인가 서비스)을 실제 정책에 적용해보겠습니다.

# 외부 인가 서비스를 사용하는 CUSTOM 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
  provider:
    name: sample-ext-authz-http
  rules:
  - to:
    - operation:
        paths: ["/*"]
EOF

# 적용된 정책 확인
kubectl get authorizationpolicy -n istioinaction

이 정책은 istioinaction 네임스페이스의 webapp 워크로드에 대해 모든 경로 요청을 sample-ext-authz-http 외부 인가 서비스에 위임합니다.


호출 테스트 및 로그 확인

헤더 없이 호출 (거부됨)

kubectl -n default exec -it deploy/sleep -- curl webapp.istioinaction/api/catalog

# 외부 인가 서버 로그:
denied by ext_authz for not found header `x-ext-authz: allow` in the request

# Webapp 프록시 로그:
403 UAEX ext_authz_denied

헤더 포함 호출 (허용됨)

kubectl -n default exec -it deploy/sleep -- curl -H "x-ext-authz: allow" webapp.istioinaction/api/catalog

# 응답 코드: 200 OK
# 외부 인가 서버 로그:

[HTTP][allowed]: GET webapp.istioinaction/api/catalog


AuthorizationPolicy의 CUSTOM 액션을 통해 외부 인가 서버와 연동하면, 서비스 프록시가 요청을 위임하고 인가 여부를 판단할 수 있습니다.

이 예제에서는 요청 헤더에 x-ext-authz: allow가 포함되어야만 요청이 허용됩니다.

외부 인가 서버는 Envoy의 CheckRequest API를 구현해야 하며, 운영 환경에서는 OPA(Open Policy Agent)나 Gloo Edge ExtAuth 등의 솔루션을 사용할 수 있습니다.

Istio 설정에는 extensionProviders를 통해 외부 인가 서버를 mesh에 등록하고, 해당 이름을 AuthorizationPolicy에서 참조합니다.

이와 같이 외부 인가를 사용하면 기존 RBAC 정책만으로는 표현하기 어려운 세밀한 인가 로직을 확장 가능한 방식으로 구현할 수 있습니다.



📌 Conclusion

이번 블로그는 앞서 다뤘던 JWT 기반 인증/인가 흐름을 외부 인가 시스템과의 연동으로 확장한 실전 예제였습니다.

RequestAuthenticationAuthorizationPolicy 조합으로도 많은 제어가 가능하지만, 보다 정밀한 인가 판단이 필요한 경우에는 외부 인가 서비스(ExtAuthz)를 활용하는 것이 효과적입니다.

이번 실습을 통해 Istio가 외부 인가 서비스와 연동하여, 프록시 수준에서 인가 판단을 위임할 수 있는 구조를 확인했습니다.

CUSTOM 액션을 사용하는 AuthorizationPolicy를 통해, 요청 흐름에 외부 판단 로직을 삽입할 수 있으며,
단순 헤더 검사부터 OPA, Keycloak 같은 정책 엔진과의 통합까지 유연하게 확장 가능합니다.

단, 외부 호출에 따른 지연(latency)과 성능 트레이드오프를 고려해, 실시간 요구사항에 따라 신중히 설계해야 합니다.

profile
안녕하세요! DevOps 엔지니어 이재찬입니다. 블로그에 대한 피드백은 언제나 환영합니다! 기술, 개발, 운영에 관한 다양한 주제로 함께 나누며, 더 나은 협업과 효율적인 개발 환경을 만드는 과정에 대해 인사이트를 나누고 싶습니다. 함께 여행하는 기분으로, 즐겁게 읽어주시면 감사하겠습니다! 🚀

0개의 댓글