CKS模擬試験A
目標
実際のCKSと同じ条件で、課題17個を120分以内に解きます。合格ラインは67%で、部分点制なので、17個のうち12個に合格すれば完了として扱われます。
模擬試験です。ヒントと正解を見ずに、まず最後まで解いてみてください。 行き詰まった課題は印を付けて飛ばし、残った時間に戻ってくるほうがよいです。 採点はいつ押してもよく、何度押しても結果は変わりません。
なぜ重要なのか
CKSは、CKAへの合格が前提条件になっている唯一の試験です。Kubernetesを扱う手はすでに身に付いているものとされているため、問題は「何かを作りなさい」よりも、「この設定の何が危険かを見つけて直しなさい」に近いものです。防御と診断が試験の対象で、攻撃の手法は扱いません。
実務でも同じです。クラスターが破られる場所は、たいてい新しく作った機能ではなく、デフォルト値をそのままにした場所です。ネームスペースにPod Security Admissionを設定しておらず、デフォルトのServiceAccountトークンがすべてのPodにマウントされ、イメージが動くタグでデプロイされています。この試験が問うのは、まさにそのリストです。
試験環境(実際の試験で確認された事実)
- 見られるドキュメントは、
kubernetes.io/docs、kubernetes.io/blog、falco.org/docs、kubernetes-sigs.github.io/bom、etcd.io/docs、kubernetes.github.io/ingress-nginx、docs.cilium.io、istio.io/latest/docsです。 - ドキュメントサイトの中で検索することは許可されていますが、結果が許可リストの外に出てはいけません。
kのエイリアスとbashの自動補完は、すでに設定されています。エイリアスを作るのに時間を使わないでください。- 課題ごとに指定されたホストへ
sshして作業し、入れ子のsshはサポートされていません。次の課題に移る前にexitしてください。 - ツールは、SSHで接続したホストにだけあります。
- コピーは
Ctrl+Shift+C、貼り付けはCtrl+Shift+Vです。
この模擬試験環境での違い
このラボのクラスターは、Podの中で動く1人用のクラスターです。kube-apiserverは本物なので、マニフェスト、RBAC、Pod Security Admission、アドミッションポリシーは、実際に動作し、実際に拒否します。ただし、コンテナを実際に動かすランタイムがないため、次の2つは確認できません。
- NetworkPolicyは適用されますが、実際の遮断は起きません。CNIがないからです。課題1と課題2は、ポリシーオブジェクトが正しいかどうかだけを見ます。
- seccompとAppArmorのプロファイルは、カーネルにロードされません。課題7と課題8は、プロファイルファイルの内容と、Podに結び付けた配線を見ます。
残りはすべて、稼働中のクラスターで再計算して採点します。権限はkubectl auth can-iで、アドミッションポリシーはサーバーのdry-runで、その時点の判定を改めて問い合わせます。
ステップ
Cluster Setup
- ネームスペース
prodを作成し、その中のすべてのPodに対して、ingressとegressの両方をデフォルトで拒否するNetworkPolicydefault-denyを作成してください。許可ルールは1つも置きません。 - ネームスペース
prodで、app=webラベルが付いたPodが、ノードのメタデータエンドポイント169.254.169.254/32へ出られないようにし、それ以外のegressはそのまま開けておくNetworkPolicydeny-metadataを作成してください。 CN=shop.internalの自己署名証明書で、ネームスペースprodにTLS Secretweb-tlsを作成し、ホストshop.internalをそのSecretでTLS終端するIngresswebを作成してください。バックエンドはServicewebのポート80です。
Cluster Hardening
- ネームスペース
prodにServiceAccountreport-runnerを作成し、このアカウントがprodの中でPodをget、list、watchだけできるように、Rolepod-readerとRoleBindingreport-runner-pod-readerを作成してください。それ以外の権限は与えません。 - ネームスペース
prodのdefaultServiceAccountが、トークンを自動マウントしないようにしてください。そしてprodにPodfrontendを作成し、ServiceAccountreport-runnerを使い、Podスペックでもトークンの自動マウントをオフにしてください。 - セキュリティ監査担当のグループ
security-auditが、クラスター全体でPod、ネームスペース、NetworkPolicyを読み取りだけできるように、ClusterRolesecurity-auditorとClusterRoleBindingsecurity-auditorを作成してください。書き込み権限とSecretへのアクセスは与えません。
System Hardening
/root/exam/seccomp/audit.jsonにseccompプロファイルを書いてください。デフォルトの動作はSCMP_ACT_ERRNOで、最低でもいくつかのシステムコールはSCMP_ACT_ALLOWで開ける必要があります。そして、ネームスペースprodにPodprobeを作成し、このプロファイルをLocalhost方式で、profiles/audit.jsonのパスから使うようにしてください。/root/exam/apparmor/k8s-deny-writeにAppArmorプロファイルを書いてください。プロファイル名はk8s-deny-writeで、#include <tunables/global>の行と、すべての書き込みを拒否するdeny /** w,ルールが必要です。そして、ネームスペースprodにDeploymentlogshipperを作成し、PodテンプレートのsecurityContext.appArmorProfileがこのプロファイルをLocalhostで使うようにしてください。
Minimize Microservice Vulnerabilities
- ネームスペース
paymentsを作成し、Pod Security Admissionのenforce、audit、warnの3つのモードをすべてrestrictedに設定して、3つのモードのバージョンをlatestに固定してください。 - ネームスペース
paymentsに、restrictedに違反するPodbad-podをapplyしてみて、拒否された応答を標準エラー出力も含めて/root/exam/denied.txtに保存してください。Podが実際に作成されてはいけません。 - RuntimeClass
gvisor(handlerはrunsc)を作成し、ネームスペースpaymentsにレプリカが2つのDeploymentcheckoutを作成してください。PodテンプレートはこのRuntimeClassを使い、restrictedを通過する必要があります。コンテナ名はappで、PodレベルにrunAsNonRoot: trueとseccompProfile.type: RuntimeDefaultを、コンテナレベルにallowPrivilegeEscalation: false、capabilities.drop: [ALL]、readOnlyRootFilesystem: trueを置いてください。
Supply Chain Security
- ネームスペース
supplyを作成し、Deploymentpayments-apiを作成してください。コンテナ名はapiで、イメージはタグではなくダイジェストで固定する必要があります。値はregistry.internal/payments-api@sha256:140eab0459241fb1643767dd4cc3576d282b0059a6328521e714cb545fa4adeaで、imagePullPolicyはIfNotPresentです。 /root/exam/Dockerfileに、デプロイ用のイメージをビルドするDockerfileを書いてください。ビルドステージと実行ステージを分けたマルチステージで、すべてのFROMのベースを@sha256:ダイジェストで固定し、アーティファクトはCOPY --from=でのみ取り込む必要があります。ADDは使わず、最後のUSERは0ではない数値のUIDにし、ENVやARGにシークレットに見える名前を残さないでください。- 承認されていないレジストリのイメージを止めてください。ValidatingAdmissionPolicy
trusted-imagesとValidatingAdmissionPolicyBindingtrusted-imagesを作成し、すべてのコンテナイメージがregistry.internal/で始まることを要求するようにしてください。バインディングのvalidationActionsはDenyで、適用範囲はネームスペースsupplyだけです。ほかのネームスペースは影響を受けてはいけません。
Monitoring, Logging and Runtime Security
/root/exam/audit/policy.yamlに監査ポリシーを書いてください。apiVersionはaudit.k8s.io/v1、kindはPolicy、最上位のomitStagesはRequestReceivedの1つです。ルールは正確に3つで、順序が重要です。1つ目は、コアグループのsecretsとconfigmapsをMetadataレベルで残します。2つ目は、pods/exec、pods/attach、pods/portforwardをRequestResponseレベルで残します。3つ目は、対象を書かないMetadataルールで、残りのすべてを受けます。/root/exam/audit/kube-apiserver.yamlに、監査ログを有効にしたkube-apiserverの静的Podマニフェストを書いてください。最初のコンテナ名はkube-apiserverで、次の5つのフラグが必要です。--audit-policy-file=/etc/kubernetes/audit/policy.yaml、--audit-log-path=/var/log/kubernetes/audit/audit.log、--audit-log-maxage=30、--audit-log-maxbackup=10、--audit-log-maxsize=100。そして、hostPathボリュームaudit-policyとaudit-logsをそれぞれマウントしますが、ポリシーのマウントはreadOnly: trueにし、ログのマウントは読み取り専用にしてはいけません。- ネームスペース
prodにDeploymentledgerを作成してください。コンテナ名はappで、readOnlyRootFilesystem: true、allowPrivilegeEscalation: false、capabilities.drop: [ALL]を置きます。書き込みが必要な/tmpはemptyDirボリュームscratchで提供し、hostPathボリュームとhostNetwork、hostPID、hostIPCは使わないでください。
参考
- ラベルを付け直すときは、
kubectl label ... --overwriteを使ってください。付けないと、すでにあるラベルでエラーになります。 - Pod Security Admissionは、Deploymentを止めるのではなく、そのDeploymentが作成するPodを止めます。Deploymentは作成されたのにPodが1つもないなら、
kubectl -n <네임스페이스> describe rsで理由を確認してください(プレースホルダーはネームスペースです)。 - ServiceAccountに与えた権限が正しいかどうかは、推測せずに
kubectl auth can-i <동사> <자원> --as=system:serviceaccount:<ns>:<이름>で確認してください(プレースホルダーは動詞、リソース、ServiceAccountの名前です)。グループは--as-groupで模擬します。 - よくある間違いは2つあります。NetworkPolicyに
policyTypesを書かないと、egressは制御されません。そして、automountServiceAccountTokenはServiceAccountとPodの両方にあり、Pod側が優先されます。
prodネームスペースのデフォルト拒否
NetworkPolicyのpodSelectorを空のオブジェクトにすると、そのネームスペースのすべてのPodを選択します。そして、policyTypesにIngressとEgressの両方を書かないと、双方向が塞がれません。ルール(ingress/egress)をまったく書かなければ、「許可するものがない」という意味になります。
ノードのメタデータを遮断する
ipBlockは、cidrで範囲を開け、exceptでその中の一部を切り取ります。すべてを許可しながら1つのアドレスだけを塞ぐには、cidrを0.0.0.0/0にして、exceptにそのアドレスを書きます。この課題はEgressだけを扱います。
TLS終端のIngress
openssl req -x509 -nodesで鍵と証明書を一度に作成し、kubectl create secret tlsで入れます。Ingressは、spec.tlsにホストとsecretNameを書かないと、そのホストをその証明書で終端しません。spec.rulesのhostと同じ名前でなければなりません。
ServiceAccountの最小権限
Roleは、ネームスペースの中でのみ効力があります。verbsに書かなかった動詞は許可されないため、get、list、watchだけを書けば十分です。RoleBindingのサブジェクトは、--serviceaccount=<ネームスペース>:<名前>の形式で指定します。採点は、kubectl auth can-iで境界を両側から確認します。
トークンの自動マウントを遮断する
automountServiceAccountTokenはServiceAccountとPodの両方にあり、Pod側が優先されます。ServiceAccount側はkubectl patch serviceaccountでオフにし、Podはspecに直接書きます。両方ともfalseである必要があります。
読み取り専用のクラスター監査担当
ネームスペースをまたぐ権限は、RoleではなくClusterRoleで与え、ClusterRoleBindingで結び付けます。サブジェクトの種類はServiceAccountではなくGroupで、apiGroupはrbac.authorization.k8s.ioです。networkpoliciesはコアグループではなく、networking.k8s.ioグループにあります。
seccompプロファイルの作成と接続
seccompプロファイルは、defaultActionで既定の判定を決め、syscallsの配列で例外を開けます。既定が拒否(SCMP_ACT_ERRNO)なら、許可リストが必ず必要です。Podでは、securityContext.seccompProfileのtypeをLocalhostにし、localhostProfileにノードのseccompルートを基準にした相対パスを書きます。
AppArmorプロファイルの作成と接続
AppArmorプロファイルは、profile <名前> { ... }のブロックで、その中にアクセスのルールを書きます。書き込みをすべて塞ぐルールは、denyで始まり、パスのパターンと権限の文字、カンマで終わります。Pod側は、securityContext.appArmorProfileフィールドを使います。古いアノテーション方式ではなく、フィールド方式で書いてください。
Pod Security Admissionのrestricted
Pod Security Admissionは、ネームスペースのラベルで有効にします。モードはenforce、audit、warnの3つで、それぞれpod-security.kubernetes.io/<モード>ラベルにレベルを書きます。バージョンは、同じ接頭辞に-versionを付けたラベルです。ラベルは6つになります。
違反したPodが拒否されることを確認する
拒否メッセージは標準エラー出力に出ます。リダイレクトに2>&1を書き忘れると、空のファイルが残ります。違反するPodは、難しく作る必要はなく、securityContextに何も書かなければ十分です。restrictedは4つを同時に要求するため、そのまま引っかかります。
サンドボックスランタイムのワークロード
RuntimeClassはクラスタースコープのリソースで、handlerが1つあれば作成できます。Podテンプレートでは、runtimeClassNameで指します。restrictedは4つを要求しますが、2つはPodレベル、2つはコンテナレベルに書きます。Deploymentは作成されたのにPodがないなら、restrictedに引っかかっています。
イメージをダイジェストで固定する
タグは動きますが、ダイジェストは動きません。ダイジェストで固定するときは、名前と値をコロンではなくアットマークでつなぎ、タグを併記しません。ダイジェストで固定すれば、imagePullPolicyをAlwaysにしておく理由はありません。
イメージビルドのハイジーン
マルチステージは、FROMを2回以上書き、前のステージにASで名前を付けたあと、COPY --from=で成果物だけを取り込む方式です。ベースも、タグではなくダイジェストで固定します。ADDは、リモートのリソースを取得して圧縮も自動で展開するため、何が入ってくるかを予測しにくくなります。USERに名前を書くと、その名前がイメージの中に存在する必要があるため、数値のUIDが安全です。
信頼するレジストリの強制
ValidatingAdmissionPolicyはCEL式で判定し、実際に止めるには、バインディングのvalidationActionsにDenyが必要です。Warnだけだと、警告が出るだけでPodは作成されます。適用範囲は、バインディングのmatchResourcesで絞ります。ネームスペースを1つだけ選ぶには、kubernetes.io/metadata.nameラベルを使えば構いません。このラベルは、すべてのネームスペースに自動で付きます。
監査ポリシーの作成
監査ポリシーは、上から走査して、最初に一致したルールで止まります。そのため、包括ルールを前に置くと、後ろの細かいルールは永遠に使われません。サブリソースは、pods/execのようにスラッシュで書きます。omitStagesは、最上位に置けばすべてのルールに適用されます。
監査ログの配線
フラグだけを書いても、apiserverはポリシーファイルを見つけられません。静的Podは、ノードのファイルシステムをhostPathボリュームとしてマウントしないと、そのパスを見られません。ポリシーファイルは読むだけでよく、ログのディレクトリは書き込む必要があるため、2つのマウントのreadOnlyが異なります。hostPathにtypeを書くと、ファイルとディレクトリを区別してくれます。
ランタイムの不変性
読み取り専用のルートにすると、ほとんどのアプリケーションが一時ファイルを書けずに落ちます。そのため、書き込みが必要なパスだけを選んでボリュームとして開けます。ノードのファイルシステムをそのままマウントするのは、隔離を壊すため、emptyDirを使います。ホストネームスペースを共有すると、コンテナの境界そのものがなくなります。