CyberFix Note
防御・ハードニング

Kubernetesクラスタのセキュリティ設定の基礎

対象の目安: Kubernetesを運用するインフラ担当とSRE / 実務

アオイ防御・運用担当
・ 約28分で読めます
Kubernetesクラスタのセキュリティ設定の基礎

Kubernetesは、コンテナを大量に並べて動かすための共通基盤です。動かすだけなら既定の設定でも問題なく回りますが、既定値はいくつかの層で開いた状態から始まります。NetworkPolicyを置くまでPod同士の通信に制限はなく、Pod Security Admissionを設定するまでPodが要求できる権限にも上限がありません。APIの操作権限については、既定のRBACポリシーはkube-system以外のServiceAccountにAPI discovery用のロール以外を与えないため出発点は狭いのですが、導入方式やアドオンによって初期状態は変わります。クラスタが本番のワークロードを抱えるようになると、この開いたままの層が侵入後の被害の広がりやすさになって表れます。

この記事では、コンテナ1つ1つの作り方ではなく、クラスタという層の設定に絞って整理します。誰がAPIサーバーに何を頼めるのかを決めるRBAC、Podが要求できる権限の上限を決めるPod Security Standards、Pod同士の通信を絞るNetworkPolicy、Secretsの保管、kubeletとコントロールプレーンの設定、そして監査ログとアドミッション制御という順で見ていきます。イメージの作り方やコンテナランタイムの絞り込みは別記事で扱っているため、そちらとあわせて読むと全体像がつながります。

記載する設定値やバージョンは、執筆時点のKubernetes公式ドキュメントで確認できた内容だけを載せています。Kubernetesは変更が速いため、実際に適用する前には運用中のバージョンのドキュメントで裏取りしてください。

先に、この記事で扱う層と、既定のままだと何が開いているか、どの機能で締めるかを一覧にします。

守る対象既定の状態締めるための機能
APIサーバーへの操作権限与えたRoleの範囲がそのまま権限になり、拒否ルールは存在しないRBACのRoleとRoleBinding
Podが要求できる権限制限なし。特権コンテナもホスト名前空間も要求できるPod Security StandardsとPod Security Admission
Pod間の通信全Podが全Podと双方向に通信できるNetworkPolicy(CNIプラグインの対応が前提)
Secretsの保管etcdに暗号化されずに保存されるEncryptionConfiguration、外部シークレットストア
kubeletのAPI認可モードの既定はAlwaysAllow--authorization-mode=Webhookと--anonymous-auth=false
操作の記録監査ログは無効監査ポリシーとログバックエンドの設定

クラスタ運用の層で何を守るのかを決める

Kubernetesのセキュリティを考えるとき、対象を層で分けると設定の抜けが見つけやすくなります。イメージの中身とビルド、コンテナランタイムの権限、そしてクラスタそのものの設定は、担当も道具も別々です。NIST SP 800-190はコンテナ技術のリスクをイメージ、レジストリ、オーケストレータ、コンテナ、ホストOSという構成要素ごとに整理しており、この記事が扱うのはそのうちオーケストレータに当たる部分です。

イメージとコンテナ実行時の絞り込みについては別記事で扱っています。クラスタの設定を固める前に、そもそも中で動くものが小さく非rootであるかを確認しておくと、この記事の設定がそのまま通りやすくなります。

あわせて読みたい

コンテナ(Docker)セキュリティの基礎と実務で効く守り方

クラスタ層でよく問題になる箇所は、外部の整理とも一致します。OWASPのKubernetes Top Tenは、執筆時点で2025年版のリストを掲載しており、K02として過剰に緩い認可設定、K03としてSecrets管理の失敗、K04としてクラスタ全体でのポリシー適用の欠如、K05としてネットワーク分離の不足、K06としてKubernetesコンポーネントの過剰な露出を挙げています。この記事で扱う項目とほぼ重なる並びです。

OWASP Kubernetes Top Tenは、Kubernetes環境で頻出するリスクを10項目に整理したプロジェクトです。認可設定、Secrets管理、ポリシー適用、ネットワーク分離、コンポーネントの露出が上位に並んでいます。

RBACで許可を足しながら権限を設計する

Kubernetesの認可はRBACで行います。kube-apiserverを--authorization-mode=RBACで起動すると、APIへの各リクエストがRoleに書かれたルールと突き合わせられます。ここで押さえておきたい性質が1つあります。RBACのルールは許可を足すだけで、拒否ルールが存在しません。「Secretsだけは読ませない」という書き方はできず、Secretsを含まないRoleを作るという書き方になります。

権限の単位は4種類です。Roleは1つのNamespaceの中の権限、ClusterRoleはクラスタ全体やクラスタスコープの資源に対する権限を定義します。RoleBindingはRoleまたはClusterRoleをNamespace内に限って結び付け、ClusterRoleBindingは全Namespaceに結び付けます。ClusterRoleをRoleBindingで結ぶと、そのClusterRoleの内容が1つのNamespaceの中だけで効くため、共通のRole定義を各Namespaceに配る使い方ができます。

# 1つのNamespaceでPodの参照だけを許可するRole
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: app-prod
  name: pod-reader
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]
---
# 特定のServiceAccountにだけ上のRoleを結び付ける
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: app-prod
  name: pod-reader-binding
subjects:
  - kind: ServiceAccount
    name: metrics-collector
    namespace: app-prod
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

公式のRBACグッドプラクティスは、注意して扱うべき権限を具体的に挙げています。Secretsに対するlistwatchは、getと違って中身をまとめて読めてしまうため、実質的に全Secretsの閲覧権限になります。Podを作成できる権限は、そのNamespaceのSecretsやConfigMapやServiceAccountを間接的に扱えることを意味します。PersistentVolumeを作成できるとhostPathを使ってノードのファイルシステムへ到達する道が開きます。nodes/proxyへのgetは読み取り専用ではなく、kubeletのAPIを通じてノード上のコンテナでコマンドを実行できる強い権限です。escalatebindimpersonateも、自分の持たない権限を獲得する経路になります。

権限を絞る作業は、cluster-adminを配らないところから始めると進みます。運用者には必要なNamespaceに限ったRoleBindingを与え、ワークロードにはワークロードごとに専用のServiceAccountを用意します。APIを呼ばないワークロードではautomountServiceAccountToken: falseを設定してトークンのマウント自体を止めると、Podが乗っ取られたときにAPIへの足がかりを渡さずに済みます。ワイルドカードのresources: ["*"]verbs: ["*"]は、後から資源が増えたときに権限が自動的に広がるため避けます。最小権限の考え方そのものは、Kubernetesに限らない共通の設計です。

あわせて読みたい

最小権限の原則(Least Privilege)。なぜ権限を絞ることが最強の防御の一つなのか

Kubernetes公式のRBACドキュメントは、RBACの権限が純粋に追加型であり拒否ルールを持たないこと、そしてescalatebindの各verbが権限昇格の防止のために特別に扱われることを説明しています。

Pod Security Standardsの3水準を使い分ける

RBACが「誰がAPIに何を頼めるか」を決めるのに対し、Podが実際にどこまでの権限を要求できるかを決めるのがPod Security Standardsです。公式が定義する水準は3つで、内容は累積的になっています。

privilegedは制限のない水準で、システム基盤のワークロードのように信頼された利用者が管理する対象に向けたものです。ホストネットワークへの参加や特権コンテナの利用がそのまま通ります。

baselineは既知の権限昇格を防ぐ最小限の制限です。hostNetworkhostPIDhostIPCの共有を禁止し、privileged: trueを禁止し、追加できるcapabilityをCHOWNNET_BIND_SERVICEなどの安全な一覧に限定します。hostPathボリュームを禁止し、seccompプロファイルのUnconfinedを禁止し、SELinuxのtypeをcontainer_tなどに限定し、AppArmorの既定プロファイルの無効化を防ぎます。

restrictedは、現時点のPodハードニングの実践に沿った強い制限です。baselineの内容に加えて、使えるボリューム種別をconfigMapemptyDirpersistentVolumeClaimsecretなどに限定し、allowPrivilegeEscalationfalseに固定し、runAsNonRoottrueにしてrunAsUserに0を認めず、seccompプロファイルをRuntimeDefaultLocalhostに明示させ、capabilityはALLをdropしたうえでNET_BIND_SERVICEだけ足し戻すことを認めます。ルートファイルシステムの読み取り専用化はrestrictedの要件には含まれていないため、必要なら別途securityContextで指定します。

以前この役割を担っていたPodSecurityPolicyは、Kubernetes v1.21で非推奨となり、v1.25で削除されました。古い記事や社内手順にPodSecurityPolicyが残っている場合は、後継のPod Security Admissionへの移行が済んでいるかを確認する必要があります。

Kubernetes公式のPod Security Standardsは、privileged、baseline、restrictedの3水準を累積的なポリシーとして定義し、各水準で制限されるフィールドと許容値を一覧で示しています。PodSecurityPolicyがv1.21で非推奨となりv1.25で削除されたことは、このページではなく公式のPod Security Policiesのページに記載されています。

Pod Security AdmissionをNamespaceに適用する

Pod Security Standardsは定義であり、それを実際に効かせる仕組みがPod Security Admissionです。Kubernetes v1.25で安定版になった組み込みのアドミッションコントローラで、Namespaceに付けたラベルで水準とモードを指定します。

モードは3つあります。enforceは違反したPodを拒否し、auditは監査イベントに注釈を残して通し、warnは操作した利用者に警告を返して通します。この3つを組み合わせると、いきなり拒否せずに影響を測ってから締める進め方ができます。まずaudit と warn だけをrestrictedにして違反の量を見て、直し終えてからenforceをbaseline、さらにrestrictedへ上げる手順が現実的です。

apiVersion: v1
kind: Namespace
metadata:
  name: app-prod
  labels:
    # 拒否する水準
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/enforce-version: v1.31
    # 通すが記録と警告を出す水準
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

<MODE>-versionのラベルでポリシーのバージョンを固定できます。指定しない場合はlatestとして扱われ、クラスタを更新したときにポリシーの中身が変わり、既存ワークロードのPodを再作成するときに拒否される可能性があります。なお、enforceのラベルや水準を変更した時点では、Namespace内の既存Podはポリシーと照合されて違反が警告として返るだけで、稼働中のPodがその場で止められるわけではありません。運用中のクラスタでは、バージョンを明示しておくほうが更新時の挙動が読めます。

適用範囲には注意点があります。enforceはPodオブジェクトに対してのみ働くため、DeploymentやCronJobのようなワークロード資源を作った時点では拒否されず、そこからPodが作られる段になって拒否されます。auditとwarnはワークロード資源にも適用されるので、warnを併用しておくとマニフェストを適用した担当者がその場で気付けます。また、Namespaceのラベルを書き換えられる権限はポリシーを無効化できる権限でもあるため、Namespaceの更新権限は運用者に限定します。

注意

enforceをrestrictedにすると、非rootでの実行やcapabilityのdropに対応していないコンテナは起動できなくなります。本番のNamespaceでいきなりenforceを上げると稼働中のワークロードが再作成時に立ち上がらなくなるため、warnとauditで違反を洗い出し、対応が済んでから切り替えてください。

NetworkPolicyで通信を既定拒否にする

Kubernetesの既定では、Podはingressについてもegressについても隔離されていません。同じクラスタにいるPodはNamespaceをまたいで互いに接続でき、外部への通信も自由です。この状態では、1つのPodが侵害されたときにクラスタ内を横に動く経路がそのまま残ります。

NetworkPolicyは、この既定を選択的に閉じるための資源です。あるPodがいずれかのNetworkPolicyのpodSelectorに選ばれた時点で、そのPodはそのpolicyTypeについて隔離状態になり、ポリシーで明示的に許可された通信だけが通ります。複数のポリシーは足し合わせで評価されるため、既定拒否のポリシーを1枚置いたうえで、必要な経路を許可するポリシーを足していく形になります。通信が成立するには、送信側のegressと受信側のingressの両方で許可されている必要があります。

前提として、NetworkPolicyを実際に処理するのはネットワークプラグインです。NetworkPolicyに対応していないCNIを使っているクラスタでは、資源を作っても何も起きません。適用前にプラグインの対応を確認してください。

# Namespace内の全Podについて、ingressとegressを既定拒否にする
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: app-prod
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
---
# 必要な経路だけを足し戻す。受信側(API)のingressを許可
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-api
  namespace: app-prod
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080
---
# 送信側(フロント)のegressも許可しないと通信は成立しない
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-egress-to-api
  namespace: app-prod
spec:
  podSelector:
    matchLabels:
      app: frontend
  policyTypes:
    - Egress
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: api
      ports:
        - protocol: TCP
          port: 8080

既定拒否でEgressまで閉じた場合、受信側のingressを許可するだけでは通信は通りません。上の例のように、送信側のegressと受信側のingressを対で用意する必要があります。

既定拒否を入れるとDNSも止まるため、kube-systemのDNSへのegressを許可するポリシーを忘れずに用意します。クラウド上のクラスタでは、公式のセキュリティチェックリストが挙げているとおり、ワークロードからメタデータAPI(169.254.169.254)への到達を塞ぐことも検討対象です。ここを踏み台にすると、Podの侵害からクラウドの認証情報へ手が届く経路ができます。ネットワークを区切って被害を止める考え方そのものは、Kubernetesの外でも同じです。

あわせて読みたい

ネットワークセグメンテーションで侵入後の被害を広げない設計

Kubernetes公式のNetwork Policiesは、Podが既定ではingressにもegressにも隔離されていないこと、ポリシーが追加的に評価されること、そしてNetworkPolicyを実装するネットワークプラグインが必要であることを説明しています。

Secretsの保管と受け渡しを固める

KubernetesのSecretは、値をbase64で符号化して保持します。base64は符号化であって暗号化ではないため、マニフェストを読める相手には中身がそのまま読めます。さらに、既定ではAPIサーバーはSecretsを暗号化せずにetcdへ書き込みます。etcdのデータファイルやバックアップを取得できる相手には、Secretsの中身が読める状態です。

保存時の暗号化は、EncryptionConfigurationをkube-apiserverの--encryption-provider-configに渡して有効化します。プロバイダにはidentity(暗号化しない)、aescbcaesgcmsecretbox、そして外部の鍵管理サービスを使うkmsがあります。一覧の先頭に書いたプロバイダが新規書き込みの暗号化に使われ、後続のプロバイダは復号にだけ使われます。この順序を利用して鍵の入れ替えを行います。

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      # 先頭が新規書き込みに使われる
      - aescbc:
          keys:
            - name: key1
              secret: <32バイトの鍵をbase64で記述>
      # 既存の平文データを読むための退避先
      - identity: {}

設定を反映しても、すでにetcdへ書かれているSecretsは平文のままです。全Secretsを読み直して書き戻すことで、既存分も暗号化された状態に置き換わります。鍵の材料をGitに置かないこと、全コントロールプレーンノードに同じ設定を配ることも必要です。本番では、鍵をクラスタの外で管理できるkmsプロバイダのほうが安全側に倒せます。

アプリケーションへの渡し方も設計対象です。公式のグッドプラクティスは、Secretのマニフェストをソース管理へ入れないこと、Secretを必要とするコンテナにだけボリュームをマウントすること、そしてSecretsへのlistwatchを特権的なシステムコンポーネントに限ることを挙げています。クラスタの外にあるシークレットストアからマウントする方式として、Secrets Store CSI Driverのような仕組みも用意されています。認証情報の管理全般については別記事で整理しています。

あわせて読みたい

シークレット管理の実務。APIキー・認証情報をハードコードせず、Vaultやマネージドサービスで守りローテーションする

kubeletとコントロールプレーンの設定を締める

クラスタの構成要素そのものが外から触れる状態になっていると、上で積んだ設定を回避されます。公式のセキュリティチェックリストは、Kubernetes API、kubelet API、etcdをインターネットへ公開しないことを最初に挙げています。

kubeletは特に注意が要る箇所です。公式ドキュメントによると、kubeletのHTTPSエンドポイントへのリクエストのうち、ほかの認証方式で拒否されなかったものは既定で匿名リクエストとして扱われ、system:anonymousという利用者名とsystem:unauthenticatedというグループが割り当てられます。さらに、認可モードの既定はAlwaysAllowで、すべてのリクエストを通します。運用するクラスタでは--anonymous-auth=falseで匿名アクセスを止め、--authorization-mode=Webhookを指定してAPIサーバーのSubjectAccessReviewに認可を委譲します。あわせて--client-ca-fileでクライアント証明書の検証を設定します。

etcdはクラスタの全データを持つため、ここを取られるとクラスタ全体を取られたのと同じです。専用の認証局で発行した証明書による相互TLSでアクセスを制限し、コントロールプレーン以外から到達できないようにします。証明書の管理では、ルート証明書を保護し、中間証明書と末端証明書の有効期間を長くしすぎないことがチェックリストに挙げられています。また、ブートストラップ以降はsystem:mastersグループを利用者やコンポーネントの認証に使わないことも項目に入っています。このグループはRBACの評価を素通りするためです。

設定項目は多岐にわたるため、点検には既存の基準を使うほうが早く進みます。CIS Kubernetes Benchmarkはコミュニティの合意形成で作られた設定基準で、非商用利用ではPDFを無償で入手できます。EKSやAKSやGKEといったマネージドサービス向けの版も用意されているため、自分の環境に合う版を選べます。NSAとCISAが公開したKubernetes Hardening Guideも、2021年8月の初版以降にコミュニティの意見を反映して更新されており、設定の勘所を通しで確認する資料として使えます。ノードのOS自体の堅牢化については、サーバー堅牢化の記事も参考になります。

あわせて読みたい

サーバーのハードニング基礎。最小化・最小権限・更新・設定堅牢化をCISベンチマークから学ぶ

Kubernetes公式のkubelet認証と認可のドキュメントは、ほかの認証方式で拒否されなかったリクエストが既定で匿名として扱われること、認可モードの既定がAlwaysAllowであること、そしてnodes/proxyへの権限がノード上のコンテナでコマンドを実行できる強い権限であることを記載しています。

監査ログとアドミッション制御で運用を回す

設定を入れた後は、意図した状態が保たれているかを確認し続ける仕組みが要ります。Kubernetesの監査ログは、いつ誰がどの資源に何をしたかをAPIサーバーの側で記録します。監査は既定で無効で、--audit-policy-fileで監査ポリシーを渡して初めて動き始めます。

監査ポリシーでは、リクエストの段階(RequestReceived、ResponseStarted、ResponseComplete、Panic)と記録水準(None、Metadata、Request、RequestResponse)を資源ごとに指定します。全部をRequestResponseで取ると量が膨れ、APIサーバーのメモリ消費も増えます。見落としやすいのは、RequestやRequestResponseで記録すると、リクエストやレスポンスの本文がそのまま監査ログへ複製される点です。公式の例でもSecretsとConfigMapはMetadataで記録しており、Secretsは値を残さない水準にしたうえで、操作の検知や通知の側を厚くするのが基本です。RBAC資源の変更のように本文を残しても差し支えのない操作は、必要に応じてRequestやRequestResponseまで上げ、それ以外はMetadataに寄せる、といった配分にします。出力は--audit-log-pathでファイルへ書くログバックエンドと、--audit-webhook-config-fileで外部へ送るwebhookバックエンドがあります。ノードのディスクに置いたまま放置すると侵害時に消されるため、外部の収集基盤へ送る前提で設計します。ログを集めた後の使い方は、ログ管理の基本と共通です。

あわせて読みたい

ログ管理の基本。何を・どこまで・どれだけ残すか

ポリシーの適用側では、Pod Security Admissionで足りない要件をアドミッション制御で補います。Kubernetes v1.30で安定版になったValidatingAdmissionPolicyは、CEL(Common Expression Language)で書いた条件をAPIサーバーの中で評価する仕組みで、外部のwebhookサーバーを運用せずに検証ルールを足せます。イメージのレジストリを許可した一覧に限る、latestタグを禁じる、といったルールをここで表現できます。

イメージの署名検証のように、外部の鍵や証明書との照合が必要な検証は、アドミッションwebhookとして動く専用のコントローラを入れる形になります。ここで注意したいのは、アドミッションwebhookの設定を書き換えられる権限が、そのwebhookが担う検証を無効化できる権限になる点です。RBACグッドプラクティスは、validatingwebhookconfigurationsmutatingwebhookconfigurationsを操作できる利用者が、クラスタに受け入れられるあらゆるオブジェクトを読み取るwebhookを仕込め、mutating webhookであればオブジェクトの改変までできると指摘しています。この点を踏まえ、webhook設定の書き込み権限は厳しく制限します。

メモ

クラスタの設定を検証する目的でPodの脱出や権限昇格を試す場合は、自分が管理権限を持つ検証用クラスタに限って行ってください。他者が管理するクラスタや共用環境で無断に試す行為は、不正アクセス禁止法などの法令に触れる可能性があります。本番クラスタでの検証は、範囲と時間帯を書面で合意したうえで実施します。

まとめ

Kubernetesの既定値は、通信とPodの権限要求について制限のない状態から始まります。クラスタを守る作業は、この開いた既定を、層ごとに閉じ直していく作業です。RBACでは不要な許可やRoleBindingを取り除いて必要な許可だけを残し、Pod Security AdmissionでPodが要求できる権限に上限を置き、NetworkPolicyで通信を既定拒否にし、Secretsを保存時に暗号化し、kubeletとetcdを外から触れない状態にして、監査ログで記録を残します。

一度にすべてを入れる必要はありません。warnとauditで影響を測ってからenforceへ進む、既定拒否のNetworkPolicyを開発用のNamespaceから試す、といった順で進めると運用を止めずに締められます。設定の点検には、公式のセキュリティチェックリストやCIS Kubernetes Benchmarkのような既存の基準を土台にして、自分のクラスタで抜けている項目を洗い出す進め方が現実的です。

Kubernetesクラスタ設定の点検リスト

  • cluster-adminの割り当てを棚卸しし、運用者にはNamespace単位のRoleBindingを使っているか
  • Secretsへのlistとwatch、Podの作成、nodes/proxy、escalate、bind、impersonateの権限を絞っているか
  • APIを呼ばないワークロードでautomountServiceAccountToken: falseを設定しているか
  • 全NamespaceにPod Security Admissionのラベルを付け、バージョンも明示しているか
  • warnとauditで違反を確認してからenforceの水準を上げる手順になっているか
  • 各Namespaceに既定拒否のNetworkPolicyを置き、DNSと必要な経路だけ許可しているか
  • CNIプラグインがNetworkPolicyに対応していることを確認したか
  • EncryptionConfigurationでSecretsの保存時暗号化を有効にし、既存Secretsを書き戻したか
  • kubeletで--anonymous-auth=falseと--authorization-mode=Webhookを設定しているか
  • Kubernetes API、kubelet API、etcdをインターネットへ公開していないか
  • 監査ポリシーを設定し、監査ログを外部の収集基盤へ送っているか
  • アドミッションwebhookの設定を書き換えられる権限を限定しているか

出典・参考

この記事をシェア

関連する記事