TT Lab
はじめる
学ぶ 学習パス コース

CKS — Kubernetesセキュリティスペシャリスト

CKS模擬試験A

TT Labで続きを見る

目標

実際のCKSと同じ条件で、課題17個を120分以内に解きます。合格ラインは67%で、部分点制なので、17個のうち12個に合格すれば完了として扱われます。

模擬試験です。ヒントと正解を見ずに、まず最後まで解いてみてください。 行き詰まった課題は印を付けて飛ばし、残った時間に戻ってくるほうがよいです。 採点はいつ押してもよく、何度押しても結果は変わりません。

なぜ重要なのか

CKSは、CKAへの合格が前提条件になっている唯一の試験です。Kubernetesを扱う手はすでに身に付いているものとされているため、問題は「何かを作りなさい」よりも、「この設定の何が危険かを見つけて直しなさい」に近いものです。防御と診断が試験の対象で、攻撃の手法は扱いません。

実務でも同じです。クラスターが破られる場所は、たいてい新しく作った機能ではなく、デフォルト値をそのままにした場所です。ネームスペースにPod Security Admissionを設定しておらず、デフォルトのServiceAccountトークンがすべてのPodにマウントされ、イメージが動くタグでデプロイされています。この試験が問うのは、まさにそのリストです。

試験環境(実際の試験で確認された事実)

この模擬試験環境での違い

このラボのクラスターは、Podの中で動く1人用のクラスターです。kube-apiserverは本物なので、マニフェスト、RBAC、Pod Security Admission、アドミッションポリシーは、実際に動作し、実際に拒否します。ただし、コンテナを実際に動かすランタイムがないため、次の2つは確認できません。

残りはすべて、稼働中のクラスターで再計算して採点します。権限はkubectl auth can-iで、アドミッションポリシーはサーバーのdry-runで、その時点の判定を改めて問い合わせます。

ステップ

Cluster Setup

  1. ネームスペースprodを作成し、その中のすべてのPodに対して、ingressとegressの両方をデフォルトで拒否するNetworkPolicydefault-denyを作成してください。許可ルールは1つも置きません。
  2. ネームスペースprodで、app=webラベルが付いたPodが、ノードのメタデータエンドポイント169.254.169.254/32へ出られないようにし、それ以外のegressはそのまま開けておくNetworkPolicydeny-metadataを作成してください。
  3. CN=shop.internalの自己署名証明書で、ネームスペースprodにTLS Secretweb-tlsを作成し、ホストshop.internalをそのSecretでTLS終端するIngresswebを作成してください。バックエンドはServicewebのポート80です。

Cluster Hardening

  1. ネームスペースprodにServiceAccountreport-runnerを作成し、このアカウントがprodの中でPodをget、list、watchだけできるように、Rolepod-readerとRoleBindingreport-runner-pod-readerを作成してください。それ以外の権限は与えません。
  2. ネームスペースprodのdefaultServiceAccountが、トークンを自動マウントしないようにしてください。そしてprodにPodfrontendを作成し、ServiceAccountreport-runnerを使い、Podスペックでもトークンの自動マウントをオフにしてください。
  3. セキュリティ監査担当のグループsecurity-auditが、クラスター全体でPod、ネームスペース、NetworkPolicyを読み取りだけできるように、ClusterRolesecurity-auditorとClusterRoleBindingsecurity-auditorを作成してください。書き込み権限とSecretへのアクセスは与えません。

System Hardening

  1. /root/exam/seccomp/audit.jsonにseccompプロファイルを書いてください。デフォルトの動作はSCMP_ACT_ERRNOで、最低でもいくつかのシステムコールはSCMP_ACT_ALLOWで開ける必要があります。そして、ネームスペースprodにPodprobeを作成し、このプロファイルをLocalhost方式で、profiles/audit.jsonのパスから使うようにしてください。
  2. /root/exam/apparmor/k8s-deny-writeにAppArmorプロファイルを書いてください。プロファイル名はk8s-deny-writeで、#include <tunables/global>の行と、すべての書き込みを拒否するdeny /** w,ルールが必要です。そして、ネームスペースprodにDeploymentlogshipperを作成し、PodテンプレートのsecurityContext.appArmorProfileがこのプロファイルをLocalhostで使うようにしてください。

Minimize Microservice Vulnerabilities

  1. ネームスペースpaymentsを作成し、Pod Security Admissionのenforce、audit、warnの3つのモードをすべてrestrictedに設定して、3つのモードのバージョンをlatestに固定してください。
  2. ネームスペースpaymentsに、restrictedに違反するPodbad-podをapplyしてみて、拒否された応答を標準エラー出力も含めて/root/exam/denied.txtに保存してください。Podが実際に作成されてはいけません。
  3. RuntimeClassgvisor(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

  1. ネームスペースsupplyを作成し、Deploymentpayments-apiを作成してください。コンテナ名はapiで、イメージはタグではなくダイジェストで固定する必要があります。値はregistry.internal/payments-api@sha256:140eab0459241fb1643767dd4cc3576d282b0059a6328521e714cb545fa4adeaで、imagePullPolicyはIfNotPresentです。
  2. /root/exam/Dockerfileに、デプロイ用のイメージをビルドするDockerfileを書いてください。ビルドステージと実行ステージを分けたマルチステージで、すべてのFROMのベースを@sha256:ダイジェストで固定し、アーティファクトはCOPY --from=でのみ取り込む必要があります。ADDは使わず、最後のUSERは0ではない数値のUIDにし、ENVやARGにシークレットに見える名前を残さないでください。
  3. 承認されていないレジストリのイメージを止めてください。ValidatingAdmissionPolicytrusted-imagesとValidatingAdmissionPolicyBindingtrusted-imagesを作成し、すべてのコンテナイメージがregistry.internal/で始まることを要求するようにしてください。バインディングのvalidationActionsはDenyで、適用範囲はネームスペースsupplyだけです。ほかのネームスペースは影響を受けてはいけません。

Monitoring, Logging and Runtime Security

  1. /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ルールで、残りのすべてを受けます。
  2. /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にし、ログのマウントは読み取り専用にしてはいけません。
  3. ネームスペースprodにDeploymentledgerを作成してください。コンテナ名はappで、readOnlyRootFilesystem: true、allowPrivilegeEscalation: false、capabilities.drop: [ALL]を置きます。書き込みが必要な/tmpはemptyDirボリュームscratchで提供し、hostPathボリュームとhostNetwork、hostPID、hostIPCは使わないでください。

参考

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を使います。ホストネームスペースを共有すると、コンテナの境界そのものがなくなります。