監査人が来たのに「有効になっている」証拠がない
目標
複数のセキュリティ統制項目(匿名アクセスの遮断、Pod Securityの強制、ServiceAccountトークンの自動マウントの解除、リソースクォータ)を
クラスターで直接確認し、その結果を、統制IDごとのpass/failレポートと、ギャップの一覧として残します。
なぜ重要なのか
CIS Kubernetes Benchmark、NSA/CISAの強化ガイド、SOC 2・ISO 27001のようなフレームワークは、結局のところ
「この統制は有効になっているか」を問う質問の一覧です。監査で問題になるのは、統制がない場合よりも、
有効になっていると書いたのに、実際には無効になっている場合です。そのためレポートの1行1行は、設定ファイルや
記憶ではなく、いまクラスターが返す答え(auth can-i、オブジェクトのフィールド、ラベル)から出てくるものでなければなりません。
また、強化した場所だけを見て終わりにしてはいけません。誰も手を付けていないdefaultネームスペースのように、統制の外に残された
場所を探してギャップとして記録することが、コンプライアンス点検の半分です。
ステップ
- 点検対象のネームスペース
kcsa-compを作成します。 system:anonymousがシークレットをlistできるかを尋ね、anon.txtに記録します。kcsa-compにpod-security.kubernetes.io/enforce=restrictedラベルを付けます。- restrictedの要件をすべて備えたPod
hardenedを作成します。 defaultServiceAccountのトークンの自動マウントを無効にします。- ResourceQuota
quotaで、ネームスペースのリソースの上限を設定します。 - 4つの統制項目の実際の結果を、
compliance.csvレポートとして書きます。 - enforceラベルのないネームスペースを探し、
gaps.txtにギャップとして記録します。
参考
- 成果物はすべて
/root/kcsa-comp/の下に置きます。採点ツールは、ファイルの値をクラスターの実際の状態ともう一度照合します。 kubectl auth can-i ... --as=<사용자>は、その主体がSelfSubjectAccessReviewを作成できる場合にだけ答えます(プレースホルダーはユーザーです)。匿名ユーザーのようにそれすらできない主体については、管理者がSubjectAccessReviewで代わりに尋ねます。kubectl get ns -L <라벨키>で、ネームスペースごとのラベルを1つの表で見られます(プレースホルダーはラベルのキーです)。- 公式ドキュメント: Pod Security Standards・ 監査(Auditing)・ 認可とSubjectAccessReview。
監査対象のネームスペースを立てる
コンプライアンス点検の対象になるネームスペースkcsa-compを作成してください。
監査は、範囲を決めるところから始まります。ネームスペースは、createを--dry-run=clientで生成してapplyすれば、何度実行しても安全です。
ログインしていない誰かがシークレットを見られるか
認証されていないユーザー(system:anonymous)が、ネームスペースkcsa-compでシークレットをlistできるかをクラスターに直接尋ね、その答えを/root/kcsa-comp/anon.txtにanonymous-list-secrets=<yes|no>の1行で書いてください。
まずkubectl auth can-iに--as=system:anonymousを付けてみてください。答えの代わりに拒否が返ります。can-iはそのユーザー自身としてSelfSubjectAccessReviewを作成しますが、匿名ユーザーはそれすらできないからです。そこで、管理者が代わりに尋ねるSubjectAccessReview(authorization.k8s.io/v1)を作成して、spec.userとgroups(system:unauthenticated)、resourceAttributesを埋め、status.allowedを読みます。falseならnoです。
ネームスペースにrestrictedのゲートキーパーを置く
ネームスペースkcsa-compに、Pod Security Admissionのラベルpod-security.kubernetes.io/enforce=restrictedを付けてください(必要ならwarn・auditのラベルも一緒に)。
Pod Security Standardsは、ネームスペースのラベル1つで有効になります。ラベルのキーはpod-security.kubernetes.io/<모드>で、値はprivileged・baseline・restrictedのどれかです(プレースホルダーはモードです)。kubectl labelに--overwriteを付ければ、もう一度実行しても安全です。
restrictedの基準を通過するPodを受け入れる
ネームスペースkcsa-compにPodhardened(イメージはnginx:1.27-alpine)を作成してください。PodレベルにrunAsNonRoot: trueとseccompProfile.type: RuntimeDefault、コンテナレベルにallowPrivilegeEscalation: false、capabilities.drop: [ALL]、runAsNonRoot: trueを宣言して、restrictedプロファイルの要件をすべて備えます。
restrictedプロファイルは、rootで動かないこと、権限昇格を防ぐこと、すべてのcapabilityを捨てること、seccompプロファイルを指定することを要求します。securityContextにはPodレベル(spec.securityContext)とコンテナレベル(containers[].securityContext)の2か所があり、フィールドごとに置ける場所が違います。要件が1つでも欠けると、ステップ3のラベルにより、作成が拒否されることがあります。
使いもしないトークンがすべてのPodに差し込まれる
ネームスペースkcsa-compのdefaultServiceAccountにautomountServiceAccountToken: falseを設定して、別途要求していないPodにはAPIトークンが自動でマウントされないようにしてください。
ネームスペースが作成されると、コントローラーがdefaultServiceAccountを作成してくれます。まだなければ、少し待ってから確認してください。このフィールドはServiceAccountオブジェクトのトップレベルのフィールドなので、patchで変更できます。
1つのチームがクラスターを食い尽くさないように
ネームスペースkcsa-compにResourceQuotaquotaを作成して、pods=10、requests.cpu=2、requests.memory=2Giを上限として設定してください。
ResourceQuotaは、ネームスペース全体の合計を制限するオブジェクトで、spec.hardの下にリソース名と上限を書きます。kubectl create quotaでは、--hardで複数の項目をカンマでつないで指定できます。
監査担当者に提出する統制項目のレポート
/root/kcsa-comp/compliance.csvを作成してください。1行目はヘッダーcontrol,statusで、その下に4つの統制項目anonymous-access、psa-enforce、sa-token-automount、resource-quotaを1行ずつ書き、statusには実際に確認した結果(passまたはfail)を書きます。4つの項目すべてが、通過の状態である必要があります。
レポートの値は、宣言ではなく証拠です。採点ツールは行ごとに該当する統制をクラスターでもう一度確認し、書かれたstatusと実際の状態が違えば失敗にします。ステップ2・3・5・6で見た結果を、それぞれどの統制項目に対応させるかを考えて、書く前にもう一度確認してください。
デフォルトのネームスペースは誰が守っているのか
PSAのenforceラベルがないネームスペースは、コンプライアンス上のギャップ(gap)です。クラスターのdefaultネームスペースにenforceラベルがあるかを確認し、ギャップのあるネームスペースの名前を/root/kcsa-comp/gaps.txtにpsa-missing-namespace=<네임스페이스>の1行で書いてください(プレースホルダーはネームスペース名です)。(ラベルを付けて直さないでください。このステップは、発見を記録するステップです。)
すべてのネームスペースのラベルを一度に見るには、kubectl get nsにラベルの列を追加するオプションを使ってください。いま強化したネームスペースと、誰も手を付けていないネームスペースを並べて比較すれば、ギャップが見えます。