> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Ensure Audit Policy Covers Key Security Concerns

### More Info:

Ensure that the audit policy created for the cluster covers key security concerns.

### Risk Level

High

### Address

Security

### Compliance Standards

* CIS Kubernetes

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Locate and identify the audit policy file**
           * On every control plane node, inspect the API server manifest to find the `--audit-policy-file` path:
             ```bash theme={null}
             sudo grep -E -- '--audit-policy-file=' /etc/kubernetes/manifests/kube-apiserver.yaml
             ```
           * Note the file path (for example `/etc/kubernetes/audit-policy.yaml`). If the flag is missing or the file does not exist, plan to create/enable an audit policy file at a standard path (e.g. `/etc/kubernetes/audit-policy.yaml`).

        2. **Collect the current audit policy for review**
           * On every control plane node, display the current policy file (replace the path with the one found in step 1):
             ```bash theme={null}
             sudo cat /etc/kubernetes/audit-policy.yaml
             ```
           * Save a copy for change control:
             ```bash theme={null}
             sudo cp /etc/kubernetes/audit-policy.yaml /etc/kubernetes/audit-policy.yaml.backup.$(date +%F_%H%M%S)
             ```

        3. **Review coverage for Secrets, ConfigMaps, and TokenReviews (metadata-only)**
           * In the policy file, manually check for rules that match:
             * `resources: ["secrets", "configmaps"]` and `group: ""` (core API group), and
             * `resources: ["tokenreviews"]` and `group: "authentication.k8s.io"`,\
               with `level: Metadata` (or higher) and **not** logging the full object (`omitStages`/`omitManagedFields` as appropriate to avoid sensitive content).
           * If missing or overly broad (e.g. `level: RequestResponse` for these resources), edit the policy file with a root-capable editor on the control plane node, for example:
             ```bash theme={null}
             sudo vi /etc/kubernetes/audit-policy.yaml
             ```
             and add or adjust rules so that these resources are covered at least at `Metadata` level while avoiding sensitive payload logging.

        4. **Review coverage for Pod/Deployment modifications**
           * In the same policy file, manually check for rules that:
             * Target `verbs` like `create`, `update`, `patch`, `delete`, `deletecollection`, and
             * Apply to `resources` such as `pods` (group `""`) and `deployments` (group `apps`).
           * Ensure these rules have at least `level: Metadata` (or higher, according to your risk tolerance) so that changes to workloads are auditable. If absent, edit `/etc/kubernetes/audit-policy.yaml` to add such rules.

        5. **Review coverage for exec/portforward/proxy usage**
           * In the policy file, check for rules that match:
             * `resources: ["pods/exec", "pods/portforward", "pods/proxy"]` in the core group `""`, and
             * `resources: ["services/proxy"]` in the core group `""`,\
               with `verbs` typically including `create` and `get` as applicable, and `level: Metadata` or higher.
           * If these subresources are not explicitly covered or are only matched by a very generic low-level rule (e.g. `level: None`), update `/etc/kubernetes/audit-policy.yaml` to add specific rules that log at least metadata for these actions.

        6. **Apply and validate the updated audit policy**
           * Saving changes to `/etc/kubernetes/audit-policy.yaml` on a control plane node that runs the API server as a static pod under `/etc/kubernetes/manifests` will cause the kubelet to automatically restart the kube-apiserver container to pick up the updated policy; expect a brief control-plane disruption.
           * After a minute, confirm the API server has restarted and is using the policy file:
             ```bash theme={null}
             sudo crictl ps | grep kube-apiserver
             sudo grep -E -- '--audit-policy-file=' /etc/kubernetes/manifests/kube-apiserver.yaml
             ```
           * Optionally, trigger a covered action (for example, creating a test Pod or reading a Secret) and inspect the audit log file configured via `--audit-log-path` on the control plane node:
             ```bash theme={null}
             sudo grep -E '"resource":"(secrets|pods|deployments|pods/exec|pods/portforward|pods/proxy|services/proxy)"' /var/log/kubernetes/audit.log | head
             ```
             to verify entries are recorded at least at the metadata level for the required resources and operations.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot modify the API server’s audit policy or its manifest at `/etc/kubernetes/manifests/kube-apiserver.yaml`, because those are host-level files and flags on the control plane nodes. To address this finding, make the changes directly on every control plane node as described in the **Manual Steps** section.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Purpose: Summarize Kubernetes API audit policy coverage for key security concerns.
        # Scope:   Run on ANY MACHINE with:
        #            - kubectl access to the cluster
        #            - ssh access to EVERY CONTROL PLANE NODE (for policy files and flags)
        #
        # NOTE: This script DOES NOT fix anything. It only reports current state for review.

        set -euo pipefail

        # -----------------------------
        # Helper: print section header
        # -----------------------------
        sec() {
          printf '\n==== %s ====\n' "$*"
        }

        # ---------------------------------------------------
        # 1. Discover control plane nodes via kubectl labels
        # ---------------------------------------------------
        sec "Discovering control plane nodes"

        CONTROL_PLANE_NODES=$(kubectl get nodes -l node-role.kubernetes.io/control-plane= -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)

        if [ -z "$CONTROL_PLANE_NODES" ]; then
          # Older clusters may use the master label
          CONTROL_PLANE_NODES=$(kubectl get nodes -l node-role.kubernetes.io/master= -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)
        fi

        if [ -z "$CONTROL_PLANE_NODES" ]; then
          echo "No control plane nodes discovered via labels."
          echo "You may need to specify them manually in this script."
          exit 1
        fi

        echo "Control plane nodes:"
        echo "$CONTROL_PLANE_NODES"

        # ----------------------------------------------------
        # 2. For each control plane node, inspect kube-apiserver
        # ----------------------------------------------------
        for NODE in $CONTROL_PLANE_NODES; do
          sec "Node: $NODE"

          # Assumes SSH access via the same name; adjust ssh target format if needed.
          SSH_TARGET="$NODE"

          # 2.1 Confirm kube-apiserver manifest path and audit flags
          echo "-- kube-apiserver manifest and flags --"
          ssh "$SSH_TARGET" 'sudo test -f /etc/kubernetes/manifests/kube-apiserver.yaml && echo "Found /etc/kubernetes/manifests/kube-apiserver.yaml" || echo "MISSING: /etc/kubernetes/manifests/kube-apiserver.yaml"' || true

          ssh "$SSH_TARGET" '
            if [ -f /etc/kubernetes/manifests/kube-apiserver.yaml ]; then
              echo
              echo "kube-apiserver command and audit-related flags:"
              # Print container command and all args
              yq e ".spec.containers[] | select(.name==\"kube-apiserver\") | .command, .args[]" /etc/kubernetes/manifests/kube-apiserver.yaml 2>/dev/null || \
              python - <<PY 2>/dev/null
        import yaml
        from pathlib import Path
        p = Path("/etc/kubernetes/manifests/kube-apiserver.yaml")
        if p.is_file():
            data = yaml.safe_load(p.read_text())
            for c in data.get("spec", {}).get("containers", []):
                if c.get("name") == "kube-apiserver":
                    for v in c.get("command", []):
                        print(v)
                    for v in c.get("args", []):
                        print(v)
        PY

              echo
              echo "Filtered audit flags:"
              yq e ".spec.containers[] | select(.name==\"kube-apiserver\") | .args[]" /etc/kubernetes/manifests/kube-apiserver.yaml 2>/dev/null \
                | grep -E -- "--audit-(log-path|policy-file|maxage|maxbackup|maxsize|webhook-config-file|webhook-mode|webhook-batch-max-wait|webhook-batch-max-size)" || true
            else
              echo "kube-apiserver manifest not found; cannot detect audit configuration on this node."
            fi
          '

          # 2.2 Extract audit-policy-file path from manifest
          AUDIT_POLICY_PATH=$(
            ssh "$SSH_TARGET" '
              if [ -f /etc/kubernetes/manifests/kube-apiserver.yaml ]; then
                yq e ".spec.containers[] | select(.name==\"kube-apiserver\") | .args[]" /etc/kubernetes/manifests/kube-apiserver.yaml 2>/dev/null \
                  | grep -E "^--audit-policy-file=" | head -n1 | sed "s/^--audit-policy-file=//"
              fi
            ' 2>/dev/null || true
          )

          if [ -z "$AUDIT_POLICY_PATH" ]; then
            echo
            echo "WARNING: No --audit-policy-file flag detected on $NODE."
            echo "This likely means audit is not governed by a custom policy file on this node."
            continue
          fi

          echo
          echo "Detected audit policy file on $NODE: $AUDIT_POLICY_PATH"

          # 2.3 Show high-level policy info: existence and top-level rules count
          ssh "$SSH_TARGET" "
            if [ -f '$AUDIT_POLICY_PATH' ]; then
              echo
              echo 'Audit policy file exists. Top-level rules count:'
              (yq e '.rules | length' '$AUDIT_POLICY_PATH' 2>/dev/null || python - <<PY 2>/dev/null
        import yaml, sys
        p = '$AUDIT_POLICY_PATH'
        try:
            with open(p) as f:
                d = yaml.safe_load(f)
            print(len(d.get('rules', []) or []))
        except Exception as e:
            print('ERROR parsing policy:', e, file=sys.stderr)
        PY
              ) || true
            else
              echo
              echo 'WARNING: Audit policy file path configured but file not found: $AUDIT_POLICY_PATH'
            fi
          "

          # 2.4 Inspect coverage of key security concerns
          if [ -n "$AUDIT_POLICY_PATH" ]; then
            ssh "$SSH_TARGET" "
              if [ -f '$AUDIT_POLICY_PATH' ]; then
                echo
                echo '--- Policy excerpts: SECRETS, CONFIGMAPS, TOKENREVIEWS (should log at least metadata, avoid full object) ---'
                echo
                echo 'Rules referencing secrets/configmaps/tokenreviews:'
                yq e '.rules[] | select((.resources[]?.resources[]? == \"secrets\") or (.resources[]?.resources[]? == \"configmaps\") or (.resources[]?.resources[]? == \"tokenreviews\"))' '$AUDIT_POLICY_PATH' 2>/dev/null || python - <<PY 2>/dev/null
        import yaml
        from pprint import pprint
        p = '$AUDIT_POLICY_PATH'
        with open(p) as f:
            d = yaml.safe_load(f)
        for r in d.get('rules', []):
            for rs in r.get('resources', []) or []:
                for res in rs.get('resources', []) or []:
                    if res in ('secrets', 'configmaps', 'tokenreviews'):
                        pprint(r)
                        print('---')
                        break
        PY

                echo
                echo '--- Policy excerpts: MODIFICATION of pods & deployments ---'
                echo
                echo 'Rules that match pods/deployments with verb create/update/patch/delete:'
                yq e '.rules[] | select((.resources[]?.resources[]? == \"pods\" or .resources[]?.resources[]? == \"deployments\") and (.verbs[]? == \"create\" or .verbs[]? == \"update\" or .verbs[]? == \"patch\" or .verbs[]? == \"delete\"))' '$AUDIT_POLICY_PATH' 2>/dev/null || python - <<PY 2>/dev/null
        import yaml
        from pprint import pprint
        p = '$AUDIT_POLICY_PATH'
        with open(p) as f:
            d = yaml.safe_load(f)
        interesting = {'pods', 'deployments'}
        verbs = {'create','update','patch','delete'}
        for r in d.get('rules', []):
            has_res = False
            for rs in r.get('resources', []) or []:
                if any(res in interesting for res in (rs.get('resources') or [])):
                    has_res = True
            if not has_res:
                continue
            if r.get('verbs') and any(v in verbs for v in r['verbs']):
                pprint(r)
                print('---')
        PY

                echo
                echo '--- Policy excerpts: pods/exec, pods/portforward, pods/proxy, services/proxy ---'
                echo
                echo 'Rules matching subresources exec/portforward/proxy:'
                yq e '.rules[] | select(.resources[]?.resources[]? == \"pods\" or .resources[]?.resources[]? == \"services\") | select(.resources[]?.subresources[]? == \"exec\" or .resources[]?.subresources[]? == \"portforward\" or .resources[]?.subresources[]? == \"proxy\")' '$AUDIT_POLICY_PATH' 2>/dev/null || python - <<PY 2>/dev/null
        import yaml
        from pprint import pprint
        p = '$AUDIT_POLICY_PATH'
        with open(p) as f:
            d = yaml.safe_load(f)
        interesting_res = {'pods', 'services'}
        interesting_sub = {'exec','portforward','proxy'}
        for r in d.get('rules', []):
            found = False
            for rs in r.get('resources', []) or []:
                if any(res in interesting_res for res in (rs.get('resources') or [])):
                    subs = set(rs.get('subresources') or [])
                    if subs & interesting_sub:
                        found = True
            if found:
                pprint(r)
                print('---')
        PY
              else
                echo
                echo 'Cannot inspect policy; file missing: $AUDIT_POLICY_PATH'
              fi
            "
          fi
        done

        sec "INTERPRETING THIS OUTPUT (what indicates a PROBLEM)"

        cat <<'EOF'
        For each control plane node:

        1. Missing or misconfigured audit policy
           - Problem if:
             - kube-apiserver manifest is missing, OR
             - There is NO --audit-policy-file flag, OR
             - The configured audit policy file path does not exist.
           - Impact: API requests may not be audited according to a policy.

        2. Secrets / ConfigMaps / TokenReviews coverage
           - Problem if:
             - No rules are printed that mention resources: ["secrets"], ["configmaps"], or ["tokenreviews"], OR
             - The matching rules log at Level: None (i.e., effectively not logged).
           - Risk: Access to sensitive objects is not visible in audit logs.
           - Additional risk: If Level is Request, RequestResponse, or includes ResponseBody, sensitive payloads may be logged rather than just metadata.

        3. Modification of Pods and Deployments
           - Problem if:
             - No rules are printed that include resources: ["pods"] or ["deployments"] AND verbs including create, update, patch, or delete.
           - Risk: Changes to workload definitions are not audited.

        4. pods/exec, pods/portforward, pods/proxy, services/proxy
           - Problem if:
             - No rules are printed referencing:
                 - resource: pods with subresources: exec, portforward, proxy
                 - resource: services with subresource: proxy
           - Risk: Interactive and lateral-movement style access paths are not recorded.

        5. Logging level
           - General recommendation from the benchmark:
             - For most requests, minimally log at the Metadata level.
           - Problems include:
             - Level: None for the above key areas (no auditing).
             - Very verbose levels capturing response bodies for Secrets/ConfigMaps/TokenReviews (potential exposure of sensitive data).

        This script only surfaces the current configuration; you must manually review the printed rules to ensure they align with the benchmark guidance and your organization's requirements.
        EOF
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>

### Additional Reading:

* [https://github.com/k8scop/k8s-security-dashboard/blob/master/configs/kubernetes/adv-audit.yaml](https://github.com/k8scop/k8s-security-dashboard/blob/master/configs/kubernetes/adv-audit.yaml)
