セキュリティ設定が実際に塞ぐものを見る
このラボはセキュリティが実際に強制されるクラスターで動きます
VMの中に本物のk3sが起動しています。kubeletとcontainerdが実際に動いているため、securityContextに違反したPodは本当に拒否され、読み取り専用のルートは本当に書き込みを防ぎます。
CKSコースのほかのラボはkwokの上で動きます。そこでは違反したPodもそのままRunningになるため、設定を書く練習はできますが、その設定が守られていることを確認する練習はできません。
最初の起動には2分ほどかかります。
目標
セキュリティ統制がいつ、どこで止めるのかを区別します。アドミッションで止まるものとランタイムで止まるものは、症状も直す場所も違います。
なぜ重要なのか
実務で事故が起きるのは、「設定を間違えて書いたこと」ではなく、設定をすべて書いたのに、それが適用されていないことです。後者は静かだからです。
そして、同じ「Podが起動しない」でも、原因は複数の層に散らばっています。
| どこで止まるか | 症状 | 直す場所 |
|---|---|---|
| アドミッション(作成時点) | kubectl applyそのものが拒否される |
ネームスペースのラベル、ポリシー |
| kubelet(コンテナ作成時) | Podは作成され、CreateContainerConfigErrorになる |
securityContext |
| ランタイム(プロセスの中) | PodはRunningで、アプリケーションだけが失敗する | ボリューム、capability |
この3つを区別できないと、障害を見るたびに最初から調べ直すことになります。
ステップ
runAsNonRoot: trueを設定したPodで、rootで動くイメージを起動してみてください。拒否の理由を保存し(保存先:/root/cks/nonroot.txt)、正しいPod(good-nonroot)も一緒に起動してください。違反するPodの名前はbad-rootです。hardenedPodにreadOnlyRootFilesystem: trueを設定し、書き込みが実際に防がれることを確認して、結果を保存してください(保存先:/root/cks/readonly.txt)。一時ファイルを書き込む場所も一緒に与える必要があります。- 同じPodで、capabilityをALLで落とし、
allowPrivilegeEscalation: falseを設定してから、その効果を保存してください(保存先:/root/cks/caps.txt)。 lockedネームスペースにPod Security Admissionをrestrictedで設定し、特権Podが作成時点で拒否されることを保存してください(保存先:/root/cks/psa.txt)。lockedにServiceAccountapp-saとapp-roleを作成して、configmapの読み取りだけを許可してください。結果を保存します(保存先:/root/cks/rbac.txt)。app-secretを作成してPodにマウントし、Podの中でそのファイルがどのような姿をしているかを確認して、結果を保存してください(保存先:/root/cks/secret.txt)。- 監査ポリシーを書いてください(保存先:
/root/cks/audit-policy.yaml)。最初のルールで、coreのsecrets・configmaps・serviceaccounts/tokenを、ユーザー・動詞・ネームスペースの制限なしでMetadataとして保護してください。次は、/healthz、/readyz、/livezと、それぞれの/*配下のパスだけをNoneにし、最後は条件なしのMetadataです。omitStagesで省略できるのはRequestReceivedだけです。ルールは全部で3つか、デフォルトの前にRBACの書き込みの詳細ルールを加えた4つを使います。任意の詳細ルールは、rbac.authorization.k8s.ioのroles・rolebindings・clusterroles・clusterrolebindingsに対するcreate・update・patch・delete・deletecollectionだけをRequestResponseで記録します。レベル、最初の一致、本文の保護、ログを残す理由を説明してください(保存先:/root/cks/audit.txt)。このステップはポリシーの作成であり、APIへの適用の証明ではありません。 nonroot_reject_reason=、psa_level=、rbac_denied=の3行と一緒に、いつ、どこで止まるのかをまとめてください(保存先:/root/cks/report.md)。
参考
- 違反したPodの状態は、
kubectl get pod bad-root -o jsonpath='{.status.containerStatuses[0].state}'で確認します。 - PSAのラベルは
pod-security.kubernetes.io/enforce=restrictedです。auditとwarnも別々に設定でき、実務ではwarnから設定して何が引っかかるかを見たあと、enforceに上げます。 - 権限の確認は、
kubectl auth can-i <동사> <자원> --as=system:serviceaccount:<ns>:<sa> -n <ns>です(プレースホルダーは動詞とリソースです)。 - 読み取り専用のルートを使うなら、
/tmpのような場所にemptyDirを付ける必要があります。そうしないと、一時ファイルを書き込むほとんどのプログラムが落ちます。 - よくある間違い1:
runAsNonRoot: trueだけを設定してrunAsUserを設定しない場合です。イメージがrootで動くと拒否されますが、その事実はイメージを見ないとわからないため、混乱します。 - よくある間違い2: PSAをいきなり
enforceで設定する場合です。すでに動いているワークロードが、次の再デプロイですべてブロックされます。先にwarnで測ってください。
70分のラボなので、期限が切れる前に+時間で延長してください(最大180分)。セッションが終了すると、作業物が消えます。
rootで動くとそもそも起動しない
runAsNonRoot: trueを設定したPodで、rootで動くイメージを起動してみてください。拒否の理由を保存し(保存先: /root/cks/nonroot.txt)、正しいPod(good-nonroot)も一緒に起動してください。違反するPodの名前はbad-rootです。
runAsNonRoot: trueは、kubeletがコンテナを作成する直前に、イメージのUSERを見て判断します。アドミッションではなくkubeletが止めるというのが要点です。
ルートを読み取り専用にする
hardenedPodにreadOnlyRootFilesystem: trueを設定し、書き込みが実際に防がれることを確認して、結果を保存してください(保存先: /root/cks/readonly.txt)。一時ファイルを書き込む場所も一緒に与える必要があります。
readOnlyRootFilesystem: trueだけを設定すると、一時ファイルを書き込むプログラムが落ちます。emptyDirを一緒に付けてください。
capabilityをすべて落とす
同じPodで、capabilityをALLで落とし、allowPrivilegeEscalation: falseを設定してから、その効果を保存してください(保存先: /root/cks/caps.txt)。
drop: ["ALL"]のあとで、本当に必要なものだけをaddで載せ直します。ほとんどのアプリケーションは、何も必要としません。
作成時点で止める
lockedネームスペースにPod Security Admissionをrestrictedで設定し、特権Podが作成時点で拒否されることを保存してください(保存先: /root/cks/psa.txt)。
ネームスペースにpod-security.kubernetes.io/enforce=restrictedラベルを付けます。これはアドミッションなので、kubectl applyそのものが拒否されます。
必要なものだけを与える
lockedにServiceAccountapp-saとapp-roleを作成して、configmapの読み取りだけを許可してください。結果を保存します(保存先: /root/cks/rbac.txt)。
kubectl auth can-i --as=system:serviceaccount:<ns>:<sa>で、許可と拒否の両方を確認してください。開いているものだけを見るのでは、半分です。
SecretはPodの中でどう見えるか
app-secretを作成してPodにマウントし、Podの中でそのファイルがどのような姿をしているかを確認して、結果を保存してください(保存先: /root/cks/secret.txt)。
ボリュームとしてマウントしたあと、ls -laで中をのぞいてみてください。..dataのシンボリックリンク構造と保存先のメディアが要点です。
何を残すかを選ぶ
監査ポリシーを書いてください(保存先: /root/cks/audit-policy.yaml)。最初のルールで、coreのsecrets・configmaps・serviceaccounts/tokenを、ユーザー・動詞・ネームスペースの制限なしでMetadataとして保護してください。次は、/healthz、/readyz、/livezと、それぞれの/*配下のパスだけをNoneにし、最後は条件なしのMetadataです。omitStagesで省略できるのはRequestReceivedだけです。ルールは全部で3つか、デフォルトの前にRBACの書き込みの詳細ルールを加えた4つを使います。任意の詳細ルールは、rbac.authorization.k8s.ioのroles・rolebindings・clusterroles・clusterrolebindingsに対するcreate・update・patch・delete・deletecollectionだけをRequestResponseで記録します。レベル、最初の一致、本文の保護、ログを残す理由を説明してください(保存先: /root/cks/audit.txt)。このステップはポリシーの作成であり、APIへの適用の証明ではありません。
保護ルールが先に一致する必要があります。omitStagesは本文の削除ではありません。ポリシーの作成と、実際の完了イベントの収集を区別してください。
いつ、どこで止まるのか
nonroot_reject_reason=、psa_level=、rbac_denied=の3行と一緒に、いつ、どこで止まるのかをまとめてください(保存先: /root/cks/report.md)。
nonroot_reject_reason=、psa_level=、rbac_denied=の3行と一緒に、アドミッション・kubelet・ランタイムの3つの層をまとめてください。