본문 바로가기
Work/개발 노트

[istio 스터디] 5주차 - 마이크로서비스 통신 보안

by ★용호★ 2025. 5. 11.

애플리케이션 네트워크 보안의 필요성

  • 네트워크 보안에서는 인증, 인가, 전송 데이터 암호화와 같은 요소들이 중요함
    • 인증은 사용자나 서비스의 신원을 확인하는 과정
    • 인가는 인증된 사용자나 서비스가 특정 리소스나 작업에 접근할 수 있는 권한이 있는지 확인하는 절차
    •  Istio에서는 서비스 간 인증과 최종 사용자 인증이라는 두 가지 유형의 인증이 존재함
      • 서비스 간 인증은 인프라 레벨에서 이루어지는 반면, 최종 사용자 인증은 사용자의 ID와 비밀번호 등을 통해 이루어짐
    • 테스트를 위해 Istio install 시 profile을 demo로 지정
      • 기본적으로 디버깅 레벨이 높고 샘플링이 100%로 설정되어 데모 테스트에 적합
      • Istio 프록시 컨테이너에서 TCP 덤프를 직접 가능하게 하기 위해 privileges 권한을 부여하는 옵션 추가
  • 인증(Authentication)은 사용자의 신원을 확인하는 자격증명 과정이고, 인가(Authorization)는 인증된 사용자에게 각기 다른 권한을 부여하는 것

SPIFFE 프레임워크

  • SPIFFE(Secure Production Identity Framework For Everyone)는 고도로 동적이고 다양한 환경에서 워크로드에 안전한 ID를 제공하는 오픈소스 표준
  • Istio는 SPIFFE 프레임워크를 구현한 대표적인 오픈소스
  • SPIFFE는 특히 X.509 SVID, JWT SVID, SDS API 등에서 사용됨
  • 전통적인 온프레미스 환경에서는 고정 IP를 신뢰의 근거로 사용했지만 클라우드나 쿠버네티스 환경에서는 파드(Pod)의 IP가 자주 변경되므로 IP를 신뢰의 근거로 사용할 수 없음
  • 대신 SPIFFE와 같은 프레임워크를 통해 고유한 ID를 제공하여 로그 수집, 통제, 인증 등을 수행

JWT와 최종 사용자 인증

  • JWT(JSON Web Token)는 최종 사용자 인증에 사용되는 자격증명
  • mTLS 기반 통신이 설정된 후에도 추가적인 사용자 인증이 필요할 수 있으며, 이때 JWT를 사용함
    • JWT는 인증 서버에 의해 발급되고 검증됨

마이크로서비스 아키텍처에서의 보안

  • 모놀리식 아키텍처와 달리 마이크로서비스 환경에서는 IP 기반의 신뢰가 어려움
    • 클라우드 환경에서 파드는 자주 재시작되며 IP가 변경될 수 있고, 오토스케일링으로 인스턴스가 증가하면 IP가 계속 바뀌기 때문
    • 마이크로서비스 환경에서는 SPIFFE와 같은 표준을 통해 워크로드에 고유한 ID를 제공하는 것이 중요함

SPIFFE ID(SVID) 구현

SPIFFE ID는 URI 호환 형식으로 구성됨

spiffe://<trust domain>/<path>
  • 여기서 trust domain은 발급자이며 쿠버네티스에서는 보통 cluster.local을 사용함
  • path는 구현자가 지정할 수 있으며, Istio에서는 서비스 어카운트로 채워짐
  • SVID(SPIFFE Verifiable Identity Document)는 X.509 인증서로 인코딩되어 워크로드의 고유한 ID를 제공함

Istio의 보안 구현

Istio는 다음 세 가지 API 리소스를 통해 인증과 인가를 구현함

  1. PeerAuthentication: 서비스 간 트래픽을 인증하는 설정 (mTLS)
  2. RequestAuthentication: 최종 사용자의 JWT 자격증명 검증
  3. AuthorizationPolicy: 인증된 정보를 바탕으로 인가 결정

이 세 가지 리소스는 Envoy 프록시의 필터로 구현됨

  • PeerAuthentication은 Envoy의 mTLS 필터를 설정
  • RequestAuthentication은 JWT 필터를 설정
  • AuthorizationPolicy는 두 인증에서 추출한 메타데이터를 바탕으로 인가를 결정

트래픽 흐름

  1. 트래픽이 들어오면 먼저 Peer 인증과 최종 사용자 인증을 거침
  2. 인증 정보는 메타데이터로 저장됨
  3. Authorization 정책이 메타데이터를 기반으로 인가 결정
  4. 인증과 인가가 모두 통과하면 애플리케이션으로 요청 전달

자동 상호 TLS(mTLS)

  • 자동 상호 TLS(Auto mTLS)는 Istio가 제공하는 강력한 기능으로, 서비스 간 트래픽을 자동으로 암호화하고 인증함
  • Istio에서는 서비스의 트래픽이 기본적으로 암호화되며 상호 인증이 이루어짐
  • Istio CA는 Istio 컨트롤 플레인의 일부로 각 서비스에 고유한 SPIFFE ID를 발급하고, 인증서를 생성하여 제공함
  • 인증서는 만료 기간이 있으며, 자동으로 갱신되고, 이렇게 발급된 인증서를 통해 서비스 간에 안전한 mTLS 통신이 가능해짐

PeerAuthentication 정책 테스트

메시 전체 정책 설정

모든 메시 트래픽에 대해 mTLS를 강제하는 전체 정책 설정:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT
  • 이 정책을 적용하면 메시 내 모든 서비스 간 통신은 mTLS를 사용해야 함
  • mTLS를 지원하지 않는 레거시 워크로드(예: sleep 파드)는 메시 서비스와 통신할 수 없게 됨

Permissive 모드 설정

특정 네임스페이스에 Permissive 모드 설정

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-injection
spec:
  mtls:
    mode: PERMISSIVE
  • Permissive 모드는 암호화된 mTLS 트래픽과 일반 HTTP 트래픽 모두를 허용함
  • 이 설정으로 레거시 워크로드와 메시 서비스 간 통신이 가능해짐

워크로드 특정 정책 설정

보다 세밀한 제어를 위해 특정 워크로드에만 Permissive 모드를 적용할 수 있음

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: webapp-peer-policy
  namespace: istio-injection
spec:
  selector:
    matchLabels:
      app: webapp
  mtls:
    mode: PERMISSIVE
  • 위 예시에서는 app: webapp 레이블이 있는 워크로드에만 Permissive 모드 적용
  • 다른 워크로드(예: catalog)는 여전히 전체 메시 정책(STRICT)을 따름

TCP 덤프로 서비스 간 트래픽 분석

Istio 프록시 컨테이너에 privileges 권한을 부여하면 컨테이너 내에서 직접 TCP 덤프를 실행할 수 있음

kubectl exec deploy/webapp -c istio-proxy -n istio-injection -- sudo tcpdump -i any -s 0 'ip and tcp and (((tcp[12:1] & 0xf0) >> 2) > 0) and not port 53'

 

이 명령어는 DNS 패킷을 제외하고 TCP 페이로드가 0이 아닌 패킷만 캡처함

  1. sleep이 webapp으로 HTTP 요청을 보냄 (평문 통신)
  2. webapp이 catalog에 HTTPS(mTLS) 요청을 보냄 (암호화된 통신으로 내용 확인 불가)
  3. catalog가 webapp에게 HTTPS로 응답
  4. webapp이 sleep에게 HTTP로 응답

이를 통해 mTLS가 활성화된 구간의 통신은 암호화되어 보이지 않고, 평문 통신은 내용을 볼 수 있음을 확인할 수 있음

워크로드 ID 검증

인증서 확인

Istio는 각 워크로드에 SPIFFE ID가 포함된 X.509 인증서를 발급함. 아래 명령으로 인증서 확인

kubectl exec deploy/webapp -c istio-proxy -n istio-injection -- openssl s_client -connect catalog.istio-injection:3000 -CAfile /etc/certs/root-cert.pem

# 디코딩 결과 예시
X509v3 extensions:
    X509v3 Key Usage: critical
        Digital Signature, Key Encipherment
    X509v3 Extended Key Usage: 
        TLS Web Server Authentication, TLS Web Client Authentication
    X509v3 Basic Constraints: critical
        CA:FALSE
    X509v3 Subject Alternative Name: critical
        URI:spiffe://cluster.local/ns/istio-injection/sa/catalog
  • 위 명령어로 webapp에서 catalog 서비스로 TLS 연결을 시도하고, 인증서 체인을 확인함
  • 이를 통해 인증서에 SPIFFE ID(spiffe://cluster.local/ns/istio-injection/sa/catalog)가 포함되어 있음을 확인할 수 있음
    • 이 ID는 서비스의 네임스페이스와 서비스 어카운트 정보를 포함

인증서 서명 확인

인증서의 신뢰성을 검증하기 위해 Istio CA의 서명을 확인할 수 있음

kubectl exec deploy/webapp -c istio-proxy -n istio-injection -- openssl verify -CAfile /etc/certs/root-cert.pem /tmp/cert.pem
  • "OK"가 반환되면 인증서가 Istio CA에 의해 서명되었으며 신뢰할 수 있음을 의미

PKI(Public Key Infrastructure) 기반 인증

  • PKI는 디지털 인증서와 공개키/개인키 쌍을 사용하여 인증과 암호화를 지원하는 프레임워크
  • X.509는 공개키 인증서의 표준 형식으로, TLS 프로토콜에서 인증과 암호화에 사용됨

TLS 핸드셰이크 과정:

  1. 클라이언트와 서버가 서로 Hello 메시지를 교환
  2. 서버가 인증서를 제공하고 클라이언트가 검증
  3. 암호화 알고리즘과 대칭키를 협상
  4. 대칭키를 사용하여 암호화된 통신 시작

인증서는 통신 자체를 암호화하지 않고, 안전한 대칭키를 교환하는 데 사용됨 (실제 데이터 암호화는 대칭키를 사용하여 이루어짐)

SPIFFE 프레임워크 심화

SPIFFE는 다음 구성 요소로 이루어집니다:

  • SPIFFE ID: URI 형식의 워크로드 식별자 (spiffe://<trust domain>/<path>)
  • 워크로드 API: 컨트롤 플레인의 구성 요소로, 인증서 서명 요청(CSR)을 처리
  • 워크로드 엔드포인트: 데이터 플레인의 구성 요소로, 워크로드의 ID를 증명

워크로드 ID 부트스트랩 과정

  1. 쿠버네티스가 파드의 서비스 어카운트에 토큰 발급 (/var/run/secrets/kubernetes.io/serviceaccount/token)
  2. 토큰과 함께 Istio CA에 CSR 제출
  3. Istio CA는 TokenReview API를 사용하여 쿠버네티스 API 서버에 토큰 유효성 확인
  4. 토큰이 유효하면 Istio CA가 SPIFFE ID가 포함된 인증서 발급
  5. 파일럿 에이전트가 유닉스 도메인 소켓을 통해 SDS API로 Envoy 프록시에 인증서 전달
  6. Envoy 프록시는 이 인증서를 사용하여 mTLS 통신 수행

서비스 어카운트와 토큰 관리

  • 쿠버네티스의 서비스 어카운트는 애플리케이션 프로세스에 대한 식별자
  • 서비스 어카운트에 발급된 토큰은 일정 시간(기본 1시간) 후 자동으로 갱신되어 보안성이 향상됨
  • 토큰은 다른 플랫폼과의 연동에도 활용될 수 있음
    • 예를 들어, HashiCorp Vault와 같은 시스템과 통합할 때 JWT 토큰을 통한 인증 가능
  • 쿠버네티스는 애플리케이션 파드를 위한 토큰 기반 인증 메커니즘을 제공하여, 개발자가 별도의 인증 코드나 라이브러리 없이도 다양한 서비스와 안전하게 연동할 수 있도록 함

필터 메타데이터와 요청 ID 이해

  • Istio 서비스 메시에서 인증과 인가는 단계적 절차로 이루어짐
  • 먼저 피어 인증(Peer Authentication)이 수행되고, 그 다음 JWT(JSON Web Token) 기반의 최종 사용자 인증이 진행됨
    • 이 과정에서 메타데이터가 수집되며, 이 메타데이터는 AuthorizationPolicy에서 인가 결정을 내릴 때 활용
  • 요청 ID는 필터에 저장된 값으로 표현됨
    • 이 ID는 JWT나 X.509 인증서에서 수집되며, 이러한 소스는 기본적으로 신뢰성이 있음
  • 인증 과정에서 수집된 정보가 필터 메타데이터에 저장되고, 이 메타데이터는 요청 주체(Principal), 네임스페이스(Namespace), 요청 인증 클레임(Claims) 등의 정보를 포함

필터 메타데이터 확인

로깅을 설정한 후 요청을 보내면, 로그에서 필터 메타데이터를 확인 가능

"dynamic_metadata": {
  "istio_authn": {
    "key": "...",
    "groups": ["user"],
    "iat": "...",
    "iss": "testing@secure.istio.io",
    "sub": "..."
  },
  "filter_metadata": {
    "istio_authn": {
      "request.auth.principal": "...",
      "request.auth.audiences": "...",
      "request.auth.claims": {
        "groups": "user"
      }
    }
  }
}

 

  • JWT 인증 필터에서 수집한 메타데이터에는 그룹, 만료 시간, 발급자, 주체 등의 정보가 포함됨
  • 필터 메타데이터는 피어 인증 필터와 JWT 인증 필터 두 곳에서 수집되어 AuthorizationPolicy에서 활용됨

요청 처리 흐름

Istio에서 요청이 처리되는 흐름:

  1. 트래픽이 들어오면 먼저 피어 인증과 JWT 인증을 거침
  2. 인증 정보는 필터 메타데이터로 저장됨
  3. AuthorizationPolicy가 메타데이터를 기반으로 인가 결정
    • 커스텀 인가 필터를 먼저 체크하고, 그 다음 거부(DENY) 인가 필터, 허용(ALLOW) 인가 필터를 순서대로 확인함
    • 만약 어떤 필터에도 매치되지 않는다면 암묵적인 인가 필터를 체크하게 됨
  4. 인가가 통과되면 애플리케이션으로 요청 전달

서비스 트래픽 인가 구현

AuthorizationPolicy는 작업 수행 권한을 체크하는 인가(Authorization) 기능을 담당하며, 이는 사용자의 신원이 확인된 후(인증), 해당 사용자가 특정 작업을 수행할 권한이 있는지 확인하는 단계

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: policy-name
  namespace: default
spec:
  selector:
    matchLabels:
      app: webapp
  action: ALLOW
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/default/sa/sleep"]
    to:
    - operation:
        paths: ["/api/catalog"]
        methods: ["GET"]
  • selector: 정책을 적용할 워크로드 지정
  • action: ALLOW(허용), DENY(거부), CUSTOM(커스텀) 중 선택
  • rules: 요청을 식별하는 규칙 목록
    • from: 소스 지정 (principals, namespaces, IP 블록)
    • to: 목적지 지정 (operations, paths)
    • when: 조건부 적용을 위한 조건

메시 범위 정책 적용

워크로드에 하나 이상의 ALLOW 정책이 적용되면, 모든 트래픽은 기본적으로 거부되며 트래픽을 받으려면 ALLOW 정책에 부합하는 요청이 있어야 함

 

모든 요청을 거부하는 메시 범위 정책:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: istio-system
spec: {}
  • 이 정책을 적용하면 메시 내 모든 요청이 거부됨
    • 빈 spec을 가진 AuthorizationPolicy가 모든 요청을 거부하는 효과가 있음

메시 범위에서 모든 요청을 거부한 후, 특정 네임스페이스의 요청만 허용하는 정책 추가

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-from-default-ns
  namespace: istio-injection
spec:
  action: ALLOW
  rules:
  - from:
    - source:
        namespaces: ["default"]
    to:
    - operation:
        methods: ["GET"]
 
  • default 네임스페이스에서 시작하는 GET 요청만 허용
  • 사이드카 프록시가 없는 워크로드는 신원 확인을 위한 ID가 없기 때문에 이 정책만으로는 사이드카 프록시가 없는 레거시 워크로드의 요청은 허용되지 않음

레거시 워크로드에 사이드카 프록시를 주입하여 문제 해결 가능

kubectl label namespace default istio-injection=enabled
kubectl delete pod sleep
 

미인증 레거시 워크로드의 요청을 허용하는 정책을 추가할 수도 있음

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-unauthenticated
  namespace: istio-injection
spec:
  selector:
    matchLabels:
      app: webapp
  action: ALLOW
  rules:
  - to:
    - operation:
        methods: ["GET"]
  • 이 정책은 특정 워크로드(webapp)에 대해 from 필드를 제거함으로써 미인증 요청도 허용

특정 서비스 어카운트의 요청만 허용하는 정책

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: catalog-viewer
  namespace: istio-injection
spec:
  selector:
    matchLabels:
      app: catalog
  action: ALLOW
  rules:
  - from:
    - source:
        principals: ["spiffe://cluster.local/ns/istio-injection/sa/webapp"]
  • 이 정책은 webapp 서비스 어카운트의 SPIFFE ID를 가진 요청만 catalog 앱에 접근할 수 있도록 함
    • SPIFFE ID는 X.509 인증서에 인코딩되어 있는 서비스 식별자

조건부 정책 적용 (JWT 토큰의 클레임을 조건으로 사용)

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: conditional-policy
spec:
  action: ALLOW
  rules:
  - from:
    - source:
        requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"]
    when:
    - key: request.auth.claims[groups]
      values: ["admin"]
  • JWT 토큰의 groups 클레임이 "admin"인 요청만 허용
  • 비교 표현식(exact, prefix, suffix, presence)을 사용하여 더 복잡한 조건을 만들 수 있음

인가 정책 평가 순서

Istio에서 인가 정책이 평가되는 순서:

  1. 커스텀(CUSTOM) 정책이 있으면 가장 먼저 평가됨
  2. 거부(DENY) 정책이 평가되고, 일치하는 정책이 있으면 요청 거부
  3. 허용(ALLOW) 정책이 평가되고, 일치하는 정책이 있으면 요청 허용
  4. 위 단계에서 일치하는 정책이 없으면:
    • ALLOW 정책이 하나라도 있으면 요청 거부 (암묵적 거부)
    • ALLOW 정책이 없으면 요청 허용 (암묵적 허용)\

(참고) JSON Web Token(JWT)의 기본 개념

JWT는 공개/비공개 키를 사용한 서명 또는 암호화된 데이터를 만들기 위한 표준. JWT는 헤더, 페이로드, 서명 세 부분으로 구성되며, 각 부분은 점(.)으로 구분됨

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT를 디코딩하면 다음과 같은 정보를 확인할 수 있음:

  • 헤더: 알고리즘, 토큰 타입 정보 포함
  • 페이로드(클레임): 토큰에 담긴 정보(발급자, 주체, 만료 시간, 그룹 등)
  • 서명: 토큰의 무결성을 검증하기 위한 부분

JWT는 최종 사용자 인증에 활용되며, Istio에서는 RequestAuthentication 리소스를 통해 JWT 토큰을 검증함

(참고) 최종 사용자 인증과 mTLS의 차이

Istio에서는 두 가지 인증 레이어를 제공:

  1. mTLS(Mutual TLS): 서비스 간 인증을 위한 인프라 레벨 보안 레이어로, 개발팀 입장에서는 VPN과 유사하게 동작
    1. 서비스 메시 내의 모든 통신이 암호화되고 서로 인증됨
  2. JWT 인증: 최종 사용자 인증을 위한 추가 레이어로, 서비스 메시 내에서 탈취된 파드가 중요한 내부 서비스에 접근하는 것을 방지

두 레이어를 함께 사용하면 보안이 강화됨

  • mTLS가 기본 보안 레이어를 제공하고, JWT 인증이 중요한 서비스에 대한 추가 보안을 제공
  • Istio에서는 JWT 인증을 주로 인그레스 게이트웨이에서 처리하는 것을 권장
    • JWT 헤더를 제거하고 내부 서비스에는 검증된 정보만 전달할 수 있어, 재전송 공격을 방지할 수 있음

(참고) 서비스 어카운트 토큰과 볼륨 프로텍션

  • 쿠버네티스에서는 서비스 어카운트 토큰을 안전하게 제공하기 위한 메커니즘이 있음
  • 기존에는 시크릿으로 관리했지만, 토큰의 대상(audience)이나 유효 기간 같은 속성을 지정할 필요성이 생겨 "보호된 볼륨(projected volume)"이라는 개념이 도입됨
  • 바인드된 서비스 어카운트 토큰 볼륨을 통해 이러한 속성을 설정할 수 있으며, 이는 보안을 강화하는 데 중요한 역할을 함
    • 토큰의 자동 갱신, 유효 기간 제한 등의 기능 제공

RequestAuthentication 리소스 구현 및 적용

  • Istio에서는 RequestAuthentication 리소스를 통해 JWT(JSON Web Token) 기반 인증을 구현할 수 있음
  • Ingress Gateway는 클러스터 외부에서 들어오는 트래픽의 진입점으로, 여기에 인증 정책을 적용하는 것이 일반적

먼저 RequestAuthentication 리소스를 생성

apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: jwt-example
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  jwtRules:
  - issuer: "testing@secure.istio.io"
    jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.17/security/tools/jwt/samples/jwks.json"
 
  • selector: Istio Ingress Gateway를 선택
  • jwtRules: JWT 토큰의 발급자(issuer)와 JWKS(JSON Web Key Set) URI를 지정
    • JWKS는 JWT 검증에 필요한 공개키를 제공
  • 리소스를 적용한 후, Envoy 리스너 구성을 확인하면 메타데이터 교환과 JSON Authentication 필터가 추가된 것을 볼 수 있음

JWT 토큰 검증 테스트

유효한 발급자의 토큰 테스트

우선 유효한 발급자(issuer)가 서명한 JWT 토큰을 디코딩하여 확인 ("testing@secure.istio.io"라는 발급자로부터 생성)

curl -s --header "Authorization: Bearer $TOKEN" http://gateway-url:3000/api/catalog | grep "HTTP"
  • 유효한 토큰을 사용하는 경우, 요청은 성공적으로 처리되며 HTTP 200 OK 응답을 받게됨

유효하지 않은 발급자의 토큰 테스트

토큰의 발급자는 "not-configured-issuer"로, RequestAuthentication에서 정의한 발급자와 일치하지 않음

curl -s --header "Authorization: Bearer $INVALID_TOKEN" http://gateway-url:3000/api/catalog | grep "HTTP"
  • 이 경우, "401 JWT Authentication access denied" 오류가 발생함
  • 발급자가 일치하지 않기 때문에 인증 거부

토큰이 없는 요청 테스트

기본적으로 RequestAuthentication은 토큰이 없는 요청을 허용함 (프론트엔드 서비스의 경우 모든 요청에 토큰을 요구하는 것이 현실적이지 않기 때문)

curl -s http://gateway-url:3000/api/catalog | grep "HTTP"
  • 토큰 없이 요청을 보내도 HTTP 200 OK 응답을 받음

더 엄격한 인증 정책 적용

토큰이 없는 요청도 거부하도록 더 엄격한 인증 정책을 만들기 위해 AuthorizationPolicy를 추가

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: app-gateway-require-jwt
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  action: DENY
  rules:
  - from:
    - source:
        notRequestPrincipals: ["*"]
    to:
    - operation:
        hosts: ["webapp.example.com:3000"]
 
  • action: DENY: 규칙에 일치하는 요청을 거부함
  • notRequestPrincipals: ["*"]: JWT 인증을 통과하지 못한(즉, 토큰이 없거나 유효하지 않은) 모든 요청을 대상으로 함
  • hosts: ["webapp.example.com:3000"]: 이 호스트로 들어오는 요청에만 정책을 적용함
  • 이 정책을 적용한 후, 토큰 없이 요청을 보내면 "403" 오류가 발생함
  • JWT 토큰이 없으면 접근이 거부되고, 유효한 토큰을 제공하는 경우에만 요청이 허용됨

JWT 토큰의 클레임을 활용한 다양한 접근 수준 제어

JWT 토큰에는 다양한 클레임(claims)이 포함될 수 있으며, 이를 활용하여 세분화된 접근 제어가 가능함. 아래 예시는 그룹(group) 클레임을 활용해 일반 사용자와 관리자를 구분

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: app-gateway-user-group
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  action: ALLOW
  rules:
  - from:
    - source:
        requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"]
    to:
    - operation:
        hosts: ["webapp.example.com:3000"]
        methods: ["GET"]
    when:
    - key: request.auth.claims[groups]
      values: ["user"]

  - from:
    - source:
        requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"]
    to:
    - operation:
        hosts: ["webapp.example.com:3000"]
    when:
    - key: request.auth.claims[groups]
      values: ["admin"]
  • 일반 사용자(groups: "user")는 GET 메소드만 사용할 수 있음
  • 관리자(groups: "admin")는 모든 HTTP 메소드(GET, POST, PUT 등)를 사용할 수 있음

위 설정을 적용하면:

  • 일반 사용자 토큰으로 GET 요청을 보내면 성공
  • 일반 사용자 토큰으로 POST 요청을 보내면 "403 RBAC: access denied" 오류 발생
  • 관리자 토큰으로는 GET과 POST 모두 성공

JWT 토큰의 클레임은 필드 메타데이터로 추출되어 AuthorizationPolicy에서 참조되며, 이는 PeerAuthentication과 RequestAuthentication의 두 인증 필터에서 수집된 정보를 기반으로 함

외부 인가 서비스 연동

  • 보다 복잡한 인가 로직이 필요한 경우, Istio는 외부 인가 서비스와의 연동을 지원함
  • Envoy의 External Authorization 서비스 필터 플러그인을 통해 구현됨

Istio 메시 구성 업데이트

Istio가 외부 인가 서비스를 인식할 수 있도록 MeshConfig의 extensionProviders 설정 업데이트

apiVersion: v1
kind: ConfigMap
metadata:
  name: istio
  namespace: istio-system
data:
  mesh: |-
    extensionProviders:
    - name: "sample-ext-authz-http"
      envoyExtAuthzHttp:
        service: "ext-authz.default.svc.cluster.local"
        port: "8000"
        includeHeadersInCheck: ["x-ext-authz-allow"]
  • 외부 인가 서비스의 이름, 서비스 주소, 포트를 지정함
  • 인가 체크 시 포함할 헤더 지정 (여기서는 "x-ext-authz-allow")

커스텀 AuthorizationPolicy 생성

외부 인가 서비스를 사용하는 AuthorizationPolicy를 생성

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: ext-authz
  namespace: istio-injection
spec:
  selector:
    matchLabels:
      app: webapp
  action: CUSTOM
  provider:
    name: sample-ext-authz-http
  • action: CUSTOM: 커스텀 인가 처리를 지정
  • provider.name: 사용할 외부 인가 서비스 제공자를 지정

외부 인가 서비스 테스트

헤더 없이 요청을 보내면 외부 인가 서비스는 요청을 거부함

curl -s http://gateway-url:3000/api/catalog # 결과: 403 Denied by ext-authz

 

요청에 특정 헤더를 추가하면 외부 인가 서비스는 요청을 허용함

curl -s -H "x-ext-authz-allow: allow" http://gateway-url:3000/api/catalog # 결과: 200 OK
  • 로그를 확인하면 외부 인가 서비스가 헤더 값을 기반으로 인가 결정을 내리는 것을 확인할 수 있음
  • 예제에서 외부 인가 서비스는 "x-ext-authz-allow" 헤더의 존재 여부만 확인하지만, 실제 환경에서는 더 복잡한 로직을 구현할 수 있음

댓글