[Istio] 보안 (Securing - 1)

xgro·2025년 5월 10일
0

Istio

목록 보기
9/17
post-thumbnail

📌 Notice

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

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

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

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



📌 Overview

서비스 메시 환경에서 네트워크 보안은 단순한 암호화를 넘어, 누가 누구에게 무엇을 할 수 있는지를 세밀하게 제어하는 방향으로 진화하고 있습니다.

Istio는 이를 위해 자동 상호 TLS(mTLS)세분화된 인가 정책(AuthorizationPolicy) 을 제공하며, 이 모든 기능을 프록시 레벨에서 투명하게 수행합니다.

이번 블로그에서는 다음과 같은 내용을 단계별 실습과 함께 학습하게 됩니다:

  • 자동 인증서 발급과 회전을 포함한 Auto mTLS의 구조와 작동 원리
  • PeerAuthentication 리소스를 통한 네트워크 암호화 수준 제어
  • AuthorizationPolicy 리소스를 활용한 서비스 간 접근 통제 및 조건 기반 인가 정책 구성
  • 레거시 워크로드와 혼합된 환경에서의 예외 처리 및 보안 타협 관리
  • tcpdump와 envoy 로그를 활용한 실시간 인증/인가 흐름 추적
  • 정책 평가 순서 및 deny-by-default 전략 설계



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

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

Istio는 마이크로서비스 기반 시스템에서 보안 강화의 핵심 요소인 인증(Authentication), 인가(Authorization), 전송 중 데이터 암호화(Data-in-transit Encryption)를 자동화하고 체계화하는 기능을 제공합니다.

이 장에서는 애플리케이션 보안의 기본 개념부터 시작해, Istio가 이를 어떻게 실현하는지까지 다룹니다.

✅ 애플리케이션 보안이란?

애플리케이션 보안은 인가받지 않은 사용자가 중요 데이터를 훔치거나 오염시키지 못하도록 보호하는 모든 행위를 포함합니다.

특히 사용자 데이터를 보호하려면 다음 세 가지가 반드시 필요합니다:
1. 인증(Authentication): 사용자가 누구인지 확인하는 절차입니다. 이는 패스워드, 인증서, 지문 등 사용자가 알고 있거나 소유하고 있거나 타고난 속성을 통해 이뤄집니다.
2. 인가(Authorization): 인증된 사용자가 어떤 작업을 수행할 수 있을지를 판단하고 허용 또는 거부하는 절차입니다.
3. 전송 중 데이터 암호화: 클라이언트에서 서비스를 호출하는 도중, 네트워크 경로상에서 발생할 수 있는 도청을 방지하기 위해 데이터가 암호화되어야 합니다.


✅ 서비스 간 인증 (Service-to-Service Authentication)

서비스 간 통신에서도 인증이 필요합니다. 한 서비스가 다른 서비스를 호출할 때, 단순히 호출한다고 해서 신뢰할 수 있는 것이 아니라, 상대 서비스의 신원을 확인하고 검증해야만 신뢰할 수 있습니다.

이를 위해 Istio는 SPIFFE (Secure Production Identity Framework For Everyone) 라는 프레임워크를 활용합니다.

SPIFFE는 다양한 환경과 경계를 넘나드는 서비스에 대해 신뢰 가능한 짧은 수명의 암호화된 ID 문서(SVID)를 발급하고, 이 문서를 통해 서비스가 서로 TLS 기반 통신이나 JWT 서명 등을 통해 인증할 수 있게 합니다.

참고: https://spiffe.io/docs/latest/spiffe-about/overview


✅ 최종 사용자 인증 (End-User Authentication)

최종 사용자 인증은 웹 애플리케이션의 핵심 보안 기능입니다.

대부분의 인증 시스템은 사용자를 인증 서버로 리다이렉션하고, 로그인 성공 시 JWT나 HTTP 쿠키 등의 자격 증명을 제공합니다.

사용자는 이후 요청 시 이 자격 증명을 서비스에 전달하고, 서비스는 이 자격 증명의 유효성을 인증 서버에 확인합니다.


✅ 인가 (Authorization)

인가 절차는 인증 이후에 진행되며, 사용자가 수행하려는 작업에 대해 허용할지 여부를 판단합니다.

예를 들어, 사용자가 특정 리소스를 읽거나 수정하거나 삭제할 수 있는 권한이 있는지 확인하고 허용 또는 거부합니다.

Istio는 서비스 간 또는 사용자와 서비스 간 통신에 대해 세분화된 인가 정책을 설정할 수 있도록 지원합니다.


✅ 모놀리스 vs 마이크로서비스 보안

모놀리스 애플리케이션은 일반적으로 정적인 인프라에서 실행되며, 고정된 IP 주소를 기반으로 인증서를 설정하거나 방화벽 규칙을 구성하는 것이 용이합니다. 반면 마이크로서비스는 수백에서 수천 개의 동적 서비스로 구성되며, 클라우드 및 컨테이너 환경에서 실행되므로 전통적인 고정 IP 기반 방식은 보안 ID의 근거로 적합하지 않습니다.

Istio는 이러한 환경에서도 안정적인 ID 제공을 위해 SPIFFE 스펙을 기반으로 신뢰 가능한 ID를 제공합니다.


✅ Istio의 SPIFFE 구현 방식

SPIFFE ID는 spiffe://trust-domain/path 형식의 URI로 구성되며, trust-domain은 조직을, path는 조직 내 특정 워크로드를 식별합니다.

Istio는 이 path를 서비스 어카운트로 채워 ID를 부여합니다. 해당 ID는 SVID(X.509 인증서 형식)로 인코딩되며, Istio의 컨트롤 플레인인 Istiod가 각 워크로드에 대해 자동 생성하고 전달합니다.

이렇게 발급된 인증서는 서비스 간 전송 데이터를 암호화하는 데 사용됩니다.


✅ Istio 보안 아키텍처 요약

Istio는 다음 세 가지 커스텀 리소스를 활용해 서비스 메시의 인증 및 인가를 구성합니다.

  • PeerAuthentication: 서비스 간 통신에서 프록시가 상대방의 인증서를 검증하고, 필요한 메타데이터(예: SVID)를 추출합니다.
  • RequestAuthentication: 최종 사용자의 자격 증명을 인증 서버를 통해 확인하며, JWT나 쿠키 등에 인코딩된 정보를 추출합니다.
  • AuthorizationPolicy: 앞에서 추출된 피어 정보 및 사용자 정보를 기반으로 요청을 허가하거나 거부합니다.

이러한 리소스는 다음과 같은 흐름으로 동작합니다:

  1. 프록시가 요청을 수신하면, PeerAuthentication 및 RequestAuthentication 리소스를 통해 인증 수행.
  2. 인증된 정보(SVID 또는 JWT 등)는 필터 메타데이터로 저장되며, 이는 커넥션 ID 역할을 합니다.
  3. AuthorizationPolicy는 이 메타데이터를 기반으로 요청을 인가하거나 거부합니다.

Istio 보안 컴포넌트 구성도

  • Istio CA는 키 및 인증서를 관리하며, 이 인증서의 SAN 필드는 SPIFFE 형식을 따릅니다.
  • Istiod는 전체 사이드카 프록시에 보안 정책(인증/인가)을 배포합니다.
  • 사이드카 프록시는 Istiod에서 배포된 정책에 따라 인증 및 인가를 수행합니다.


👉 Step 02. 자동 상호 TLS (Auto mTLS)

✅ 들어가며

Istio는 서비스 간 통신을 자동으로 암호화하고 인증하는 기능을 제공합니다. 특히 사이드카 프록시가 주입된 서비스들 사이의 트래픽은 기본적으로 암호화되며, 상호 인증이 이루어집니다.

이는 인증서를 수동으로 발급하고 갱신하던 기존 방식에서 자주 발생하던 오류와 서비스 중단을 방지할 수 있는 핵심적인 이점입니다. Istio는 이러한 인증서 발급 및 로테이션 절차를 자동화하여 운영 복잡도를 줄이고 기본적인 보안을 제공합니다.

  • 워크로드는 이스티오 인증 기관에서 발급받은 SVID 인증서를 사용해 상호 인증을 수행합니다.
  • 기본적으로는 안전한 상태를 제공하지만, 보안을 강화하려면 추가 설정이 필요합니다.

왜 기본값이 아니었을까?

서비스 메시가 서로 인증한 트래픽만 허용하도록 설정하는 것은 보안상 매우 중요합니다. 하지만 Istio가 설치 시 기본으로 이 기능을 활성화하지 않은 이유는 다음과 같습니다:

  • 여러 팀이 각자 서비스를 운영하는 대규모 조직에서는 전환 작업이 복잡하고 시간이 오래 걸리기 때문에, 채택을 유도하기 위해 초기 설정을 느슨하게 구성한 것입니다.
  • 점진적인 도입을 고려한 설계 결정입니다.

최소 권한 원칙과 서비스 정책

서비스 간 인증이 가능해지면, 각 서비스는 정책을 기반으로 최소 권한 원칙을 구현할 수 있습니다. 이는 다음과 같은 보안적 이점을 제공합니다:

  • 각 인증서는 특정 서비스의 ID를 대표하므로, 만약 유출되더라도 피해 범위는 그 ID가 접근할 수 있는 서비스로 한정됩니다.
  • 이를 통해 보안 사고 시 피해를 최소화할 수 있습니다.


✅ TLS vs mTLS

TLS란?

TLS(Transport Layer Security)는 네트워크 통신 과정에서 도청, 간섭, 위조를 방지하기 위한 암호화 프로토콜입니다.

기본적으로 다음의 세 단계를 거칩니다:
1. 지원 가능한 알고리즘 교환
2. 키 교환 및 인증
3. 대칭키를 이용한 암호화 및 메시지 인증

자세한 설명:
암호화방식 | 인증서 | TLS Handshake

mTLS란?

mTLS(mutual TLS)는 TLS의 확장 개념으로, 서버뿐 아니라 클라이언트도 인증서를 제공하여 상호 인증을 수행합니다.

클라이언트의 신원을 검증함으로써, 서버는 클라이언트가 신뢰할 수 있는 대상인지 확인할 수 있습니다.

이 방식은 Zero Trust 아키텍처 구현에 적합하며, 클라이언트에 대한 액세스 제어도 가능하게 만듭니다.

자세한 설명:
Cloudflare 설명 | 기초 소개


출처 - https://blog.cloudflare.com/protecting-the-origin-with-tls-authenticated-origin-pulls/



✅ 환경 설정하기 (실습)

mTLS 기능을 실습하기 위해 세 개의 서비스를 준비합니다.

특히 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
...
    spec:
      serviceAccountName: sleep
      containers:
      - name: sleep
        image: governmentpaas/curl-ssl
        command: ["/bin/sleep", "3650d"]
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - mountPath: /etc/sleep/tls
          name: secret-volume
      volumes:
      - name: secret-volume
        secret:
          secretName: sleep-secret
          optional: true

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 워크로드는 governmentpaas/curl-ssl 이미지를 사용하여 평문 통신을 테스트합니다.

📡 기본 통신 확인

kiali에서 default → webapp 구간이 평문으로 표시되는 것을 확인할 수 있습니다. 이는 기본적으로 Istio가 모든 트래픽을 mTLS로 강제하지 않고, 점진적인 메시 도입을 허용한다는 의미입니다.

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

# 반복 요청
watch 'kubectl exec deploy/sleep -c sleep -- curl -s webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"'


✅ 이스티오의 PeerAuthentication 리소스 이해하기

PeerAuthentication 리소스를 사용하면 워크로드 간 통신에서 mTLS를 강제하거나, 평문 트래픽도 허용할 수 있도록 설정할 수 있습니다.

인증 모드는 STRICT, PERMISSIVE, UNSET, DISABLE 네 가지가 있으며, 적용 범위는 메시 전체, 네임스페이스, 워크로드 단위로 다양하게 설정할 수 있습니다.

🔒 메시 범위 정책으로 평문 트래픽 거부하기

메시 전체에 대해 STRICT 모드를 적용하면 모든 평문 트래픽은 거부됩니다. 이 정책은 istio-system 네임스페이스에 default라는 이름으로 생성해야 합니다.

# meshwide-strict-peer-authn.yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT
kubectl apply -f ch9/meshwide-strict-peer-authn.yaml -n istio-system
kubectl exec deploy/sleep -c sleep -- curl -s http://webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"

평문 요청이 차단되고 NR (Non Route)filter_chain_not_found 로그가 나타납니다.

STRICT 정책은 강력하지만, 실무에서는 점진적 전환을 위해 PERMISSIVE 모드를 함께 활용하는 것이 일반적입니다.

상호 인증하지 않은 트래픽 허용하기

특정 네임스페이스에 대해 PERMISSIVE 모드를 적용하면, 메시 전체의 STRICT 정책을 덮어쓰고 HTTP 요청을 허용할 수 있습니다.

cat <<EOF | kubectl apply -f -
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istioinaction
spec:
  mtls:
    mode: PERMISSIVE
EOF
kubectl exec deploy/sleep -c sleep -- curl -s http://webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"

# 다음 실습을 위해서 삭제합니다.
kubectl delete pa default -n istioinaction

이를 통해 sleep → webapp 통신은 허용되고, 다른 워크로드에는 영향을 주지 않습니다.

워크로드 단위로 PERMISSIVE 적용하기

특정 워크로드에만 PERMISSIVE를 적용하고 싶은 경우 selector.matchLabels를 이용해 타겟을 지정합니다.

# 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
kubectl apply -f ch9/workload-permissive-peer-authn.yaml

  • sleep → webapp은 통신 가능하지만, sleep → catalog는 여전히 거부됩니다.
  • 이를 통해 메시 채택이 느린 레거시 서비스에만 유연한 정책을 제공할 수 있습니다.

🧩 기타 인증 모드

모드설명
STRICTmTLS가 설정되지 않은 요청은 거부
PERMISSIVEmTLS와 평문 트래픽 모두 수락
UNSET상위(부모) 정책을 상속
DISABLE프록시 없이 직접 통신, 트래픽 암호화 안됨

tcpdump로 트래픽 스니핑하기

kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy \
  -- sudo tcpdump -l --immediate-mode -vv -s 0 'tcp port 3000 or tcp port 8080'
  • sleep → webapp 요청은 평문(HTTP)
  • webapp → catalog 요청은 암호화된 트래픽(HTTPS)으로 확인됩니다.

SPIFFE ID 인증서 검증하기

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

결과 예시:

  • Subject Alternative Name: spiffe://cluster.local/ns/istioinaction/sa/catalog
  • 이는 서비스 계정 catalog와 일치
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)
# 출력: OK
  • 발급된 인증서가 Istio CA에 의해 서명되었고, 서비스 어카운트 기반의 ID를 포함하고 있음을 확인합니다.



👉 Step 03. 서비스 간 트래픽 인가하기

참고 링크: Istio Authorization Policy 상세 설명 - ssup2 블로그

서비스 메시 환경에서는 인증(Authentication) 이후, 인가(Authorization)를 통해 특정 주체가 리소스에 대해 어떤 작업을 수행할 수 있는지를 결정합니다. 인가는 단순히 인증된 사용자의 신원을 확인하는 것을 넘어, 그 사용자가 무엇을 할 수 있는지를 명확히 정의하는 보안 절차입니다.

예를 들어, 사용자가 단순히 로그인했다고 해서 모든 자원에 접근 가능한 것은 아니며, 리소스 접근, 편집, 삭제 등 작업 수행 권한은 인가 정책을 통해 명시적으로 허용되어야 합니다. 이러한 접근 제어는 서비스 간 보안 강화에 있어 매우 중요한 요소입니다.

Istio는 이를 위해 AuthorizationPolicy라는 리소스를 제공합니다. 이 리소스를 통해 메시 전체, 네임스페이스 범위, 또는 특정 워크로드 단위로 접근 정책을 선언적으로 구성할 수 있습니다.

아래 그림은 특정 ID가 유출되었을 경우에도, 인가 정책을 통해 공격 범위를 도난당한 ID가 접근할 수 있는 자원 범위로만 제한하는 효과를 보여줍니다.

이를 통해 우리는 승인된 ID에 대해서만 최소 권한의 정책을 부여하고, 나머지 트래픽은 차단함으로써, 내부 위협 또는 외부 공격의 확산을 방지할 수 있습니다.

AuthorizationPolicy 리소스는 다음과 같은 형식으로 구성되며, 이 리소스의 구체적인 적용 방법은 실습을 통해 자세히 살펴보겠습니다.



✅ 이스티오에서 인가 이해하기

Istio에서 인가는 AuthorizationPolicy 리소스를 통해 정의되며, 이 리소스는 각 서비스에 함께 배포되는 프록시가 인가 집행(enforcement)을 수행할 수 있도록 구성됩니다.

서비스 프록시는 모든 요청에 대해 허용 여부를 직접 판단하며, 이를 위해 필요한 인가 정책들을 로컬에 포함하고 있기 때문에, 접근 제어가 매우 효율적으로 작동합니다.

즉, 인증된 트래픽이 들어왔을 때, 프록시가 해당 요청을 허용할지 거절할지를 자체적으로 판단할 수 있습니다. 이 모든 판단은 AuthorizationPolicy 리소스를 통해 정의됩니다.

예시는 다음과 같습니다:

# 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 경로로 들어오는 요청을 허용(ALLOW) 합니다.
  • 이 정책이 생성되면, istiod는 이를 감지하고 다른 Istio 리소스처럼 데이터 플레인 프록시에 반영합니다.

인가 정책의 주요 속성 (Properties of an Authorization Policy)

AuthorizationPolicy 리소스는 다음 세 가지 주요 필드를 통해 정책을 정의합니다:

  1. selector
    정책을 적용할 워크로드의 부분집합을 정의합니다.

  2. action
    정책이 어떤 동작을 할지를 지정합니다.

    • ALLOW: 요청을 허용
    • DENY: 요청을 차단
    • CUSTOM: 외부 플러그인에 위임
  3. rules
    요청이 일치할 경우 적용될 규칙 목록을 정의합니다. 규칙 중 하나라도 일치하면 action이 실행됩니다.


인가 정책의 규칙 이해하기 (Understanding Authorization Policy Rules)

AuthorizationPolicy의 규칙은 다음과 같은 세 가지 필드를 포함합니다:

  1. from: 요청의 출처(Source) 를 지정합니다.

    • principals: 요청의 SPIFFE ID 등 ID 정보
    • namespaces: 요청의 네임스페이스 (SVID에서 추출됨)
    • ipBlocks: 요청의 IP 또는 CIDR 범위

      출처 기반 필드는 일반적으로 mTLS가 활성화되어야 작동합니다.

  2. to: 요청의 작업(Operation) 조건을 지정합니다.

    • 경로(path), 메서드(method), 호스트(host) 등
  3. when: 추가 조건들을 정의합니다.

    • 헤더, 시간대, 요청 속성 등을 기반으로 세부 조건 추가 가능

AuthorizationPolicy는 하나의 규칙만 일치하더라도 동작합니다. 각 필드는 정교하게 설정할 수 있으며, 조합을 통해 매우 세밀한 접근 제어가 가능합니다.

공식 문서: Istio AuthorizationPolicy Reference

이를 통해 Istio는 서비스 메시 내에서 ‘누가, 무엇을 할 수 있는가’를 선언적으로 정의할 수 있으며, 모든 인가 판단은 각 워크로드의 사이드카 프록시에서 직접 수행되기 때문에 성능 상의 이점도 큽니다.



✅ 작업 공간 설정하기

이번 실습은 9.2에서 구성한 환경을 그대로 이어서 사용합니다. PeerAuthentication 설정까지 포함된 상태에서 AuthorizationPolicy를 테스트할 준비가 완료된 상태입니다.

실습 환경 구성 확인

# 9.2.1 에서 이미 배포된 리소스들
kubectl -n istioinaction apply -f services/catalog/kubernetes/catalog.yaml
kubectl -n istioinaction apply -f services/webapp/kubernetes/webapp.yaml
kubectl -n istioinaction apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml
kubectl -n default apply -f ch9/sleep.yaml

# Gateway 및 VirtualService 확인
kubectl -n istioinaction get gw,vs

🔐 PeerAuthentication 설정 확인

# ch9/meshwide-strict-peer-authn.yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT
kubectl -n istio-system apply -f ch9/meshwide-strict-peer-authn.yaml
kubectl get peerauthentication -n istio-system
# 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
kubectl -n istioinaction apply -f ch9/workload-permissive-peer-authn.yaml
kubectl get peerauthentication -n istioinaction

webapp은 HTTP 트래픽을 받아들이고 있으며, catalog 서비스는 상호 인증이 필요합니다.


실습 환경 요약

  • sleep 워크로드는 default 네임스페이스에 배포되어 있으며, 평문 HTTP 요청을 생성하는 데 사용됩니다.
  • webapp 워크로드는 istioinaction 네임스페이스에 배포되어 있으며, default 네임스페이스의 워크로드로부터 미인증 요청을 허용합니다.
  • catalog 워크로드 역시 istioinaction 네임스페이스에 배포되어 있으며, 같은 네임스페이스의 인증된 워크로드로부터의 요청만 수락합니다.

이제 이 환경을 기반으로 AuthorizationPolicy를 적용하여 서비스 간 인가 제어를 실습할 준비가 완료되었습니다.



✅ 워크로드에 정책 적용 시 동작 확인

AuthorizationPolicy를 실제 워크로드에 적용할 때 반드시 기억해야 할 중요한 동작이 있습니다. 이를 이해하지 못하면 문제 해결에 많은 시간을 낭비하게 될 수 있습니다.

ALLOW 정책의 기본 동작: Deny by Default

워크로드에 하나 이상의 ALLOW 정책이 적용되면, 해당 워크로드에 대한 기본 동작은 모든 요청을 거부(Deny) 하는 것입니다. 다시 말해, 하나라도 정책과 일치하지 않으면 모든 트래픽이 거부됩니다.

이를 deny-by-default 동작이라 합니다.


예시: webapp에 특정 경로만 허용하는 정책 적용

# 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
  • /api/catalog* 경로에 대한 요청만 허용하는 정책입니다.

정책 적용 전/후 테스트

# 적용 전 정상 응답 확인
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world  # 404 응답

# AuthorizationPolicy 적용
kubectl apply -f ch9/allow-catalog-requests-in-web-app.yaml
kubectl get authorizationpolicy -n istioinaction

정책 적용 결과 확인

# 정책 적용 후 호출 테스트
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world  # 403 응답
  • 첫 번째 요청은 허용됩니다 (/api/catalog 경로 일치).
  • 두 번째 요청은 명시적으로 거부되지 않았지만, ALLOW 정책과 일치하지 않기 때문에 기본적으로 거부됩니다.
# 프록시 로그에서 403 및 rbac_access_denied_matched_policy 확인
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

[2025-05-03T10:08:52.918Z] "GET /hello/world HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] ...
  • 이는 예상된 동작으로, ALLOW 정책이 적용된 워크로드는 반드시 일치하는 요청만 허용합니다.

다음 실습을 위해 정책 삭제

kubectl delete -f ch9/allow-catalog-requests-in-web-app.yaml

인가 정책 설계 팁: catch-all DENY 정책 활용

서비스마다 허용 정책만 설계하면 되도록, 다음과 같이 catch-all DENY 정책을 명시적으로 추가하는 것도 좋은 전략입니다.

이렇게 하면 “기본적으로 거부하되, 허용하고 싶은 트래픽만 명시적으로 ALLOW”하는 모델을 구성할 수 있습니다. 결과적으로, 허용 정책에만 집중하면 되고 디버깅도 쉬워집니다.



✅ 전체 정책으로 기본적으로 모든 요청 거부하기

보안성을 높이고 인가 정책 설계를 단순화하기 위해, 기본적으로 모든 요청을 거부(Deny-by-default) 하는 메시 범위의 인가 정책을 설정할 수 있습니다. 이는 catch-all DENY 정책으로, 명시적으로 ALLOW 정책이 존재하지 않는 요청은 모두 차단됩니다.


메시 범위 기본 거부 정책 적용

다음 AuthorizationPolicy는 istio-system 네임스페이스에 정의되며, 모든 워크로드에 적용됩니다.

# 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
# 정책 적용
kubectl apply -f ch9/policy-deny-all-mesh.yaml
kubectl get authorizationpolicy -A
# 적용 후 요청 테스트 (모두 거부됨)
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
curl -s http://webapp.istioinaction.io:30000/api/catalog
# 로그 확인
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-05-03T14:45:31.051Z] "GET /api/catalog HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] ...
  • 위 로그는 일치하는 정책이 없기 때문에 요청이 거부되었음을 나타냅니다.


참고: 모든 요청 허용 정책 (반대 개념)

반대로, 모든 요청을 허용하는 정책도 정의할 수 있습니다. 이 경우 rules 필드에 빈 객체를 추가하면 됩니다:

# ch9/policy-allow-all-mesh.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-all
  namespace: istio-system
spec:
  rules:
  - {}
  • 위 정책은 명시적 허용 없이도 모든 요청을 수락합니다.

기본적으로 모든 요청을 차단한 뒤, 필요한 요청만 ALLOW 정책으로 명시적으로 허용하는 방식은 보안상 매우 효과적이며, 인가 정책의 유지보수도 명확해집니다. 이후에는 서비스별로 필요한 요청만 열어주는 구조로 안전하고 단순한 접근 제어를 구현할 수 있습니다.


✅ 특정 네임스페이스에서 온 요청 허용하기

서비스 간 인가 정책을 구성할 때, 특정 네임스페이스에서 시작된 요청만 허용하고 싶은 상황이 자주 발생합니다. 이를 위해 Istio는 source.namespaces 속성을 제공합니다.


특정 네임스페이스 요청 허용 정책 예제

다음 AuthorizationPolicy는 default 네임스페이스에서 발생한 HTTP GET 요청만 허용합니다.

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: webapp-allow-view-default-ns
  namespace: istioinaction
spec:
  rules:
  - from:
    - source:
        namespaces: ["default"]
    to:
    - operation:
        methods: ["GET"]
# 적용
kubectl apply -f ch9/webapp-allow-view-default-ns.yaml
kubectl get AuthorizationPolicy -A

호출 테스트

# 테스트 1: sleep → webapp
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog

문제 발생: 요청 거부

sleep 서비스는 레거시 워크로드로, 사이드카 프록시가 주입되지 않아 SPIFFE ID가 없음 → 프록시는 요청의 출처를 판단할 수 없어 요청을 거부합니다.


해결 방법 1: sleep 워크로드에 사이드카 프록시 주입

# default 네임스페이스에 자동 프록시 주입 활성화
kubectl label ns default istio-injection=enabled

# sleep 파드 재시작 (프록시 포함되도록)
kubectl delete pod -l app=sleep
# 주입 확인
docker exec -it myk8s-control-plane istioctl proxy-status
# 다시 호출 테스트
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction           # ✅ 성공
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog # ❌ 실패

  • webapp → catalog 요청이 거부됨
  • 이는 catalog에 대한 접근이 deny-all 정책에 의해 차단되기 때문입니다.

로그 확인

# webapp 로그에서 catalog 요청이 거부되는 모습 확인
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
"GET /api/catalog HTTP/1.1" 500 ...
webapp → catalog: 403 (deny-all)
# catalog 서비스 직접 호출 테스트
kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items  # ✅ 성공

실습 후 클린업

# default 네임스페이스 프록시 주입 비활성화
kubectl label ns default istio-injection-

# sleep 파드 롤링 재시작
kubectl rollout restart deploy/sleep

# 확인
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction # ❌ 요청 거부됨

source.namespaces는 요청 출처를 기반으로 인가 정책을 설정할 수 있는 강력한 도구입니다. 이를 활용하려면 요청을 보내는 워크로드에 반드시 사이드카 프록시가 주입되어 있어야 하며, SPIFFE ID를 기반으로 상호 인증이 가능해야 합니다.

레거시 워크로드는 사이드카가 없기 때문에 기본적으로 출처 기반 인가 정책을 사용할 수 없습니다. 권장 접근법은 해당 워크로드에 사이드카를 주입하고 보안성을 확보하는 것입니다.



✅ 미인증 레거시 워크로드에서 온 요청 허용하기

서비스 메시에는 사이드카 프록시가 없는 레거시 워크로드가 존재할 수 있으며, 이들은 mTLS 인증 정보 없이 요청을 보냅니다. 이 경우 요청의 출처(source)를 판별할 수 없기 때문에, source.namespaces, source.principals와 같은 조건이 있는 인가 정책은 작동하지 않습니다.

이런 요청을 허용하려면 from 조건 없이 정책을 구성해야 합니다.


예제: GET 요청 허용 정책 (인증 없이)

아래 AuthorizationPolicy는 GET 요청만 허용하며, webapp 워크로드에만 적용됩니다. from 필드가 없기 때문에 출처 검증을 수행하지 않습니다.

# 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
kubectl get AuthorizationPolicy -A

요청 테스트 및 정책 우선순위 확인

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

요청은 성공하며, 이는 webapp에 적용된 미인증 허용 정책에 의해 허용된 것입니다.


정책 우선순위 확인

# 정책 적용 상태 확인
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json | jq

deny-all, webapp-allow-view-default-ns, webapp-allow-unauthenticated-view-default-ns 정책이 모두 적용되었음을 확인할 수 있습니다.

여러 정책이 동시에 적용되는 경우, 요청이 하나의 정책이라도 만족하면 허용됩니다.


catalog로의 요청은 여전히 차단됨

# webapp → catalog 요청 테스트
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
# 로그 확인
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-05-04T02:36:49.857Z] "GET /api/catalog HTTP/1.1" 500 ...
webapp → catalog: 403 (deny-all)

이는 메시 범위에 존재하는 deny-all 정책이 여전히 catalog로의 요청을 차단하고 있기 때문입니다.



✅ 특정 서비스 어카운트에서 온 요청 허용하기

서비스 간 인가 정책을 구성할 때, 특정 워크로드(예: webapp)가 인증된 요청을 보냈는지를 판별하려면, 해당 워크로드의 서비스 어카운트(ServiceAccount) 를 기반으로 필터링하는 방식이 가장 확실하고 안전합니다.

Istio는 SPIFFE(SVID) 기반 인증서를 통해 서비스 어카운트 정보를 포함하며, 상호 인증(mTLS)을 통해 요청의 주체(principal) 를 검증하고 이를 필터 메타데이터에 저장합니다.


예제: catalog 서비스에 대해 webapp 서비스 어카운트만 허용

# 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
    to:
    - operation:
        methods: ["GET"]
# 적용
kubectl apply -f ch9/catalog-viewer-policy.yaml
kubectl get AuthorizationPolicy -A

이 정책은 catalog 워크로드에 적용되며, webapp 서비스 어카운트로부터의 GET 요청만 허용합니다.


정책 구성 확인

docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction --port 15006 -o json
"io.istio.peer_principal": {
  "stringMatch": {
    "exact": "spiffe://cluster.local/ns/istioinaction/sa/webapp"
  }
}
  • 필터 메타데이터에서 요청 주체가 webapp 서비스 어카운트인지 정확히 확인하도록 구성됨을 확인할 수 있습니다.

요청 흐름 테스트

# 호출 흐름: sleep (레거시, 인증 없음) → webapp → catalog

# 1. sleep → webapp (허용됨)
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction

# 2. webapp → catalog (webapp 서비스 어카운트 기반 허용됨)
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
# 옵션: sleep → catalog 직접 호출 테스트 (거부될 수 있음)
kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items

보안적 의의

이제 다음과 같은 엄격한 인가 흐름을 구성하게 되었습니다:

  • sleepwebapp: 미인증 트래픽도 허용 (from 조건 없음)
  • webappcatalog: 인증된 서비스 어카운트(webapp)만 허용 (principals 조건)

이 구조는 만약 webapp의 워크로드 ID가 탈취되더라도, 해당 ID가 가진 접근 권한 범위가 catalog 서비스에 한정되도록 구성되어 있습니다.

🛡️ 엄격한 인가 정책은 보안 사고 발생 시 피해 범위를 최소화하는 중요한 수단입니다.


✅ 정책의 조건부 적용

일부 인가 정책은 요청이 특정 조건을 만족할 때만 조건부로 적용되도록 구성할 수 있습니다.
예를 들어 사용자가 관리자인 경우에만 특정 리소스에 대한 접근을 허용하는 방식입니다.

이러한 정책은 when 필드를 통해 정의할 수 있으며, JWT 토큰의 클레임 등 다양한 속성을 조건으로 삼을 수 있습니다.


예제: admin 그룹 사용자의 요청만 허용하는 메시 범위 정책

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

이 정책은 다음 두 조건을 모두 만족할 때만 요청을 허용합니다:

  1. 요청의 JWT 토큰은 auth@istioinaction.io 도메인에서 발급된 request principal 이어야 합니다.
  2. 토큰의 클레임 내에 groups=admin 값이 포함돼 있어야 합니다.

조건 필드 설명

  • requestPrincipals: 최종 사용자 인증(RequestAuthentication) 을 통해 전달된 주체 정보입니다. 보통 JWT 토큰 발급자 도메인을 기준으로 매칭됩니다.
  • when: 요청의 추가 속성을 조건으로 지정할 수 있으며, 이 값이 정확히 일치해야 허용됩니다.
    • request.auth.claims[groups]: JWT 클레임 중 groups 값을 참조
    • values: 반드시 포함돼야 하는 값
    • notValues: 포함되면 거부되는 값 지정 가능

참고: 조건에 사용할 수 있는 Istio 속성 목록

Istio에서 사용 가능한 모든 조건 키 목록은 공식 문서에서 확인할 수 있습니다:

👉 Istio 조건 속성 문서 보기


추가 팁: Peer Principals vs. Request Principals

구분설명
principalsmTLS 기반 인증의 주체로, 워크로드 간 인증에서 사용됩니다. (PeerAuthentication 기반)
requestPrincipalsJWT 기반 인증의 주체로, 최종 사용자 인증(RequestAuthentication)에 사용됩니다.

📎 공식 가이드: AuthorizationPolicy Source 필드 차이

조건부 정책을 사용하면, 더 세밀하고 상황에 맞는 보안 통제를 구성할 수 있습니다.
예를 들어 일반 사용자에게는 읽기만 허용하고, admin 그룹에 속한 사용자만 쓰기 작업을 할 수 있도록 쉽게 구현할 수 있습니다.


✅ 값 비교 표현식 이해하기

앞서 살펴본 인가 정책 예제들에서는 값이 정확히 일치해야 정책이 적용되었습니다. 하지만 Istio는 다양한 값 비교 표현식(Value-Match Expressions) 을 지원하여 더 유연한 정책 구성이 가능합니다.

지원하는 비교 방식

비교 방식설명
Exact정확히 일치 (예: "GET""GET"과만 일치)
Prefix접두사 매칭 (예: "/api/catalog*""/api/catalog/1"에 일치)
Suffix접미사 매칭 (예: "*.istioinaction.io""login.istioinaction.io"에 일치)
Presence필드가 존재하면 어떤 값이든 허용 ("*")

복잡한 규칙 평가 흐름 예시

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"]
정책 평가 방식 요약
  • 두 개의 rules 블록 중 하나만 만족하면 정책이 적용됩니다.
  • 각 rule 내부의 구성 요소는 다음과 같이 AND/OR 연산으로 처리됩니다:
항목내부 논리설명
from 목록OR여러 소스 중 하나만 부합하면 됨
to 목록OR여러 작업 조건 중 하나만 부합하면 됨
operation 내부AND메서드 + 경로 등 모두 일치해야 함
when 목록AND모든 조건이 일치해야 함

이처럼 규칙의 구조를 정확히 이해하면, 원하는 조건을 보다 정밀하게 설정할 수 있습니다.


✅ 인가 정책이 평가되는 순서 이해하기

Istio는 정책의 명시적 우선순위(priority) 를 설정하지 않고, 고정된 평가 순서에 따라 인가 정책을 처리합니다. 이 순서를 정확히 이해하면 정책 충돌이나 예기치 않은 거부를 방지할 수 있습니다.

평가 순서

  1. CUSTOM 정책
    → 외부 인가 시스템과 연동된 사용자 정의 정책

  2. DENY 정책
    → 요청이 일치하면 즉시 거부

  3. ALLOW 정책
    → 하나라도 일치하면 허용, 없거나 모두 불일치하면 다음 단계로 진행

  4. Catch-all 정책 존재 여부에 따라 결과 결정

    • 존재하는 경우: 그 정책이 요청 허용 여부 결정
    • 존재하지 않는 경우:
      • ALLOW 정책이 없다면 허용
      • ALLOW 정책이 있지만 아무 것도 일치하지 않으면 거부

평가 흐름 요약

CUSTOM → DENY → ALLOW → Catch-all 정책 확인 → 최종 허용/거부 결정

📌 ALLOW 정책이 단 하나라도 있는 경우, 해당 정책 중 아무 것도 부합하지 않으면 요청은 기본적으로 거부됩니다.


참고 이미지

출처: Istio Security 개념 문서



📌 Conclusion

이번 장에서는 Istio가 제공하는 워크로드 간 인증(mTLS)세분화된 인가 정책(AuthorizationPolicy) 기능을 실습을 통해 체계적으로 다뤘습니다.

자동 인증서 발급과 트래픽 암호화, 서비스 간 접근 제어까지 보안의 기본부터 실제 운영 관점의 정책 구성 방식까지 폭넓게 학습했습니다.

실습에서는 다음과 같은 흐름을 따라 실제 트래픽 보안이 어떻게 구성되는지 확인할 수 있었습니다:

  • 기본 mTLS 적용과 PeerAuthentication 리소스를 통한 보안 수준 설정
  • 서비스별 인가 정책 적용을 통해 경로, 메서드, 서비스 어카운트 기반의 접근 통제 구현
  • 레거시 워크로드, JWT 클레임 기반 조건부 인가 등 다양한 시나리오에 따른 대응 전략 수립
  • tcpdump 및 envoy 로그 분석을 통해 실제 정책 동작을 시각적으로 검증

특히, 모든 통신을 기본적으로 암호화하고, 필요한 요청만 명시적으로 허용하는 구조가 실무에서 얼마나 중요한지 체감할 수 있었습니다. 또한 AuthorizationPolicy는 우선순위 기반이 아닌 조건 일치 기반으로 평가되기 때문에, 정책의 구성 방식과 적용 순서를 명확히 이해하는 것이 중요하다는 점도 확인할 수 있었습니다.

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

0개의 댓글