Skip to content
Published on

Istioセキュリティ実践 — mTLS、AuthorizationPolicy、そしてゼロトラストメッシュ

Authors

はじめに

サービスメッシュを導入する二番目に大きな理由はセキュリティです。Kubernetesクラスタの内部はしばしば「信頼できるネットワーク」として扱われますが、実際にはPodを一つ乗っ取られただけで、クラスタ内のすべてのサービスに平文でアクセスできてしまうフラットなネットワークであることが多いのです。ゼロトラストの出発点は、この前提をひっくり返すことです。内部ネットワークも信頼せず、すべてのサービス間通信に暗号化と認証と認可を要求するのです。

問題は、これをアプリケーションコードで実装しようとすると、すべてのサービスにTLS証明書の発行・更新ロジック、相手検証ロジック、権限チェックロジックを入れなければならない点です。数十のサービス、複数の言語スタックでこれを一貫して維持するのは事実上不可能です。Istioはこの三つ(暗号化、ワークロード認証、認可)をインフラ層へ引き下ろし、コード修正なしに一括適用します。

この記事は、Istioセキュリティの動作原理(SPIFFEアイデンティティ、istiod CA、証明書の自動更新)から始めて、PeerAuthenticationの段階的なSTRICT移行、AuthorizationPolicyの最小権限設計、RequestAuthenticationのJWT検証、OPA外部認可の連携、カスタムCA統合、そしてポリシーデバッグと導入ロードマップまでを実践の順序で整理します。例はサイドカーモード基準で、AmbientモードではL4セキュリティ(mTLS)はztunnelが、L7認可はwaypointが担当するという違いだけがあり、APIは同一です。

メッシュセキュリティモデル — アイデンティティがすべての基礎

SPIFFEアイデンティティ

Istioセキュリティの核心は「ワークロードごとに暗号学的に検証可能な身元を付与する」ことです。この身元はSPIFFE(Secure Production Identity Framework For Everyone)標準のフォーマットに従います。

spiffe://cluster.local/ns/payments/sa/payments-api
         -------------    --------    ------------
         トラストドメイン    ネームスペース  サービスアカウント

つまりIstioにおけるワークロードの身元は、IPアドレスでもPod名でもなく、Kubernetesのサービスアカウントです。IPはPodが再起動すれば変わりスプーフィングも可能ですが、サービスアカウントベースのSPIFFE IDは証明書に刻まれ、mTLSハンドシェイクで暗号学的に検証されます。後で扱うAuthorizationPolicyのprincipalsフィールドが参照するのが、まさにこのSPIFFE IDです。

istiod CAと証明書の発行・更新フロー

istiodはメッシュの認証局(CA)の役割を兼ねます。ワークロード証明書が発行され更新されるフローを図で見てみましょう。

+----------------------------------------------------------------------+
|  コントロールプレーン                                                   |
|  +----------------------------------------------------------------+  |
|  | istiod (内蔵CA)                                                 |  |
|  |  - ルート/中間CAキーを保管 (istio-ca-secretまたはcacerts)          |  |
|  |  - CSRに署名し、SPIFFE IDをSANに記録                             |  |
|  +----------------------------------------------------------------+  |
|        ^                                        |                    |
|        | 3. CSR提出                              | 4. 署名済み証明書    |
|        |    (サービスアカウントトークンで認証)        |    (デフォルト24時間)|
+--------|----------------------------------------|--------------------+
         |                                        v
+--------|----------------------------------------------------------+
|  ワークロードPod                                                    |
|  +--------------------------+    +------------------------------+ |
|  | istio-agent (pilot-agent) |<-->| Envoyサイドカー               | |
|  |  1. キーペア生成           | SDS |  - 発行された証明書で          | |
|  |  2. CSR作成               |    |    mTLSハンドシェイク         | |
|  |  5. SDSでEnvoyへ配布       |    |  - 相手証明書のSPIFFE IDを検証 | |
|  |  6. 期限前に自動再発行       |    +------------------------------+ |
|  +--------------------------+                                      |
+--------------------------------------------------------------------+

運用の観点で重要なポイントは次のとおりです。

  • 秘密鍵はPodの外に出ません。istio-agentがPod内でキーペアを生成し、CSRだけをistiodへ送ります。
  • 証明書はデフォルトで24時間有効で、期限前(デフォルトでは寿命の半分の時点から)にistio-agentが自動的に再発行を受けます。人が更新作業をすることはありません。
  • EnvoyはSDS(Secret Discovery Service)で証明書を受け取るため、証明書更新時にコネクションのドレインやPod再起動は不要です。
  • 証明書のSAN(Subject Alternative Name)にSPIFFE IDが入り、これが認可ポリシーのマッチング対象になります。

信頼チェーンとトラストドメイン

デフォルトのインストールでは、istiodは自己署名のルートCAを生成します(istio-systemネームスペースのistio-ca-secret)。マルチクラスタメッシュでは、すべてのクラスタが同じルートを共有しないとクラスタ間mTLSが成立しないため、本番環境では最初から共通のルートCAをcacertsシークレットとして注入するのが定石です。この部分は後のカスタムCAのセクションで再び扱います。

PeerAuthentication — mTLSモードとSTRICT移行戦略

三つのモード

PeerAuthenticationは「このワークロードが受信トラフィックにmTLSをどう要求するか」を定めます。

モード動作使う時点
PERMISSIVEmTLSと平文の両方を受け入れマイグレーション期間 (デフォルト)
STRICTmTLSのみ受け入れ、平文は拒否目標状態
DISABLEmTLS無効外部TLS終端装置の背後などの例外

デフォルトがPERMISSIVEであることが重要です。サイドカーを注入するとメッシュ内部トラフィックは自動的にmTLSになりますが、サイドカーのないPodやメッシュ外から来る平文トラフィックも依然として受け入れられます。つまりPERMISSIVEは「暗号化はされているが強制ではない」状態であり、平文経路が残っている限りゼロトラストとは呼べません。

適用範囲: メッシュ全域、ネームスペース、ワークロード

# 1) メッシュ全域のデフォルト — istio-system(ルートネームスペース)に置く
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT
---
# 2) ネームスペース単位 — 該当ネームスペースのすべてのワークロードに適用
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: payments
spec:
  mtls:
    mode: STRICT
---
# 3) ワークロード単位 — selectorで特定ワークロードのみ、ポート別の例外も可能
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: legacy-metrics-exception
  namespace: payments
spec:
  selector:
    matchLabels:
      app: legacy-batch
  mtls:
    mode: STRICT
  portLevelMtls:
    9090:                    # メッシュ外のPrometheusがスクレイプするポートだけ平文許可
      mode: PERMISSIVE

優先順位は、ワークロード単位がネームスペース単位に勝ち、ネームスペース単位がメッシュ全域に勝ちます。狭い範囲が常に勝つと覚えてください。

PERMISSIVEからSTRICTへ — ネームスペース単位の段階移行

メッシュ全体を一度にSTRICTへ切り替えるのはギャンブルです。サイドカーのないクライアント(メッシュ外のcron、レガシーVM、kubeletヘルスチェックではないカスタムプローブなど)が一つでもあれば、その経路は即座に切断されます。推奨手順は次のとおりです。

ステップ0: 現状観察 (PERMISSIVE維持)
  - Grafana/Kialiで平文トラフィックの割合を確認
  - メトリクス: istio_requests_totalの
    connection_security_policyラベル (mutual_tls / none)
  - noneが出る送信元を全数調査
    → サイドカー未注入のPodか、メッシュ外クライアントか

ステップ1: 平文の送信元を除去
  - サイドカー未注入のネームスペースに注入ラベルを追加
  - メッシュ外クライアントはメッシュへ編入するか、
    ingress gateway経由に変更

ステップ2: 非中核ネームスペースからSTRICT適用
  - dev → staging → 本番非中核 → 本番中核の順
  - ネームスペースごとにPeerAuthentication一つで明示的に適用
  - 適用後、5xx/接続エラーのダッシュボードを一定期間観察

ステップ3: メッシュ全域のデフォルトをSTRICTへ
  - istio-systemのdefault PeerAuthenticationをSTRICTに
  - 残る例外はportLevelMtlsまたはワークロード単位で狭く明示

ステップ4: 例外リストを定期的に再審査
  - DISABLE/PERMISSIVEの例外は有効期限を定めて管理

移行中に平文トラフィックがどこから来るかを確認するコマンドは次のとおりです。

# 特定ワークロードのmTLS適用状態とポリシーの出どころを確認
istioctl x describe pod payments-api-6c9f7d-abcde -n payments

# ネームスペースの認証ポリシーの衝突/漏れを点検
istioctl analyze -n payments

# Prometheusで平文リクエストを追跡 (noneが0にならないとSTRICT不可)
# istio_requests_total{connection_security_policy="none"}

AuthorizationPolicy — 最小権限の認可設計

mTLSが「誰なのか」を証明するなら、AuthorizationPolicyは「では何ができるのか」を定めます。まず評価ルールを正確に理解する必要があります。

AuthorizationPolicyの評価順序 (リクエスト1件ごと):

1. CUSTOMポリシー評価 → 外部認可器が拒否したら即拒否
2. DENYポリシー評価   → 一つでもマッチしたら即拒否
3. ALLOWポリシー評価
   - 対象ワークロードにALLOWポリシーが一つもなければ → 許可 (デフォルト開放)
   - ALLOWポリシーが一つでもあれば → マッチするものがあって初めて許可、
     なければ拒否 (デフォルト拒否へ転換)

核心は最後の行です。ALLOWポリシーが存在した瞬間、そのワークロードはホワイトリストモードになります。この性質を利用してデフォルト拒否を構成します。

デフォルト拒否 — ゼロトラストの出発点

# 空のspecのALLOWポリシー = 何も許可しない = ネームスペースのデフォルト拒否
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: payments
spec: {}

空のspecは「ALLOWポリシーは存在するがマッチングルールがない」という意味なので、すべてのリクエストが拒否されます。この状態から必要な経路だけを明示的に開けていくのが最小権限設計です。

サービス間の最小権限 — 呼び出しグラフどおりにのみ許可

注文サービスだけが決済APIを呼び出せるようにするなら、次のように書きます。

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: payments-api-allow
  namespace: payments
spec:
  selector:
    matchLabels:
      app: payments-api
  action: ALLOW
  rules:
    - from:
        - source:
            principals:
              - cluster.local/ns/commerce/sa/orders-api   # SPIFFE IDベース
      to:
        - operation:
            methods: ["POST"]
            paths: ["/v1/charges", "/v1/refunds"]
    - from:
        - source:
            principals:
              - cluster.local/ns/observability/sa/prometheus
      to:
        - operation:
            methods: ["GET"]
            paths: ["/metrics"]

principalsはmTLS証明書のSPIFFE IDとマッチングされるため、このポリシーが動作するにはmTLSが有効になっている必要があります。PERMISSIVE状態で平文で入ってきたリクエストはprincipalが空であり、principals条件にマッチしないことを覚えておいてください。STRICT移行が認可設計の前提条件である理由です。

メソッド・パス制限とDENYの活用

ALLOWでホワイトリストを作りつつ、運用者の観点で「何があっても止めるべきもの」はDENYでもう一層敷いておくのが安全です。DENYはALLOWより先に評価されるため、ALLOWポリシーのミスを防御するセーフティネットになります。

# 管理用パスはどの送信元からでもメッシュ内部呼び出しを禁止
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: deny-admin-paths
  namespace: payments
spec:
  selector:
    matchLabels:
      app: payments-api
  action: DENY
  rules:
    - to:
        - operation:
            paths: ["/admin/*", "/internal/*"]
---
# 特定ネームスペース全体から来るトラフィックを遮断 (例: サンドボックス隔離)
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: deny-from-sandbox
  namespace: payments
spec:
  action: DENY
  rules:
    - from:
        - source:
            namespaces: ["sandbox"]

条件(when)を使った細かい制御

# ingress gatewayを経由して入った外部トラフィックのみ許可し、
# 特定ヘッダーのあるリクエストだけ通す例
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: frontend-via-gateway-only
  namespace: commerce
spec:
  selector:
    matchLabels:
      app: storefront
  action: ALLOW
  rules:
    - from:
        - source:
            principals:
              - cluster.local/ns/istio-ingress/sa/ingress-gateway
      when:
        - key: request.headers[x-api-version]
          values: ["v1", "v2"]

when条件にはリクエストヘッダーのほか、source.ip、connection.sni、request.auth.claims(JWTクレーム)などが使えます。特にJWTクレーム条件は、次のセクションのRequestAuthenticationと組み合わさります。

RequestAuthentication — JWTによるエンドユーザー認証

mTLSのprincipalが「どのワークロードが呼んだか」だとすれば、JWTは「どのエンドユーザー(またはどのクライアントアプリ)のリクエストか」を表します。RequestAuthenticationはJWTの署名・発行者・有効期間を検証し、検証済みクレームを認可条件として公開します。

apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
  name: jwt-auth
  namespace: commerce
spec:
  selector:
    matchLabels:
      app: storefront-api
  jwtRules:
    - issuer: "https://idp.example.com/"          # issクレームと一致する必要あり
      audiences:
        - "storefront-api"                         # audクレームを検証
      jwksUri: "https://idp.example.com/.well-known/jwks.json"
      forwardOriginalToken: true                   # バックエンドへトークンを転送

ここで最も多い誤解: RequestAuthenticationだけ作っても「JWTのないリクエストは通過」します。このリソースは「JWTがあるなら有効でなければならない」だけを強制するからです。JWTを必須にするにはAuthorizationPolicyを組み合わせる必要があります。

# 有効なJWT principalのあるリクエストのみ許可 → トークンがなければ拒否
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: require-jwt
  namespace: commerce
spec:
  selector:
    matchLabels:
      app: storefront-api
  action: ALLOW
  rules:
    - from:
        - source:
            requestPrincipals: ["https://idp.example.com//*"]  # iss/sub形式
---
# クレームベースの細分化: 管理者スコープがあって初めて書き込み許可
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: write-requires-admin-scope
  namespace: commerce
spec:
  selector:
    matchLabels:
      app: storefront-api
  action: ALLOW
  rules:
    - to:
        - operation:
            methods: ["GET"]
    - to:
        - operation:
            methods: ["POST", "PUT", "DELETE"]
      when:
        - key: request.auth.claims[scope]
          values: ["storefront:admin"]

運用のヒントを三つ挙げます。

  • jwksUriのキーはEnvoyが定期的にキャッシュ・更新します。IdP側でキーをローテーションするときは、新しいキーを先にJWKSへ追加し、一定期間重ねて運用してから古いキーを除去する標準手順を守れば無停止です。
  • audiencesの検証を省略すると、同じIdPが発行した別サービス向けのトークンが再利用されかねません。サービスごとにaudienceを分離し、必ず検証してください。
  • JWT検証は入口(ingress gatewayまたは最前段サービス)で一度行い、内部への伝播はmTLS principalとforwardOriginalTokenで処理するのが一般的な構成です。

外部認可 — ext-authzでOPA連携

内蔵のAuthorizationPolicyは高速で宣言的ですが、「注文金額がユーザーの限度額以内か」のようなデータ依存の判断や、組織全体でのポリシーコード再利用には限界があります。そういうときはCUSTOMアクションで外部認可器(OPA、自前の認可サービスなど)に判断を委譲します。

まずメッシュ設定に外部認可プロバイダを登録します。

# MeshConfig (IstioOperatorまたはistio ConfigMap)
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    extensionProviders:
      - name: opa-ext-authz
        envoyExtAuthzGrpc:
          service: opa.authz-system.svc.cluster.local
          port: 9191
          timeout: 0.5s          # 認可器の遅延が全体遅延に直結 — 短く

次にCUSTOMポリシーで適用範囲を指定します。

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: opa-authz
  namespace: payments
spec:
  selector:
    matchLabels:
      app: payments-api
  action: CUSTOM
  provider:
    name: opa-ext-authz
  rules:
    - to:
        - operation:
            paths: ["/v1/charges"]    # コストの高い外部認可は必要なパスのみ

OPA側のポリシー(Rego)の骨格は次のとおりです。

# Regoポリシー例 (envoy.authzパッケージ)
package envoy.authz

default allow := false

# 注文サービスが呼び出し、かつJWTのtierクレームがpremiumなら許可
allow if {
    input.attributes.source.principal ==
        "spiffe://cluster.local/ns/commerce/sa/orders-api"
    claims := input.attributes.metadata_context.filter_metadata["envoy.filters.http.jwt_authn"].fields
    claims["tier"] == "premium"
}

設計原則: 外部認可はリクエストごとにネットワークホップが一つ追加されるコストがあります。メッシュ全体に敷かず、複雑な判断が本当に必要なパスにだけ狭く適用してください。単純な「誰がどこを呼べるか」は内蔵のALLOW/DENYのほうがはるかに安価で高速です。また認可器自体が単一障害点になるため、認可器ダウン時の動作(fail-closeがデフォルト)とtimeoutを明示的に決めておく必要があります。

カスタムCA統合 — cert-managerと社内PKI

デフォルトの自己署名CAはPoCには十分ですが、本番環境、特に規制業種では次の要求が付いてきます。社内PKI階層にメッシュCAを編入すること、ルートキーをHSMなど安全な場所に保管すること、証明書発行の履歴を監査すること。

方法1: cacertsシークレットで中間CAを注入

最も単純な統合です。社内ルートCAからメッシュ用の中間CAを発行してもらい、istiodへ注入します。

# 社内PKIから受け取った中間CAでcacertsシークレットを構成
kubectl create secret generic cacerts -n istio-system \
  --from-file=ca-cert.pem \
  --from-file=ca-key.pem \
  --from-file=root-cert.pem \
  --from-file=cert-chain.pem
# istiod再起動後、新規ワークロード証明書はこのチェーンで発行される

この方式の長所は単純さ、短所は中間CAの秘密鍵がKubernetesシークレットとしてクラスタ内に存在する点です。etcd暗号化とシークレットアクセスのRBACを必ず併せて点検してください。

方法2: istio-csrでcert-managerに署名を委譲

cert-managerのistio-csrプロジェクトを使うと、istiodが直接署名せず、ワークロードのCSRがcert-managerのIssuerへ転送されます。キーがクラスタのシークレットに留まらず、Vault、AWS Private CA、社内PKIのような外部発行者へつなげられます。

# cert-manager Issuerの例: Vault PKIバックエンドへ署名を委譲
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: vault-istio-ca
spec:
  vault:
    server: https://vault.corp.example:8200
    path: pki_int_mesh/sign/istio-workload
    auth:
      kubernetes:
        role: istio-csr
        mountPath: /v1/auth/kubernetes
        secretRef:
          name: istio-csr-vault-token
          key: token
istio-csr構成時の発行フロー:

ワークロード istio-agent
   → CSR → istiod
            → istio-csr (gRPC CAサーバーとして登録)
               → cert-manager CertificateRequest
                  → ClusterIssuer (Vault / Private CA / 社内PKI)
                     → 署名済み証明書が逆順で伝達される

効果:
- メッシュCAキーがクラスタの外(HSM/Vault)に保管される
- 発行履歴がcert-managerリソースとして残り監査可能
- 発行者の交換がIssuerの交換に単純化される

証明書の有効期間とローテーション運用

項目デフォルト本番推奨
ワークロード証明書の有効期間24時間24時間を維持 (短いほど窃取被害を縮小)
更新開始時点有効期間の約半分デフォルト維持
中間CAの有効期間10年 (自己署名)1~3年 + ローテーション手順の文書化
ルートCAの有効期間10年PKIポリシーに従う、HSM保管

ワークロード証明書は自動更新されるため運用負担はありませんが、中間CAとルートCAのローテーションには計画が必要です。中間CAのローテーションは「新しい中間CAを注入 → istiod再起動 → ワークロードが24時間以内に自然更新され新チェーンへ置き換わる」という順序で無停止が可能です。ルートのローテーションは新旧ルートの両方を信頼する過渡期(combined root)を経る必要があるため、必ず事前リハーサルをお勧めします。期限の監視は次のコマンドとメトリクスで行います。

# ワークロード証明書のチェーンと期限を確認
istioctl proxy-config secret deploy/payments-api -n payments -o json

# istiod CA証明書の期限を監視 (Prometheus)
# メトリクス: citadel_server_root_cert_expiry_timestamp
# 期限30日前のアラートルールを必ず仕掛けておくこと

ポリシーデバッグ — 拒否ログとdry-run

誰が拒否したのかを探す

認可の拒否はクライアントにはHTTP 403(またはTCP接続の切断)として現れます。最初のステップは、対象ワークロードのEnvoyログからRBAC拒否の記録を探すことです。

# 1) RBACデバッグログを有効化 (対象Pod)
istioctl proxy-config log deploy/payments-api -n payments \
  --level rbac:debug

# 2) サイドカーログで拒否理由を確認
kubectl logs deploy/payments-api -n payments -c istio-proxy --tail=100
# "RBAC: access denied" とともにマッチに失敗したprincipal/パスが出力される

# 3) ワークロードに適用中の認可ポリシーを一覧
istioctl x describe pod payments-api-6c9f7d-abcde -n payments

# 4) ポリシーの整合性を一括点検
istioctl analyze -n payments

よく出会う拒否原因は三つ: 第一に、PERMISSIVE状態で平文のまま入ってきてprincipalが空になり、principals条件のマッチに失敗。第二に、ポリシーのネームスペース/サービスアカウントの打ち間違い。第三に、ALLOWポリシーを追加した瞬間デフォルト拒否に転換するという事実を忘れ、既存経路を開けておかなかった場合です。

dry-runで無停止のポリシー検証

本番トラフィックに影響を与えずポリシーの効果を事前に見るには、dry-runアノテーションを使います。

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: deny-all-dryrun
  namespace: payments
  annotations:
    istio.io/dry-run: "true"     # 評価のみ行い、実際にはブロックしない
spec: {}

dry-runポリシーの評価結果は、Envoyのアクセスログとメトリクスにshadow結果として記録されます。「このポリシーを実際に有効化したら何が拒否されるのか」を本番トラフィックで確認し、拒否対象が意図と一致したときにアノテーションを外すのが安全なデプロイ手順です。

推奨ポリシーデプロイ手順:

1. dry-runでポリシーをデプロイ
2. 一定期間shadow deniedのログ/メトリクスを収集
   (istio_requests_totalおよびアクセスログのdry-run結果を確認)
3. 拒否リストが意図と一致するかレビュー
   → 意図しない拒否があればrulesを補完
4. dry-runアノテーションを除去 (ポリシー有効化)
5. 有効化直後は403率のダッシュボードを集中観察

コンプライアンスの観点 — メッシュセキュリティが埋めてくれるもの

規制環境(金融機関における通信区間暗号化の要求、PCI DSSのカードデータ伝送暗号化、ISO 27001のアクセス制御など)では、メッシュセキュリティは監査対応コストを大きく削減してくれます。

  • 伝送暗号化の証跡: STRICT mTLSが適用された範囲では、「サービス間通信がTLSで暗号化されている」ことをポリシーリソースとメトリクス(connection_security_policy)で証明できます。アプリケーションごとのTLS設定を個別確認するより監査範囲が単純になります。
  • アクセス制御の証跡: AuthorizationPolicyはそのまま宣言的なアクセス制御リストです。ポリシーをGitに置けば、変更履歴と承認手続き(PRレビュー)がそのまま監査証跡になります。
  • キー管理: istio-csrと外部PKI統合により、「キーの発行・保管・ローテーション」の統制を既存のPKI監査体系に編入できます。

ただし、メッシュのmTLSはPod間区間の暗号化なので、クラスタ流入前の区間(クライアントからロードバランサーまで)、メッシュ外のデータベース接続、保存データの暗号化は別の統制で埋める必要があるという点を、監査文書で明確に区別しておいてください。この記事は一般的な技術解説であり、特定の規制に対する法的助言ではありません。

性能への影響 — 測定値で語る

mTLSと認可のコストはしばしば過大評価されます。実測基準で整理すると次のとおりです。

  • ハンドシェイクのコストはコネクション確立時の1回です。Envoy間のコネクションはkeep-aliveで再利用されるため、定常状態でのリクエストあたりコストは対称鍵の暗号化・復号だけであり、最近のCPUのAES-NIでは非常に小さいものです。
  • レイテンシ影響の大半はmTLS自体ではなく、サイドカープロキシ経由のコスト(リクエストあたり数ミリ秒以下)です。つまり「メッシュを使うと決めた時点」でほぼ支払い済みのコストであり、mTLSを切っても大きくは回収できません。
  • 認可ポリシーの評価はEnvoy内部で行われ、ルール数十個の規模では無視できます。ただしルール数百個の巨大ポリシー、過度なregexパスマッチング、そしてCUSTOM外部認可(ネットワークホップ追加)は測定してから使うべきです。
  • 証明書の更新はデータパスと分離されており(SDS)、トラフィックに影響しません。

結論: 性能を理由にSTRICT mTLSを先送りするのは、ほとんどの場合根拠が弱いです。心配なら代表的なサービスでp99レイテンシをPERMISSIVE/STRICTで比較測定してから決めてください。

導入ロードマップ — 観察からゼロトラストまで

+------------------------------------------------------------------+
| フェーズ1. 観察 (1~2週間)                                           |
|  - サイドカー注入完了、PERMISSIVEデフォルトを維持                     |
|  - Kiali/Grafanaでサービス呼び出しグラフと平文比率を把握               |
|  - 成果物: サービス間呼び出しマトリクス (認可ポリシーの設計入力)         |
+------------------------------------------------------------------+
| フェーズ2. mTLS STRICT移行 (ネームスペース単位、2~4週間)              |
|  - 平文送信元の除去 → devから段階適用 → 全域STRICT                   |
|  - 例外はportLevelMtlsで狭く、有効期限を管理                         |
+------------------------------------------------------------------+
| フェーズ3. 認可ポリシー (中核サービスから)                             |
|  - 呼び出しマトリクスに基づくALLOWポリシー作成 → dry-run検証          |
|    → 有効化                                                       |
|  - ネームスペースのデフォルト拒否は十分なdry-runの後に                 |
|  - 管理パスのDENY、gateway経由の強制などセーフティネットを追加         |
+------------------------------------------------------------------+
| フェーズ4. 高度化                                                   |
|  - RequestAuthenticationでJWT検証を入口に                          |
|  - データ依存の判断はOPA ext-authzで狭く                            |
|  - カスタムCA/社内PKI統合、CA期限監視の体系化                        |
+------------------------------------------------------------------+

各フェーズの完了基準をメトリクスで定義することが重要です。例えばフェーズ2の完了基準は「connection_security_policyがnoneのリクエストが7日間0件」のように測定可能でなければなりません。

チェックリスト

  • メッシュ全域のPeerAuthenticationデフォルトをSTRICTに設定した (例外は明示的・期限付き)
  • 平文トラフィックのメトリクスが0であることを確認してからSTRICTへ移行した
  • 中核ネームスペースにデフォルト拒否(空のALLOW)ポリシーを置き、必要な経路のみ開けた
  • AuthorizationPolicyのprincipalsをサービスアカウント(SPIFFE ID)基準で書いた
  • ワークロードごとに専用のサービスアカウントを付与した (default SAの共有禁止)
  • JWT検証にissuerとaudiencesの両方を明示した
  • 新しい認可ポリシーはdry-runで検証してから有効化する
  • 403急増とRBAC拒否ログに対するダッシュボード/アラートがある
  • CA証明書の期限アラート(30日前)を仕掛けてある
  • 本番環境は自己署名CAの代わりに社内PKI/cert-manager統合を適用した
  • 外部認可(CUSTOM)は必要なパスにのみ適用し、timeoutと障害時の動作を定義した
  • 認可ポリシーをGitで管理し、変更履歴が監査証跡として残る

おわりに

Istioセキュリティの価値は「ゼロトラストをコード修正なしに、漸進的に」達成できる点にあります。その土台はサービスアカウントベースのSPIFFEアイデンティティと自動更新される短命証明書であり、その上にPeerAuthentication(暗号化の強制)、AuthorizationPolicy(最小権限)、RequestAuthentication(エンドユーザー検証)が層をなして積み上がります。

失敗する導入に共通するのは、順序を飛ばすことです。平文の送信元を整理せずにSTRICTを有効化したり、呼び出しグラフを知らないままデフォルト拒否を敷いたり、dry-runなしで認可ポリシーを本番に押し込んだりするケースです。観察 → PERMISSIVEでの整理 → STRICT → dry-run認可 → 有効化という階段を一段ずつ踏めば、メッシュセキュリティは大きな事故なく到達可能な目標です。証明書が毎日自動で更新され、呼び出し権限がGitの宣言的ポリシーで管理されるメッシュは、セキュリティチームとプラットフォームチームの双方にとって、運用負担ではなく資産になります。

参考資料