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

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

残さなかったログは、なかったことになる

TT Labで続きを見る

一言でいうと

監査ポリシーは、「何を記録するか」ではなく「何を捨てるか」を決める文書です。ルールは上から順番に評価され、最初に一致した1つだけが適用されるため、順序を誤ると、黙って何も残らなくなります。

なぜ必要なのか

侵害対応の最初の問いは、いつも同じです。いつ、誰が、何をしたのか。Kubernetesでこの問いに答えられる唯一の資料が、APIサーバーの監査ログです。ところが、すべてを記録すると保存コストが爆発し、いい加減に記録すると、肝心なときに何も残っていません。そのため、ポリシーが必要です。

レベルは4段階です。

レベル 記録の範囲
None 記録しない
Metadata リクエスト元、時刻、動詞、リソースなどのメタデータのみ
Request メタデータ + リクエスト本文
RequestResponse メタデータ + リクエスト本文 + レスポンス本文

ここに、CKSが繰り返し問う落とし穴が1つあります。SecretにRequestResponseを設定すると、監査ログファイルの中にSecretの値がそのまま入ります。監査ログは、たいていアプリケーションよりアクセス権限が広い場所に収集されるため、Secretを守ろうとした措置が、Secretを広めてしまう結果になります。そのため、SecretとConfigMapはMetadataまでしか記録しないのが定石です。

どう動くのか

ポリシーファイルはaudit.k8s.io/v1のPolicyリソースで、rules配列を上から評価して、最初に一致したルール1つを適用します。そのため、順序がそのまま意味になります。たとえば、kube-systemのSecretのgetリクエストを除外したいなら、そのlevel: NoneルールがSecret関連のルールより上になければなりません。下に置くと、上のルールが先に一致してしまい、除外がまったく動作しません。

omitStagesも一緒に使います。1つのリクエストは、RequestReceived、ResponseStarted、ResponseComplete、Panicの4つの段階でイベントを作れますが、RequestReceivedはほとんどいつもノイズです。最上位のomitStagesにこの段階を入れると、ログの量が目に見えて減ります。

ポリシーファイルを作成したら、APIサーバーに接続する必要があります。--audit-policy-fileでポリシーを、--audit-log-pathで出力先を指定し、--audit-log-maxage(保管日数)、--audit-log-maxbackup(ファイル数)、--audit-log-maxsize(MB)でローテーションのポリシーを決めます。この3つを抜かすとディスクが埋まり、ディスクが埋まるとAPIサーバーが止まります。

RBAC側の危険な動詞は、特別扱いします。escalate・bind・impersonateは、権限昇格の試みそのものなので、RequestResponseで残して、何をどのように変更しようとしたかまで記録します。Podの変更はRequest程度がバランス点です。マニフェストを残せば、侵害後に何がデプロイされたかを再構成でき、レスポンス本文まではたいてい必要ありません。

現場での姿

監査ログでは答えられない領域があり、その場所を埋めるのがランタイム検知です。Falcoは、カーネルのシステムコールを観察して、ルールに引っかかる行動を通知します。標準で提供されるルールの中で最も有名なのが、コンテナの中でシェルが起動する瞬間を捉えるもので、条件はevt.type=execveに、コンテナかどうかと、プロセス名がシェルの一覧にあるかどうかを重ねた形です。APIを経由しない行動は、監査ログにまったく現れないため、この2つの層は代替ではなく、異なる問いに答えるツールです。

ノードで実際に何が動いているかは、crictlで確認します。ただし、境界を守る必要があります。crictlはAPIサーバーを迂回するため、診断には強力ですが、状態を変えるために使うと危険です。kubeletは、自分が作成したコンテナとサンドボックスを常に監視しているので、人がcrictl rmで削除すると、kubeletの認識と実際の状態が食い違い、おかしな中間状態にとどまります。ルールを単純に決めると、こうなります。Kubernetesが何をしようとしているかを知りたければkubectl、ランタイムが実際に何をしたかを知りたければcrictl。2つの視点が食い違う場所が、そのまま問題の位置です。

診断がまるごと無意味になる事例もあります。crictlが見当違いのソケットに接続していると、空っぽの一覧が表示され、「コンテナが1つもない」という誤った結論に至ります。crictl infoでいま接続している対象を先に確認し、/etc/crictl.yamlのruntime-endpointがkubeletの--container-runtime-endpointと同じかどうかを突き合わせるのが、最初のステップです。エンドポイントを指定しないと、crictlが既知のソケット候補を順番に試すため、コマンド1つに十数秒かかることもあります。

侵害されたPodを見つけたときの順序も決まっています。すぐ削除するのではなく、証拠の保全が先です。ログと状態を収集し、NetworkPolicyでそのPodの通信を切って隔離し、侵害の経路と範囲を把握したあとで、脆弱性を修正した新しいイメージに置き換えます。先に削除すると、フォレンジックの証拠が消えます。

次のラボですること

audit.k8s.io/v1のポリシーファイルを、ルールを1つずつ積み上げて作ります。最下段の包括ルールから始めて、omitStagesでノイズを減らし、SecretはMetadataまで、kube-systemのSecretのgetはそもそもNoneに、RBACの危険な動詞はRequestResponseに、Podの変更はRequestにするという順序を、自分で作ります。最後に、APIサーバーに付ける監査ログバックエンドのフラグを文書化します。