Istio로 인증/인가 처리 시도 및 적용 실패 이유 (RequestAuthentication, AuthorizationPolicy)

hynnch2·2025년 8월 19일

새로운 서비스에 인증/인가를 적용하려고 하는데, Istio를 통해 처리하면 애플리케이션 코드 수정 없이 인프라 레벨에서 처리할 수 있다고 해서 공부하게 된 내용입니다.

무심코 "Istio로 토큰을 검증하면 된다"고만 알고 있었는데, 실제로 어떻게 동작하는지, 어떤 리소스를 어떻게 조합해서 써야 하는지 제대로 파악이 안 되어 있어서 정리하고 공유하고자 글을 쓰게 되었습니다.


Q) Istio에서 인증/인가는 어떻게 처리되는 걸까?

서비스에 인증/인가를 적용하려고 할 때, 보통 두 가지 방법이 있습니다.

  1. 애플리케이션 코드에서 직접 검증 로직 구현
  2. Istio 같은 서비스 메시에서 처리

Istio를 사용하면 사이드카 프록시(Envoy)가 요청을 가로채서 토큰 검증과 접근 제어를 수행해줍니다. 애플리케이션은 이미 검증된 요청만 받게 되므로 코드가 깔끔해지고, 인증 로직을 중앙에서 관리할 수 있다는 장점이 있습니다.

근데 Istio 문서를 보니 RequestAuthentication이랑 AuthorizationPolicy라는 두 가지 리소스가 있던데, 이게 각각 뭘 하는 건지, 왜 둘 다 필요한 건지 궁금해졌습니다.


RequestAuthentication

먼저 RequestAuthentication부터 확인해보겠습니다.

apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
  name: jwt-auth
  namespace: my-service
spec:
  selector:
    matchLabels:
      app: my-api
  jwtRules:
  - issuer: "https://xxxx.com"
    jwksUri: "https://xxxx.com/.well-known/jwks.json"

이 리소스는 JWT 토큰의 검증을 담당합니다. issuer와 jwksUri를 설정하면, Envoy 프록시가 들어오는 요청의 Authorization 헤더에서 JWT를 꺼내 서명을 검증합니다.

여기서 중요한 점이 있습니다.

  • 유효한 JWT가 있으면 → 요청 통과 (+ JWT claim 정보가 요청에 추가됨)
  • 잘못된 JWT가 있으면 → 401 Unauthorized
  • JWT가 아예 없으면 → 그냥 통과

마지막 JWT가 없는 경우 그냥 통과하는 게 중요했습니다. 왜 이렇게 동작하는지 잘 몰랐고, 이후에 다른 부분을 확인하면서 그 이유를 알게 되었습니다.

이유는 RequestAuthentication의 역할이 "토큰이 있다면 유효한지 확인"하는 것이기 때문입니다. JWT를 필수로 만들려면 추가 설정이 필요합니다.


AuthorizationPolicy

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: require-jwt
  namespace: my-service
spec:
  selector:
    matchLabels:
      app: my-api
  action: DENY
  rules:
  - from:
    - source:
        notRequestPrincipals: ["*"]

이 리소스는 요청에 대한 접근 제어를 담당합니다. 위 설정은 notRequestPrincipals: ["*"] 조건, 즉 "JWT가 없는 요청"을 DENY하겠다는 의미입니다.

RequestAuthentication을 통과한 요청 중에서, JWT가 있으면 requestPrincipal이 설정되고 없으면 비어있습니다. 이걸 이용해서 JWT 없는 요청을 거부하는 것입니다.

Action 종류와 평가 순서

AuthorizationPolicy에는 네 가지 action이 있습니다.

  • CUSTOM: 외부 인가 시스템에 위임 (가장 먼저 평가)
  • DENY: 매칭되면 거부 (두 번째로 평가)
  • ALLOW: 매칭되면 허용 (세 번째로 평가)
  • AUDIT: 감사 로그 기록 (허용/거부에 영향 없음)

평가 순서가 CUSTOM → DENY → ALLOW 순이라는 게 중요합니다.

그리고 ALLOW 정책의 동작 방식이 좀 독특합니다.

  • ALLOW 정책이 하나도 없으면 → 모든 요청 허용
  • ALLOW 정책이 하나라도 있으면 → 매칭되는 것만 허용, 나머지는 거부

즉, ALLOW 정책을 추가하는 순간 화이트리스트 방식으로 동작하기 시작합니다.


둘을 조합해서 쓰는 이유

여기까지 확인해보니 왜 두 리소스를 같이 써야 하는지 이해가 됩니다.

RequestAuthentication만으로는 JWT가 없는 요청을 막을 수 없습니다. JWT 검증 자체는 "토큰이 있으면 유효한지 확인"하는 역할만 하기 때문입니다.

AuthorizationPolicy를 추가해서 notRequestPrincipals: ["*"] 조건으로 JWT가 없는 요청을 DENY해야 비로소 JWT 인증이 "필수"가 됩니다.

또한 AuthorizationPolicy에서 JWT claim 값을 조건으로 사용할 수 있습니다. 예를 들어 JWT의 issuer가 특정 값인 경우에만 허용한다거나, 특정 claim이 있는 사용자만 특정 경로에 접근하게 하는 세밀한 제어가 가능합니다.

when:
- key: request.auth.claims[iss]
  values: ["https://trusted-issuer.com"]
- key: request.auth.claims[role]
  values: ["admin"]

실제 적용 예시

새로운 서비스에 적용한다고 가정하고 설정을 작성해보겠습니다.

1. JWT 검증 설정

apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
  name: jwt-auth
  namespace: my-service
spec:
  selector:
    matchLabels:
      app: my-api
  jwtRules:
  - issuer: "https://xxxx.com"
    jwksUri: "https://xxxx.com/.well-known/jwks.json"

2. JWT 필수화 (없으면 거부)

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: require-jwt
  namespace: my-service
spec:
  selector:
    matchLabels:
      app: my-api
  action: DENY
  rules:
  - from:
    - source:
        notRequestPrincipals: ["*"]
    to:
    - operation:
        notPaths: ["/health", "/ready"]  # 헬스체크는 제외

3. 세부 접근 제어

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: api-access-policy
  namespace: my-service
spec:
  selector:
    matchLabels:
      app: my-api
  action: ALLOW
  rules:
  # 헬스체크는 누구나 접근 가능
  - to:
    - operation:
        paths: ["/health", "/ready"]
  # 유효한 JWT가 있으면 API 접근 가능
  - from:
    - source:
        requestPrincipals: ["*"]
    to:
    - operation:
        paths: ["/api/*"]
  # 특정 서비스만 내부 API 접근 가능
  - from:
    - source:
        principals: ["cluster.local/ns/other-service/sa/backend"]
    to:
    - operation:
        paths: ["/internal/*"]

적용 전 테스트: Dry-run

실제 적용하기 전에 정책이 의도대로 동작하는지 확인하고 싶다면, dry-run 어노테이션을 사용할 수 있습니다.

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: test-policy
  namespace: my-service
  annotations:
    istio.io/dry-run: "true"  # 실제 차단 없이 로깅만
spec:
  # ... 정책 내용

이렇게 하면 정책이 매칭되는 요청을 실제로 차단하지 않고 로그만 남깁니다. 프로덕션 트래픽에 영향 없이 정책을 테스트할 수 있어서 유용합니다.


결론

RequestAuthentication과 AuthorizationPolicy가 어떻게 동작하는지, 왜 둘을 같이 써야 하는지 확인하려고 글을 쓰면서 공부했는데, Istio의 인증/인가 구조를 좀 더 명확히 이해하게 된 것 같습니다.

  1. RequestAuthentication은 JWT 토큰의 유효성을 검증합니다.

    • 단, JWT가 없는 요청은 기본적으로 통과시킵니다.
    • JWT를 필수로 만들려면 AuthorizationPolicy와 함께 사용해야 합니다.
  2. AuthorizationPolicy는 요청에 대한 접근 제어를 수행합니다.

    • DENY → ALLOW 순으로 평가됩니다.
    • notRequestPrincipals: ["*"]로 JWT 없는 요청을 거부할 수 있습니다.
    • JWT claim 값을 조건으로 세밀한 제어가 가능합니다.
  3. 새로운 서비스에 적용할 때는 dry-run으로 먼저 테스트하고, 헬스체크 경로는 예외 처리하는 게 좋습니다.


실제 적용 - 실패

아쉽게도 우리 프로젝트에서는 적용하지 못했습니다.
현재 우리 서버에서는 최소 3가지 Token 종류가 필요합니다.

  1. Client 토큰 (FE, 각 channel 별)
  2. Server to Server Token
  3. Internal Member Access Token

여기서 1, 2는 우리가 처리할 수 있기 때문에 문제가 되지 않았습니다. 모두 JWT 토큰으로 서명하고, 우리가 제공하는 JWT 공개 서명키를 사용하면 가능했습니다.

다만, 3의 경우 서버의 맨 앞에서 Gateway같은 서비스가 있었고, Member의 AccessToken(uuid)을 가로채서 Internal Token(JWT)로 변경해서 넘기고 있었습니다.

gRPC API 호출 흐름도
Client(FE, uuid) -> Gateway(uuid => JWT Token) -> Internal Server

여기서 Internal Token은 내부에서 처리하고 있기 때문에 공개키를 제공하고 있지 않았고, 각 서버들은 인증되었다고 판단하고 처리합니다. (access token -> jwt 토큰을 변환하는 과정에서 이미 인증함)

즉, 저희는 RequestAuthentication에서 issuer 및 공개키를 등록할 수 없었고, 3)을 통해 들어오는 요청의 경우 401 Error를 반환할 수밖에 없었습니다.

기존 사내 서비스가 제공하고 있던 인증 서비스로 인해 도입은 못했지만, 많이 공부한 계기가 되었습니다.

궁금한 부분이 있다면 질문 남겨주시길 바랍니다. 긴 글 읽어주셔서 감사합니다.


참고 자료

profile
more than yesterday

1개의 댓글

comment-user-thumbnail
2026년 4월 17일

재밌게 읽었습니다. 감사합니다

답글 달기