Skip to content
Published on

Istioセキュリティモデル分析:mTLS、認証、認可

Authors

はじめに

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    ├── トークンのネームスペース/SACSRSPIFFE IDと一致するか
    └── CSR形式の妥当性
[6] istiod CAX.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 → ペイロードを指定ヘッダーに追加
    └── 検証失敗:
        ├── 無効なJWT401 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フィールドマッピング

AuthorizationPolicyEnvoy RBAC説明
source.principalsauthenticated.principal_nameSPIFFE IDマッチング
source.namespacesauthenticated.principal_name (prefix)ネームスペースマッチング
source.ipBlockssource_ipIP範囲マッチング
source.requestPrincipalsmetadata (JWT claims)JWT主体マッチング

Operationフィールドマッピング

AuthorizationPolicyEnvoy RBAC説明
operation.hostsheader (:authority)ホストマッチング
operation.methodsheader (:method)HTTPメソッドマッチング
operation.pathsurl_pathパスマッチング
operation.portsdestination_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 → リクエスト処理を継続
    │   ├── DENY403を返却
    │   └── タイムアウト:
    │       ├── 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つの階層で構成されます:

  1. アイデンティティ:SPIFFEベースのワークロードアイデンティティと自動証明書管理
  2. 認証:mTLS(PeerAuthentication)とJWT(RequestAuthentication)
  3. 認可:RBACベースのきめ細かいアクセス制御(AuthorizationPolicy)

この3つの階層がEnvoyプロキシのフィルターチェーン内で有機的に動作し、ゼロトラストセキュリティを実現します。

次の記事では、Istio Ambient Meshの内部構造を見ていきます。