k8s の audit 設定をちゃんと理解する

# タイトル
k8s の audit 設定をちゃんと理解する

# 目的
- api-server に設定する audit 機能を理解する

# 手段
- マニュアルベースで手を動かす

### ざっくり理解する
![k8s の audit 設定をちゃんと理解する.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474478/6ec3dd1c-e697-4ce8-964a-f6f203ba723d.png)

- 必要な設定内容
  - kube-apiserver に audit 関連のオプション設定追加
  - kube-apiserver に audti 設定ファイルとロギング用をマウント
  - audit 用の設定ファイル作成(yaml)

### 環境
- killercode 環境を活用

### audit 機能とは？
audit 機能は k8s クラスター内の一連アクション監査ログを提供します。(いわゆる監査ログ)
kube-apiserver 内で処理されるリクエストを各ステージごとにロギングします。各ステージは以下の通りです。
  - RequestReceived : audit handler はリクエストを受けたら可能な限り早くイベントを生成するステージ
  - ResponseStarted : response header が送信され、response body が送信される前のステージ
  - ResponseComplete : response body 送信が完了したステージ
  - Panic : panic 発生時のステージ(エラーの様なイメージ)

また、どのような監査ログ(ログレベル)を記録するか設定ファイルで定義します。
  - None : イベントを記録しません
  - Metadata : リクエストのメタデータ(リクエストしたユーザ、タイムスタンプ、リソース、動作など)を記録します。リクエストやレスポンスのボディは記録しません
  - Request : イベントのメタデータとリクエストボディを記録します。レスポンスボディは記録しません
  - RequestResponse : イベントのメタデータ・リクエストとレスポンスのボディを記録します


### audit 機能の設定方法
autdit 機能は kube-apiserver.yaml を用いて有効化します。必要設定は以下の通りです。
  1. --audit-policy-file : audit 設定ファイル
  2. --audit-log-path : audit ログ保存パス
  3. --audit-log-maxage : audit ログの最大保持日数を指定
  4. --audit-log-maxbackup : audit ログの最大保持ファイル数を指定
  5. --audit-log-maxsize : audit ログがローテーションされるまでの最大ファイルサイズを指定(1ファイルの最大サイズ)

```bash
controlplane $ diff kube-apiserver.yaml /root/kube-apiserver.yaml 
43,46d42
<     - --audit-log-maxsize=7
<     - --audit-log-maxbackup=2
<     - --audit-log-path=/etc/kubernetes/audit-logs/audit.log
<     - --audit-policy-file=/etc/kubernetes/audit-policy/policy.yaml
# kube-apiserver.yaml にフラグを追加します

98,103d93
<     - mountPath: /etc/kubernetes/audit-policy/policy.yaml
<       name: audit
<       readOnly: true
# kube-apiserver に監査ログ用の設定ファイルを readonly でマウントします
<     - mountPath: /etc/kubernetes/audit-logs
<       name: audit-log
<       readOnly: false
# kube-apiserver に監査ログ保存ディレクトリをマウントします

131,138d120
<   - name: audit
<     hostPath:
<       path: /etc/kubernetes/audit-policy/policy.yaml
<       type: File
<   - name: audit-log
<     hostPath:
<       path: /etc/kubernetes/audit-logs/
<       type: DirectoryOrCreate
# 監査ログ設定ファイルとログディレクトリのボリュームを作成します
```

### 動作確認方法
動作確認方法は以下の通りです。

```bash
controlplane $ stat /etc/kubernetes/audit-logs/audit.log 
  File: /etc/kubernetes/audit-logs/audit.log
  Size: 984661          Blocks: 1928       IO Block: 4096   regular file
Device: fc01h/64513d    Inode: 1327002     Links: 1
Access: (0600/-rw-------)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2025-02-10 10:21:18.846192987 +0000
Modify: 2025-02-10 10:23:35.235664013 +0000
Change: 2025-02-10 10:23:35.235664013 +0000
 Birth: -
# ログファイルの書き込み有無を確認
```

```bash
controlplane $ cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep audit-policy-file=/etc/kubernetes/audit-policy/policy.yaml
    - --audit-policy-file=/etc/kubernetes/audit-policy/policy.yaml
controlplane $ 
controlplane $ cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep audit-log-path=/etc/kubernetes/audit-logs/audit.log
    - --audit-log-path=/etc/kubernetes/audit-logs/audit.log
controlplane $ 
controlplane $ cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep audit-log-maxsize=7
    - --audit-log-maxsize=7
controlplane $ 
controlplane $ cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep audit-log-maxbackup=2
    - --audit-log-maxbackup=2
# kube-apiserver.yaml 内の設定値を確認
```

### audit 用の yaml ファイルを作成する
まずは、Pods 関連のみロギングする。

```bash
controlplane $ cat policy.yaml
# /etc/kubernetes/audit/policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:

# log Secret resources audits, level Metadata
- level: Metadata
  resources:
  - group: ""
    resources: ["pods"]
```

```bash
controlplane $ tail -f audit.log |grep pods
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"7f926292-edc9-48d0-8975-595ffb966675","stage":"RequestReceived","requestURI":"/api/v1/pods?fieldSelector=spec.nodeName%3Dcontrolplane\u0026resourceVersion=2545","verb":"list","user":{"username":"system:node:controlplane","groups":["system:nodes","system:authenticated"]},"sourceIPs":["172.30.1.2"],"userAgent":"kubelet/v1.31.0 (linux/amd64) kubernetes/9edcffc","objectRef":{"resource":"pods","apiVersion":"v1"},"requestReceivedTimestamp":"2025-02-13T11:20:35.906449Z","stageTimestamp":"2025-02-13T11:20:35.906449Z"}
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"7f926292-edc9-48d0-8975-595ffb966675","stage":"ResponseComplete","requestURI":"/api/v1/pods?fieldSelector=spec.nodeName%3Dcontrolplane\u0026resourceVersion=2545","verb":"list","user":{"username":"system:node:controlplane","groups":["system:nodes","system:authenticated"]},"sourceIPs":["172.30.1.2"],"userAgent":"kubelet/v1.31.0 (linux/amd64) kubernetes/9edcffc","objectRef":{"resource":"pods","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":200},"requestReceivedTimestamp":"2025-02-13T11:20:35.906449Z","stageTimestamp":"2025-02-13T11:20:35.908974Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":""}}
# Pod関連ログのみロギングされている。
```

次に、ログレベルを `Metadata` → `RequestResponse` に変更する。kube-apiserverをリスタート(Pod再作成)させるために kube-apiserver.yaml を編集して、kube-apiserverをリスタートさせるのが手間ですね。コマンドで実行したいです。

```bash
controlplane $ cat policy.yaml
# /etc/kubernetes/audit/policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:

# log Secret resources audits, level Metadata
- level: RequestResponse
  resources:
  - group: ""
    resources: ["pods"]
# level を変更済み
```

```bash
controlplane $ tail -f audit.log |grep RequestResponse
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"RequestResponse","auditID":"af23f5d2-c145-413d-a48a-09bb54425274","stage":"ResponseStarted","requestURI":"/api/v1/pods?allowWatchBookmarks=true\u0026resourceVersion=2962\u0026timeout=7m12s\u0026timeoutSeconds=432\u0026watch=true","verb":"watch","user":{"username":"system:kube-controller-manager","groups":["system:authenticated"]},"sourceIPs":["172.30.1.2"],"userAgent":"kube-controller-manager/v1.31.0 (linux/amd64) kubernetes/9edcffc/shared-informers","objectRef":{"resource":"pods","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":200},"requestReceivedTimestamp":"2025-02-13T11:25:28.542419Z","stageTimestamp":"2025-02-13T11:25:28.542797Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":"RBAC: allowed by ClusterRoleBinding \"system:kube-controller-manager\" of ClusterRole \"system:kube-controller-manager\" to User \"system:kube-controller-manager\""}}
# "RequestResponse" レベルのログがロギングされている
```

続いて、namespace でロギング対象を制限してみます。

```bash
controlplane $ cat policy.yaml
# /etc/kubernetes/audit/policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:

# log Secret resources audits, level Metadata
- level: RequestResponse
  resources:
  - group: ""
    resources: ["pods"]
  namespaces: ["test"]
```

```bash
controlplane $ tail -f audit.log |grep test
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"RequestResponse","auditID":"b13d607d-60f5-4640-9ada-8b222029b59f","stage":"RequestReceived","requestURI":"/api/v1/namespaces/test/pods?fieldManager=kubectl-run","verb":"create","user":{"username":"kubernetes-admin","groups":["kubeadm:cluster-admins","system:authenticated"]},"sourceIPs":["172.30.1.2"],"userAgent":"kubectl/v1.31.0 (linux/amd64) kubernetes/9edcffc","objectRef":{"resource":"pods","namespace":"test","apiVersion":"v1"},"requestReceivedTimestamp":"2025-02-13T11:42:39.035337Z","stageTimestamp":"2025-02-13T11:42:39.035337Z"}
# test namespace のみロギングされる。
```

続いて、omitSatges 設定とロギング設定をバッティングさせてみます。想定では、ロギングされない動作になると思います。

```bash
controlplane $ cat ../audit-policy/policy.yaml
# /etc/kubernetes/audit/policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:

omitStages:
  - "RequestResponse"
# log Secret resources audits, level Metadata
- level: RequestResponse
  resources:
  - group: ""
    resources: ["pods"]
  namespaces: ["test"]
# 結果的に、kube-apiserver は起動しませんでした。policy.yaml ファイルに不備があるためでしょう。
```

namespaces を "" 指定した場合の動作を確認します。"" はnamespace に所属しないオブジェクトを対象にするようです。

```bash
controlplane $ cat ../audit-policy/policy.yaml
# /etc/kubernetes/audit/policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:

# log Secret resources audits, level Metadata
- level: RequestResponse
  resources:
  - group: ""
    resources: ["pods"]
  namespaces: [""]
```

```bash
controlplane $ k run -n test nginx2 --image=nginx
pod/nginx2 created
# test に nginx2 を作成

controlplane $ tail -f ../audit-logs/audit.log |grep nginx2
# nginx2 pod 関連葉ロギングされません
```

最後のサンプル設定を読み解きます。

```bash
apiVersion: audit.k8s.io/v1 # This is required.
kind: Policy
# Don't generate audit events for all requests in RequestReceived stage.
omitStages:
  - "RequestReceived"
# audit ポリシーは設定を上から適用するため、最初にomitStages で設定すれば、当該ステージのログはロギングされない
rules:
  # Log pod changes at RequestResponse level
  - level: RequestResponse
    resources:
    - group: ""
      # Resource "pods" doesn't match requests to any subresource of pods,
      # which is consistent with the RBAC policy.
      resources: ["pods"]
  # Log "pods/log", "pods/status" at Metadata level
  - level: Metadata
    resources:
    - group: ""
      resources: ["pods/log", "pods/status"]
      # ロギング対象を pod 内のログやステータスに限定している
  # Don't log requests to a configmap called "controller-leader"
  - level: None
    resources:
    - group: ""
      resources: ["configmaps"]
      resourceNames: ["controller-leader"]
      # ロギング対象をリソース名で限定しています
  # Don't log watch requests by the "system:kube-proxy" on endpoints or services
  - level: None
    users: ["system:kube-proxy"]
    verbs: ["watch"]
    # ロギング対象を操作内容で限定しています
    resources:
    - group: "" # core API group
      resources: ["endpoints", "services"]
      # 複数オブジェクト存在する場合はカンマで区切って指定します
  # Don't log authenticated requests to certain non-resource URL paths.
  - level: None
    userGroups: ["system:authenticated"]
    nonResourceURLs:
    - "/api*" # Wildcard matching.
    - "/version"

  # Log the request body of configmap changes in kube-system.
  - level: Request
    resources:
    - group: "" # core API group
      resources: ["configmaps"]
    # This rule only applies to resources in the "kube-system" namespace.
    # The empty string "" can be used to select non-namespaced resources.
    namespaces: ["kube-system"]
    # namespace でロギング対象を限定しています
  # Log configmap and secret changes in all other namespaces at the Metadata level.
  - level: Metadata
    resources:
    - group: "" # core API group
      resources: ["secrets", "configmaps"]

  # Log all other resources in core and extensions at the Request level.
  - level: Request
    resources:
    - group: "" # core API group
    - group: "extensions" # Version of group should NOT be included.

  # A catch-all rule to log all other requests at the Metadata level.
  - level: Metadata
    # Long-running requests like watches that fall under this rule will not
    # generate an audit event in RequestReceived.
    omitStages:
      - "RequestReceived"
  # 最後のルールはその他ルールに該当しない全てのリクエストを対象とします。
  # 基本、None にしたり、上記のように Metadata のみロギングするなど設定します。
```

audit log 出力時の deployment は group に apps を指定するべきか確認する。

```bash
controlplane $ cat ../audit-policy/policy.yaml
# /etc/kubernetes/audit/policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:

# log Secret resources audits, level Metadata
- level: RequestResponse
  resources:
  - group: "apps"
    resources: ["deployments"]
  namespaces: ["test"]
```

```bash
controlplane $ tail -f ../audit-logs/audit.log |grep dep
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"RequestResponse","auditID":"8bdfc262-1d6d-42b6-97c9-8d77defb9045","stage":"RequestReceived","requestURI":"/apis/apps/v1/namespaces/test/deployments/nginx-deploy","verb":"get","user":{"username":"system:serviceaccount:kube-system:generic-garbage-collector","uid":"6fd135c6-2b99-483d-a01e-c5272aa913f2","groups":["system:serviceaccounts","system:serviceaccounts:kube-system","system:authenticated"],"extra":{"authentication.kubernetes.io/credential-id":["JTI=5da5661c-6262-4c8b-9554-22bcdad8008d"]}},"sourceIPs":["172.30.1.2"],"userAgent":"kube-controller-manager/v1.31.0 (linux/amd64) kubernetes/9edcffc/system:serviceaccount:kube-system:generic-garbage-collector","objectRef":{"resource":"deployments","namespace":"test","name":"nginx-deploy","apiGroup":"apps","apiVersion":"v1"},"requestReceivedTimestamp":"2025-02-13T12:09:19.131306Z","stageTimestamp":"2025-02-13T12:09:19.131306Z"}
```

group に "apps" を指定すべきかどうかは `kubectl resources` コマンドの APIVERSION 列を確認すれば良いです。

```bash
controlplane $ kubectl api-resources |grep -e dep -e NAME 
NAME                                SHORTNAMES   APIVERSION                        NAMESPACED   KIND
deployments                         deploy       apps/v1                           true         Deployment
# deplyments リソースは `apps/v1` ですので、group に apps を指定する必要があります
```

```bash
controlplane $ kubectl api-resources |grep -e pod -e NAME 
NAME                                SHORTNAMES   APIVERSION                        NAMESPACED   KIND
pods                                po           v1                                true         Pod
# pods リソースｈ `v1` ですので、group は空で問題ありません
```

# あとがき
ログレベルの書き方に注意が必要ですね。例えば、namespace 単位での設定方法など、設定単位を整理する必要がありそうです。

# ソース
- [Auditing](https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/)