攻撃者の目でクラスタを開ける
このラボは本物のクラスター上で動きます
KCSAは、脅威モデルと攻撃対象領域です。それを学ぶには、実際に盗んでみる必要があります。 ストレージから平文を取り出し、権限がないのにシークレットを読み出し、 監査ログにその痕跡が残るのを見ます。
偽のクラスターにはストレージもapiserverの認証チェーンも監査ログもないため、 これらはすべて文章としてしか残っていませんでした。base64は暗号化ではないと言葉で聞くのと、 ストレージから平文を取り出して見るのとは違います。
CKSとは重ならないようにします。CKSは防ぐラボで、ここのKCSAはなぜ防がなければならないのかを 攻撃で見せます。
最初に起動するまでに2分ほどかかります。
ステップ
- シークレットを作成し、ストレージから平文で取り出してください(保存先:
/root/kcsa/plaintext.txt)。 - 保存データの暗号化を有効にし、平文が消えることを記録してください(保存先:
/root/kcsa/encrypt.txt)。 - 古いシークレットは書き直して初めて暗号化されることを記録してください(保存先:
/root/kcsa/rewrite.txt)。 appにPSAのenforce=restricted、enforce-version=v1.36を適用してください。runnerSAには、appのpodsのget・list・createだけを付与し、secretsのgetは許可しないでください。/root/kcsa/leak.yamlに、ネームスペースappのleakPodを書いてください。restartPolicy=Never、runnerのSA、自動トークンマウントの無効化、runAsNonRootとUID 65532、RuntimeDefaultのseccomp、allowPrivilegeEscalation=false、capabilitiesのdrop ALLを使います。コンテナは1つで、busybox:1.36でcommand: [sha256sum, /s/password]を実行し、dbのSecretボリュームsを/sに読み取り専用でマウントします。管理者による作成で代用せず、--as=system:serviceaccount:app:runnerで作成してください。Succeededを確認したあと、/root/kcsa/escalate.txtに、direct_secret=no、create_pod=yes、psa=restricted、secret_mount=readableと、実際のログのハッシュを1行、PSAが防ぐ実行設定と防がないデータアクセスの境界を説明してください。合成シークレットの原文は出力しません。- 匿名・デフォルトのSAでapiserverを叩いて、認証の境界を記録してください(保存先:
/root/kcsa/anon.txt)。 - 監査ポリシーを書いてください(保存先:
/etc/kubernetes/audit.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で記録します。/etc/rancher/k3s/config.yaml.d/90-labhub-audit.yamlでkube-apiserver-arg+により監査の引数を追加して、既存の暗号化の設定を保持し、k3sを再起動してください。ログは/var/log/kubernetes/audit.log、blockingモード、maxsize=5・maxbackup=1・maxage=1に制限します。準備が完了したら、提供されているヘルパーpython3 /opt/fixtures/audit-evidence.py capture --policy /etc/kubernetes/audit.yaml --log /var/log/kubernetes/audit.log --case /root/kcsa/audit-case.json --events /root/kcsa/audit-evidence.jsonlで、合成Secretの作成201・取得200・拒否403・削除200を収集してください。本文のないMetadataイベント4つとポリシーのフィンガープリントを保存し、/root/kcsa/audit.txtに結果を説明してください。ヘルパーはポリシーを変更せず、実際のシークレットを入れません。 - ここまでを4CとSTRIDEで整理してください(保存先:
/root/kcsa/model.txt)。 plaintext_found=yes、encrypted=yes、audit_lines=の3行と説明を書いてください(保存先:/root/kcsa/report.md)。
参考
- ストレージは
/var/lib/rancher/k3s/server/db/state.dbです。sqlite3で直接開きます。 - 値はbase64ではなくrawで入っています。
hex(value)で取り出して、平文を探してください。 - apiserverを再び起動したら、
kubectl get --raw=/readyzがokになるまで待ってください。 - よくある誤解: 暗号化を有効にすれば、古いシークレットも暗号化されるというもの。書き直して初めて変わります。
- よくある誤解:
can-i get secretsがnoなら安全だというもの。Secretボリュームを許可するワークロードの作成権限で、間接的にアクセスできます。RestrictedもSecretボリュームは許可します。
70分のラボなので、期限切れの前に+時間で延長してください(最大180分)。セッションが終了すると、作業物は消えます。
base64は暗号化ではない
シークレットを作成し、ストレージから平文で取り出してください(保存先: /root/kcsa/plaintext.txt)。
sqlite3でストレージを開き、hex(value)から平文を探してください。
保存データの暗号化を有効にする
保存データの暗号化を有効にし、平文が消えることを記録してください(保存先: /root/kcsa/encrypt.txt)。
EncryptionConfigurationを書いて、apiserverに--encryption-provider-configで渡します。
古いシークレットは書き直して初めて変わる
古いシークレットは書き直して初めて暗号化されることを記録してください(保存先: /root/kcsa/rewrite.txt)。
kubectl get secret -o json | kubectl replace -f -で書き直します。
RestrictedなPodもSecretボリュームを読める
appにPSAのenforce=restricted、enforce-version=v1.36を適用してください。
runnerSAには、appのpodsのget・list・createだけを付与し、secretsのgetは許可しないでください。
/root/kcsa/leak.yamlに、ネームスペースappのleakPodを書いてください。restartPolicy=Never、
runnerのSA、自動トークンマウントの無効化、
runAsNonRootとUID 65532、RuntimeDefaultのseccomp、allowPrivilegeEscalation=false、
capabilitiesのdrop ALLを使います。コンテナは1つで、busybox:1.36で
command: [sha256sum, /s/password]を実行し、dbのSecretボリュームsを/sに読み取り専用で
マウントします。管理者による作成で代用せず、--as=system:serviceaccount:app:runnerで
作成してください。Succeededを確認したあと、/root/kcsa/escalate.txtに、direct_secret=no、
create_pod=yes、psa=restricted、secret_mount=readableと、実際のログのハッシュを1行、
PSAが防ぐ実行設定と防がないデータアクセスの境界を説明してください。合成シークレットの原文は出力しません。
リクエストの主体と、PodのSAは別です。runnerで作成し、PSAの実行条件とSecretの参照を区別してください。
認証の境界を叩く
匿名・デフォルトのSAでapiserverを叩いて、認証の境界を記録してください(保存先: /root/kcsa/anon.txt)。
匿名のリクエストとデフォルトのSAトークンで、apiserverを直接呼び出してみてください。
残さなければ、なかったことになる
監査ポリシーを書いてください(保存先: /etc/kubernetes/audit.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で記録します。/etc/rancher/k3s/config.yaml.d/90-labhub-audit.yamlでkube-apiserver-arg+により監査の引数を追加して、既存の暗号化の設定を保持し、k3sを再起動してください。ログは/var/log/kubernetes/audit.log、blockingモード、maxsize=5・maxbackup=1・maxage=1に制限します。準備が完了したら、提供されているヘルパーpython3 /opt/fixtures/audit-evidence.py capture --policy /etc/kubernetes/audit.yaml --log /var/log/kubernetes/audit.log --case /root/kcsa/audit-case.json --events /root/kcsa/audit-evidence.jsonlで、合成Secretの作成201・取得200・拒否403・削除200を収集してください。本文のないMetadataイベント4つとポリシーのフィンガープリントを保存し、/root/kcsa/audit.txtに結果を説明してください。ヘルパーはポリシーを変更せず、実際のシークレットを入れません。
保護するルールが先に一致する必要があります。omitStagesは、本文の除去ではありません。ポリシーの作成と、実際の完了イベントの収集を区別してください。
4CとSTRIDEで整理する
ここまでを4CとSTRIDEで整理してください(保存先: /root/kcsa/model.txt)。
ここまでに見た攻撃を、4Cの層とSTRIDEのカテゴリーにそれぞれ対応させてください。
何を学んだか
plaintext_found=yes、encrypted=yes、audit_lines=の3行と説明を書いてください(保存先: /root/kcsa/report.md)。
plaintext_found=yes、encrypted=yes、audit_lines=の3行と説明を書いてください。