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

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

kube-benchでCIS修復【CKS第2回】

公開

新卒インフラエンジニア向け「Kubernetes 実践教科書 ③ CKS セキュリティ・ハードニング編」(全19回)の第2回です。第1回では第3巻の地図を渡し、最後に kube-bench をプレ実行して現状のセキュリティスコア(39 PASS / 10 FAIL / 11 WARN)を控えました。本回はその FAIL を実際に修復します。ただし「FAIL を全部消す」ことが目的ではありません。CIS ベンチマークの Remediation には、そのまま打てないもの・打つとクラスタ機能が壊れるものが混ざっています。直せるもの・直してはいけないもの・後の回に送るものを仕分けられるようになることが、本回の到達点です。

  • ラボ Kubernetes v1.36.2
  • CKS 試験環境 v1.35
  • kube-bench v0.15.6
  • CIS プロファイル cis-1.12
  • AlmaLinux 10.2
  • containerd v2.2.6
  • 確認日 2026-07-26
目次
  1. 第2回のスコープ・今ここマップ
  2. この回のゴール
  3. CIS Kubernetes Benchmark とは ——業界標準と kube-bench の関係
    1. CoreDNS(kubedns)はどこで評価されるのか
  4. kube-bench の実行モデル ——targets・判定区分・Remediation の読み方
    1. ターゲットと CIS 章の対応
    2. 判定区分は 4 種
    3. サマリは 2 ブロック出る
    4. Remediation の読み方と、その限界
    5. 調査に効くフラグ
  5. static Pod マニフェスト編集の鉄則 ——バックアップ・自動再起動・3 点セット
    1. 鉄則1:編集前にバックアップ。置き場所は manifests の外
    2. 鉄則2:保存=即再起動。確認は crictl と /healthz で行う
    3. 鉄則3:フラグを書き間違えると起動しない。戻し方を先に覚える
    4. 鉄則4:ファイル参照フラグは「3 点セット」で足す
    5. 鉄則5:HA クラスタでは 3 台すべてに、1 台ずつ適用する
  6. やってみよう①:Control Plane の FAIL を修復する
    1. ステップ1:現状の FAIL を一覧する
    2. ステップ2:–profiling=false を 3 つのコンポーネントに追加する
    3. ステップ3:–service-account-extend-token-expiration=false を追加する
    4. ステップ4:再起動を確認する
    5. ステップ5:etcd データディレクトリの所有者(1.1.12)——Remediation をそのまま打てない例
    6. この修復に、どれだけの意味があるのか
    7. ステップ6:再評価する
    8. 試験ではどの粒度で問われるか
  7. やってみよう②:Workload Node を評価して修復する
    1. ステップ1:–targets node は「実行したノード自身」を評価する
    2. ステップ2:Workload Node には tar が入っていない
    3. ステップ3:kube-bench を導入する
    4. ステップ4:kubeconfig が無いと FAIL が PASS に化ける
    5. ステップ5:2 件を修復する
    6. ステップ6:再評価する
    7. ステップ7:残りのノードにも適用する
  8. やってみよう③:Remediation を鵜呑みにすると壊れる(–kubelet-certificate-authority)
    1. ステップ1:Remediation を読む
    2. ステップ2:バックアップを取ってから適用する(cp-01 のみ)
    3. ステップ3:一見、成功して見える
    4. ステップ4:しかし kubectl logs と exec が壊れている
    5. ステップ5:原因を切り分ける
    6. ステップ6:HA クラスタでは「たまに失敗する」障害になる
    7. ステップ7:ロールバックする(必須)
    8. この演習から試験に持ち帰るもの
  9. 残った FAIL・WARN の棚卸し ——どれをいつ回収するか
  10. やってみよう④:プラットフォームバイナリの SHA256 検証
    1. ステップ1:バイナリとチェックサムを取得する
    2. ステップ2:照合する
    3. ステップ3:改ざんを検知させる
    4. ステップ4:稼働中のバイナリを照合する
    5. ステップ5:後片付け
    6. 何を暗記すべきか、その線引き
  11. fanclub-api への影響確認
  12. 暗記必須コマンド(第2回)
  13. まとめ
  14. 理解度チェック(○×形式・全 9 問)
  15. 次回予告

第2回のスコープ・今ここマップ

本回は CKS ドメイン D1: Cluster Setup(配点 15%) の前半にあたります。D1 は 5 つのコンピテンシーからなり、本回はそのうち「CIS ベンチマークで Kubernetes コンポーネント(etcd / kubelet / kubedns / kube-apiserver)のセキュリティ設定をレビューする」と「プラットフォームバイナリをデプロイ前に検証する」の 2 つを担当します。残る 3 つ(NetworkPolicy・TLS 付き Ingress・ノードメタデータ保護)は第3回です。

第3巻 19 回のうち、現在位置は次のとおりです。

第1部 第3巻オリエンテーション
    第1回: 第3巻スコープ + CKA 境界 + 4C/脅威モデル + Kubestronaut

第2部 クラスタ堅牢化(D1/D2)
  ★ 第2回: kube-bench + CIS ベンチマーク修復 + バイナリ検証(D1)  ← 今ここ
    第3回: NetworkPolicy 完全設計 + ノードメタデータ保護 + TLS Ingress(D1)
    第4回: RBAC 監査 + ServiceAccount トークン管理(D2)
    第5回: API Server 堅牢化 + kubeadm アップグレード(D2)

起点となる環境は第1回の完了状態です。第2巻から引き継いだ 9 VM 構成(Control Plane 3 台 + Workload Node 2 台 + 作業端末・レジストリ・LB・プロキシ)が稼働し、k8s-cp-01 に kube-bench v0.15.6 が導入済みであることを前提とします。

この回のゴール

本回を終えると、次のことができるようになります。到達できたかは記事末の「やってみよう」と「理解度チェック」で確認します。

  • kube-bench を master / etcd / controlplane / node / policies の各ターゲットで実行し、結果を読める
  • PASS / FAIL / WARN / INFO の意味と Remediation の読み方を説明できる
  • static Pod マニフェストを「バックアップ → 編集 → 自動再起動の確認」の手順で安全に修正できる
  • Remediation をそのまま適用すると機能が壊れる場合があることを、実際に壊して復旧した経験として説明できる
  • Workload Node を評価するには、そのノード上で kube-bench を動かす必要があると説明できる
  • kubectl / kubelet バイナリの SHA256 を公式チェックサムと照合し、改ざんを検知できる

本回で扱わない範囲も先に確認します。CIS が指摘する項目のうち、設計や運用の議論を伴うものは後の回にまとめて回収します。

本回で扱わない領域該当する CIS 項目回収する回
認証・認可の体系的堅牢化(--anonymous-auth / --authorization-mode / NodeRestriction)1.2.1 ほか(実機では既に PASS)第5回
監査ログ設計1.2.16〜1.2.19(FAIL 4 件)第16回
etcd 保存時暗号化1.2.27 / 1.2.28(WARN 2 件)第9回
seccomp の既定適用4.2.14(WARN 1 件)第6回

本書ラボの API Server には、すでに --authorization-mode=Node,RBAC--enable-admission-plugins=NodeRestriction が設定されており、該当する CIS 項目は PASS しています。とはいえ「PASS しているから終わり」ではなく、なぜその設定が要るのか・ほかに何を組み合わせるのかは第5回で体系的に扱います。

CIS Kubernetes Benchmark とは ——業界標準と kube-bench の関係

CIS Kubernetes Benchmark は、Center for Internet Security が公開している設定ハードニングの基準です。Control Plane コンポーネント(kube-apiserver / controller-manager / scheduler / etcd)、Workload Node(kubelet)、運用ポリシー(RBAC・Pod Security Standards・NetworkPolicy)について「この設定はこうすべき」を章立てでまとめた文書であり、それ自体はツールではありません。

一方 kube-bench は、その文書の各項目を対象ノード上で自動チェックする OSS(Aqua Security)です。ノード上のファイル権限を stat で読み、プロセスの起動引数を読み取り、CIS の推奨値と突き合わせて PASS / FAIL を返します。基準(文書)と評価ツール(実装)は別物である、という区別が本回の随所で効いてきます。ツールが見ていない項目は「基準に無い」ことを意味しませんし、ツールが PASS を返すことは「安全である」ことを意味しません。

第3巻で最初に実装する回がこれである理由も、この位置づけから説明できます。第1回で扱った 4C モデルで言えば、CIS ベンチマークが対象とするのは「Cluster」層の土台です。NetworkPolicy(第3回)や RBAC(第4回)という個別の防御を積む前に、クラスタ設定そのものの抜けを塞ぐほうが順序として理にかなっています。CKS 試験でも「kube-bench が指摘した項目を Remediation どおりに直せ」という形式の出題がありえます。

ここで第1回の制約を思い出してください。kube-bench の公式ドキュメントは、CKS 試験中に参照できる 8 件に含まれません。Falco のような専用の許可枠が与えられていないためです。したがって kube-bench の使い方は暗記対象になります。本回で扱うコマンドは末尾の「暗記必須コマンド」に集約します。

もう 1 点、第1回で扱ったプロファイル解決の話も前提になります。kube-bench はクラスタの K8s バージョンから CIS プロファイルを選びますが、同梱の対応表は v1.34 が上限です。そのためラボの v1.36.2 も、CKS 試験環境の v1.35 も、マイナーを 1 つずつ下げて同じ cis-1.12 に解決されます。ラボと試験でバージョンが 1 つ違っても、評価される基準は同じということです。

CIS Kubernetes Benchmark(基準・文書)とkube-bench(評価ツール)とクラスタ(評価対象)の三者関係、およびCISの章立てと--targetsの対応を示す図
図1:CIS Benchmark(基準)・kube-bench(評価ツール)・クラスタ(評価対象)の関係と --targets の対応

CoreDNS(kubedns)はどこで評価されるのか

CKS の公式コンピテンシーは、CIS ベンチマークでレビューする対象として etcd / kubelet / kubedns / kube-apiserver の 4 つを挙げています。このうち 4 つ目をどう扱うかは、事実・解釈・手順の順に分けて考えると迷いません。

まず事実を確認します。本書ラボが使う CIS プロファイル cis-1.12 のディレクトリを見ます。

実行コマンド(k8s-cp-01 上・root):

# ls -1 /etc/kube-bench/cfg/cis-1.12/

実行結果:

config.yaml
controlplane.yaml
etcd.yaml
master.yaml
node.yaml
policies.yaml

6 ファイルのうち config.yaml はプロファイル自体の設定なので、チェック定義は残り 5 本です。この 5 本を検索します。

実行コマンド(k8s-cp-01 上・root):

# grep -ril 'coredns\|kubedns' /etc/kube-bench/cfg/cis-1.12/

実行結果はです。1 行も出力されません。つまり該当は 1 件もありません。検索式自体が機能していることは、範囲を /etc/kube-bench/cfg/ 全体(47 のプロファイルディレクトリ)へ広げると k3s-cis-1.9/policies.yaml が 1 件だけヒットすることで確認できます。本書ラボが使う cis-1.12 には無い、というのが実測結果です。

次に解釈です。ここで言えるのは「kube-bench v0.15.6 に同梱される cis-1.12 プロファイルに CoreDNS / kubedns の項目が無い」ことであって、「CIS Kubernetes Benchmark という文書に項目が存在しない」ではありません。本セクション冒頭で述べた「基準(文書)と評価ツール(実装)は別物」という区別を、ここで自分から崩さないでください。確認したのは評価ツールの守備範囲です。

コンピテンシーが挙げる 4 つのうち、kube-apiserver は master.yaml、etcd は etcd.yaml、kubelet は node.yaml で自動評価できます。DNS アドオンだけは評価ツールの守備範囲外であり、手動レビューの対象になります。

なお公式が kubedns という名前を使っているのは、Kubernetes の既定 DNS アドオンが kube-dns だった時代の記述が残っているためです。現在の kubeadm が導入するのは CoreDNS で、指している対象は同じです。公式の記述が誤っているわけではありません。

最後に手順です。自動評価できないなら、何を見ればよいのかを具体化します。まず Pod の securityContext を確認します。

実行コマンド(k8s-ops 上・developer):

$ kubectl -n kube-system get deploy coredns -o jsonpath='{.spec.template.spec.containers[0].securityContext}'

実行結果:

{"allowPrivilegeEscalation":false,"capabilities":{"add":["NET_BIND_SERVICE"],"drop":["ALL"]},"readOnlyRootFilesystem":true}

続いて、CoreDNS に与えられている権限を確認します。

実行コマンド(k8s-ops 上・developer):

$ kubectl get clusterrole system:coredns -o yaml

実行結果(rules 以降):

rules:
- apiGroups:
  - ""
  resources:
  - endpoints
  - services
  - pods
  - namespaces
  verbs:
  - list
  - watch
- apiGroups:
  - discovery.k8s.io
  resources:
  - endpointslices
  verbs:
  - list
  - watch

手動レビューの観点は 3 つに整理できます。

観点実機値(kubeadm 既定・v1.36.2)見るポイント
Pod の securityContextallowPrivilegeEscalation: false / drop: ["ALL"] + add: ["NET_BIND_SERVICE"] / readOnlyRootFilesystem: trueRestricted 相当が最初から入っている。Pod Security Standards として第8回で改めて扱う
ServiceAccount と権限SA は coredns。ClusterRole system:corednsendpoints / services / pods / namespaces / endpointslices に対する list watch のみで、書き込み権限を持たない最小権限になっているか。RBAC 監査として第4回で扱う
Corefile の ConfigMapkube-system Namespace の configmap/coredns誰が書き込めるか。ここを書き換えられると DNS 応答を差し替えられる

3 つ目が実質的な要点です。DNS 応答を書き換えられると、Pod が意図しない宛先へ接続してしまい、通信先ごと乗っ取られます。つまり CoreDNS の防御は「Corefile を保持する ConfigMap への書き込み権限を誰が持つか」に集約されます。この観点は第3回(通信経路の制御)・第4回(RBAC の最小化)・第17回(脅威検知)へ接続します。

kube-bench の実行モデル ——targets・判定区分・Remediation の読み方

修復に入る前に、kube-bench の出力を読むための前提を固めます。第1回で確定した内容も含め、修復作業で必要になるものをここに集約します。

ターゲットと CIS 章の対応

--targetsCIS の章評価対象実行すべきノード本書ラボのベースライン
master1 Control Plane Security Configurationapiserver / scheduler / controller-manager の起動フラグとファイル権限Control Plane Node39 / 10 / 11
etcd2 Etcd Node Configurationetcd の起動フラグControl Plane Node(etcd 同居)7 / 0 / 0
controlplane3 Control Plane Configuration認証・認可方針(手動確認のみ)Control Plane Node0 / 0 / 5
node4 Worker Node Security Configurationkubelet の設定とファイル権限評価したいノード自身17 / 2 / 6
policies5 Kubernetes PoliciesRBAC・PSS・NetworkPolicy 等の運用方針任意3 / 0 / 31

数値は「PASS / FAIL / WARN」の順です。第1回でも強調したとおり、mastercontrolplane新旧の言い換えではなく別セクションです。master は CIS 第 1 章(自動判定できる起動フラグとファイル権限)、controlplane は CIS 第 3 章(認証・認可方針の手動確認)を指します。試験で「Control Plane を評価せよ」と言われて controlplane を打つと、手動確認項目 5 件しか出ずに手が止まります。

判定区分は 4 種

区分意味対応
PASS推奨設定に合致している対応不要
FAIL推奨設定に未達(自動判定)Remediation を読んでから修正する
WARN自動判定できず手動確認が必要内容を読んで個別に判断する
INFO参考情報判断材料として把握する

サマリは 2 ブロック出る

--targets master のようにターゲットを 1 つだけ指定しても、出力末尾には次の 2 ブロックが並びます。ターゲットが 1 つなので数値は同じになります。読者が画面と記事を突き合わせる箇所なので、形を覚えておいてください。

実行結果(末尾のサマリ部分):

== Summary master ==
39 checks PASS
10 checks FAIL
11 checks WARN
0 checks INFO

== Summary total ==
39 checks PASS
10 checks FAIL
11 checks WARN
0 checks INFO

== Summary <target> == はそのターゲットの集計、== Summary total == は全ターゲットの合算です。--targets を省略して 5 ターゲットすべてを走らせた場合、ターゲットごとのサマリは末尾にまとまって並ぶのではなく、各ターゲットのチェック出力の直後にそれぞれ現れます。そしてすべてが終わったあとに合算が 1 つだけ出ます。実機で == で始まる行だけを抜き出すと、次の順序でした。

== Remediations master ==
== Summary master ==
== Summary etcd ==
== Remediations controlplane ==
== Summary controlplane ==
== Remediations node ==
== Summary node ==
== Remediations policies ==
== Summary policies ==
== Summary total ==

etcd だけ == Remediations etcd == がありません。FAIL も WARN も 0 件なので、修正すべきことが無いためです。合算だけを消したい場合は --nototals を付けます。

Remediation の読み方と、その限界

各 FAIL / WARN には check ID と Remediation が併記され、出力の == Remediations <target> == セクションにまとまって出ます。「どのファイルのどのフラグをどう変えるか」が具体的に書かれているため、修復はここを読むところから始まります。

ただし Remediation は、そのまま打てばよいとはかぎりません。本回では次の 3 パターンを実際に踏みます。

  • 例示されたパスが自分の環境と違う——4.1.1 は /lib/systemd/system/kubelet.service と書きますが、AlmaLinux 10 の実体は /usr/lib/systemd/system/kubelet.service です
  • 前提とする構成が違う——1.1.12 は OS パッケージの etcd デーモンを想定しており、kubeadm の static Pod 構成には etcd という OS ユーザーすら存在しません
  • そのまま適用すると機能が壊れる——1.2.5 を素直に適用すると kubectl logskubectl exec が使えなくなります

調査に効くフラグ

修復作業では、全項目を毎回流すのではなく、直した項目だけを再評価します。そのために使うフラグを整理します。いずれも実機のヘルプで確認済みです。

フラグ用途
-c, --check "1.1.1,1.1.2"特定 ID だけ再評価する。修復後の確認に使う
-g, --group "1.1"章単位で再評価する
--include-test-outputFAIL 時に実測値を表示する(1.1.12 なら root:root が出る)
--nototals== Summary total == を抑止する
--skip "1.2.5"受容すると決めた例外を除外する
--benchmark cis-1.12CIS プロファイルを明示する(Workload Node で必須になる)

static Pod マニフェスト編集の鉄則 ——バックアップ・自動再起動・3 点セット

本回の修復対象の多くは kube-apiserver・controller-manager・scheduler の起動フラグです。kubeadm クラスタでは、これらは static Pod/etc/kubernetes/manifests/*.yaml)として動いています。kubelet がこのディレクトリを監視しており、ファイルを保存した瞬間に Pod を作り直します。ここで扱う作法は、第5回(API Server 堅牢化)・第9回(Secret 管理)・第16回(監査ログ)でも繰り返し使います。

鉄則1:編集前にバックアップ。置き場所は manifests の外

実行コマンド(k8s-cp-01 上・root):

# cp /etc/kubernetes/manifests/kube-apiserver.yaml /root/kube-apiserver.yaml.bak

/etc/kubernetes/manifests/ の中に .bak を置いてはいけません。kubelet はそのディレクトリ内のファイルをすべて Pod 定義として読もうとするため、意図しない Pod が起動します。本書では退避先を /root/ に統一し、ファイル名も次の 3 つで固定します。

  • /root/kube-apiserver.yaml.bak
  • /root/kube-controller-manager.yaml.bak
  • /root/kube-scheduler.yaml.bak

鉄則2:保存=即再起動。確認は crictl と /healthz で行う

編集して保存すると apiserver Pod が作り直されます。この間 kubectl は応答しません。API が落ちている間は kubectl 自体が使えないため、ノード上の crictl で確認します(第2巻で使ったコンテナランタイム CLI です)。

実行コマンド(k8s-cp-01 上・root):

# crictl ps | grep kube-apiserver

CREATED が数十秒前になり、ATTEMPT が 0 の新しいコンテナが出ていれば再生成されています。実機では保存から 約 40 秒で入れ替わりました(通し検証 2 回・計 6 回の書き換えで 39〜43 秒)。あわせて API の健全性を確認します。

ここが本回で最初に root で kubectl を打つ箇所です。root は既定で kubeconfig を持たないため、先に KUBECONFIG を渡します。渡さずに打つと The connection to the server localhost:8080 was refused になります。

実行コマンド(k8s-cp-01 上・root):

# export KUBECONFIG=/etc/kubernetes/admin.conf
# kubectl --server=https://192.168.1.125:6443 get --raw=/healthz

実行結果:

ok

--server で cp-01 自身の API Server を直接指しているのには理由があります。本書ラボは k8s-lb(HAProxy)が Control Plane 3 台へ振り分けるため、kubectl をそのまま打つとどの API Server に当たったのか分かりません。編集したノードの結果を確かめたいときは --server で固定します。admin.confserver: は k8s-lb を指していますが、apiserver 証明書の SAN(Subject Alternative Name・その証明書が名乗れるホスト名と IP の一覧)に advertise-address が含まれるため、IP へ上書きしても TLS 検証は通ります(--insecure-skip-tls-verify は不要です)。この SAN が、やってみよう③ で壊れる原因そのものになります。

鉄則3:フラグを書き間違えると起動しない。戻し方を先に覚える

YAML のインデントを崩したり、存在しないフラグを書いたりすると、apiserver は起動しません。復旧はバックアップを書き戻すだけです。手順を先に覚えておけば、壊れても落ち着いて戻せます。

実行コマンド(k8s-cp-01 上・root):

# cp /root/kube-apiserver.yaml.bak /etc/kubernetes/manifests/kube-apiserver.yaml

鉄則4:ファイル参照フラグは「3 点セット」で足す

--audit-policy-file(第16回)や --encryption-provider-config(第9回)のようにホスト上のファイルを参照するフラグを足すときは、フラグ単体では足りません。static Pod はコンテナなので、そのファイルがコンテナから見えるように volumeMountsvolumes(hostPath)も同時に追加する必要があります。本書ではこれを 3 点セット原則と呼びます。

本回で足すのは --profiling=false のような値だけのフラグなので 3 点セットは要りません。実際に 3 点セットを組むのは第5回が最初で、第9回・第14回・第16回・第18回でも同じ形を使います(全 5 回)。

鉄則5:HA クラスタでは 3 台すべてに、1 台ずつ適用する

本書ラボは Control Plane が 3 台あります。1 台だけ直しても、残り 2 台は FAIL のままです。しかも HAProxy の振り分けによって、リクエストごとに挙動が変わります。この状態がどれほど厄介かは、やってみよう③で実際に確認します。

適用は 1 台ずつ行い、次の 3 点を確認してから次の台へ進みます。3 台同時に触ると API が完全に止まります。

  1. kubectl --server=https://<そのノードの IP>:6443 get --raw=/healthzok を返す
  2. kubectl get pod -n kube-system -o wide | grep <そのノード名> で該当 static Pod が Running かつ RESTARTS が変更前の値から 1 増える程度にとどまっている(何度も増え続けているなら CrashLoop を疑う)
  3. etcd を触る変更では etcdctl ... endpoint health --cluster が 3 台とも true を返す

3 点目の etcdctl は、そのままでは打てません。kubeadm の etcd はクライアント証明書認証が必須なので、第2巻で使ったのと同じく証明書のフラグを添えます。--cluster を付ければ、エンドポイントを 1 つ指定するだけで全メンバーを自動で列挙してくれます。

実行コマンド(k8s-cp-01 上・root):

# etcdctl --endpoints=https://192.168.1.125:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key endpoint health --cluster -w table

実行結果:

+----------------------------+--------+-------------+-------+
|          ENDPOINT          | HEALTH |    TOOK     | ERROR |
+----------------------------+--------+-------------+-------+
| https://192.168.1.125:2379 |   true |  9.880768ms |       |
| https://192.168.1.127:2379 |   true | 12.998562ms |       |
| https://192.168.1.126:2379 |   true | 14.034016ms |       |
+----------------------------+--------+-------------+-------+

もうひとつ、編集時に驚かないための補足です。本書ラボの static Pod には、第2巻で設定したプロキシ環境変数(HTTP_PROXY / HTTPS_PROXY / NO_PROXY)が env: として入っています。CIS とは無関係の設定なので、そのまま残してください。

static Pod編集のライフサイクル。バックアップから編集保存、kubeletの検知、Pod再生成、crictlと/healthzでの確認、失敗時にbakから戻す分岐を示す図
図2:static Pod 編集のライフサイクル(正常系と、失敗時に .bak から戻す分岐)

やってみよう①:Control Plane の FAIL を修復する

ここから実作業です。本演習では --profiling=false を 3 コンポーネントに追加し、--service-account-extend-token-expiration=false を追加し、etcd データディレクトリの所有者を直します。作業対象は k8s-cp-01 です。

ステップ1:現状の FAIL を一覧する

実行コマンド(k8s-cp-01 上・root):

# export KUBECONFIG=/etc/kubernetes/admin.conf
# kube-bench run --targets master | grep '^\[FAIL\]'

実行結果:

[FAIL] 1.1.12 Ensure that the etcd data directory ownership is set to etcd:etcd (Automated)
[FAIL] 1.2.5 Ensure that the --kubelet-certificate-authority argument is set as appropriate (Automated)
[FAIL] 1.2.15 Ensure that the --profiling argument is set to false (Automated)
[FAIL] 1.2.16 Ensure that the --audit-log-path argument is set (Automated)
[FAIL] 1.2.17 Ensure that the --audit-log-maxage argument is set to 30 or as appropriate (Automated)
[FAIL] 1.2.18 Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate (Automated)
[FAIL] 1.2.19 Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate (Automated)
[FAIL] 1.2.30 Ensure that the --service-account-extend-token-expiration parameter is set to false (Automated)
[FAIL] 1.3.2 Ensure that the --profiling argument is set to false (Automated)
[FAIL] 1.4.1 Ensure that the --profiling argument is set to false (Automated)

10 件を 3 つに仕分けます。この仕分けが本回の骨格です。

扱い該当 IDどこで
本回で直す(5 件)1.1.12 / 1.2.15 / 1.2.30 / 1.3.2 / 1.4.1やってみよう①(本セクション)
壊れる実例として扱う(1 件)1.2.5やってみよう③(恒久対応は第5回)
後の回へ送る(4 件)1.2.16 / 1.2.17 / 1.2.18 / 1.2.19第16回(監査ログ設計)

ステップ2:–profiling=false を 3 つのコンポーネントに追加する

--profiling が有効だと /debug/pprof エンドポイントが開き、メモリや goroutine のダンプを取得できます。障害解析には有用ですが、常時開けておく必要はありません。内部構造の情報が漏れる経路になるため、CIS は無効化を推奨しています。

ただし 3 つを同列に扱うのは正確ではありません。本書ラボの controller-manager と scheduler は --bind-address=127.0.0.1 で動いており、そもそもノードの外から到達できません。実効性が高いのは API Server の 1.2.15 で、1.3.2 / 1.4.1 は露出が限定的です。それでも 3 つとも切るのは、bind アドレスの設定が将来変わったときに取り残されないようにするためです。この「PASS の重みは項目ごとに違う」という感覚は、次のステップ5 でさらにはっきりします。

まず 3 本ともバックアップを取ります。

実行コマンド(k8s-cp-01 上・root):

# cp /etc/kubernetes/manifests/kube-apiserver.yaml /root/kube-apiserver.yaml.bak
# cp /etc/kubernetes/manifests/kube-controller-manager.yaml /root/kube-controller-manager.yaml.bak
# cp /etc/kubernetes/manifests/kube-scheduler.yaml /root/kube-scheduler.yaml.bak

次に 1 本ずつ編集します。kubeadm はフラグをアルファベット順に並べているので、それに合わせて挿入します。挿入する行はどれも同じです。

追加する行:

    - --profiling=false
ファイル挿入位置
/etc/kubernetes/manifests/kube-apiserver.yaml--proxy-client-cert-file の行の直前
/etc/kubernetes/manifests/kube-controller-manager.yaml--requestheader-client-ca-file の行の直前
/etc/kubernetes/manifests/kube-scheduler.yaml--leader-elect=true の行の直後(引数リストの末尾)

実行コマンド(k8s-cp-01 上・root):

# vi /etc/kubernetes/manifests/kube-apiserver.yaml
# vi /etc/kubernetes/manifests/kube-controller-manager.yaml
# vi /etc/kubernetes/manifests/kube-scheduler.yaml

1 本保存するごとに、そのコンポーネントが作り直されます。3 本を続けて編集してもかまいませんが、どれかで YAML を壊した場合の切り分けが難しくなります。慣れるまでは 1 本ずつ保存し、次のステップ4 の確認を挟んでから次へ進んでください。

ステップ3:–service-account-extend-token-expiration=false を追加する

1.2.30 の対象です。kube-apiserver.yaml--service-account-issuer の行の直前に追加します。アルファベット順でも extend-token-expirationissuer より前に来るので、位置は整合します。

追加する行:

    - --service-account-extend-token-expiration=false

このフラグの既定値は true です。有効期限を見ない古いクライアント向けに、ServiceAccount トークンの有効期限を延長する互換動作が入っています。false にすると、トークンの寿命が指定どおりに短くなります。

既定の true ではトークンが最大 1 年まで延長されます。false にすると、発行時に要求された期限(通常 1 時間程度)どおりに戻ります。

実機で確認した範囲では副作用はありませんでした。全 Pod 異常 0、ArgoCD の Application は Synced Healthy、fanclub-api への HTTPS アクセスは 200 を維持しています。ただし古いクライアントライブラリを使うアプリケーションでは影響が出うるため、本番では事前に影響範囲を確認してから適用してください

確認の手段は用意されています。API Server は serviceaccount_stale_tokens_total というメトリクスを公開しており、これは「期限切れのはずのトークンが延長措置によって受け入れられた回数」を数えています。この値が増えていなければ、古い挙動に依存しているワークロードは無いと判断できます。切り替え前にしばらく観察してから適用するのが定石です。

ServiceAccount トークンの寿命そのものを設計する話は、第4回(RBAC 監査 + ServiceAccount トークン管理)で扱います。

ステップ4:再起動を確認する

鉄則2 の手順で確認します。

実行コマンド(k8s-cp-01 上・root):

# crictl ps | grep kube-apiserver
# kubectl --server=https://192.168.1.125:6443 get --raw=/healthz

実行結果(2 コマンド目):

ok

crictl ps の出力で、CREATED が直近であり ATTEMPT が 0 のコンテナに入れ替わっていることを確認してください。ok が返らない場合は、YAML の記述ミスか、フラグ名の誤りです。鉄則3 のとおりバックアップから戻し、差分を見直します。

ステップ5:etcd データディレクトリの所有者(1.1.12)——Remediation をそのまま打てない例

まず何が期待値と違うのかを実測で見ます。--include-test-output を付けると、FAIL の実測値が表示されます。

実行コマンド(k8s-cp-01 上・root):

# kube-bench run --targets master --check 1.1.12 --nototals --include-test-output

実行結果(冒頭。このあとに == Remediations master ==== Summary master == が続きます):

[INFO] 1 Control Plane Security Configuration
[INFO] 1.1 Control Plane Node Configuration Files
[FAIL] 1.1.12 Ensure that the etcd data directory ownership is set to etcd:etcd (Automated)
	 root:root

期待値は etcd:etcd、実測は root:root です。Remediation は chown etcd:etcd /var/lib/etcd と指示します。ところが、そのユーザーが存在しません。

実行コマンド(k8s-cp-01 上・root):

# id etcd

実行結果:

id: `etcd': そのようなユーザは存在しません

kubeadm クラスタには etcd という OS ユーザーが存在しません。etcd は registry.k8s.io/etcd:3.6.8-0 の static Pod として動いており、etcd.yamlrunAsUser の指定がないためコンテナ内 root で実行されます。CIS が想定しているのは「OS パッケージの etcd デーモンを専用ユーザーで動かす構成」であり、kubeadm 構成とは前提が違います。

PASS にするには、ユーザーとグループを作ってから chown します。

実行コマンド(k8s-cp-01 上・root):

# groupadd -r etcd
# useradd -r -g etcd -s /sbin/nologin -d /var/lib/etcd -M etcd
# id etcd

実行結果:

uid=995(etcd) gid=995(etcd) groups=995(etcd)

実行コマンド(k8s-cp-01 上・root):

# chown etcd:etcd /var/lib/etcd
# ls -ld /var/lib/etcd
# kube-bench run --targets master --check 1.1.12 --nototals

実行結果(冒頭。このあとに == Summary master == が続きます):

drwx------. 3 etcd etcd 20  7月 26 09:44 /var/lib/etcd
[INFO] 1 Control Plane Security Configuration
[INFO] 1.1 Control Plane Node Configuration Files
[PASS] 1.1.12 Ensure that the etcd data directory ownership is set to etcd:etcd (Automated)

実機では、この変更後に etcd の static Pod を意図的に再起動しても正常に起動し、etcdctl endpoint health は 3 台とも true を返しました。データディレクトリの所有者を変えても etcd は動き続けます。

この修復に、どれだけの意味があるのか

ここは本回の主題のひとつなので、正直に書きます。kube-bench は PASS に変わりましたが、実際のリスクはほとんど減っていません

  • /var/lib/etcd は変更前から drwx------ root:root(700)で、他ユーザーからは元々読めなかった
  • etcd はコンテナ内 root で動くため、所有者が誰であろうと DAC(Discretionary Access Control・ファイルの所有者とパーミッションによる従来のアクセス制御)を素通りして読み書きできる
  • 実際、/var/lib/etcd/member の中身は root:root のまま変わらない。Remediation はトップディレクトリだけを対象にしている
  • 厳密に言えば、変更後は uid 995(etcd ユーザー)でも読める状態になる。700 の所有者が root から etcd へ移るため、読める主体はむしろ 1 つ増えている

ただし、ここから「CIS が間違っている」と結論しないでください。この項目が意味を持つのは「etcd を専用ユーザーで動かす構成」——OS パッケージの etcd デーモン、あるいは runAsUser を指定した Pod——の場合です。その構成では、専用ユーザー以外がデータディレクトリを読めない状態に意味があります。kubeadm の static Pod 構成がそれに該当しないだけであり、前提とする構成が違う、というのが正しい結論です。

そして、価値がゼロというわけでもありません。監査やコンプライアンス要件で「CIS スコア N% 以上」を求められる現場では、リスク低減がほとんど無くても証跡としては意味を持ちます。この項目を「受容する(--skip で除外し、判断理由を記録に残す)」のか「形式的に PASS にする」のかは、技術判断ではなく組織の要求で決まります。どちらを選ぶにせよ、理由を説明できる状態にしておくことが運用側の仕事です。

「ベンチマークが通ること」と「安全であること」は別だ——第3巻を通じて繰り返す主題の、これが最初の実例です。第6回では同じ構図がより大きな形で現れます。CIS はホスト OS の SELinux が有効かどうかしか見ないため、コンテナ側が実質的に無拘束でも FAIL しません。

本番ガードレール

etcd ユーザーを作るなら、Control Plane 3 台とも -r(システムユーザー)指定で作成してください。UID / GID が台ごとにずれると、バックアップやリストアの際に所有者が食い違い、原因の分かりにくいトラブルになります。

ステップ6:再評価する

実行コマンド(k8s-cp-01 上・root):

# kube-bench run --targets master | grep '^\[FAIL\]'
# kube-bench run --targets master | tail -12

実行結果(1 コマンド目):

[FAIL] 1.2.5 Ensure that the --kubelet-certificate-authority argument is set as appropriate (Automated)
[FAIL] 1.2.16 Ensure that the --audit-log-path argument is set (Automated)
[FAIL] 1.2.17 Ensure that the --audit-log-maxage argument is set to 30 or as appropriate (Automated)
[FAIL] 1.2.18 Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate (Automated)
[FAIL] 1.2.19 Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate (Automated)

10 件あった FAIL が 5 件になりました。実行結果(2 コマンド目):

== Summary master ==
44 checks PASS
5 checks FAIL
11 checks WARN
0 checks INFO

== Summary total ==
44 checks PASS
5 checks FAIL
11 checks WARN
0 checks INFO

tail -12 はサマリ 2 ブロックがちょうど収まる行数です。39 / 10 / 11 だったスコアが 44 PASS / 5 FAIL / 11 WARN になりました。残る FAIL 5 件は 1.2.5 と監査ログ 4 件で、いずれも意図して残したものです。

本番ガードレール

本書ラボでは cp-01 だけを直していますが、本番では Control Plane 3 台すべてに同じ変更を入れます。適用は 1 台ずつ行い、鉄則5 の 3 点(/healthzok / 該当 static Pod が RunningRESTARTS が変更前から 1 増える程度 / etcd を触ったなら証明書フラグ付きの etcdctl ... endpoint health --cluster が 3 台とも true)を確認してから次の台へ進んでください。

試験ではどの粒度で問われるか

本演習は通しで 25〜35 分かかりますが、CKS 試験ではこの作業がもっと小さな単位で 1 問ずつ出ます。試験相当の粒度に分解すると次のようになります。

ステップ試験相当の所要時間
ステップ2・4(--profiling ×3 の無効化と再起動確認)約 5〜7 分
ステップ3(1.2.30 の追加)約 2〜3 分
ステップ5(1.1.12 を PASS にする)約 3〜5 分
ステップ6(全体を再評価してベースラインとの差分を確認する)約 2 分

やってみよう②:Workload Node を評価して修復する

CIS 第 4 章(Worker Node Security Configuration)は kubelet を対象とします。ここには Control Plane で作業していると気づきにくい落とし穴が 2 つあります。

ステップ1:–targets node は「実行したノード自身」を評価する

cp-01 で --targets node を実行しても、評価されるのは cp-01 の kubelet であって wl-01 ではありません。kube-bench はノード上のファイルとプロセスを直接読むツールなので、評価したいノードの上で動かす必要があります。第1回で kube-bench を導入したのは cp-01 だけなので、wl-01 への導入から始めます。

ステップ2:Workload Node には tar が入っていない

第1回で cp-01 に導入したときと同じコマンドを打つと、失敗します。

実行コマンド(k8s-wl-01 上・developer):

$ curl -sSL https://github.com/aquasecurity/kube-bench/releases/download/v0.15.6/kube-bench_0.15.6_linux_amd64.tar.gz | tar -xz

実行結果:

bash: 行 1: tar: コマンドが見つかりません
curl: (23) Failure writing output to destination, passed 1378 returned 1349

Control Plane には入っていた tar が、Workload Node には入っていません。curlscp は使えます。まず tar を導入します。

実行コマンド(k8s-wl-01 上・developer):

$ sudo dnf install -y tar

本番ガードレール

ノードの攻撃面を減らす観点では、評価ツールをノードに常駐させないほうが安全です(第7回で扱います)。本番では評価後に削除するか、kube-bench を Job として実行する運用にします。本書では学習のため入れたままにします。

ステップ3:kube-bench を導入する

取得と展開からやり直します。

実行コマンド(k8s-wl-01 上・developer):

$ curl -sSL https://github.com/aquasecurity/kube-bench/releases/download/v0.15.6/kube-bench_0.15.6_linux_amd64.tar.gz | tar -xz
$ ls

実行結果:

cfg  kube-bench

展開結果は cfgkube-bench の 2 つだけです(cp-01 では第2巻で置いた admin.conf が並んでいました)。続いて標準パスへ配置します。

実行コマンド(k8s-wl-01 上・root):

# install -m 0755 /home/developer/kube-bench /usr/bin/kube-bench
# mkdir -p /etc/kube-bench
# cp -r /home/developer/cfg /etc/kube-bench/cfg
# kube-bench version

実行結果:

0.15.6

配置先を選んでいる理由を再掲します。第1回で確定した内容ですが、ここで手を動かすので繰り返します。

  • バイナリは /usr/local/bin ではなく /usr/bin へ置く。AlmaLinux 10 では root の PATH にも sudosecure_path にも /usr/local/bin が含まれないため
  • cfg はカレントディレクトリのままでは読まれない。--config-dir の既定値が /etc/kube-bench/cfg なので、そこへ配置する

ステップ4:kubeconfig が無いと FAIL が PASS に化ける

Workload Node には admin.conf がありません。そのまま実行するとどうなるかを確認します。

実行コマンド(k8s-wl-01 上・root):

# kube-bench run --targets node

実行結果(冒頭):

Warning: Kubernetes version was not auto-detected because kubectl could not connect to the Kubernetes server. This may be because the kubeconfig information is missing or has credentials that do not match the server. Assuming default version 1.18
Warning: Kubernetes version was not auto-detected because kubectl could not connect to the Kubernetes server. This may be because the kubeconfig information is missing or has credentials that do not match the server. Assuming default version 1.18
[INFO] 4 Worker Node Security Configuration
[INFO] 4.1 Worker Node Configuration Files
[PASS] 4.1.1 Ensure that the kubelet service file permissions are set to 644 or more restrictive (Automated)
[PASS] 4.1.2 Ensure that the kubelet service file ownership is set to root:root (Automated)

警告行が 2 回出るのは kube-bench の仕様で、異常ではありません。そして 4.1.1 が PASS しています。次にプロファイルを明示して、同じノードで同じファイルを評価します。

実行コマンド(k8s-wl-01 上・root):

# kube-bench run --targets node --benchmark cis-1.12

実行結果(抜粋):

[FAIL] 4.1.1 Ensure that the kubelet service file permissions are set to 600 or more restrictive (Automated)
[FAIL] 4.1.9 If the kubelet config.yaml configuration file is being used validate permissions set to 600 or more restrictive (Automated)

判定文言の 「644」と「600」に注目してください。バージョンを検出できなかった kube-bench は v1.18 相当の古いプロファイルを使い、そこでは「644 以下ならよい」ので PASS になります。正しいプロファイル(cis-1.12)では「600 以下」なので FAIL です。同じ実機・同じ 644 のファイルなのに、判定が逆になっています

第1回では --benchmark cis-1.12 を「KUBECONFIG を渡せない場面の代替手段」として紹介しました。Workload Node では代替手段ではなく必須です。冒頭の Assuming default version 1.18 という警告行を読み飛ばすと、「合格しているつもりで未対応」という最も避けたい状態になります。

ステップ5:2 件を修復する

4.1.1 の Remediation は chmod 600 /lib/systemd/system/kubelet.service と書いています。しかし AlmaLinux 10 では /lib/usr/lib へのシンボリックリンクで、実体は /usr/lib/systemd/system/kubelet.service にあります(kubeadm の drop-in も /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf です)。Remediation の例示パスをそのまま打たず、自分の環境の実体を確認する——この原則の実例です。

実行コマンド(k8s-wl-01 上・root):

# chmod 600 /usr/lib/systemd/system/kubelet.service
# chmod 600 /var/lib/kubelet/config.yaml
# systemctl daemon-reload
# systemctl restart kubelet

ここで再起動している理由を正確に押さえてください。chmod はファイルの中身を変えないので、再起動しなくても kubelet の挙動は変わりません。再起動するのは「権限を絞った状態でも kubelet が起動できることを確かめる」ための検証手順です。systemctl daemon-reload も、モード変更だけなら本来は不要ですが、「ユニットファイルを触ったら daemon-reload」という定石として打っています。

実機では再起動後も kubelet は active を維持し、ノードは Ready のままでした。

本番ガードレール

Workload Node で systemctl restart kubelet を打つと、実行中コンテナは落ちないものの、短時間ノードが NotReady になりうる点に注意してください。本番では業務時間帯を避けて実施します。

ステップ6:再評価する

実行コマンド(k8s-wl-01 上・root):

# kube-bench run --targets node --benchmark cis-1.12 | tail -12

実行結果:

== Summary node ==
19 checks PASS
0 checks FAIL
6 checks WARN
0 checks INFO

== Summary total ==
19 checks PASS
0 checks FAIL
6 checks WARN
0 checks INFO

17 / 2 / 6 から 19 PASS / 0 FAIL / 6 WARN になりました。残る WARN 6 件の内訳も見ておきます。

ID内容本回での扱い
4.1.3 / 4.1.4kube-proxy の kubeconfig の権限・所有者kube-bench が探す既定パス(/etc/kubernetes/proxy.conf ほか)が実機に無いため判定できない。本書ラボの kube-proxy は DaemonSet で動いており、該当ファイルがノード上に無い
4.2.9--tls-cert-file / --tls-private-key-fileやってみよう③ で扱う kubelet のサーバ証明書の話。第5回serverTLSBootstrap と合わせて判断する
4.2.12強い暗号スイートの使用既定のままでも実用上の問題は無い。要件がある現場で個別に判断する項目
4.2.13Pod あたりの PID 数の上限1 つの Pod がノードのプロセステーブルを食い尽くす攻撃への対策。テナント分離の文脈なので第10回で扱う
4.2.14--seccomp-defaultコンテナ既定の seccomp プロファイル。第6回

6 件とも「直せていない」のではなく、「判断した結果として今は動かさない」ものです。この区別は次の「残った FAIL・WARN の棚卸し」でもう一度整理します。

そして、この修復は一度きりでは終わりません/usr/lib/systemd/system/kubelet.servicekubelet パッケージが所有するファイルであり、第5回で扱う kubeadm アップグレード(本書ラボの kubelet は versionlock で固定してあるため、その解除を含む手順になります)を実施すると、ファイルモードがパッケージ既定の 644 に戻りうるためです。CIS 準拠は「一度直せば終わり」ではなく、「アップグレードのたびに再評価して直す」運用項目である、と捉えてください。第5回でクラスタを更新したあとは、本回と同じ確認をもう一度実施することになります。

ステップ7:残りのノードにも適用する

CIS 第 4 章の対象は「kubelet が動くすべてのノード」です。Workload Node だけでなく、Control Plane 3 台も対象になります。ベースラインの表で node(cp-01 上)が 17 / 2 / 6 だったのは、cp-01 の kubelet も同じ 2 件が FAIL しているためです。

  • wl-02: 同じ手順を最初から適用します。wl-02 でも tar の導入から必要です
  • cp-01 / cp-02 / cp-03: tar と kube-bench は cp-01 に既にあるので、cp-02 / cp-03 への kube-bench 導入と、3 台での chmod 600 2 件を適用します

出力は wl-01 と同一になるため、ここでは省略します。この作業をしておかないと、cp-01 で --targets を省略して kube-bench を実行したときに 4.1.1 / 4.1.9 が FAIL として出続けます。

試験の粒度で言えば、本演習は「指定されたノードで kubelet のファイル権限を CIS 準拠にせよ」という 1 問に相当し、5〜8 分が目安です。ただし本演習の前半(tar の導入と kube-bench の配置)は本書ラボ固有の準備で、試験でこの手順が要るかどうかは公式に示されていません。持ち帰るべきは「評価はそのノードの上で行う」「プロファイルを明示しないと判定基準がずれる」の 2 点です。

やってみよう③:Remediation を鵜呑みにすると壊れる(–kubelet-certificate-authority)

この演習を始める前に

  • 本演習は意図的にクラスタ機能を壊します学習用ラボ専用であり、本番クラスタでは実施しないでください
  • スナップショット等で復元できる環境で実施してください
  • ステップ7 のロールバックは任意ではなく必須です。適用したまま先へ進むと、第3回以降の演習で kubectl logs / kubectl exec が断続的に失敗します
  • 復旧の確認は「6 回連続で成功」を基準にします(理由はステップ6・7 で説明します)

ここが本回の中心です。CIS の Remediation を素直に適用し、機能が壊れ、原因を切り分け、ロールバックするまでを一通り実施します。

ステップ1:Remediation を読む

1.2.5 の Remediation は「apiserver と kubelet の間の TLS 接続をセットアップし、--kubelet-certificate-authority に CA 証明書のパスを設定せよ」と指示します。素直に読めば、kubeadm クラスタなら /etc/kubernetes/pki/ca.crt を指定すればよさそうに見えます。

この項目の意図自体は正当です。既定では API Server は kubelet のサーバ証明書を検証していません。中間者攻撃で kubelet になりすまされると、kubectl exec の内容や Pod のログが盗まれます。だから検証を有効にせよ、というのが CIS の主張であり、主張は正しいです。

ステップ2:バックアップを取ってから適用する(cp-01 のみ)

やってみよう① のステップ2 でバックアップを取っている場合も、現在の内容で取り直してください。--profiling 等の修復を含んだ状態に戻せるようにしておきます。

実行コマンド(k8s-cp-01 上・root):

# cp /etc/kubernetes/manifests/kube-apiserver.yaml /root/kube-apiserver.yaml.bak
# vi /etc/kubernetes/manifests/kube-apiserver.yaml

挿入位置は --kubelet-client-certificate の直前です。やってみよう① で立てたアルファベット順の原則どおり、編集後は次の並びになります。

編集後の該当箇所:

    - --kubelet-certificate-authority=/etc/kubernetes/pki/ca.crt
    - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
    - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key

ここで鉄則4(3 点セット原則)を思い出した方は、鋭いところに気づいています。--kubelet-certificate-authority=/etc/kubernetes/pki/ca.crt は明らかにホスト上のファイルを参照するフラグなのに、volumeMountsvolumes を足していません。それでも動きます。

理由は、参照先がすでにコンテナへマウント済みだからです。kubeadm が生成する apiserver の static Pod は、k8s-certs という名前の hostPath で /etc/kubernetes/pki をまるごとマウントしています。ca.crt はその配下にあるので、フラグを足すだけでコンテナから見えます。

つまり 3 点セット原則の実務上の使い方は、「ファイル参照フラグなら必ず 3 つ足す」ではなく、「そのパスが既存のマウントに含まれていないかを先に確認し、含まれていなければ 3 つ足す」です。第9回で追加する暗号化設定ファイルも、第14回で追加する Admission 設定ファイルも、第16回・第18回で追加する監査ポリシーファイルも、/etc/kubernetes/pki の外に置くことになるため、そちらでは 3 点セットが必要になります。

ステップ3:一見、成功して見える

保存後、鉄則2 の手順で確認します。cp-01 を直した直後なので、--server で cp-01 を名指しします。

実行コマンド(k8s-cp-01 上・root):

# export KUBECONFIG=/etc/kubernetes/admin.conf
# crictl ps | grep kube-apiserver
# kubectl --server=https://192.168.1.125:6443 get --raw=/healthz

やってみよう② で k8s-wl-01 に移っていた場合、k8s-cp-01 のシェルは張り直しになっています。KUBECONFIG はシェルをまたいで引き継がれないため、ここで再度エクスポートします。

実行結果(3 コマンド目):

ok

apiserver は再生成され、健全性チェックも通っています。ここで作業を終えると「直った」と思ってしまいます。CIS の FAIL が 1 件減り、API Server も正常に応答しているのですから、判断材料としては十分に見えます。

ステップ4:しかし kubectl logs と exec が壊れている

fanclub-api のデータベース Pod のログを取得します。ここでも --server で cp-01 を名指しします。名指ししないと k8s-lb が別の Control Plane へ振り分けてしまい、直していない台に当たれば成功してしまうためです(この性質はステップ6 で正面から扱います)。

実行コマンド(k8s-cp-01 上・root):

# kubectl --server=https://192.168.1.125:6443 -n fanclub logs fanclub-db-0 --tail=3

実行結果:

Error from server: Get "https://192.168.1.128:10250/containerLogs/fanclub/fanclub-db-0/postgres?tailLines=3": tls: failed to verify certificate: x509: cannot validate certificate for 192.168.1.128 because it doesn't contain any IP SANs

実行コマンド(k8s-cp-01 上・root):

# kubectl --server=https://192.168.1.125:6443 -n fanclub exec fanclub-db-0 -- echo hello

実行結果:

error: unable to upgrade connection: error dialing backend: tls: failed to verify certificate: x509: cannot validate certificate for 192.168.1.128 because it doesn't contain any IP SANs

API Server は正常なのに、kubelet へ中継する操作だけが失敗しています。192.168.1.128 は k8s-wl-01 の IP、10250 は kubelet の API ポートです。

エラーに出る IP は、fanclub-db-0 が動いているノードのものです。読者の環境で k8s-wl-02 に配置されていれば 192.168.1.129 になります。kubectl get pod -n fanclub -o wide で配置を確認しておくと、数字が違っても迷いません。

ステップ5:原因を切り分ける

切り分けの起点は、同じ cp-01 を名指ししているのに kubectl top nodes成功したままである点です。

実行コマンド(k8s-cp-01 上・root):

# kubectl --server=https://192.168.1.125:6443 top nodes

実行結果:

NAME        CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
k8s-cp-01   442m         22%      2469Mi          69%
k8s-cp-02   951m         47%      2355Mi          66%
k8s-cp-03   603m         30%      2859Mi          80%
k8s-wl-01   525m         13%      5027Mi          66%
k8s-wl-02   507m         12%      3642Mi          48%

正確に言うと、kubectl top nodes 自体は API Server(集約 API 層)を通って metrics-server に届きます。壊れていないのは metrics-server が kubelet を叩く経路のほうです。そこは API Server が張る接続ではないので、--kubelet-certificate-authority の影響を受けません。壊れたのは「API Server が kubelet へ中継する経路」だけで、壊れ方が部分的なので気づきにくいわけです。

ここには考えさせられる事実があります。metrics-server は多くの導入形態で --kubelet-insecure-tls を付けて動いており、kubelet のサーバ証明書検証を最初から放棄しています。1.2.5 が指摘している弱点は metrics-server 側にも同じ形で存在し、そちらは「検証しない」ことで回避しているのです。CIS が見ているのは API Server の設定だけなので、この経路は評価対象にすら入りません。ベンチマークが見ている範囲と、実際の攻撃面は一致しない——ここでも同じ構図が現れます。

次に、kubelet のサーバ証明書を確認します。

実行コマンド(k8s-wl-01 上・root):

# ls -l /var/lib/kubelet/pki/

実行結果:

合計 12
-rw-------. 1 root root 1114  7月 25 22:21 kubelet-client-2026-07-25-22-21-31.pem
lrwxrwxrwx. 1 root root   59  7月 25 22:21 kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2026-07-25-22-21-31.pem
-rw-r--r--. 1 root root 2275  7月 25 22:21 kubelet.crt
-rw-------. 1 root root 1679  7月 25 22:21 kubelet.key

kubelet-client-current.pem(kubelet が API Server へ接続するときのクライアント証明書)はありますが、kubelet-server-current.pem——クラスタ CA が署名したサーバ証明書——は存在しません。あるのは kubelet.crtkubelet.key だけです。これは kubelet が自己署名のサーバ証明書を自分で作って使っていることを意味します。

実際に kubelet が提示する証明書を見てみます。本書ラボの Workload Node には openssl が入っていないので、curl の TLS ハンドシェイク出力から読み取ります。

実行コマンド(k8s-wl-01 上・root):

# curl -k -v --noproxy '*' https://192.168.1.128:10250/healthz 2>&1 | grep -E 'subject:|issuer:|date'

実行結果:

*  subject: CN=k8s-wl-01@1784985691
*  start date: Jul 25 12:21:31 2026 GMT
*  expire date: Jul 25 12:21:31 2027 GMT
*  issuer: CN=k8s-wl-01-ca@1784985691

issuerCN=k8s-wl-01-ca@...——つまりこの kubelet 自身が作った CA であって、クラスタ CA ではありません。さらに subjectAltName の行が出ていないことからも分かるとおり、IP SAN を持っていません

つまり因果関係はこうです。API Server に「クラスタ CA で kubelet の証明書を検証せよ」と指示したが、kubelet が提示する証明書はクラスタ CA が発行したものではなく、しかも接続先 IP に対応する SAN も持っていない。だから検証に失敗し、x509 エラーになります。

クラスタ CA で検証させるには、kubelet 側で serverTLSBootstrap: true を有効にし、発行された CSR を承認して、クラスタ CA 署名のサーバ証明書を配る必要があります。API Server にフラグを 1 行足すだけでは完結しません

しかも、この CSR は自動では承認されません。kube-controller-manager が自動承認するのは kubelet-client 系の CSR だけで、サーバ証明書用の kubernetes.io/kubelet-serving は対象外です。手動で kubectl certificate approve するか、専用の承認コントローラを用意する必要があります。kubeadm がこれを既定で有効にしていないのは、この運用負担があるためです。恒久対応は第5回(API Server 堅牢化)で扱います。

ステップ6:HA クラスタでは「たまに失敗する」障害になる

ここが本演習で最も実務に効く部分です。cp-01 だけを直した状態で、k8s-ops から(=--server を付けず k8s-lb 経由で)同じ操作を繰り返します。次のループは「ログ取得が成功したら OK、失敗したら NG を印字する」だけのものです。

実行コマンド(k8s-ops 上・developer):

$ for i in 1 2 3 4 5 6; do kubectl -n fanclub logs fanclub-db-0 --tail=1 >/dev/null 2>&1 && echo "try$i: OK" || echo "try$i: NG"; done

実行結果:

try1: NG
try2: OK
try3: OK
try4: NG
try5: OK
try6: OK

k8s-lb(HAProxy)が Control Plane 3 台へ振り分けるため、直した台に当たったときだけ失敗します。上の並びは実測の一例で、実行するたびに OK と NG の位置は変わります。3 台のうち 1 台だけを直した状態なので、失敗はおおむね 3 回に 1 回の割合で現れます。記事とまったく同じ並びにならなくても問題ありません。

ステップ3・ステップ4 で --server を付けて cp-01 を名指ししていたときは、結果が毎回同じでした。名指しをやめた途端に成否が揺れます。この対比が、鉄則2 で --server を使う理由そのものです。

本番でこれが起きると「再現しない障害」として長時間の切り分けを強いられます。鉄則5(HA では全台に同じ変更を入れる)が効いてくる場面であり、裏を返せば壊す変更も全台に広がるということでもあります。だからこそ 1 台目で確認してから次へ進みます。

ステップ7:ロールバックする(必須)

実行コマンド(k8s-cp-01 上・root):

# cp /root/kube-apiserver.yaml.bak /etc/kubernetes/manifests/kube-apiserver.yaml

約 70 秒待ってから、ステップ6 と同じループで復旧を確認します。

実行コマンド(k8s-ops 上・developer):

$ for i in 1 2 3 4 5 6; do kubectl -n fanclub logs fanclub-db-0 --tail=1 >/dev/null 2>&1 && echo "try$i: OK" || echo "try$i: NG"; done

実行結果:

try1: OK
try2: OK
try3: OK
try4: OK
try5: OK
try6: OK

確認は「6 回連続で成功」を基準にします。1 回成功しただけでは、HAProxy がたまたま直していない台に振り分けただけという可能性が残ります。壊れ方が確率的だったのですから、直り方も確率で確かめます。これは本番の障害対応でも同じ考え方です。

あわせて、やってみよう① の成果まで巻き戻していないことも確かめます。ステップ2 で .bak を取り直しておいたのは、このためです。

実行コマンド(k8s-cp-01 上・root):

# grep -c 'kubelet-certificate-authority' /etc/kubernetes/manifests/kube-apiserver.yaml
# grep -c 'profiling' /etc/kubernetes/manifests/kube-apiserver.yaml

実行結果:

0
1

1 行目が 0(本演習で足したフラグが消えている)、2 行目が 1(やってみよう① の --profiling=false は残っている)であれば、狙いどおりの状態です。

そして、バックアップさえ取っておけば完全に戻せます。これが鉄則1 の意味です。

この演習の到達点は、「FAIL を全部消す」ことが目的ではないと理解することです。直す・受容する・後で直すの 3 つを判断できることが、CIS を運用に載せるということです。

この演習から試験に持ち帰るもの

「壊してから直す」という形式そのものは CKS の出題にはありません。それでもこの演習を通す理由は、試験本番で効く判断が 3 つ含まれているからです。

  • /healthzok だからといって完了と判断しない。設定を変えたら、その設定が影響する機能(今回なら kubectl logs / exec)まで触って確かめる。試験は最終状態だけを採点するので、途中で気づかないまま次の問題へ進むと取り返せない
  • 壊したまま次へ進まない。CKS は 1 つのクラスタで複数の問題を解く形式があり、前の問題で壊した機能が後の問題の採点に響きます。作業を終える前に、直前に触った機能が生きていることを確認する
  • フラグ 1 行では完結しない項目があると知っておく--kubelet-certificate-authority のように、証明書の配布側(kubelet の serverTLSBootstrap と CSR 承認)とセットで初めて成立する設定があります。問題文が「フラグを設定せよ」で終わっているのか、「機能させよ」まで求めているのかを読み分ける

残った FAIL・WARN の棚卸し ——どれをいつ回収するか

修復後、cp-01 の master には FAIL が 5 件残っています。すべて意図的に残したものです。やり残しではないことを、ここで明文化しておきます。

ID内容本回で直さない理由回収する回
1.2.5--kubelet-certificate-authoritykubelet の証明書運用(serverTLSBootstrap と CSR 承認)ごと設計する必要がある第5回
1.2.16〜1.2.19--audit-log-* 4 件監査ポリシー設計と 3 点セット(フラグ + volumeMounts + volumes)が必要第16回

WARN も同様に整理します。

対象件数説明
policies(CIS 5 章)31RBAC・PSS・NetworkPolicy 等の運用方針。自動判定できないため全件 WARN で出る。「修復対象ではない手動確認項目」であり、内容は第4回(RBAC)・第8回(PSS)で実装として回収する
controlplane(CIS 3 章)5認証・認可方針の手動確認
1.2.27 / 1.2.28(--encryption-provider-config2etcd の保存時暗号化。第9回
4.2.14(--seccomp-default1コンテナ既定の seccomp プロファイル。第6回

WARN をゼロにしようとしないでください。WARN は「判断せよ」という指示であって「直せ」ではありません。policies の 31 件を「FAIL ではないのに残っている」と捉えて機械的に潰そうとすると、実装が伴わない形式的な対応になります。実務では、判断結果(受容する / 対応する / 対応時期はいつか)を記録に残すことが求められます。

この整理を言い換えると、CIS の各項目に対して取りうる選択肢は 直す・受容する・後で直す の 3 つです。受容すると決めた項目は --skip フラグで評価対象から外せるので、日々のレポートには「未対応として残っている項目」だけが並ぶようになります。受容の判断そのものは消えず、記録として残る——この運用ができて初めて、ベンチマークが監査に耐える形になります。

masterターゲットのFAIL10件を本回で直す5件・壊れる実例1件・第16回送り4件に色分けし、修復後に44PASS/5FAIL/11WARNへ変化する前後を示す図
図3:FAIL 10 件の仕分けと、修復前後のスコア変化(39/10/11 → 44/5/11)

やってみよう④:プラットフォームバイナリの SHA256 検証

CIS ベンチマークとは別軸の D1 コンピテンシー「Verify platform binaries before deploying」を扱います。kubectl / kubelet / kubeadm は、ダウンロード経路での改ざんが供給リスクになります。Kubernetes 公式は各バイナリの SHA256 チェックサムを dl.k8s.io で配布しており、照合すれば改ざんを検知できます。

ステップ1:バイナリとチェックサムを取得する

実行コマンド(k8s-ops 上・developer):

$ cd /tmp
$ curl -sLO https://dl.k8s.io/release/v1.36.2/bin/linux/amd64/kubectl
$ curl -sLO https://dl.k8s.io/release/v1.36.2/bin/linux/amd64/kubectl.sha256

dl.k8s.io は第2巻で alma-proxy の whitelist に登録済みのため、追加設定は不要です(実機で HTTP 200 を確認しています)。

ここでバージョンについて 1 点補足します。本書ラボの Control Plane / Workload Node は v1.36.2 ですが、作業端末 k8s-ops の kubectl は v1.35.6 のままです。第2巻第6回でクラスタだけを v1.36 へ上げ、作業端末の kubectl は据え置いたためです(kubectl はサーバとの 1 マイナー差までサポートされるため、実務上の問題はありません)。本ステップで検証するのは「今ダウンロードした v1.36.2 のバイナリ」であって、k8s-ops にインストール済みの kubectl ではありません。

ステップ2:照合する

実行コマンド(k8s-ops 上・developer):

$ echo "$(cat kubectl.sha256)  kubectl" | LC_ALL=C sha256sum --check

実行結果:

kubectl: OK

LC_ALL=C を付けているのは、本書ラボの LANGja_JP.UTF-8 のためです。付けないと kubectl: 完了 と日本語で出ます。CKS 試験環境は英語ロケールで OK / FAILED が出るので、試験本番と同じ文字列で覚えられるように LC_ALL=C で統一します。

入力形式にも触れておきます。sha256sum --check は「チェックサム␣␣ファイル名」という行を標準入力から読みます。sha256sum 自身が出力する正規の形式は半角スペース 2 個区切りなので、書くときも 2 個で揃えます(GNU coreutils はスペース 1 個でも受け付けることを実機で確認していますが、正規形で覚えておくほうが安全です)。.sha256 ファイルにはハッシュ値しか入っていないため、echo でファイル名を連結して渡しています。

ステップ3:改ざんを検知させる

1 バイト追記してから、同じ照合を実行します。

実行コマンド(k8s-ops 上・developer):

$ printf x >> kubectl
$ echo "$(cat kubectl.sha256)  kubectl" | LC_ALL=C sha256sum --check

実行結果:

kubectl: FAILED
sha256sum: WARNING: 1 computed checksum did NOT match

OK 以外が出たバイナリは使いません。警告行だけは LC_ALL=C の有無にかかわらず英語で出力されます。

注意

この printf x >>/tmp に落としたダウンロード物に対して実行します。/usr/bin 配下の稼働中バイナリには絶対に実行しないでください。次のステップ4 で /usr/bin/kubectl を扱うため、対象を取り違えないよう注意が必要です。

ステップ4:稼働中のバイナリを照合する

ダウンロード直後の検証よりも実務価値が高いのは、すでにノードに入っているバイナリが正規のものかを確かめることです。侵害されたノードでは、コマンド自体がすり替えられている可能性があります。

実行コマンド(k8s-cp-01 上・developer):

$ curl -sSL https://dl.k8s.io/release/v1.36.2/bin/linux/amd64/kubectl.sha256 -o /tmp/kubectl.sha256
$ echo "$(cat /tmp/kubectl.sha256)  /usr/bin/kubectl" | LC_ALL=C sha256sum --check

実行結果:

/usr/bin/kubectl: OK

本書ラボの cp-01 の /usr/bin/kubectl は、公式チェックサム 1e9045ec32bea85da43de85f0065358529ea7c7a152eca78154fba5b58c27d82 と一致しました。参考までに、本書ラボの主要バイナリの実測ハッシュを挙げておきます。

対象SHA256(実測)
cp-01 の /usr/bin/kubectl(公式値と一致)1e9045ec32bea85da43de85f0065358529ea7c7a152eca78154fba5b58c27d82
cp-01 / wl-01 の /usr/bin/kubelet(両ノードで同一)3ba68cb4f0906053c08ab843131d5cec559cf8c718dbd679950a078b306abbb4
cp-01 の /usr/bin/kubeadmf95d1b345f97c673ce1c1347eb96bf2587850975155ae701cdfc629a5b575b70

kubelet と kubeadm も、URL の末尾を差し替えるだけで同じ手順で照合できます。ノード間で同一バイナリのハッシュが食い違っていたら、それ自体が調査の起点になります。

ステップ5:後片付け

ステップ3 で改ざんしたファイルが /tmp に残っています。誤って使わないよう削除します。

実行コマンド(k8s-ops 上・developer):

$ rm -f /tmp/kubectl /tmp/kubectl.sha256

何を暗記すべきか、その線引き

sha256sum は coreutils の標準コマンドです。そして CKS 試験環境には man と man pages がプリインストールされており、ディストリビューション同梱ドキュメント(/usr/share 配下)の参照も許可されています。つまり man sha256sum は試験中に引けます。

さらに重要なことに、本演習で使ったワンライナーそのものが、許可ドキュメントの 1 番目に載っていますkubernetes.io/docs の kubectl インストール手順(Linux)にある「Validate the binary」の節に、echo "$(cat kubectl.sha256) kubectl" | sha256sum --check と、kubectl: OK / kubectl: FAILED / sha256sum: WARNING: 1 computed checksum did NOT match という出力例がそのまま掲載されています。試験中は man を引くより、こちらを開くほうが速いはずです。

したがって、本回で本当に暗記が必要なのは kube-bench の 1 点だけです。許可 8 件のどこにも載っていないため、run --targets の使い分けも --check / --benchmark / --include-test-output といったフラグも、手に覚えておくしかありません。

本演習は 8〜12 分で完了します。本回の 4 本の中で、試験の出題粒度に最も近い演習です。

fanclub-api への影響確認

本回はクラスタの設定を 5 箇所変更しました(apiserver の 2 フラグ / controller-manager / scheduler / etcd データディレクトリの所有者 / kubelet のファイル権限 2 件)。変更後にアプリケーションが変わらず動いていることを確認します。

実行コマンド(k8s-ops 上・developer):

$ kubectl get pods -n fanclub
$ kubectl get application -n argocd
$ curl -sk --noproxy '*' -o /dev/null -w '%{http_code}\n' -H 'Host: fanclub.local' https://192.168.1.124/

実測では 8 Pod(backend×3 / db×1 / frontend×2 / logcollector×2)すべてが Running、ArgoCD の Application は fanclub-api-prod Synced Healthy、HTTPS の応答は 200 を維持しました。

--noproxy '*' を付けているのは、本書ラボが Squid の whitelist 方式を採っており、環境変数のプロキシ設定をここでは回避する必要があるためです(第2巻から継承した作法です)。

本回の変更はいずれも実行中ワークロードに影響しませんが、設定変更のたびにアプリケーションの稼働を確かめる習慣をここで定着させてください。設定変更がアプリケーションに波及し始めるのは第3回(NetworkPolicy で通信を絞る)以降で、そこでは影響確認の比重がさらに大きくなります。

ひとつ運用上の注意です。本書ラボの VM を停止状態から起動した直後は、Longhorn のボリュームを使う Pod(fanclub-db / backend / 監視系)が数分間 Unknown になります。第1回でも触れたとおり、待てば復帰します。起動直後にこの確認を行うと、変更の影響と区別がつかなくなるため、Pod がすべて安定してから実施してください。

暗記必須コマンド(第2回)

本回で扱った、試験中に公式ドキュメントを参照できないコマンドを集約します。

用途コマンド補足
CIS 評価(Control Plane 本体)kube-bench run --targets master事前に export KUBECONFIG=/etc/kubernetes/admin.conf
CIS 評価(Workload Node)kube-bench run --targets node --benchmark cis-1.12kubeconfig の無いノードでは --benchmark が必須
CIS 評価(etcd)kube-bench run --targets etcd公式コンピテンシーが etcd を名指ししている。Control Plane 上で実行する
CIS 評価(認証・認可方針)kube-bench run --targets controlplaneCIS 3 章。手動確認項目のみ(master とは別セクション)
CIS 評価(運用ポリシー)kube-bench run --targets policiesCIS 5 章。RBAC・PSS・NetworkPolicy の方針
特定 ID の再評価kube-bench run --targets master --check 1.2.15修復後の確認に使う
FAIL の実測値を見るkube-bench run --targets master --check 1.1.12 --include-test-output何がどう違うのかが出力される
章単位の評価kube-bench run --targets master --group 1.2
static Pod のバックアップcp /etc/kubernetes/manifests/kube-apiserver.yaml /root/kube-apiserver.yaml.bakmanifests ディレクトリの外へ
static Pod 再起動の確認crictl ps | grep kube-apiserverAPI 停止中は kubectl が使えない
受容した例外を除外kube-bench run --targets master --skip 1.2.5「受容する」運用と対応する
特定 API Server の健全性kubectl --server=https://<CP の IP>:6443 get --raw=/healthzHA では --server で固定する。ラボの IP をそのまま覚えない
バイナリ改ざん検証echo "$(cat kubectl.sha256) kubectl" | LC_ALL=C sha256sum --check半角スペース 2 個区切り
稼働バイナリの照合echo "$(cat kubectl.sha256) /usr/bin/kubectl" | LC_ALL=C sha256sum --check

本当に暗記が要るのは kube-bench だけです。許可 8 件のどこにも載っていないためで、「配布元が GitHub だから不可」という話ではありません(許可 4 番の kubernetes-sigs.github.io/bom/cli-reference/ のように、GitHub Pages でも許可されているものはあります)。表の下 3 行(sha256sum 系)は、kubernetes.io/docs の kubectl インストール手順「Validate the binary」節に同じ形が載っているので、試験中に参照できます。

もうひとつ、試験環境の作法について。CKS 試験環境には alias k=kubectl がプリインストールされています。本書は読みやすさのため kubectl の全綴りで統一していますが、試験では k で打ってください。120 分の試験では、この差が積み上がります。

まとめ

本回では次の点を確認しました。

  • CIS Kubernetes Benchmark は設定ハードニングの業界基準であり、kube-bench がそれを自動評価する。基準(文書)と評価ツール(実装)は別物である
  • ターゲットは master / etcd / controlplane / node / policies に分かれ、mastercontrolplane は新旧の言い換えではなく別セクションである
  • CoreDNS の項目は cis-1.12 プロファイルに含まれないため、securityContext・ClusterRole・Corefile の ConfigMap を手動でレビューする
  • --targets node は実行したノード自身を評価する。Workload Node を見るには、そのノードに kube-bench を入れる
  • kubeconfig を渡せないノードでは --benchmark cis-1.12 が必須。省くと古いプロファイルで評価され、FAIL が PASS に化ける
  • static Pod は保存=即再起動。バックアップ → 編集 → crictl で再起動確認 → /healthz の順で進める
  • --profiling=false ×3・--service-account-extend-token-expiration=false・kubelet のファイル権限 2 件を修復し、master は 39/10/11 → 44/5/11、node は 17/2/6 → 19/0/6 になった
  • chown etcd:etcd /var/lib/etcd は PASS に変わるが、etcd はコンテナ内 root で動くためリスクはほとんど減らない。ベンチマーク通過と安全は別である
  • --kubelet-certificate-authority を素直に足すと kubectl logs / exec が壊れる。HA では「たまに失敗する」形で現れる。恒久対応は第5回
  • 残した FAIL / WARN は第5・6・9・16回で回収する。WARN をゼロにしようとしない
  • バイナリは dl.k8s.io.sha256LC_ALL=C sha256sum --check で照合する

理解度チェック(○×形式・全 9 問)

次の各文が正しいか(○)誤りか(×)を判断してください。下の「解答と解説」を開くと答え合わせができます。

  1. 公式チェックサムと照合すれば、配布経路(ミラー・プロキシ・保存メディア)でバイナリが改変されていないことを検知できる
  2. kube-bench の WARN は「自動判定できず手動確認が必要」を意味する
  3. static Pod マニフェストは編集して保存しても、手動で再起動するまで反映されない
  4. static Pod のバックアップは /etc/kubernetes/manifests/ 内に .bak で置くのがよい
  5. Control Plane Node で kube-bench run --targets node を実行すれば、Workload Node の kubelet を評価できる
  6. kubeconfig を渡さずに kube-bench を実行すると、古い CIS プロファイルで評価されて FAIL が PASS になることがある
  7. CIS の Remediation どおりに --kubelet-certificate-authority を設定すれば、副作用なく安全性が上がる
  8. chown etcd:etcd /var/lib/etcd で 1.1.12 は PASS になるが、kubeadm 構成では実際のリスクはほとんど変わらない
  9. 認証・認可の体系的な堅牢化(anonymous-auth・authorization-mode)は本回(第2回)で完了する
解答と解説

1=○/2=○/3=×/4=×/5=×/6=○/7=×/8=○/9=×

主要な問題の解説です。問1 について、OK が保証するのは「公式が公開したハッシュ値と、手元のファイルのハッシュ値が一致すること」です。公式サイト自体が侵害されていればチェックサムごと差し替えられるため、この照合だけで出所まで保証できるわけではありませんが、配布経路(ミラー・プロキシ・保存メディア)での改変は検知できます。第14回の Cosign による署名検証は、この「配布元そのものを信頼できるか」に踏み込む手段になります。

問3 は逆です。kubelet がマニフェストのディレクトリを監視しており、保存した瞬間に Pod が再生成されます。だからこそ編集前のバックアップが要ります。問4 も同様で、/etc/kubernetes/manifests/ 内のファイルは kubelet がすべて Pod 定義として読むため、.bak を同じディレクトリに置くと不正な Pod 起動を招きます。退避先は /root/ のように監視対象外の場所にします。

問5 について、kube-bench はノード上のファイルとプロセスを直接読みます。評価したいノードの上で動かす必要があり、Control Plane から遠隔で Workload Node を評価することはできません。問6 は本回で実際に確認したとおりで、バージョンを検出できないと v1.18 相当のプロファイルに落ち、4.1.1 の判定基準が 600 から 644 に変わってしまいます。冒頭の警告行を読み飛ばさないでください。

問7 は、kubelet のサーバ証明書が自己署名のままだと API Server が検証に失敗し、kubectl logs / exec が使えなくなるため誤りです。恒久対応には serverTLSBootstrap と CSR 承認が必要で、第5回で扱います。問9 も誤りで、本回は CIS が機械的に検出する項目が中心です。--anonymous-auth=false / --authorization-mode=Node,RBAC / NodeRestriction の体系的な整理は第5回(D2)で行います。

ここに挙げなかった問2 は「kube-bench の実行モデル」の判定区分表を、問8 は「この修復に、どれだけの意味があるのか」の節をそれぞれ読み返してください。どちらも本文で正面から扱っています。

次回予告

第3回「NetworkPolicy 完全設計 + ノードメタデータ保護 + TLS Ingress」では、D1 の後半として通信制御を扱います。fanclub Namespace には第2巻の時点で NetworkPolicy が 9 本入っており、default-deny-all も適用済みです。第3回はそれを白紙に戻すのではなく、CKS の目で読み直して穴を見つけ、締める回になります。あわせてノードメタデータエンドポイント(169.254.169.254)への egress 遮断と、TLS 付き Ingress(試験は標準 Ingress、本書ラボは Gateway API HTTPRoute)の両形式を扱います。

本回では「設定の抜けを塞ぐ」ことを扱いました。次回は「通信の経路を塞ぐ」に進みます。

→ 詳しくは 第3回 NetworkPolicy 完全設計 + ノードメタデータ保護 + TLS Ingress

前の記事
次の記事