インフラエンジニアの羅針盤

インフラエンジニア1〜3年目のための技術ガイド

GKEゼロトラスト設計|RBACとポリシーの決め方

公開

第4回でノードの供給量とNamespaceごとの総量が決まりました。次に決めるのは、そのクラスタを誰に、どこまで触らせるかです。

この設計書には「ゼロトラスト」という名前が付くことが多いのですが、その言葉自体は方針であって設計ではありません。セキュリティチームから「ゼロトラストで」と言われたとき、実際に書くことになるのは、権限の一覧と、禁止事項の表と、通信の許可ルールと、シークレットの置き場所の4つです。本記事では、この4つを埋めるための判断基準を扱います。

本記事も第1回以降と同様、Google Cloud(GKE)を前提に数値と製品名を記載します。判定の考え方はクラウドに依存しませんが、GKEには「Kubernetesの一般論をそのまま適用すると設計が破綻する」箇所がいくつかあり、本記事の前半はその指摘に充てます。

目次
  1. ゼロトラストを4つの層に分解する
  2. RBACを書く前に、IAMを棚卸しする
    1. IAMとRBACは和集合である
    2. 既定の edit をそのまま渡してはいけない
    3. 最も見落とされる2つの動詞
    4. ロール設計マトリクス
    5. 本番の pods/log をどう扱うか
    6. 本番の緊急アクセス(Break-glass)
  3. Workload Identityで、鍵ファイルを設計から消す
    1. Kubernetes ServiceAccountに直接IAMロールを付与する
    2. 鍵の生成そのものを禁止する
  4. Pod Security Standardsは、運用モードで出発点が違う
    1. 3レベルと3モード
    2. Autopilotでは baseline が既に床になっている
    3. StandardでAutopilot相当のポリシーを適用する
    4. restrictedへ上げる作業は、アプリ側の改修になる
  5. ポリシーエンジンは、入れる前に要否を判定する
    1. 標準機能で書ける範囲が広がっている
    2. 外部エンジンが必要になる条件
    3. 導入コストを見積もる
    4. ポリシールール一覧
    5. 例外プロセス
    6. イメージの出所は、Kubernetesの外側でも縛れる
  6. NetworkPolicyは、既定拒否より先に例外を書き出す
    1. GKEでの前提
    2. default-denyの前に必ず開ける2つ
    3. 段階導入の順序と、ルールの粒度
    4. 遮断できたことを確認する
    5. FQDNによる制御を前提にしない
  7. シークレット管理は「K8s Secretに置かない」から始める
    1. なぜK8s Secretが選べないのか
    2. 3方式の比較
    3. 保存時暗号化は、RBACの穴を塞がない
    4. ローテーションポリシー
  8. 【実務テンプレート】ゼロトラスト・ガバナンス設計書
  9. 次回予告

ゼロトラストを4つの層に分解する

設計に着手する前に、対象を4つの層に分けます。この分け方が、そのまま設計書の章立てになります。

問い主な強制点この層の成果物
層1誰が操作できるかIAM + Kubernetes RBAC + Workload IdentityRBACマトリクス
層2何を動かせるかPod Security Admission + ポリシーエンジン + Binary Authorizationポリシールール一覧
層3何と通信できるかNetworkPolicy通信制御の方針
層4何を持てるかSecret Manager + ローテーションポリシーシークレット管理設計

層1の「誰が」には、人間だけでなくPodも含みます。Podはクラウドリソースに対する主体であり、人間と同じく権限を持ちます。Workload Identityを層1に置いているのはこのためです。

4つの層は、どれも同じ形の作業になります。

  1. 既定を拒否にする
  2. 動かなくなるものを洗い出す
  3. 例外を文書化する
  4. 例外に期限を付ける

層4だけは既定拒否の形が少し違います。層4の既定拒否は「クラスタの中にシークレットの実体を置かない」であり、例外が「置かざるを得ないもの」になります。

順序は層1から固定です。層1が抜けたまま層2以降を固めても意味がありません。クラスタの管理権限を持つ人が残っていれば、層2で設定したNamespaceのセキュリティレベルも、層3の通信制御も、本人の手で書き換えられるためです。

ゼロトラスト・ガバナンス設計を、層1の権限、層2のワークロード、層3の通信、層4のシークレットの4層に分け、各層が既定拒否から例外の期限付けまで同じ手順をとること、そして動詞を1つ渡すだけで層1と層3が同時に無効化されることを示した図
4層の全体像。4層とも「既定拒否 → 洗い出し → 例外の文書化 → 期限」という同じ手順になります。最下段の層をまたぐ無効化については、層1の章で扱います。

RBACを書く前に、IAMを棚卸しする

GKEでKubernetes RBACを設計するとき、最初に理解しておくべき仕様があります。ここを知らずに書いたRBAC設計書は、監査で不備になります。

IAMとRBACは和集合である

GKEはIAMとKubernetes RBACを統合しており、どちらか一方で十分な権限を持っていれば操作が許可されます。認可の際は、まずRBACポリシーを確認し、RBACポリシーが存在しない場合にIAMの権限を確認します。IAMはプロジェクトレベル、RBACはクラスタとNamespaceのレベルで機能します。

ここから導かれる結論は、RBACで「与えない」ことはできても、「取り上げる」ことはできない、という一文です。

理由は、Kubernetes RBACに拒否(deny)という概念がないことにあります。ルールは許可の積み上げだけで構成されます。したがって「RBACで拒否したのにIAMが上書きした」という説明は誤りで、正しくは「RBACは許可を積むだけなので、IAM側の許可を打ち消す手段が存在しない」です。ここを取り違えると、「RBACに拒否ルールを書けば解決する」という、実装できない対処に向かうことになります。

具体例を挙げます。GKEの事前定義IAMロール roles/container.developer は、公式の説明で「クラスタ内のKubernetes APIオブジェクトへのアクセスを提供する」と定義されています。このロールをプロジェクトレベルで持っている人がいる限り、Namespace単位のRoleを何枚精密に書いても、その人は本番クラスタのリソースを操作できます。

事故は監査の場で表面化します。「開発者は本番に書き込めない設計です」とRBACのマニフェストを提出し、監査人がプロジェクトのIAMバインディングを確認して指摘する、という形です。第2回で運開分離をPCI DSSの要求として扱った以上、この不整合はそのまま不備になります。

GKEの認可でKubernetes RBACを先に確認し、許可がなければプロジェクトのIAMを確認するという評価順序と、どちらか一方が許可すれば操作が通る和集合になるため、RBACではIAM側の許可を打ち消せないことを示した図
認可の評価順序。2つの経路が同じ「操作は通る」に合流するため、RBAC側だけを精密に書いても権限は締まりません。

したがって、RBAC設計は次の順序で行います。

  1. プロジェクトのIAMバインディングを一覧化する
  2. 基本ロール(オーナー、編集者)と container.* 系ロールの保有者を洗い出す
  3. 人間に残すのは、原則として roles/container.clusterViewer(クラスタの取得と一覧表示に必要な最小権限)だけにする
  4. それ以外の権限は、Kubernetes RBACで必要な分だけ積み上げる

この4手順を経ていないRBAC設計書は、レビューで受け取らないでください。手順1と2を飛ばした設計書は、書いてある内容が実態と一致しません。

既定の edit をそのまま渡してはいけない

既定のClusterRoleをそのままバインドする構成をよく見かけますが、edit には注意が必要な性質があります。

既定のClusterRoleSecretの読み取り注意点
viewできない読み取り専用。Secretは対象外
editできる加えて、Namespace内の任意のServiceAccountとしてPodを実行できる
adminできるNamespace管理者。RoleとRoleBindingも操作できる
cluster-adminできるクラスタ全体の特権

「開発者には自分のNamespaceの edit を渡しています」という構成は、そのNamespaceのSecretを開発者全員が読める構成と同義です。さらに、そのNamespaceにより強い権限を持つServiceAccountが存在すれば、そのServiceAccountとしてPodを実行して権限を得る経路が開きます。

したがって、既定のClusterRoleを直接バインドせず、必要なリソースと動詞だけを列挙したカスタムRoleを作ります。

区分対象
含めるdeployments / statefulsetsservicesconfigmapsingresses または httproutespodsget / list / watch / delete)、pods/logget)、eventsget / list
含めないsecretsserviceaccountsroles / rolebindingsresourcequotas / limitranges(第4回で運用チームが縛る側と決めたため)

あわせて、RBACには権限昇格を防ぐ仕組みがあります。自分が持っていない権限を含むRoleやRoleBindingは作成できませんescalatebind の動詞を明示的に持つ場合を除く)。この2つの動詞を開発者に渡さないでください。渡した時点で、上記のカスタムRoleは自分で書き換えられる対象になります。

最も見落とされる2つの動詞

pods/execpods/portforward を開発者のRoleに含めないでください。

secrets をRoleから除外しても、pods/exec があればコンテナの中に入れます。コンテナの中では環境変数にもマウント済みのファイルにもアクセスできるため、Secretリソースを読む必要がありません。pods/portforward も同じ構造で、層3でNetworkPolicyを使って遮断したはずの内部サービスへ、手元の端末から到達できます。

つまり、層1で secrets を外し、層3で通信を絞った設計は、pods/exec を1行渡した時点で両方とも無効化されます。層は独立していません。本記事が4層をまとめて扱う理由が、この1点に凝縮されています。

現実的な落とし所は、環境で差を付けることです。Devでは調査の利便性のために許可し、StgとProdでは外します。第3回で環境ごとにクラスタを分けているため、この差はクラスタ単位のRBACとして表現できます。

ロール設計マトリクス

以上を踏まえた設計例です。第3回で決めた3クラスタ(Prod・Stgはリージョン、Devはゾーン)を前提にしています。

ペルソナDevStgProd
開発チームカスタム編集Role(exec 許可)読み取りのみ書き込みなしpods/log は下記の基準で判断
運用チーム管理管理管理(cluster-admin は常設しない)
セキュリティチーム読み取り + ポリシー定義リソースの編集同左同左(運用実行の権限は持たない
CI/CDのServiceAccount適用に必要な範囲同左同左

セキュリティチームにポリシー定義リソースの編集権だけを与え、ワークロードの操作権限を与えないのは、第2回で決めた「セキュリティチームにAを持たせない」という結論を権限の形にしたものです。ポリシーを定義する側が運用実行の結果責任も負うと、相互牽制が働きません。RACIの結論とRBACのマトリクスが一致しているかを突き合わせてください。ここがずれていると、第2回の設計は文書の上だけのものになります。

権限設計で最も見落とされる主体は、人間ではなくCI/CDのServiceAccountです。GitOpsのコントローラは全Namespaceにリソースを適用できる必要があり、実質的にどの人間よりも強い権限を持ちます。ここも「適用に必要な範囲」で済ませず、対象Namespaceを列挙し、secretsrolebindings の作成を含めるかどうかを明示してください。含めるなら、そのコントローラを操作できる人間は実質的にクラスタ管理者と同等です。ServiceAccountの権限範囲を決めるのは本記事の管轄で、コントローラそのものの構成設計は第9回です。

バインドの対象は、個人のメールアドレスではなくGoogle Groupsにしてください。入退社と異動のたびにマニフェストを変更せずに済みます。制約が2つあり、RBACの対象にできるのは gke-security-groups というグループにネストされたグループだけで、グループ名の参照は大文字と小文字を区別します。設計書にはグループ名とRoleの対応表を載せ、これを四半期ごとの棚卸し対象にします。

本番の pods/log をどう扱うか

「本番は開発者に権限なし」と書くと、開発者は障害時に自分のアプリのログすら見られなくなります。結果として運用チームへの問い合わせが集中し、例外申請で穴が開きます。

運開分離が要求しているのは、本番データへのアクセスと本番への変更操作の分離であって、ログの閲覧そのものではありません。したがって、ログに機微データ(カード番号、個人情報、認証情報)が出力されない設計になっているかで判断します。なっているなら pods/logget は渡してよく、なっていないなら、ログ基盤側でマスキングした閲覧経路を用意するまで渡しません(ログ出力の標準化ルールは第15回)。根拠を確認せずに全面禁止にすると、運用が回らず、結局は例外で崩れます。

本番の緊急アクセス(Break-glass)

平時に誰も本番の管理権限を持たない構成にすると、障害時に誰も手を出せなくなります。緊急時に一時的に権限を付与する経路を、事前に設計しておきます。

ここでもIAMとRBACの和集合が効きます。Kubernetes RBACには有効期限を持つバインディングがありません。したがって時限付きのBreak-glassを実装する場所は、Kubernetes側ではなくIAM側です。前節で「本番クラスタを操作できるIAMロールを人間から外す」と書きましたが、外したうえで緊急時だけ時限付きで戻す、というのが完成形になります。

手段は2つあります。Privileged Access Managerは、付与ごとに時間ベースの条件を持つバインディングを作成し、条件が変更されない限り付与期間の終了とともに権限が失効します。より単純には、IAM Conditionsの条件付きロールバインディングで request.time < timestamp('2026-08-01T00:00:00.000Z') のような有効期限を設定する方法もあります。

期限の長さに、根拠のない数字を置かないでください。期限は、第16回で定義するSeverity 1の対応SLAと同じ長さに揃えます。それより短いと対応の途中で権限が切れて二次障害になり、長いと対応が終わったのに権限が残ります。第2回で運用チームの一次応答を4時間と定めているので、自組織の一次応答時間と対応枠を根拠にしてください。

失効を人間の作業にしないでください。取り消しを手作業にすると、外し忘れが残ります。自動失効する仕組みを使うか、使えないなら「毎営業日の棚卸しで残存バインドを検知する」ところまでを設計に含めます。

誰がいつ発動を判断するか(エスカレーションフロー)は第16回の管轄です。本記事は「発動できる仕組みを事前に作り、発動が監査ログに残ることを設計要件にする」ところまでを扱います。ログの保存期間とアラート化は第15回です。

Workload Identityで、鍵ファイルを設計から消す

層1の後半は、Podの権限です。サービスアカウントキーのJSONファイルをK8s Secretに置く構成は、本記事の設計では選択肢に入りません。鍵ファイルは有効期限を持たず、漏洩しても失効させない限り使い続けられるためです。

Kubernetes ServiceAccountに直接IAMロールを付与する

Workload Identity Federation for GKEには2つの方式があります。

方式内容使いどころ
直接バインドKubernetes ServiceAccountを表すプリンシパル識別子に、IAMロールを直接付与する既定。IAMサービスアカウントを作らずに済む
権限借用Kubernetes ServiceAccountをIAMサービスアカウントに紐付け、権限を借用するフェデレーションに制限のあるAPIを使う場合

直接バインドで使うプリンシパル識別子の書式は次のとおりです。

principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/NAMESPACE/sa/KSA_NAME

この書式を見れば、設計上の含意が分かります。権限の境界が「Namespace名 + Kubernetes ServiceAccount名」そのものになります。

つまり、第3回で決めたNamespaceの切り方と、ServiceAccountの命名規則が、そのままクラウド側のIAM権限の境界になります。命名規則を設計書に明記しないと、IAMのバインディング一覧を見ても何の権限か読み取れなくなります。第4回で決めた必須ラベルのキー(チーム、サービス)と、ServiceAccountの命名規則を揃えてください。按分の単位と権限の単位が一致していれば、費用と権限を同じ軸で棚卸しできます。

有効化の単位は運用モードで異なります。

  • Autopilotでは常に有効です。Podからノード上の機密メタデータへのアクセスも防がれます
  • Standardでは、まずクラスタレベルで --workload-pool=PROJECT_ID.svc.id.goog を有効にし、そのうえでノードプール単位で --workload-metadata=GKE_METADATA を設定します。クラスタレベルで有効にしてからでないと、ノードプールでは有効にできません

鍵の生成そのものを禁止する

Workload Identityを導入しても、開発者が個別にサービスアカウントキーを作ってK8s Secretに置く経路は残ります。「入れたが、古い方式も併存している」という状態が最も危険です。棚卸しの対象が二重になり、どちらも管理されなくなります。

組織のポリシーで、鍵の作成自体を禁止してください。iam.disableServiceAccountKeyCreation という制約が、新しい外部サービスアカウントキーの作成を無効にします。設計時に押さえるべき性質が2つあります。

  • 2024年5月3日以降に作成された組織では、この制約は既定で適用されています。それ以前から使っている組織では、明示的に有効化する必要があります
  • この制約は遡及しません。すでに作成済みの鍵には影響しないため、既存の鍵を洗い出して無効化する作業を、設計書の移行計画に別途書く必要があります

なお、Workload Identityはメタデータサーバ経由で動作します。層3でegressを既定拒否にすると、認証そのものが失敗します。具体的な許可ルールは層3の章で提示します。

Pod Security Standardsは、運用モードで出発点が違う

層2に入ります。まずKubernetes標準の仕組みであるPod Security Admissionから決めます。

3レベルと3モード

区分意味
レベルprivileged制限なし。既知の権限昇格を許す
レベルbaseline既知の権限昇格を防ぐ
レベルrestrictedハードニングのベストプラクティスを強制する
モードenforcePod作成を拒否し、監査イベントを記録する
モードaudit作成は許可し、監査ログに違反を記録する
モードwarn作成は許可し、利用者向けの警告を表示する

適用はNamespaceのラベルで行います。書式は pod-security.kubernetes.io/MODE=LEVEL です。PodSecurityアドミッションコントローラはGKE 1.25以降で安定版になっています。

pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted

モードは併用できます。上の例は「現行レベルをenforceしつつ、1段上のレベルを警告と監査で観測する」という構成で、これが移行期の基本形です。違反がゼロになったらenforceのレベルを1段上げます。

Autopilotでは baseline が既に床になっている

ここが本章で最も重要な点です。既定のAutopilot Pod受付ポリシーは、Pod Security Standardsのbaselineレベルの推奨をすべて含みます(使いやすさのためのいくつかの変更を伴います)。

したがってAutopilotクラスタにおける本章の設計判断は、「baselineをどう当てるか」ではなく、「restrictedまで上げるか否か」の一点に縮小します。移行の3段階が本当に必要になるのは、主にStandardクラスタと、Autopilotでrestrictedを目指す場合です。第3回で決めた運用モードによって、この章の作業量が変わります

ただし、素のbaselineと完全一致ではありません。使いやすさのための変更が3つあります。

  • allowPrivilegeEscalation の保護は、rootで実行されていないコンテナにのみ提供されます
  • NET_RAW(ping)、SYS_PTRACE(デバッグ)、NET_ADMIN(サービスメッシュ)は手動で有効化できます
  • /var/log への読み取り専用のhostPathアクセスは要求できます

「Autopilotなのでbaselineは満たしています」と監査で答えると、この3点で差分を指摘されます。設計書には、素のbaselineとの差分を明記してください。

そのほかAutopilotでは、hostNetwork、書き込み可能な hostPath、ホスト名前空間の使用、SSHアクセスが不可で、特権コンテナは検証済みパートナーのワークロードを除いて動きません。spec.externalIPs を使うServiceもブロックされます。Autopilotの組み込みセキュリティ設定を上書きするポリシーは作成できません。

EDRエージェントのような特権を要するワークロードの扱いは、第3回で扱ったAutopilotパートナーワークロードの判断手順に従ってください。製品名が一覧にあるかだけでなく、センサーの方式まで確認する必要があります。

StandardでAutopilot相当のポリシーを適用する

Standardクラスタを選んだ場合でも、Autopilotのセキュリティポリシーを個別に適用できます。--autopilot-cluster-policies フラグで、次の4つを選択します。

ポリシー内容
no-system-mutationGKEのシステムNamespace(kube-system など)のリソース作成と変更を防ぐ
no-system-impersonationシステムユーザーの偽装と、システムプリンシパルを使った証明書署名要求の作成を防ぐ
no-unsafe-webhooksNodeやPersistentVolumeなど重要なシステムリソースを横取りするアドミッションWebhookを防ぐ
no-standard-node-poolsユーザー管理のノードプールを禁止し、Autopilotノードプールのみ許可する

適用の性質が2種類あるため、移行計画に影響します。ワークロードに適用されるポリシーは新規作成と変更のときにのみ評価され、既存のPodには遡及しません。一方、ノードプールに適用されるポリシーは既存のプールを含むすべてに適用され、違反していると更新が失敗します。後者は、有効化した瞬間に既存構成が引っかかる可能性があります。

この機能があるため、「Autopilotは制約が強すぎるからStandardにした」という組織でも、システムNamespaceの保護だけを取り込むといった選択ができます。運用モードの選択は、セキュリティ設定については全か無かではありません。

restrictedへ上げる作業は、アプリ側の改修になる

restrictedで実際に弾かれるのは、次のようなコンテナです。

  • rootで動くコンテナ
  • allowPrivilegeEscalationfalse にしていないコンテナ
  • capabilitiesdrop: ["ALL"] していないコンテナ
  • seccompProfileRuntimeDefault にしていないコンテナ

いずれもプラットフォーム側の設定変更ではなく、アプリケーション側の改修です。ベースイメージの変更やファイルの権限設計が絡むため、数日で終わる作業ではありません。改修の主体は開発チームです(第2回のRACIで所在を確認してください)。したがって、設計書に書くべきは目標レベルではなく期限であり、期限を切れないならそのNamespaceはbaselineで止めます。

「全社restricted」を宣言して実行されない設計書より、baselineとrestrictedが混在する現実の設計書のほうが価値があります。推奨は、新規Namespaceは最初から enforce=restricted を既定にし、既存Namespaceはbaselineで止めて個別に期限を切る運用です。新規に厳しい既定を置くほうが、既存を追い込むより確実に進みます。

なお、PodSecurityが見るのはPodのセキュリティコンテキストだけです。イメージのタグとレジストリ、ラベルの有無、リソース要求の有無、Serviceの型は対象外です。ここから先が次章の領分になります。

ポリシーエンジンは、入れる前に要否を判定する

層2の後半です。順序を間違えないでください。ルールの一覧を先に作り、そのうち何割が標準機能で書けるかを数えます。ツールを決めてからルールを考えると、そのツールで書けることがルールになります。

標準機能で書ける範囲が広がっている

ValidatingAdmissionPolicyがKubernetes 1.30で安定版になりました。CEL(Common Expression Language)で検証ルールを記述し、外部のWebhookを立てずにAPIサーバ内で評価されます。

ポリシー本体(ValidatingAdmissionPolicy)と、それを適用範囲に結びつけるバインディング(ValidatingAdmissionPolicyBinding)の2つが最低限必要です。バインディング側の validationActions には DenyWarnAudit を指定します。

ここに移行の設計が入ります。DenyWarn の併用はできません。同じ違反をレスポンスボディとHTTP警告ヘッダに二重に出すことになるためです。移行期は [Warn, Audit]、本適用は [Deny] という使い分けになります。

この仕組みで書けるのは、対象リソース単体で完結する検証です。「latestタグ禁止」「requests未設定の拒否」「NodePort禁止」といった、よくある禁止ルールの多くがここに収まります。PodSecurityと合わせると、ルール表の相当部分が標準機能で埋まります。

外部エンジンが必要になる条件

次の条件に当てはまる場合に限って、外部のポリシーエンジンを検討します。

  • 値の書き換え(mutation)が要る。例: 未設定のラベルを自動補完する、automountServiceAccountToken: false を自動で注入する
  • クラスタ内の他リソースを参照した検証が要る。例: 「同名のホスト名を持つルーティング設定が他のNamespaceに存在しないこと」
  • 複数クラスタへポリシーを一括配布し、違反状況を横断で集計したい
  • 既製のコンプライアンス向けポリシー集をそのまま適用したい

最後の項目について補足します。GKEにはOPA GatekeeperをベースにしたPolicy Controllerがあり、PCI DSS、CIS Kubernetes Benchmark、Pod Security Standardsのbaselineとrestricted、NIST SP 800-53、NSA・CISAのKubernetesハードニングといったポリシーバンドルが用意されています。準拠を証明すべき基準が決まっているなら、自前でルールを書くより既製のバンドルを当てるほうが早い、という判断はあり得ます。どのエンジンを選ぶかの理由そのものは第6回です。

Autopilotを選んでいる場合、追加の制約があります。AutopilotではMutating Webhookが nodespersistentVolumescertificatesigningrequeststokenreviews を横取りできず、リソースやグループにアスタリスクのワイルドカードを使ってこの制限を回避することもできません。外部エンジンの設定例はワイルドカード指定のものが少なくないため、その設定のままAutopilotに持ち込むとWebhookの登録自体が拒否されます。

さらに、Autopilotで独自のPodレベルのセキュリティポリシーを作る場合、GKEのドキュメントはGatekeeperの利用を案内しています。標準機能で書くという方針が、運用モードによっては選べない可能性があります。Standardで検証したポリシー構成がAutopilotでそのまま動くとは限らない、という前提で進めてください。

導入コストを見積もる

外部エンジンはアドミッションWebhookとして動くため、可用性の判断が入ります。failurePolicyFail にするとエンジンの停止時にリソース作成が止まり、Ignore にすると停止中はポリシーをすり抜けます。どちらを取るかは、そのポリシーが規制要件に紐づくかどうかで決めてください。

費用は3手順で見積もります。エンジンの常駐Pod(コントローラと監査用)の requests を確認し、第4回で確認したノードの単価に掛けて増分を出し、第3回で決めたクラスタ数を掛けます。加えて、ポリシー違反のログと後述するNetworkPolicyの拒否ログは、どちらもCloud Loggingの取り込み量として効きます。第4回で変動費として扱った枠に、本記事の設計がそのまま乗ります。

ポリシールール一覧

本記事の主成果物です。自組織の設計書に転記して使ってください。

ルール強制手段初期最終関連
latest タグのイメージを拒否標準機能 または エンジンWarnDeny第9回
許可レジストリ以外のイメージを拒否Binary Authorization の許可リストドライランDeny第9回
必須ラベルの欠落を拒否エンジン(補完するなら mutation)AuditDeny第4回 / 第2回
requests・limits 未設定を拒否LimitRangeで既定値を注入し、上下限の範囲外を拒否(Standardのみ既定値注入範囲外はDeny第4回 / 第11回
root実行・特権コンテナ・hostPathhostNetwork を拒否PodSecurity(baseline / restricted)WarnEnforce本記事
default ServiceAccount の使用を拒否標準機能 または エンジンWarnDeny本記事(層1)
automountServiceAccountToken: false を既定にするエンジン(mutation)Mutate本記事(層1)
NodePort型Serviceを拒否(公開は所定のGateway経由に限る)標準機能 または エンジンWarnDeny第3回
Namespaceにオーナーと連絡先のラベルを必須化エンジンAuditDeny第2回 / 第4回

この表の使い方について、3点を補足します。

1つ目。強制手段は1つに決めてください。root実行の禁止は、PodSecurityでもポリシーエンジンでも書けます。両方で強制すると、違反が起きたときにどちらが弾いたのか分からなくなり、例外を出すときに2箇所を直すことになります。ルールごとに1つの手段を選び、表に書く。これがこの表の存在理由です。

2つ目。運用モードによって行数が変わります。第4回で確認したとおり、Autopilotは未指定のコンテナにリソースのデフォルト値を割り当て、最小値未満は自動的に引き上げ、最大値超過はワークロードを拒否します。つまり「requests・limits未設定を拒否」というルールは、Autopilotではプラットフォームが肩代わりするため、設計に載せる必要がありません。同様に、Autopilotの組み込み制約と重複するルールも整理の対象です。他社のルール表をそのまま流用しないでください。

3つ目。初期モードをDenyにしないでください。既存のワークロードが再デプロイできなくなります。AuditまたはWarnで観測し、違反の一覧を作り、修正の期限を決めてからDenyに上げます。

観測期間の長さは、日数ではなく「全ワークロードが最低1回はデプロイされたか」で決めます。アドミッションのルールは作成と更新のときにしか評価されないため、その期間にデプロイされなかったワークロードは違反していても一覧に出てきません。四半期に1回しかリリースしないサービスがあるなら、観測期間はそのリリースをまたぐ必要があります。第4回で決めたデプロイ頻度が、そのまま観測期間の下限になります。既存リソースへの遡及(すでに動いているが違反しているもの)をどう扱うかも、この段階で決めます。

例外プロセス

第2回で例外申請プロセスを定義しました。本記事のポリシーにも同じ形を適用します。

例外には必ず期限を付け、期限切れを検知してください。期限のない例外は、次の監査までに積み上がります。そして増えた例外は、誰がなぜ追加したのか説明できない状態になります。

実装としては、例外をNamespaceのラベルやポリシーの除外リストで表現し、除外リストそのものをレビュー対象のコードとして管理します。誰がいつ追加したかが履歴に残る形にしてください。配布の仕組みは第9回です。

期限の長さは、第2回で責任境界定義書に設定した四半期ごとの見直しに揃えるのが基本です。見直しの周期より長い例外期限を設定すると、棚卸しの場に「まだ有効な例外」しか並ばず、点検の意味がなくなります。3ヶ月を既定とし、それより長い期限を求める申請だけを個別に審査する、という運用にしてください。

イメージの出所は、Kubernetesの外側でも縛れる

ここまでのルールはクラスタ内で強制するものですが、イメージの出所だけは外側にも手段があります。Binary Authorizationのポリシーはプロジェクトに1つで、許可リストにイメージのパスを列挙します。必ずドライラン(許可しつつ監査ログにのみ記録する強制モード)から始めてください。自組織が持ち込む常駐ワークロード(監視エージェント、EDR、ログ収集、サービスメッシュのサイドカー)が許可リストから漏れていると、再スケジュールがクラスタ全体で失敗します。どのイメージを許すかの宣言は本記事、署名を作る側(CIでの構成証明の生成)は第9回です。

NetworkPolicyは、既定拒否より先に例外を書き出す

層3です。この層の作業は、default-denyを入れることではありません。例外を洗い出すことです。default-denyのマニフェスト自体は数行で書けます。

GKEでの前提

AutopilotではGKE Dataplane V2によりNetworkPolicyの適用が既定で有効です。Standardでは明示的な有効化が必要で、アドオンの有効化とクラスタ側の有効化の2段階になります。Dataplane V2はCiliumをベースにしており、Calicoとは排他です。

NetworkPolicyの適用にはノード側に常駐のリソース消費が発生するため、極端に小さいマシンタイプは選べません。第4回で「Spotの可用性を上げるには小さいマシンタイプが有利」と書いた指針と、ここで衝突します。ノードサイズは、IP消費(第3回)、Spotの可用性(第4回)、NetworkPolicyの常駐コスト(本記事)の3つを同時に見て決めてください。

そのうえで、Calicoとの決定的な差を押さえてください。

Dataplane V2では、Podのトラフィックは ipBlock ルールの対象になりません。

Calicoでは ipBlock がPodのトラフィックにも一致するため、広いCIDRを許可する場合はPod CIDRを明示的に除外する必要があります。この差を知らずにCalico前提のサンプルを持ち込むと、除外したつもりの通信が意図と違う挙動になります。ネット上のNetworkPolicyの例は、どちらの実装を前提にしているか明記されていないことが多いため、流用する前に確認してください。

default-denyの前に必ず開ける2つ

egressを既定拒否にする前に、次の2つを例外として許可してください。

宛先ポート用途
kube-system Namespace53/UDP および 53/TCP名前解決
169.254.169.254/32(Dataplane V2 の場合)80 および 8080メタデータサーバ。Workload Identityの認証

Dataplane V2以外の構成では、メタデータサーバの宛先は 169.254.169.252/32 の988と987になります。GKE 1.21.0-gke.1000 より前のバージョンでは 127.0.0.1/32 です。自分のクラスタの構成を確認してから書いてください。

この2つを開けずにegressの既定拒否を入れると、Workload Identityによる認証が失敗し、Google Cloud APIを呼ぶPodがすべて動かなくなります。そして名前解決も落ちるため、アプリケーションのログには接続エラーだけが並び、原因がNetworkPolicyであることが分かりにくくなります。「NetworkPolicyを入れたらアプリが動かなくなった」という報告の多くは、この2つです。

構成によっては、さらに例外が増えます。代表的なのは、IngressまたはGatewayからのヘルスチェック経路です(第3回で決めた公開方針と接続します)。必ず開ける2つに加えて、自組織の構成で増える分を洗い出すのが、この層の作業です。

段階導入の順序と、ルールの粒度

導入は次の順序で行います。

  1. ingressの既定拒否をNamespace単位で入れる(影響がNamespace内に閉じるため)
  2. 必要な通信を許可ルールとして積む
  3. 最後にegressの既定拒否を入れる

egressを先に入れないでください。egressの既定拒否は、前節のとおりクラスタの基本機能に影響します。

ルールの粒度は、既定をNamespace単位(namespaceSelector)にします。Pod単位まで絞るのは、同一Namespace内で機微データを扱うPodが分かれている場合に限ってください。粒度を細かくするほど、アプリの構成変更のたびにポリシーの修正が必要になります。

ここで1つ注意があります。第4回で決めたコスト按分用のラベルを、NetworkPolicyのセレクタに流用しないでください。按分の都合でラベルの値を変更した瞬間に、通信が切れます。目的の異なるラベルは、キーを分けてください。按分は会計の都合で変わり、通信制御はセキュリティの都合で変わります。変更の理由が違うものを同じキーに乗せると、片方の変更がもう片方を壊します。

遮断できたことを確認する

Dataplane V2のクラスタには、既定で NetworkLogging という名前のカスタムリソースが作られます。これを編集すると、許可された接続と拒否された接続の両方をCloud Loggingへ記録できます。

  • 許可側は、policy.network.gke.io/enable-logging: "true" のアノテーションを持つポリシーに一致したものだけに絞れます
  • 拒否側は、policy.network.gke.io/enable-deny-logging: "true" を付けたNamespaceのものだけに絞れます

制約が2つあります。1ノードあたり毎秒500接続を超えると、ログはドロップされます。また、ログの生成そのものに料金はかかりませんが、Cloud Loggingの保管費用は発生します。

運用としては、移行期間中だけ拒否ログを有効にし、違反がゼロになったら絞ります。ここで完了条件を明確にしてください。「default-denyを入れた」ことではなく、「拒否ログがゼロである」ことを完了条件にします。前者は設定した事実、後者は意図どおりに動いている事実です。

FQDNによる制御を前提にしない

「外部のSaaS宛の通信をドメイン名で許可したい」という要件はよく出ます。GKEには FQDNNetworkPolicy がありますが、現時点ではアルファ版で、解決できるIPアドレスは最大50egress専用、ワイルドカードは同一階層のサブドメインにのみ一致、という制約があります。大手SaaSはIPアドレスが50を超えることが珍しくないため、この機能を本番の前提に置かないでください。

代替は2つです。Cloud NATの固定IPを出口として相手側の許可リストに登録する(第3回のネットワーク設計と接続します)か、外向きのプロキシを1箇所に集約してドメイン単位で制御するか。どちらを選ぶかは、相手システムが送信元IPの登録に対応しているかで決まります。第1回の条件3で「相手システムが送信元IPを許可リストに登録している」ケースをK8s不採用の判定材料として扱いましたが、その制約はここでは前者を選べる根拠として働きます。

シークレット管理は「K8s Secretに置かない」から始める

層4です。この層の結論は、暗号化の話ではなくRBACの話から導かれます。

なぜK8s Secretが選べないのか

層1で確認したとおり、edit 相当の権限を持つ主体は、そのNamespaceのSecretを読めます。そして pods/exec があれば、Secretリソースを読まなくてもコンテナの中身にアクセスできます。

第2回でモデルB(共通Helm Chart型)を選び、開発チームに自Namespaceの操作権を渡す設計にしました。その設計と、本番の資格情報をそのNamespaceのSecretに置く設計は両立しません。

「Base64は暗号化ではない」という指摘をよく見かけますが、本質はそこではありません。本質は、Secretの読み取り可否がKubernetes APIの権限で決まってしまうことです。したがって解は「Kubernetes APIを経由せずにPodへ渡す」になります。

3方式の比較

方式Podへの渡り方RBACの問題判断
① K8s Secretに直接置く環境変数 / ボリューム残る(Namespaceの編集権で読める)選ばない
② 外部ストアからK8s Secretへ同期同期されたSecret経由残る(最終的にK8s Secretになるため)出所の一元管理には効くが、読み取り制御の問題は解けない
③ Secret Managerアドオンでボリュームマウントファイルシステム解消(Kubernetes APIを経由しない)既定解

方式②は誤解されやすいので補足します。外部のシークレットストアを真実の源にすること自体は正しい設計です。ただし、同期先がK8s Secretである限り、そのNamespaceの編集権を持つ人は読めます。出所を一元化する効果と、読み取りを制御する効果は別のものです。

方式③について、GKE用のSecret Managerアドオンの仕様を整理します。

項目内容
提供状況一般提供(GA)
必要なバージョンGKE 1.27.14-gke.1042001 以降
Autopilot対応
前提Standardでは Workload Identity Federation が必須(Autopilotは既定で有効)
渡り方ボリュームとしてマウントし、コンテナのファイルシステム上でアクセスする
自動ローテーションGKE 1.32.2-gke.1059000 以降。Secret Manager側で更新された値をPodへ定期的に反映する。間隔の最小値は120秒
制限「Sync as Kubernetes Secret」機能は非対応(オープンソース版のドライバにはある機能)。Windows Serverノードは非対応

設計上の影響が1つあります。方式③では、アプリケーションは環境変数ではなくファイルからシークレットを読む必要があります。これはアプリ側の改修であり、PodSecurityのrestricted化と同じ性質の作業です。移行計画に、両方を並べて期限を書いてください。

保存時暗号化は、RBACの穴を塞がない

GKEは既定で保存時のデータを暗号化します。そのうえで、Cloud KMSの鍵を使うアプリケーションレイヤの暗号化を追加できます。仕組みはエンベロープ暗号化で、データ暗号鍵(DEK)を鍵暗号鍵(KEK)で包み、KEKはKubernetes側に保存されません。採用する場合、運用上の制約を先に確認してください。

  • 鍵はクラスタと同じリージョンのものを選ぶ必要があります
  • まだデータを暗号化している鍵バージョンを破棄すると、クラスタは劣化し、最終的に動作しなくなります
  • 鍵のローテーション後は、既存のSecretを手動で再暗号化して新しい鍵バージョンで包み直す必要があります
  • 上限はクラスタあたり最大30,000個のSecret、Namespaceあたりのメタデータ平均5KiB未満です

そのうえで、最も重要な点です。アプリケーションレイヤ暗号化は、層1のRBACの穴をまったく塞ぎません。塞ぐのは「クラスタ状態のバックアップが流出した場合」の脅威です。監査要件で求められたら入れる、というのが正しい位置づけで、これを入れたからK8s Secretに本番の資格情報を置いてよい、という結論にはなりません。

ローテーションポリシー

最後に、何をどの頻度で更新するかを決めます。ここで最も危険な誤解を先に是正しておきます。

Secret Managerのローテーションスケジュールは、シークレットの値を自動的に変更しません。

この機能が行うのは、指定した時刻に SECRET_ROTATE というメッセージをPub/Subトピックへ送ることだけです。実際にローテーションを実行するのは、その通知を受け取るサブスクライバ、つまり自前の実装です。設定上の制約として、ローテーション周期は1時間未満にできず、次回ローテーション時刻は5分以上先である必要があります。

設計書に「ローテーション: 90日」とだけ書いた状態は、通知が90日ごとに飛ぶだけで値が変わらない状態を作り得ます。そして通知の購読先が未実装なら、その通知はどこにも届きません。設計書の欄には、周期と並べて「ローテーションを実行する主体」を必ず書いてください。実行手順とランブックそのものは第16回です。

運用上の指針として、公式が推奨している点を挙げます。

  • 本番では latest エイリアスではなく、特定のバージョンを指定して参照する
  • 複数のバージョンを有効に保ち、段階的な切り替えと切り戻しを可能にする
  • ローテーションで不正な値が配布されうるため、エラー率の監視と自動ロールバックを併せて設計する(監視の設計は第15回)

周期そのものについては、「90日」を慣習で書かないでください。PCI DSS 4.0は、アプリケーションやシステムのアカウントの資格情報の変更頻度について、固定の日数ではなく対象を絞ったリスク分析に基づいて定めることを求める方向に変わっています。設計書に書くべきは日数ではなく、その日数を導いた根拠です。

根拠を組み立てるための入力は3つです。

  1. 漏洩時の影響範囲 — そのシークレットで到達できるのが本番データか、読み取り専用か、開発環境限りか
  2. ローテーションの自動化度 — 差し替えが自動か、人手の作業を伴うか
  3. 相手システムの制約 — 相手側が2世代の同時有効化を許すか。許さない場合、切り替えに必ずダウンタイムが伴う

そして判断基準です。自動化されていないシークレットに、短い周期を設定してはいけません。

人手の作業を前提に30日周期を設計書に書くと、担当者の不在や優先度の低下で期限を過ぎます。そして期限切れに気づかないまま資格情報が失効し、本番が停止します。これはローテーションを設計しなかった場合より悪い結果です。順序は「まず自動化できるかを判定し、自動化できないものは周期を延ばしたうえで、期限切れの検知だけは必ず入れる」です。

【実務テンプレート】ゼロトラスト・ガバナンス設計書

以下をそのまま設計書として使ってください。第1回から第4回の成果物が入力になります。

セクション1: 前提の確認

第2回のRACIの結論(各タスクのAの所在): 参照先______________________________
運開分離の要求: [あり / なし]
第3回のクラスタ構成: Prod[______] / Stg[______] / Dev[______]
運用モード: [Autopilot / Standard / 混在]
 → Autopilotの場合、PodSecurityのbaselineは既に適用済みとして扱う
第4回の必須ラベル一覧: ______________________________


セクション2: 層1・アイデンティティと権限

2-1. IAM棚卸し
基本ロール(オーナー / 編集者)の保有者: ______________________________
container.* 系ロールの保有者: ______________________________
削減後に人間へ残すロール: ______________________________
棚卸し実施日: ____/____/____

2-2. RBACマトリクス
バインド対象: [Googleグループ / 個人](グループの場合、gke-security-groups へのネストを確認)
開発チーム向けカスタムRoleに含めるリソースと動詞: ______________________________
同、除外するリソース: ______________________________
pods/execpods/portforward: Dev[許可 / 不可] Stg[許可 / 不可] Prod[許可 / 不可]
本番の pods/log: [渡す / 渡さない] / 根拠(機微データが出ないことの確認状況)______
CI/CDのServiceAccountの権限範囲: ______________________________

2-3. Break-glass
事前定義するBindingの名称: ______________________________
付与の仕組み: [Privileged Access Manager / IAM Conditions / その他______]
期限: ____時間
 根拠(Sev1の対応SLAとの対応): ______________________________
失効の担保: [自動失効 / 日次棚卸しで検知]
発動の監査ログ: [記録される / 未確認]


セクション3: 層1・Workload Identity

Kubernetes ServiceAccountの命名規則: ______________________________
 → 第4回の必須ラベルのキーと揃っているか: [はい / いいえ]
方式: [直接バインド(既定) / 権限借用]
 権限借用を使う場合の理由: ______________________________
サービスアカウントキーの作成禁止: [組織ポリシーで適用済 / 未適用]
既存の鍵の棚卸しと無効化: 対象____件 / 完了期限____/____/____


セクション4: 層2・Pod Security Standards

新規Namespaceの既定: [enforce=restricted / enforce=baseline]
Namespace A: 現行[______] / 目標[______] / 移行期限____/____/____ / 改修担当______
Namespace B: 現行[______] / 目標[______] / 移行期限____/____/____ / 改修担当______
Autopilotの場合、素のbaselineとの差分3点を設計書に記載したか: [はい / いいえ]
Standardの場合、--autopilot-cluster-policies の採用: [no-system-mutation / no-system-impersonation / no-unsafe-webhooks / no-standard-node-pools / 採用しない]


セクション5: 層2・ポリシールール一覧

本記事の表を転記し、自組織の値で埋めること。記入時の確認事項は3つ。
 確認1: 強制手段が1ルール1つになっているか
 確認2: 運用モードによって不要になる行に印を付けたか
 確認3: 初期モードがDenyになっている行がないか

外部ポリシーエンジンの要否: [要 / 不要]
 要とする場合の該当条件: [mutation / 他リソース参照 / 複数クラスタ配布 / 既製バンドル]
 常駐Podの requests 合計: CPU______ / メモリ______
 クラスタ数を掛けた月額の増分見込み: 約______
 failurePolicy: [Fail / Ignore] / 選択理由______________________________
例外の管理場所: ______________________________
例外の既定期限: ____ヶ月


セクション6: 層3・NetworkPolicy

データプレーン: [Dataplane V2 / Calico]
 → Calicoの場合、ipBlock がPodトラフィックに一致する点を考慮したか: [はい / いいえ]
既定拒否の適用範囲: ingress[______] / egress[______]
必須の例外の記載: DNS[記載済 / 未] / メタデータサーバ[記載済 / 未] / ヘルスチェック[記載済 / 未]
その他の例外: ______________________________
拒否ログの有効化期間: ____/____/____ 〜 ____/____/____
完了条件: 拒否ログがゼロであること [達成 / 未達成]
外部SaaS宛の制御方式: [Cloud NAT固定IP + 相手側許可リスト / 外向きプロキシ集約]


セクション7: 層4・シークレット管理とローテーション

7-1. 方式
採用方式: [① K8s Secret直接 / ② 外部ストアから同期 / ③ Secret Managerアドオン]
移行対象のシークレット: 計____件
アプリのファイル読み取りへの改修: 対象____件 / 完了期限____/____/____
アプリケーションレイヤ暗号化(Cloud KMS): [採用 / 不採用]
 採用する場合の根拠(どの監査要件か): ______________________________
 鍵のリージョンがクラスタと一致しているか: [はい / いいえ]

7-2. ローテーション(シークレット種別ごとに1行。外部APIキー、DB認証情報、TLS証明書、Webhook署名鍵など)
種別: ______ / 保管先______ / 周期____日 / 根拠______ / 実行主体______ / 検知手段______
本番でのバージョン参照: [特定バージョンを指定 / latest]
自動化されていない対象に短い周期を設定していないか: [確認済 / 未確認]


セクション8: 見直し

見直し周期: ____ヶ月 / 次回____/____/____
棚卸しの対象: [IAMバインディング / RBACのグループ対応表 / 例外リスト / Break-glassの発動履歴 / 期限切れシークレット]
実施責任者: ______________________________

次回予告

4つの層すべてで、何を禁止し、何を許可するかが決まりました。次に決めるのは、それを実現する道具です。

次回の第6回「CNCFエコシステム選定書(ADR)」では、本記事で保留した選定理由を扱います。どのポリシーエンジンを使うか、CNIに何を選ぶか、GitOpsのツールをどうするか。判断基準と、代替案との比較を決定録の形で残す方法を設計します。

本記事のルール一覧が、第6回で「その道具が本当に必要か」を判定する入力になります。ルールを決める前に道具を選ぶと、選定理由が「有名だから」以外に書けなくなります。本記事でルールの何割が標準機能で書けるかを数えたのは、この順序を守るためです。

前の記事
次の記事