Skip to main content

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​

Remediation​

Manual Steps
  1. Determine cluster and region

    • On any machine with Azure CLI access:
      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:
      # 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):
      // 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:
      # 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):
      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:
      kubectl get pods -A > /dev/null
    • In Log Analytics for the configured workspace (or your chosen sink), run:
      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.
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.

Automation
#!/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:
    chmod +x aks-audit-logging-check.sh
  3. Optionally set SUBSCRIPTION_ID and LOCATION_FILTER at top of script.
  4. Run:
    ./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.