Split View: Istio 보안 모델 분석: mTLS, 인증, 인가
Istio 보안 모델 분석: mTLS, 인증, 인가
- 들어가며
- 인증서 라이프사이클
- mTLS 핸드셰이크 흐름
- PeerAuthentication 내부 동작
- RequestAuthentication 내부 동작
- AuthorizationPolicy 내부 동작
- Trust Domain과 마이그레이션
- 외부 인가 서비스 통합 (OPA)
- 보안 베스트 프랙티스
- 디버깅 도구
- 마무리
들어가며
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의 보안 모델은 세 가지 계층으로 구성됩니다:
- 아이덴티티: SPIFFE 기반 워크로드 아이덴티티와 자동 인증서 관리
- 인증: mTLS(PeerAuthentication)와 JWT(RequestAuthentication)
- 인가: RBAC 기반 세밀한 접근 제어(AuthorizationPolicy)
이 세 계층이 Envoy 프록시의 필터 체인에서 유기적으로 동작하여 제로 트러스트 보안을 구현합니다.
다음 글에서는 Istio Ambient Mesh의 내부 구조를 살펴보겠습니다.
Istio Security Model Analysis: mTLS, Authentication, Authorization
- Introduction
- Certificate Lifecycle
- mTLS Handshake Flow
- PeerAuthentication Internals
- RequestAuthentication Internals
- AuthorizationPolicy Internals
- Trust Domain and Migration
- External Authorization Integration (OPA)
- Security Best Practices
- Debugging Tools
- Conclusion
Introduction
Istio's security model implements "zero trust networking" at the service mesh level. All service-to-service communication is encrypted by default, and every request goes through authentication and authorization.
This post analyzes the internal implementation of Istio security's three core pillars: identity, authentication, and authorization.
Certificate Lifecycle
istiod CA Role
istiod acts as a built-in CA (Certificate Authority) managing certificates for all workloads in the mesh:
istiod CA
├── Root Certificate (self-signed or issued by external CA)
├── Intermediate CA Certificate (optional)
└── Workload Certificates (issued to each workload)
├── frontend → spiffe://cluster.local/ns/prod/sa/frontend
├── reviews → spiffe://cluster.local/ns/prod/sa/reviews
└── ratings → spiffe://cluster.local/ns/prod/sa/ratings
Detailed Certificate Issuance Flow
[1] Pod starts → istio-agent initializes
│
[2] istio-agent generates RSA 2048 or ECDSA P-256 key pair
│
[3] Creates CSR including SPIFFE ID
(spiffe://cluster.local/ns/NAMESPACE/sa/SA_NAME)
│
[4] Sends CSR to istiod along with Kubernetes ServiceAccount token
│
[5] istiod validation:
├── ServiceAccount token validity (TokenReview API)
├── Token namespace/SA matches CSR SPIFFE ID
└── CSR format validity
│
[6] istiod CA signs X.509 certificate
├── Subject: SPIFFE ID
├── SAN (Subject Alternative Name): SPIFFE URI
├── Validity: 24 hours (default)
└── Key Usage: Digital Signature, Key Encipherment
│
[7] Returns signed certificate + CA chain to istio-agent
│
[8] istio-agent delivers certificate to Envoy via SDS
│
[9] Automatic renewal before expiry (around 50% of validity period)
External CA Integration
Production environments can use external CAs:
External CA integration methods:
├── 1. Plug-in CA: Mount external CA intermediate cert in istiod
│ └── istiod signs workload certs as intermediate CA
│
├── 2. CSR API: Use Kubernetes CertificateSigningRequest API
│ └── External signer approves/signs Kubernetes CSRs
│
└── 3. Custom CA (istio-csr): cert-manager + Istio CSR Agent
└── cert-manager issues certs from external CA (Vault, AWS ACM, etc.)
mTLS Handshake Flow
Sidecar-to-Sidecar mTLS
The mTLS connection establishment between two services:
Client Pod (frontend) Server Pod (reviews)
[App] → [Envoy Proxy] ←→ [Envoy Proxy] → [App]
1. App sends request to reviews:9080
(plaintext HTTP, internal to localhost)
│
2. iptables redirects traffic to Envoy (15001)
│
3. Envoy checks TLS settings for destination cluster
(DestinationRule or auto mTLS)
│
4. TLS handshake begins:
├── ClientHello (supported TLS versions, cipher suites)
├── ServerHello + Server Certificate
│ └── reviews presents its SPIFFE certificate
├── Client Certificate
│ └── frontend presents its SPIFFE certificate
├── Certificate Verification (bidirectional)
│ ├── CA chain validation
│ ├── SPIFFE ID verification
│ └── Certificate expiry check
└── Finished (session key exchange complete)
│
5. HTTP request sent over encrypted channel
│
6. Server Envoy terminates TLS
│
7. Forwards as plaintext to App (localhost)
Auto mTLS
Istio enables auto mTLS by default:
Does the destination have a sidecar?
│
├── Yes → automatically use mTLS
│ (even without TLS settings in DestinationRule)
│
└── No → use plaintext
(forcing mTLS to sidecar-less services fails)
This maintains communication even when sidecars are being gradually injected.
PeerAuthentication Internals
Policy Scope and Priority
Priority (highest first):
1. Workload-level (selector specified)
2. Namespace-level (no selector, specific namespace)
3. Mesh-level (istio-system namespace, no selector)
Envoy Configuration by mTLS Mode
STRICT mode:
{
"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 certificate"
}
}
}
}
}
]
}
Connections without a valid client certificate are immediately rejected.
PERMISSIVE mode:
{
"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"
}
}
]
}
Two filter chains exist: one for TLS connections and one for plaintext. The TLS Inspector classifies connections.
Per-Port mTLS Configuration
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 # Disable mTLS for health check port
RequestAuthentication Internals
JWT Validation Flow
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'
Processing in Envoy:
Request arrives (Authorization: Bearer TOKEN)
│
▼
JWT Authn Filter (envoy.filters.http.jwt_authn)
│
├── Extract JWT token (Authorization header)
│
├── Check JWKS cache
│ ├── Cache hit → verify signature with public key
│ └── Cache miss → download keys from jwksUri
│
├── Token validation:
│ ├── Signature validity
│ ├── issuer (iss) match
│ ├── Expiry (exp) check
│ └── audience (aud) check (if configured)
│
├── Validation success:
│ ├── Store payload in filter metadata
│ ├── forwardOriginalToken: true → preserve original token
│ └── outputPayloadToHeader → add payload to specified header
│
└── Validation failure:
├── Invalid JWT → 401 Unauthorized
└── No JWT → request passes through (unauthenticated)
Handling Requests Without JWT
Important characteristics of RequestAuthentication:
- If JWT is present, it must be valid (invalid returns 401)
- If JWT is absent, the request passes through (unauthenticated)
- To also reject requests without JWT, use with AuthorizationPolicy
# Pattern to also reject requests without JWT
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: require-jwt
spec:
selector:
matchLabels:
app: reviews
action: DENY
rules:
- from:
- source:
notRequestPrincipals: ['*']
AuthorizationPolicy Internals
Evaluation Order
Request arrives
│
▼
[1] CUSTOM policy evaluation
├── Matched and denied → 403 Forbidden
├── Matched and allowed → proceed to [2]
└── Not matched → proceed to [2]
│
▼
[2] DENY policy evaluation
├── Matched → 403 Forbidden
└── Not matched → proceed to [3]
│
▼
[3] Check for ALLOW policies
├── No ALLOW policies → allow (default allow)
└── ALLOW policies exist:
├── Matched → allow
└── Not matched → 403 Forbidden
Translation to 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 filter configuration:
{
"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 Field Mapping
| AuthorizationPolicy | Envoy RBAC | Description |
|---|---|---|
| source.principals | authenticated.principal_name | SPIFFE ID matching |
| source.namespaces | authenticated.principal_name (prefix) | Namespace matching |
| source.ipBlocks | source_ip | IP range matching |
| source.requestPrincipals | metadata (JWT claims) | JWT subject matching |
Operation Field Mapping
| AuthorizationPolicy | Envoy RBAC | Description |
|---|---|---|
| operation.hosts | header (:authority) | Host matching |
| operation.methods | header (:method) | HTTP method matching |
| operation.paths | url_path | Path matching |
| operation.ports | destination_port | Port matching |
Trust Domain and Migration
Trust Domain Overview
Trust Domain: scope of trust for a CA that issues certificates
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
When changing CA or migrating trust domains:
# Configure trust domain aliases in MeshConfig
meshConfig:
trustDomain: 'new-domain.example.com'
trustDomainAliases:
- 'old-domain.example.com'
This allows certificates issued under the old trust domain to continue being trusted.
External Authorization Integration (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/*']
Register External Authz Provider in MeshConfig
meshConfig:
extensionProviders:
- name: 'opa-ext-authz'
envoyExtAuthzGrpc:
service: 'opa.opa-system.svc.cluster.local'
port: 9191
timeout: 5s
failOpen: false
Request Flow
Request arrives
│
▼
Envoy ext_authz filter
│
├── Send authorization request to OPA service via gRPC
│ ├── Request headers
│ ├── Path, method
│ ├── Source principal (SPIFFE ID)
│ └── Custom attributes
│
├── OPA response:
│ ├── ALLOW → continue processing request
│ ├── DENY → return 403
│ └── Timeout:
│ ├── failOpen: true → allow request
│ └── failOpen: false → deny request
│
└── OPA can include additional headers in response
(e.g., authorization context info)
Security Best Practices
1. Gradual mTLS Rollout
# Phase 1: Mesh-wide PERMISSIVE
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: PERMISSIVE
# Phase 2: Per-namespace STRICT transition
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: production
spec:
mtls:
mode: STRICT
# Phase 3: Mesh-wide STRICT
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT
2. Default Deny Policy
# Deny all requests by default, explicitly allow
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: production
spec: {} # Empty spec = ALLOW action, empty rules = deny all requests
3. Namespace Isolation
# Allow only traffic within the same namespace
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-same-namespace
namespace: production
spec:
action: ALLOW
rules:
- from:
- source:
namespaces: ['production']
Debugging Tools
# Check mTLS status
istioctl authn tls-check PODNAME.NAMESPACE
# AuthorizationPolicy application status
istioctl x authz check PODNAME.NAMESPACE
# Check certificate information
istioctl proxy-config secret PODNAME.NAMESPACE -o json
# Enable Envoy RBAC debug logging
kubectl exec PODNAME -c istio-proxy -- \
curl -X POST "localhost:15000/logging?rbac=debug"
# Check RBAC denial statistics
kubectl exec PODNAME -c istio-proxy -- \
curl -s localhost:15000/stats | grep rbac
Conclusion
Istio's security model consists of three layers:
- Identity: SPIFFE-based workload identity and automatic certificate management
- Authentication: mTLS (PeerAuthentication) and JWT (RequestAuthentication)
- Authorization: RBAC-based fine-grained access control (AuthorizationPolicy)
These three layers work organically within Envoy proxy's filter chain to implement zero trust security.
In the next post, we will explore the internal architecture of Istio Ambient Mesh.