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
Remediation​
Manual Steps
-
Determine cluster and region
- On any machine with Azure CLI access:
az account show --query "{subscriptionId:id}"az aks list -o table
- Identify the
resourceGroupandnameof the target AKS cluster.
- On any machine with Azure CLI access:
-
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 resourceaz 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
categoryvalues such askube-auditandkube-audit-admin, and confirm thelogsare sent to a Log Analytics workspace, storage account, or Event Hub.
- On any machine with Azure CLI access:
-
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 workspacestorageAccountId→ 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 workspaceAzureDiagnostics| where Category in ("kube-audit", "kube-audit-admin")| where ClusterName == "<CLUSTER_NAME>"| take 10
- For each diagnostic setting from step 2, note where logs are sent:
-
Configure or correct diagnostic settings if missing or incomplete
- If no diagnostic settings exist, or
kube-audit/kube-audit-adminare not enabled, create or update them to send logs off-cluster. Example (Log Analytics) on any machine with Azure CLI access:# Get resource IDsAKS_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 settingaz 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-accountor--event-hub/--event-hub-rule-idand ensure immutability / restricted access as per your security requirements.
- If no diagnostic settings exist, or
-
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.
- For a storage account (on any machine with Azure CLI access):
-
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.
- Wait a few minutes after configuring diagnostics, then generate some API activity (e.g., list pods) from any kubectl-capable machine:
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):
- Save as
aks-audit-logging-check.sh. - Make executable:
chmod +x aks-audit-logging-check.sh
- Optionally set
SUBSCRIPTION_IDandLOCATION_FILTERat top of script. - Run:
./aks-audit-logging-check.sh
What indicates a problem:
- Lines starting with
STATE: PROBLEMmean:- 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: WARNINGmean:- No log category whose name matches
Auditorkube-auditis enabled; you must manually confirm whether control-plane audit-equivalent logs are actually being captured.
- No log category whose name matches