> ## 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.

# Audit Logging Should Be Enabled And Shipped Off-Cluster

### More Info:

Advisory: Kubernetes API audit logging should be enabled and forwarded to an external, tamper-resistant store so control-plane activity is retained independently of the cluster.

### Risk Level

Medium

### Address

Security

### Compliance Standards

* Cloudanix Best Practice

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Determine cluster and region**
           * On any machine with Azure CLI access:
             ```bash theme={null}
             az account show --query "{subscriptionId:id}"
             az aks list -o table
             ```
           * Identify the `resourceGroup` and `name` of the target AKS cluster.

        2. **Check if Azure Policy / diagnostic settings are configured for AKS control plane logs**
           * On any machine with Azure CLI access:
             ```bash theme={null}
             # List diagnostic settings on the managed cluster resource
             az monitor diagnostic-settings list \
               --resource $(az aks show --resource-group <RESOURCE_GROUP> --name <CLUSTER_NAME> --query id -o tsv)
             ```
           * Review whether any diagnostic setting includes `category` values such as `kube-audit` and `kube-audit-admin`, and confirm the `logs` are sent to a Log Analytics workspace, storage account, or Event Hub.

        3. **Verify logs are sent to an external, tamper-resistant store**
           * For each diagnostic setting from step 2, note where logs are sent:
             * `workspaceId` → Log Analytics workspace
             * `storageAccountId` → Storage account (ensure immutable / WORM policies where required)
             * `eventHubAuthorizationRuleId` / `eventHubName` → Event Hub
           * Optionally query Log Analytics (replace with your workspace and cluster name):
             ```kusto theme={null}
             // In Azure Portal > Logs for the workspace
             AzureDiagnostics
             | where Category in ("kube-audit", "kube-audit-admin")
             | where ClusterName == "<CLUSTER_NAME>"
             | take 10
             ```

        4. **Configure or correct diagnostic settings if missing or incomplete**
           * If no diagnostic settings exist, or `kube-audit` / `kube-audit-admin` are not enabled, create or update them to send logs off-cluster. Example (Log Analytics) on any machine with Azure CLI access:
             ```bash theme={null}
             # Get resource IDs
             AKS_ID=$(az aks show --resource-group <RESOURCE_GROUP> --name <CLUSTER_NAME> --query id -o tsv)
             LAW_ID=$(az monitor log-analytics workspace show \
               --resource-group <LOG_ANALYTICS_RG> \
               --workspace-name <WORKSPACE_NAME> \
               --query id -o tsv)

             # Create or update diagnostic setting
             az monitor diagnostic-settings create \
               --name "aks-controlplane-audit" \
               --resource "$AKS_ID" \
               --workspace "$LAW_ID" \
               --logs '[{"category":"kube-audit","enabled":true},{"category":"kube-audit-admin","enabled":true}]'
             ```
           * If using a storage account or Event Hub instead, adjust the command to use `--storage-account` or `--event-hub`/`--event-hub-rule-id` and ensure immutability / restricted access as per your security requirements.

        5. **Harden the log destination for tamper resistance**
           * For a storage account (on any machine with Azure CLI access):
             ```bash theme={null}
             az storage account blob-service-properties update \
               --account-name <STORAGE_ACCOUNT_NAME> \
               --resource-group <STORAGE_RG> \
               --enable-delete-retention true \
               --delete-retention-days 90
             # If compliance requires WORM/immutability, configure container-level immutability policies via Azure Portal or ARM/Bicep/Terraform.
             ```
           * For Log Analytics / Event Hub, confirm access controls (RBAC, private endpoints, retention) and any downstream export to a WORM-capable store as required by your policies.

        6. **Verify audit logs are now flowing off-cluster**
           * Wait a few minutes after configuring diagnostics, then generate some API activity (e.g., list pods) from any kubectl-capable machine:
             ```bash theme={null}
             kubectl get pods -A > /dev/null
             ```
           * In Log Analytics for the configured workspace (or your chosen sink), run:
             ```kusto theme={null}
             AzureDiagnostics
             | where Category in ("kube-audit", "kube-audit-admin")
             | where ClusterName == "<CLUSTER_NAME>"
             | where TimeGenerated > ago(15m)
             | take 20
             ```
           * Confirm recent entries exist, demonstrating Kubernetes API audit logging is enabled and shipped off-cluster.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot enable or configure Kubernetes API audit logging for an AKS control plane, because this setting is managed only through Azure (portal, CLI, or IaC) at the cloud-provider level. To address this finding, use the Azure configuration surfaces described in the Manual Steps section.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Audit AKS control-plane logging configuration across all AKS clusters in a subscription.
        # Requirements:
        #   - Azure CLI installed and logged in: az login
        #   - Sufficient RBAC to read AKS and Log Analytics / storage / Event Hub resources.

        set -euo pipefail

        SUBSCRIPTION_ID=""
        LOCATION_FILTER=""   # optional, e.g. "eastus" to limit scope

        if [ -n "${SUBSCRIPTION_ID}" ]; then
          az account set --subscription "${SUBSCRIPTION_ID}"
        fi

        echo "=== Enumerating AKS clusters ===" >&2

        AKS_LIST_JSON=$(az aks list --query '[].{name:name,rg:resourceGroup,location:location}' -o json)

        echo "${AKS_LIST_JSON}" | jq -c '.[]' | while read -r CLUSTER; do
          NAME=$(echo "${CLUSTER}" | jq -r '.name')
          RG=$(echo "${CLUSTER}" | jq -r '.rg')
          LOC=$(echo "${CLUSTER}" | jq -r '.location')

          if [ -n "${LOCATION_FILTER}" ] && [ "${LOC}" != "${LOCATION_FILTER}" ]; then
            continue
          fi

          echo
          echo "------------------------------------------------------------------"
          echo "Cluster: ${NAME}"
          echo "Resource group: ${RG}"
          echo "Location: ${LOC}"
          echo "------------------------------------------------------------------"

          # Get cluster diagnostics (Azure Monitor integration)
          DIAG_JSON=$(az aks show -g "${RG}" -n "${NAME}" --query 'addonProfiles.omsagent' -o json 2>/dev/null || echo '{}')

          OMS_ENABLED=$(echo "${DIAG_JSON}" | jq -r '.enabled // false')
          if [ "${OMS_ENABLED}" != "true" ]; then
            echo "STATE: PROBLEM - Azure Monitor (omsagent) not enabled; control-plane diagnostics likely not configured."
            continue
          fi

          WORKSPACE_ID=$(echo "${DIAG_JSON}" | jq -r '.config.logAnalyticsWorkspaceResourceID // empty')

          if [ -z "${WORKSPACE_ID}" ]; then
            echo "STATE: PROBLEM - Azure Monitor enabled, but no Log Analytics workspace configured."
            continue
          fi

          echo "Azure Monitor (omsagent): ENABLED"
          echo "Log Analytics workspace:  ${WORKSPACE_ID}"

          # List diagnostic settings on the managed resource for the cluster's control plane.
          # For AKS, control-plane diagnostics are configured on the managed resource under:
          # /subscriptions/.../resourceGroups/<RG>/providers/Microsoft.ContainerService/managedClusters/<NAME>
          MANAGED_ID="/subscriptions/$(az account show --query id -o tsv)/resourceGroups/${RG}/providers/Microsoft.ContainerService/managedClusters/${NAME}"

          echo
          echo "Fetching diagnostic settings for control-plane resource:"
          echo "  ${MANAGED_ID}"

          DIAG_SETTINGS_JSON=$(az monitor diagnostic-settings list --resource "${MANAGED_ID}" -o json 2>/dev/null || echo '[]')

          if [ "$(echo "${DIAG_SETTINGS_JSON}" | jq 'length')" -eq 0 ]; then
            echo "STATE: PROBLEM - No diagnostic settings defined for AKS managed cluster resource."
            echo "        -> Control-plane audit logs are not being shipped off-cluster."
            continue
          fi

          echo "${DIAG_SETTINGS_JSON}" | jq -c '.[]' | while read -r DS; do
            DS_NAME=$(echo "${DS}" | jq -r '.name')
            WS_ID=$(echo "${DS}" | jq -r '.workspaceId // empty')
            SA_ID=$(echo "${DS}" | jq -r '.storageAccountId // empty')
            EH_AUTH=$(echo "${DS}" | jq -r '.eventHubAuthorizationRuleId // empty')
            LOGS=$(echo "${DS}" | jq -c '.logs')

            echo
            echo "Diagnostic setting: ${DS_NAME}"
            echo "  -> workspaceId:            ${WS_ID:-"<none>"}"
            echo "  -> storageAccountId:       ${SA_ID:-"<none>"}"
            echo "  -> eventHubAuthorizationId:${EH_AUTH:-"<none>"}"

            # Check for presence of control-plane log categories typically used for audit/operations.
            # Categories vary by AKS generation; this is a best-effort check.
            HAS_AUDIT=$(echo "${LOGS}" | jq -r '[.[] | select(.category | test("Audit|kube-audit","i")) | select(.enabled==true)] | length')
            HAS_ALL=$(echo "${LOGS}" | jq -r '[.[] | select(.enabled==true)] | length')

            if [ "${HAS_ALL}" -eq 0 ]; then
              echo "  STATE: PROBLEM - Diagnostic setting exists but no log categories are enabled."
            fi

            if [ "${HAS_AUDIT}" -eq 0 ]; then
              echo "  STATE: WARNING - No enabled log category name matching /Audit|kube-audit/i."
              echo "          -> Manually verify control-plane audit/operation logs are enabled in this setting."
            else
              echo "  Audit-like categories: PRESENT (>=1 enabled category matching /Audit|kube-audit/i)."
            fi

            # Check that logs are being shipped to an external, tamper-resistant store
            if [ -z "${WS_ID}" ] && [ -z "${SA_ID}" ] && [ -z "${EH_AUTH}" ]; then
              echo "  STATE: PROBLEM - Diagnostic setting does not ship logs to any external sink."
            else
              echo "  External sinks configured:"
              [ -n "${WS_ID}" ] && echo "    - Log Analytics workspace"
              [ -n "${SA_ID}" ] && echo "    - Storage account"
              [ -n "${EH_AUTH}" ] && echo "    - Event Hub"
            fi
          done
        done
        ```

        **How to run (any machine with Azure CLI access):**

        1. Save as `aks-audit-logging-check.sh`.
        2. Make executable:
           ```bash theme={null}
           chmod +x aks-audit-logging-check.sh
           ```
        3. Optionally set `SUBSCRIPTION_ID` and `LOCATION_FILTER` at top of script.
        4. Run:
           ```bash theme={null}
           ./aks-audit-logging-check.sh
           ```

        **What indicates a problem:**

        * Lines starting with `STATE: PROBLEM` mean:
          * Azure Monitor not enabled for the cluster, **or**
          * No diagnostic settings on the AKS managed cluster resource, **or**
          * No log categories enabled in the diagnostic setting, **or**
          * No external sink (Log Analytics / storage / Event Hub) configured.
        * Lines with `STATE: WARNING` mean:
          * No log category whose name matches `Audit` or `kube-audit` is enabled; you must manually confirm whether control-plane audit-equivalent logs are actually being captured.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
