Skip to content

필사 모드: Istio 보안 모델 분석: mTLS, 인증, 인가

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

Istio의 보안 모델은 "제로 트러스트 네트워킹"을 서비스 메시 수준에서 구현합니다. 모든 서비스 간 통신이 기본적으로 암호화되고, 모든 요청이 인증 및 인가를 거치는 아키텍처입니다.

이 글에서는 Istio 보안의 세 가지 핵심 축인 **아이덴티티**, **인증(Authentication)**, **인가(Authorization)**의 내부 구현을 분석합니다.

인증서 라이프사이클

istiod CA의 역할

istiod는 내장 CA(Certificate Authority)로서 메시 내 모든 워크로드의 인증서를 관리합니다:

istiod CA

├── Root Certificate (자체 서명 또는 외부 CA에서 발급)

├── Intermediate CA Certificate (선택사항)

└── Workload Certificates (각 워크로드에 발급)

├── frontend → spiffe://cluster.local/ns/prod/sa/frontend

├── reviews → spiffe://cluster.local/ns/prod/sa/reviews

└── ratings → spiffe://cluster.local/ns/prod/sa/ratings

인증서 발급 상세 흐름

[1] Pod 시작 → istio-agent 초기화

[2] istio-agent가 RSA 2048 또는 ECDSA P-256 키 쌍 생성

[3] SPIFFE ID를 포함한 CSR 생성

(spiffe://cluster.local/ns/NAMESPACE/sa/SA_NAME)

[4] Kubernetes ServiceAccount 토큰과 함께 CSR을 istiod에 전송

[5] istiod 검증:

├── ServiceAccount 토큰 유효성 (TokenReview API)

├── 토큰의 네임스페이스/SA가 CSR의 SPIFFE ID와 일치하는지

└── CSR 형식 유효성

[6] istiod CA가 X.509 인증서 서명

├── Subject: SPIFFE ID

├── SAN (Subject Alternative Name): SPIFFE URI

├── 유효 기간: 24시간 (기본값)

└── Key Usage: Digital Signature, Key Encipherment

[7] 서명된 인증서 + CA 체인을 istio-agent에 반환

[8] istio-agent가 SDS를 통해 Envoy에 인증서 전달

[9] 만료 전 자동 갱신 (유효 기간의 약 50% 시점)

외부 CA 통합

프로덕션 환경에서는 외부 CA를 사용할 수 있습니다:

외부 CA 연동 방식:

├── 1. Plug-in CA: istiod에 외부 CA의 중간 인증서를 마운트

│ └── istiod가 중간 CA로서 워크로드 인증서 서명

├── 2. CSR API: Kubernetes CertificateSigningRequest API 사용

│ └── 외부 서명자가 Kubernetes CSR을 승인/서명

└── 3. Custom CA (istio-csr): cert-manager + Istio CSR Agent

└── cert-manager가 외부 CA(Vault, AWS ACM 등)에서 인증서 발급

mTLS 핸드셰이크 흐름

사이드카 간 mTLS

두 서비스 간의 mTLS 연결 수립 과정:

Client Pod (frontend) Server Pod (reviews)

[App] → [Envoy Proxy] ←→ [Envoy Proxy] → [App]

1. App이 reviews:9080에 요청

(평문 HTTP, localhost 내부)

2. iptables가 트래픽을 Envoy(15001)로 리다이렉트

3. Envoy가 대상 클러스터의 TLS 설정 확인

(DestinationRule 또는 auto mTLS)

4. TLS 핸드셰이크 시작:

├── ClientHello (지원하는 TLS 버전, 암호화 스위트)

├── ServerHello + Server Certificate

│ └── reviews의 SPIFFE 인증서 제시

├── Client Certificate

│ └── frontend의 SPIFFE 인증서 제시

├── Certificate Verification (양방향)

│ ├── CA 체인 검증

│ ├── SPIFFE ID 검증

│ └── 인증서 만료 확인

└── Finished (세션 키 교환 완료)

5. 암호화된 채널에서 HTTP 요청 전송

6. Server Envoy가 TLS 종료

7. 평문으로 App에 전달 (localhost)

Auto mTLS

Istio는 기본적으로 **auto mTLS**를 활성화합니다:

대상에 사이드카가 있나?

├── Yes → 자동으로 mTLS 사용

│ (DestinationRule에 TLS 설정 없어도)

└── No → 평문 사용

(사이드카 없는 서비스에 mTLS 강제 시 실패)

이를 통해 사이드카가 점진적으로 주입되는 환경에서도 통신이 유지됩니다.

PeerAuthentication 내부 동작

정책 범위와 우선순위

우선순위 (높은 순):

1. Workload-level (selector 지정)

2. Namespace-level (selector 미지정, 특정 네임스페이스)

3. Mesh-level (istio-system 네임스페이스, selector 미지정)

mTLS 모드별 Envoy 구성

**STRICT 모드:**

{

"filter_chains": [

{

"filter_chain_match": {},

"transport_socket": {

"name": "envoy.transport_sockets.tls",

"typed_config": {

"require_client_certificate": true,

"common_tls_context": {

"validation_context": {

"trusted_ca": "CA 인증서"

}

}

}

}

}

]

}

클라이언트 인증서가 없거나 유효하지 않으면 연결이 즉시 거부됩니다.

**PERMISSIVE 모드:**

{

"filter_chains": [

{

"filter_chain_match": {

"transport_protocol": "tls"

},

"transport_socket": {

"name": "envoy.transport_sockets.tls",

"typed_config": {

"require_client_certificate": true

}

}

},

{

"filter_chain_match": {},

"transport_socket": {

"name": "envoy.transport_sockets.raw_buffer"

}

}

]

}

두 개의 필터 체인이 존재합니다: TLS 연결용과 평문 연결용. TLS Inspector가 연결을 분류합니다.

포트별 mTLS 설정

apiVersion: security.istio.io/v1beta1

kind: PeerAuthentication

metadata:

name: reviews-mtls

namespace: production

spec:

selector:

matchLabels:

app: reviews

mtls:

mode: STRICT

portLevelMtls:

8080:

mode: STRICT

15021:

mode: DISABLE # 헬스 체크 포트는 mTLS 비활성화

RequestAuthentication 내부 동작

JWT 검증 흐름

apiVersion: security.istio.io/v1beta1

kind: RequestAuthentication

metadata:

name: jwt-auth

spec:

selector:

matchLabels:

app: reviews

jwtRules:

- issuer: 'https://auth.example.com'

jwksUri: 'https://auth.example.com/.well-known/jwks.json'

forwardOriginalToken: true

outputPayloadToHeader: 'x-jwt-payload'

Envoy에서의 처리:

요청 도착 (Authorization: Bearer TOKEN)

JWT Authn Filter (envoy.filters.http.jwt_authn)

├── JWT 토큰 추출 (Authorization 헤더)

├── JWKS 캐시 확인

│ ├── 캐시 히트 → 공개 키로 서명 검증

│ └── 캐시 미스 → jwksUri에서 키 다운로드

├── 토큰 검증:

│ ├── 서명 유효성

│ ├── issuer(iss) 일치 여부

│ ├── 만료(exp) 확인

│ └── audience(aud) 확인 (설정된 경우)

├── 검증 성공:

│ ├── 페이로드를 필터 메타데이터에 저장

│ ├── forwardOriginalToken: true → 원본 토큰 유지

│ └── outputPayloadToHeader → 페이로드를 지정 헤더에 추가

└── 검증 실패:

├── 유효하지 않은 JWT → 401 Unauthorized

└── JWT 없음 → 요청 통과 (인증되지 않은 상태로)

JWT가 없는 요청의 처리

RequestAuthentication의 중요한 특성:

- JWT가 **있으면** 반드시 유효해야 함 (유효하지 않으면 401)

- JWT가 **없으면** 요청이 통과함 (인증되지 않은 상태)

- JWT 없는 요청도 거부하려면 AuthorizationPolicy와 함께 사용

JWT가 없는 요청도 거부하는 패턴

apiVersion: security.istio.io/v1beta1

kind: AuthorizationPolicy

metadata:

name: require-jwt

spec:

selector:

matchLabels:

app: reviews

action: DENY

rules:

- from:

- source:

notRequestPrincipals: ['*']

AuthorizationPolicy 내부 동작

평가 순서

요청 도착

[1] CUSTOM 정책 평가

├── 매칭되고 거부 → 403 Forbidden

├── 매칭되고 허용 → [2]로 진행

└── 매칭 안 됨 → [2]로 진행

[2] DENY 정책 평가

├── 매칭됨 → 403 Forbidden

└── 매칭 안 됨 → [3]로 진행

[3] ALLOW 정책 존재 여부 확인

├── ALLOW 정책 없음 → 허용 (기본 허용)

└── ALLOW 정책 있음:

├── 매칭됨 → 허용

└── 매칭 안 됨 → 403 Forbidden

Envoy RBAC Filter로의 변환

apiVersion: security.istio.io/v1beta1

kind: AuthorizationPolicy

metadata:

name: allow-frontend

namespace: production

spec:

selector:

matchLabels:

app: reviews

action: ALLOW

rules:

- from:

- source:

principals: ['cluster.local/ns/production/sa/frontend']

to:

- operation:

methods: ['GET']

paths: ['/api/*']

Envoy RBAC 필터 구성:

{

"name": "envoy.filters.http.rbac",

"typed_config": {

"rules": {

"action": "ALLOW",

"policies": {

"allow-frontend": {

"permissions": [

{

"and_rules": {

"rules": [

{

"header": {

"name": ":method",

"string_match": {

"exact": "GET"

}

}

},

{

"url_path": {

"path": {

"prefix": "/api/"

}

}

}

]

}

}

],

"principals": [

{

"authenticated": {

"principal_name": {

"exact": "spiffe://cluster.local/ns/production/sa/frontend"

}

}

}

]

}

}

}

}

}

Source 필드 매핑

| AuthorizationPolicy | Envoy RBAC | 설명 |

| ------------------------ | ------------------------------------- | ----------------- |

| source.principals | authenticated.principal_name | SPIFFE ID 매칭 |

| source.namespaces | authenticated.principal_name (prefix) | 네임스페이스 매칭 |

| source.ipBlocks | source_ip | IP 범위 매칭 |

| source.requestPrincipals | metadata (JWT claims) | JWT 주체 매칭 |

Operation 필드 매핑

| AuthorizationPolicy | Envoy RBAC | 설명 |

| ------------------- | ------------------- | ---------------- |

| operation.hosts | header (:authority) | 호스트 매칭 |

| operation.methods | header (:method) | HTTP 메서드 매칭 |

| operation.paths | url_path | 경로 매칭 |

| operation.ports | destination_port | 포트 매칭 |

Trust Domain과 마이그레이션

Trust Domain 개요

Trust Domain: 인증서를 발급하는 CA의 신뢰 범위

cluster-1: trust domain = "cluster-1.example.com"

└── spiffe://cluster-1.example.com/ns/prod/sa/frontend

cluster-2: trust domain = "cluster-2.example.com"

└── spiffe://cluster-2.example.com/ns/prod/sa/frontend

Trust Domain Migration

CA를 변경하거나 trust domain을 마이그레이션할 때:

MeshConfig에서 trust domain alias 설정

meshConfig:

trustDomain: 'new-domain.example.com'

trustDomainAliases:

- 'old-domain.example.com'

이를 통해 이전 trust domain으로 발급된 인증서도 계속 신뢰할 수 있습니다.

외부 인가 서비스 통합 (OPA)

CUSTOM AuthorizationPolicy

apiVersion: security.istio.io/v1beta1

kind: AuthorizationPolicy

metadata:

name: ext-authz

namespace: production

spec:

selector:

matchLabels:

app: reviews

action: CUSTOM

provider:

name: 'opa-ext-authz'

rules:

- to:

- operation:

paths: ['/api/*']

MeshConfig에 외부 인가 제공자 등록

meshConfig:

extensionProviders:

- name: 'opa-ext-authz'

envoyExtAuthzGrpc:

service: 'opa.opa-system.svc.cluster.local'

port: 9191

timeout: 5s

failOpen: false

요청 흐름

요청 도착

Envoy ext_authz 필터

├── gRPC로 OPA 서비스에 인가 요청 전송

│ ├── 요청 헤더

│ ├── 경로, 메서드

│ ├── 소스 principal (SPIFFE ID)

│ └── 커스텀 속성

├── OPA 응답:

│ ├── ALLOW → 요청 계속 처리

│ ├── DENY → 403 반환

│ └── 타임아웃:

│ ├── failOpen: true → 요청 허용

│ └── failOpen: false → 요청 거부

└── OPA에서 추가 헤더를 응답에 포함할 수 있음

(예: 인가 컨텍스트 정보)

보안 베스트 프랙티스

1. 점진적 mTLS 적용

1단계: 메시 전체 PERMISSIVE

apiVersion: security.istio.io/v1beta1

kind: PeerAuthentication

metadata:

name: default

namespace: istio-system

spec:

mtls:

mode: PERMISSIVE

2단계: 네임스페이스별 STRICT 전환

apiVersion: security.istio.io/v1beta1

kind: PeerAuthentication

metadata:

name: default

namespace: production

spec:

mtls:

mode: STRICT

3단계: 메시 전체 STRICT

apiVersion: security.istio.io/v1beta1

kind: PeerAuthentication

metadata:

name: default

namespace: istio-system

spec:

mtls:

mode: STRICT

2. 기본 거부 정책

모든 요청을 기본 거부하고, 명시적으로 허용

apiVersion: security.istio.io/v1beta1

kind: AuthorizationPolicy

metadata:

name: deny-all

namespace: production

spec: {} # 빈 spec = ALLOW 액션, 빈 rules = 모든 요청 거부

3. 네임스페이스 격리

같은 네임스페이스 내부 트래픽만 허용

apiVersion: security.istio.io/v1beta1

kind: AuthorizationPolicy

metadata:

name: allow-same-namespace

namespace: production

spec:

action: ALLOW

rules:

- from:

- source:

namespaces: ['production']

디버깅 도구

mTLS 상태 확인

istioctl authn tls-check PODNAME.NAMESPACE

AuthorizationPolicy 적용 상태

istioctl x authz check PODNAME.NAMESPACE

인증서 정보 확인

istioctl proxy-config secret PODNAME.NAMESPACE -o json

Envoy RBAC 디버그 로그 활성화

kubectl exec PODNAME -c istio-proxy -- \

curl -X POST "localhost:15000/logging?rbac=debug"

RBAC 거부 통계 확인

kubectl exec PODNAME -c istio-proxy -- \

curl -s localhost:15000/stats | grep rbac

마무리

Istio의 보안 모델은 세 가지 계층으로 구성됩니다:

1. **아이덴티티**: SPIFFE 기반 워크로드 아이덴티티와 자동 인증서 관리

2. **인증**: mTLS(PeerAuthentication)와 JWT(RequestAuthentication)

3. **인가**: RBAC 기반 세밀한 접근 제어(AuthorizationPolicy)

이 세 계층이 Envoy 프록시의 필터 체인에서 유기적으로 동작하여 제로 트러스트 보안을 구현합니다.

다음 글에서는 Istio Ambient Mesh의 내부 구조를 살펴보겠습니다.

현재 단락 (1/404)

Istio의 보안 모델은 "제로 트러스트 네트워킹"을 서비스 메시 수준에서 구현합니다. 모든 서비스 간 통신이 기본적으로 암호화되고, 모든 요청이 인증 및 인가를 거치는 아키텍처입...

작성 글자: 0원문 글자: 8,353작성 단락: 0/404