- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- 証明書ライフサイクル
- mTLSハンドシェイクフロー
- PeerAuthentication内部動作
- RequestAuthentication内部動作
- AuthorizationPolicy内部動作
- Trust Domainとマイグレーション
- 外部認可サービス統合(OPA)
- セキュリティベストプラクティス
- デバッグツール
- まとめ
はじめに
Istioのセキュリティモデルは「ゼロトラストネットワーキング」をサービスメッシュレベルで実装します。すべてのサービス間通信がデフォルトで暗号化され、すべてのリクエストが認証と認可を経るアーキテクチャです。
この記事では、Istioセキュリティの3つの核となるアイデンティティ、認証(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
2つのサービス間の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レベル(selector指定)
2. Namespaceレベル(selector未指定、特定ネームスペース)
3. Meshレベル(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"
}
}
]
}
2つのフィルターチェーンが存在します: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フィルターへの変換
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マイグレーション
CAを変更またはtrust domainを移行する場合:
# MeshConfigでtrust domainエイリアスを設定
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のセキュリティモデルは3つの階層で構成されます:
- アイデンティティ:SPIFFEベースのワークロードアイデンティティと自動証明書管理
- 認証:mTLS(PeerAuthentication)とJWT(RequestAuthentication)
- 認可:RBACベースのきめ細かいアクセス制御(AuthorizationPolicy)
この3つの階層がEnvoyプロキシのフィルターチェーン内で有機的に動作し、ゼロトラストセキュリティを実現します。
次の記事では、Istio Ambient Meshの内部構造を見ていきます。