Certified Kubernetes Security Specialist (CKS) #3: CIS benchmark (kube-bench)、コンポーネントセキュリティ、Ingress TLS、バイナリ検証

前回の記事 で NetworkPolicy を使って Pod 間の通信を default deny で締めたなら、今回は クラスターを支える control plane 自体を固める 作業に移ります。apiserver が匿名リクエストを受け入れたり、kubelet が認証なしの read-only ポートを開けたままにしていると、NetworkPolicy をどれだけうまく書いても正門が開いているのと同じです。今回の記事は Cluster Setup ドメインの残り半分である コンポーネントセキュリティとクラスター点検 を扱います。

中心となるツールは kube-bench です。CIS Kubernetes benchmark という業界標準の点検表をクラスターに自動で回し、どの設定が安全でないかを PASS/FAIL/WARN で知らせてくれます。試験では kube-bench が吐いた FAIL を見て該当コンポーネントのフラグを直す作業が定番で出ます。

CIS Kubernetes benchmark とは #

CIS (Center for Internet Security) benchmark は、特定のソフトウェアを安全に設定するための項目別の点検表です。Kubernetes 用の CIS benchmark は、control plane コンポーネント (apiserver、controller-manager、scheduler、etcd) とノードコンポーネント (kubelet、kube-proxy) の設定ファイル・フラグ・ファイル権限を数百個の項目に分け、各項目がどの値であれば安全かを規定します。

各項目は 2 つの段階に分かれます。

  • Automated (自動): ツールが設定ファイルを読んで値を機械的に判定できる項目です。
  • Manual (手動): ポリシーや運用の文脈が必要で、人が直接確認しなければならない項目です。

この点検表を手で 1 つずつ確認するのは非現実的なので、Aqua Security が作った kube-bench が点検を自動化します。kube-bench は実行されるノードで設定ファイルとプロセスフラグを読み、CIS benchmark の各項目と照合したうえで結果を PASS/FAIL/WARN で出力します。

kube-bench の実行 #

kube-bench は通常、ノードで直接バイナリとして実行するか、Job の形でクラスターの中で回します。試験環境には通常バイナリがインストールされているので、control plane ノードとワーカーノードでそれぞれ適切な対象を指定して実行します。

control plane ノードで control plane 項目を点検するには master ターゲットを、ワーカーノードで kubelet などのノード項目を点検するには node ターゲットを与えます。一度に両方を指定することもできます。

# control plane ノードで: apiserver, controller-manager, scheduler, etcd を点検
kube-bench run --targets=master

# ワーカーノードで: kubelet, kube-proxy を点検
kube-bench run --targets=node

# 両方を一度に指定 (そのノードにあるコンポーネントだけが点検される)
kube-bench run --targets=master,node

特定の benchmark バージョンを明示することもできます。クラスターバージョンに合った benchmark が自動で選択されますが、ずれているときは直接指定します。

# CIS benchmark バージョンを直接指定
kube-bench run --targets=master --benchmark cis-1.23

結果が長くて特定の項目だけ見たいときは、出力を grep で絞るか、--check で項目番号を指定します。

# FAIL 項目だけを抜き出して見る
kube-bench run --targets=master | grep -E '\[FAIL\]'

# 特定の項目番号だけを点検
kube-bench run --targets=master --check 1.2.1

kube-bench の結果を読む #

kube-bench の出力は、項目番号・状態・説明から成ります。状態は 3 つです。

  • [PASS]: 項目が安全な値に設定されています。手を付ける必要はありません。
  • [FAIL]: 項目が安全でない値か、欠落しています。必ず直すべき対象 です。
  • [WARN]: 自動判定が難しい項目です。人が直接確認するようにという意味で、Manual 項目が主にここに該当します。

出力の核心は、各 FAIL 項目の下に続く remediation セクション です。kube-bench は何が間違っているかだけでなく、どう直すか までコマンドや設定変更の指針で知らせてくれます。試験ではこの remediation をそのまま読んで適用すればよいです。

[INFO] 1 Control Plane Security Configuration
[INFO] 1.2 API Server
[FAIL] 1.2.1 Ensure that the --anonymous-auth argument is set to false (Manual)
...
== Remediations master ==
1.2.1 Edit the API server pod specification file
/etc/kubernetes/manifests/kube-apiserver.yaml on the control plane node
and set the below parameter.
--anonymous-auth=false

== Summary master ==
42 checks PASS
8 checks FAIL
12 checks WARN
0 checks INFO

上の出力の流れを読む順番はこうです。まず Summary で FAIL が何個あるかを見て、本文で [FAIL] 項目を探し、その項目番号に該当する remediation を見たうえで、指示されたファイルを開いてフラグを直します。

control plane コンポーネントのセキュリティ設定 #

control plane コンポーネントは kubeadm クラスターで static Pod として実行されます。したがってこれらの設定は、普通の kubectl ではなく ノードのマニフェストファイルを直接編集 して変えます。マニフェストの位置は次のとおりです。

コンポーネントstatic Pod マニフェストパス
kube-apiserver/etc/kubernetes/manifests/kube-apiserver.yaml
kube-controller-manager/etc/kubernetes/manifests/kube-controller-manager.yaml
kube-scheduler/etc/kubernetes/manifests/kube-scheduler.yaml
etcd/etc/kubernetes/manifests/etcd.yaml

このディレクトリのファイルを保存すると、kubelet が変更を検知して該当 static Pod を 自動で再生成 します。別途 apply は必要ありません。証明書の構造と kubeconfig の基礎は、CKA #8 証明書の管理 で扱った PKI 構造を前提にします。

apiserver の主要フラグ #

apiserver はクラスターの正門なので、最も多くの項目が引っかかります。kube-bench FAIL として頻繁に出るフラグをまとめると次のとおりです。

フラグ安全な値意味
--anonymous-authfalse匿名リクエスト拒否
--authorization-modeNode,RBACAlwaysAllow 禁止、RBAC 強制
--profilingfalseプロファイリングエンドポイントの露出遮断
--insecure-port0 (または除去)認証なしの HTTP ポート無効化
--audit-log-pathパス指定監査ログ記録の有効化
--kubelet-certificate-authorityCA パスkubelet 通信の証明書検証

マニフェストの command 配列にフラグを追加するか、値を直します。

# /etc/kubernetes/manifests/kube-apiserver.yaml (抜粋)
apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
    - name: kube-apiserver
      command:
        - kube-apiserver
        - --anonymous-auth=false
        - --authorization-mode=Node,RBAC
        - --profiling=false
        - --audit-log-path=/var/log/kubernetes/audit.log
        # ... 既存のフラグを維持 ...

保存後、apiserver が再起動し、しばらく API の応答が途切れます。再び応答するまで待ってから確認します。

# apiserver static Pod が再び起動したか確認
kubectl -n kube-system get pod -l component=kube-apiserver

# マニフェスト編集に誤字があると Pod が起動しない。crictl でコンテナログを確認
sudo crictl ps -a | grep apiserver
sudo crictl logs <container-id>

マニフェスト編集時に最もよくある事故は、インデントの誤りや誤ったフラグ値で apiserver がまったく起動しないこと です。このときは kubectl 自体が効かないので、上のように crictl でコンテナログを見て原因を探します。

kubelet のセキュリティ設定 #

kubelet は各ノードで動くエージェントで、ノードの正門に当たります。kubelet も匿名リクエストと認証なしの read-only ポートを塞がなければなりません。kube-bench の node ターゲットで頻繁に出る項目です。

設定安全な値意味
anonymous.enabled (--anonymous-auth)false匿名リクエスト拒否
authorization.mode (--authorization-mode)WebhookAlwaysAllow 禁止
readOnlyPort0認証なしの 10255 ポート無効化
protectKernelDefaultstrueカーネルパラメータの変更拒否

kubelet は通常 config ファイル で設定します。kubeadm ノードでは /var/lib/kubelet/config.yaml がデフォルトの位置で、実際の実行引数は /var/lib/kubelet/kubeadm-flags.env でも確認します。

# /var/lib/kubelet/config.yaml (抜粋)
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
authentication:
  anonymous:
    enabled: false
  webhook:
    enabled: true
authorization:
  mode: Webhook
readOnlyPort: 0
protectKernelDefaults: true

kubelet は static Pod ではなく systemd サービスなので、config を直したあとは 手動で再起動 しないと適用されません。

# kubelet config 修正後に再起動
sudo systemctl restart kubelet

# 再起動後に状態確認
sudo systemctl status kubelet

フラグと config ファイルが同じ項目を同時に指定すると、通常はコマンドラインフラグが優先します。どちらが実際に効くのか紛らわしいときは、kubeadm-flags.env に同じフラグがあるかどうかから確認します。

Ingress TLS #

Cluster Setup ドメインは、外部から入ってくるトラフィックを暗号化する作業も含みます。Ingress に TLS を付けると、クライアントとクラスターの間の区間が HTTPS で保護されます。手順は 2 段階です。証明書を収めた Secret を作り、Ingress の tls セクションでその Secret を参照します。

まず証明書とキーで tls タイプの Secret を作ります。

# 証明書 (tls.crt) とキー (tls.key) で TLS Secret を生成
kubectl create secret tls web-tls \
  --cert=tls.crt \
  --key=tls.key \
  -n default

そのあと Ingress でこの Secret を tls セクションで接続します。hosts に書いたドメインと Secret 証明書の CN/SAN が一致しないと、ブラウザが信頼しません。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ingress
  namespace: default
spec:
  tls:
    - hosts:
        - app.example.com
      secretName: web-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-svc
                port:
                  number: 80

適用したあと、TLS が実際に付いたかを確認します。

kubectl apply -f web-ingress.yaml

# Ingress に TLS host が登録されたか確認
kubectl describe ingress web-ingress

# ハンドシェイクと証明書を確認 (Ingress コントローラーのアドレスへ)
curl -vk https://app.example.com/ --resolve app.example.com:443:<ingress-ip>

describe 出力の TLS セクションに host と Secret 名が見えれば、接続できています。Secret 名を間違って書いたり Secret が別の namespace にあったりすると TLS が適用されないので、Ingress と Secret が同じ namespace にあるか を必ず確認します。

バイナリ検証 #

サプライチェーンセキュリティの最も基礎は、ダウンロードしたバイナリが改竄されていないかを確認すること です。kubectl、kubeadm、kube-bench などのツールをインターネットから受け取るとき、配布元が公開したチェックサムと自分で計算したチェックサムを照合します。値が違えば転送中に破損したか改竄されたものなので、そのバイナリは捨てます。

Kubernetes バイナリは配布元が .sha256 ファイルを一緒に提供します。受け取ったあと sha256sum で自分で計算して照合します。

# バイナリと公式チェックサムファイルを一緒に受け取る
curl -LO "https://dl.k8s.io/release/v1.30.0/bin/linux/amd64/kubectl"
curl -LO "https://dl.k8s.io/release/v1.30.0/bin/linux/amd64/kubectl.sha256"

# 方法 1: 計算値と公開値を人が目で照合
sha256sum kubectl
cat kubectl.sha256

# 方法 2: --check で機械的に照合 (OK が出るべき)
echo "$(cat kubectl.sha256)  kubectl" | sha256sum --check

--checkkubectl: OK を出力すれば、無欠性が確認できたということです。FAILED が出たらバイナリを信頼せず、もう一度受け取ります。試験では「このバイナリの sha256 チェックサムが与えられた値と一致するか確認せよ」のような形で出て、一致するかどうかを答えるか、一致しないファイルを選び出す作業になります。

イメージ署名 (cosign) と SBOM のようなさらに深いサプライチェーン検証は #15 で扱い、今回の記事の sha256 検証はその出発点です。

試験ポイント #

  • kube-bench FAIL を直す作業が定番 です。kube-bench run --targets=master を回して FAIL 項目を探し、その下の remediation をそのまま読んで該当マニフェストや config を直す流れを手に覚えさせます。
  • control plane は static Pod です。/etc/kubernetes/manifests/ のマニフェストを編集すれば kubelet が自動で再生成します。apply は必要ありません。
  • kubelet は systemd サービス です。/var/lib/kubelet/config.yaml を直したあと systemctl restart kubelet で直接再起動しないと適用されません。
  • マニフェスト編集後に apiserver が起動しないと kubectl も効きませんcrictl ps -acrictl logs で原因を探す手順をあらかじめ覚えておきます。
  • 覚えておく安全な値: apiserver --anonymous-auth=false--authorization-mode=Node,RBAC--profiling=false。kubelet anonymous.enabled=falseauthorization.mode=WebhookreadOnlyPort=0
  • Ingress TLS は Secret と Ingress が同じ namespace にある必要があります。kubectl create secret tls で作り、tls セクションの secretName で参照します。
  • バイナリ検証は sha256sum --check で機械的に照合します。OK が出るかを確認します。

まとめ #

今回の記事で押さえたこと:

  • CIS Kubernetes benchmark はコンポーネント別の安全設定の点検表で、kube-bench がこれを自動で点検して PASS/FAIL/WARN で出力します。
  • kube-bench の結果の核心は FAIL の下の remediation です。何をどのファイルでどう直すかをそのまま知らせてくれます。
  • control plane コンポーネントは /etc/kubernetes/manifests/ の static Pod マニフェストで、kubelet は /var/lib/kubelet/config.yaml で設定を変えます。
  • apiserver と kubelet の匿名認証・認可モード・read-only ポート・プロファイリングを安全な値に締めます。
  • Ingress TLS は tls タイプの Secret を作り、Ingress の tls セクションで参照します。
  • バイナリは sha256sum で公開チェックサムと照合して無欠性を確認します。

次へ — RBAC 最小権限 #

Cluster Setup ドメインの 2 つの記事でネットワークとコンポーネントを締めました。これから 2 番目のドメインである Cluster Hardening に移り、クラスターの中で 誰が何をできるか を狭めます。

#4 RBAC 最小権限の深掘り では、過度に広い ClusterRole をどう見つけ出すか、Role と RoleBinding で namespace 単位の最小権限をどう設計するか、kubectl auth can-i で権限を検証する方法、そして試験で頻繁に出る「この ServiceAccount に正確にこの動作だけを許可せよ」というタイプまで、直接作ってみながら整理します。

X