Ensure That The 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 GKE
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Identify cluster type and constraints
- On any machine with
gcloudaccess, confirm this is a GKE cluster and note its mode (standard vs autopilot), as GKE manages the API server:gcloud container clusters listgcloud container clusters describe CLUSTER_NAME --zone=CLUSTER_ZONE \--format="value(name,location,releaseChannel.channel,loggingConfig)"
- On any machine with
-
Review current GKE audit/logging configuration
- On any machine with
gcloudaccess, check which control-plane logs are enabled (these are GKE’s effective “audit surfaces”):gcloud container clusters describe CLUSTER_NAME --zone=CLUSTER_ZONE \--format="yaml(loggingConfig,monitoringConfig)" - Confirm that at least API server audit logs / control plane logs are enabled (names and options vary by GKE version; look for entries under
loggingConfig.componentConfig.enableComponentssuch asAPISERVER,AUDIT, orCONTROLPLANE).
- On any machine with
-
Review log sink and retention settings
- On any machine with
gcloudaccess, determine where logs go and for how long, as this impacts audit usefulness:# List sinks that may capture GKE control plane logsgcloud logging sinks list# Show retention for the main logging bucketgcloud logging buckets list --location=globalgcloud logging buckets describe _Default --location=global - Verify logs are retained for a period that meets your policy (e.g., 90–365 days) and that sinks route them to an appropriate SIEM or storage location if required.
- On any machine with
-
Sample current audit-related events for key security concerns
- On any machine with
gcloudaccess, query Cloud Logging for recent events that reflect key security concerns, such as:- Authentication/authorization failures
- Changes to
ClusterRole,Role,RoleBinding,ClusterRoleBinding - Changes to
Namespace,NetworkPolicy,PodSecurityPolicy/equivalent,Pod,ServiceAccount,Secret
- Example queries:
# Any failed API callsgcloud logging read \'resource.type="k8s_cluster" severity>=WARNINGprotoPayload.status.code!=""' \--limit=20 --format="table(timestamp,protoPayload.methodName,protoPayload.status.code)"# Changes to RBAC objectsgcloud logging read \'resource.type="k8s_cluster"protoPayload.methodName=~"io.k8s.authorization.rbac.v1.(Role|ClusterRole|RoleBinding|ClusterRoleBinding).*"' \--limit=20 --format="table(timestamp,protoPayload.authenticationInfo.principalEmail,protoPayload.methodName)"
- Adjust filters in the Cloud Console Logs Explorer to visually verify that such actions are being logged.
- On any machine with
-
Compare observed logging coverage with your security requirements
- Using your organization’s policies, confirm that the available GKE audit/control-plane logs:
- Capture administrative actions on critical resources (RBAC, namespaces, network/security policies, workloads, service accounts, secrets).
- Include enough context (actor, source IP, target object, verb, timestamp) to support incident response and forensics.
- Are reliably exported and retained per policy.
- If gaps exist that cannot be closed due to GKE’s managed nature (for example, you cannot customize the underlying
/etc/kubernetes/manifests/kube-apiserver.yamlor audit policy file), document the residual risk and any compensating controls (e.g., higher-level IAM logging, workload-level audit, admission webhooks, or service mesh logs).
- Using your organization’s policies, confirm that the available GKE audit/control-plane logs:
-
Decide and document compliance status
- If GKE’s built-in control-plane logging and your sinks/retention meet your documented requirements for “key security concerns,” record this control as “Covered by managed GKE audit/control plane logging; configuration not directly modifiable” and keep evidence from steps 2–4.
- If requirements are not met and cannot be met within GKE’s constraints, mark the control as accepted risk/partially compliant, attach the evidence and rationale, and define any additional monitoring or architectural changes (e.g., moving to a more configurable environment) needed to close the gap.
Using kubectl
kubectl cannot be used to configure or modify the API server audit policy for this control, because it is defined via the control plane’s host-level configuration in /etc/kubernetes/manifests/kube-apiserver.yaml (and, in GKE, is not user-modifiable). Please refer to the Manual Steps section for guidance on how to review this setting and what decisions are required.
Automation
#!/usr/bin/env bash
#
# Purpose:
# Summarize GKE audit logging coverage across all clusters/contexts
# to help review whether key security concerns are being audited.
#
# Requirements:
# - gcloud installed and authenticated (for cluster discovery)
# - kubectl installed
# - Sufficient IAM to list clusters and use their kubeconfigs
#
# Notes:
# - In GKE, the audit policy itself is not editable.
# - This script does NOT fix anything; it collects evidence to review.
#
set -euo pipefail
############################################################
# Helper: print a section header
############################################################
section() {
echo
echo "==================== $* ===================="
}
############################################################
# 1. List GKE clusters and their logging/audit configuration
############################################################
section "GKE CLUSTERS AND LOGGING / AUDIT SETTINGS"
# Change this if you want to scope to specific project(s)/regions.
PROJECTS=$(gcloud projects list --format='value(projectId)')
for PROJECT in $PROJECTS; do
echo "Project: $PROJECT"
CLUSTERS=$(gcloud container clusters list \
--project "$PROJECT" \
--format='value(name,location)')
if [ -z "$CLUSTERS" ]; then
echo " No clusters found."
continue
fi
# Header
printf " %-30s %-15s %-10s %-15s %-20s\n" \
"CLUSTER" "LOCATION" "VERSION" "MASTER_LOGS" "WORKLOAD_LOGGING"
printf " %-30s %-15s %-10s %-15s %-20s\n" \
"-------" "--------" "-------" "-----------" "----------------"
while read -r NAME LOCATION; do
# Fetch cluster details in JSON
INFO=$(gcloud container clusters describe "$NAME" \
--project "$PROJECT" \
--region "$LOCATION" 2>/dev/null || \
gcloud container clusters describe "$NAME" \
--project "$PROJECT" \
--zone "$LOCATION" 2>/dev/null || true)
if [ -z "$INFO" ]; then
printf " %-30s %-15s %-10s %-15s %-20s\n" "$NAME" "$LOCATION" "N/A" "N/A" "N/A"
continue
fi
VERSION=$(echo "$INFO" | yq '.currentMasterVersion // .currentMasterVersion' 2>/dev/null || echo "N/A")
# Master (control plane) logging – relevant for API server audit visibility
MASTER_LOGGING=$(echo "$INFO" | yq '.loggingService // .loggingConfig.componentConfig.enableComponents[]? // ""' 2>/dev/null || echo "")
if echo "$MASTER_LOGGING" | grep -qi "none"; then
MASTER_STATUS="DISABLED"
else
MASTER_STATUS="ENABLED"
fi
# Workload logging mode
WORKLOAD_LOGGING=$(echo "$INFO" | yq '.loggingConfig.componentConfig.enableComponents[]? // .loggingConfig // ""' 2>/dev/null || echo "")
printf " %-30s %-15s %-10s %-15s %-20s\n" \
"$NAME" "$LOCATION" "$VERSION" "$MASTER_STATUS" "${WORKLOAD_LOGGING:0:18}"
done <<< "$CLUSTERS"
done
############################################################
# 2. Per-cluster: verify Audit / API logs presence in Cloud Logging
############################################################
section "CLOUD LOGGING: API / AUDIT LOG SINK PRESENCE (SAMPLE CHECK)"
# Adjust time range as needed for your environment
TIME_RANGE='timestamp >= "'$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)'"'
for PROJECT in $PROJECTS; do
echo "Project: $PROJECT"
# Check for ADMIN_READ / DATA_WRITE / DATA_READ audit logs for Kubernetes API
# This is a heuristic: presence shows that GKE audit/API logs are reaching Cloud Logging.
for LOG_TYPE in "ADMIN_READ" "DATA_WRITE" "DATA_READ"; do
echo " Checking presence of Kubernetes audit-like entries: logType=$LOG_TYPE (last 1h)"
COUNT=$(gcloud logging read \
"logName:\"cloudaudit.googleapis.com\" \
protoPayload.serviceName=\"kubernetes.io\" \
protoPayload.methodName:\"\" \
protoPayload.authenticationInfo.principalEmail!=\"\" \
protoPayload.@type=\"type.googleapis.com/google.cloud.audit.AuditLog\" \
protoPayload.methodName!=\"io.k8s.core.v1.Event\" \
protoPayload.metadata.\"@type\"!=\"type.googleapis.com/google.cloud.audit.AuditLog\" \
protoPayload.metadata=\"\" \
protoPayload.requestMetadata.callerIp!=\"\" \
protoPayload.metadata.severity!=\"DEBUG\" \
protoPayload.metadata.log_type=\"$LOG_TYPE\" \
$TIME_RANGE" \
--project="$PROJECT" \
--format='value(insertId)' \
--limit=1 2>/dev/null | wc -l | tr -d ' ')
if [ "$COUNT" -gt 0 ]; then
echo " FOUND sample entries for logType=$LOG_TYPE"
else
echo " NOT FOUND sample entries for logType=$LOG_TYPE <-- REVIEW"
fi
done
done
############################################################
# 3. Per-cluster API server flags snapshot (where visible via /configz)
# Run from the active kubectl context only.
############################################################
section "KUBE-APISERVER AUDIT-RELATED FLAGS (CURRENT KUBECTL CONTEXT)"
# This requires that the current context points at a GKE cluster,
# and that the /configz endpoint is reachable/authorized.
set +e
API_CONFIGZ=$(kubectl get --raw /configz 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$API_CONFIGZ" ]; then
echo "Unable to query /configz via kubectl. This is expected on many GKE clusters."
echo "Use Cloud Logging views and IAM audit logs for evaluation."
exit 0
fi
set -e
echo "Extracting kube-apiserver audit-related flags from /configz:"
echo "$API_CONFIGZ" | jq -r '
.kubeAPIServerConfig | [
.auditLogPath,
.auditPolicyFile,
.auditWebhookConfigFile,
.auditLogMaxAge,
.auditLogMaxBackup,
.auditLogMaxSize,
.auditWebhookMode,
.auditBatchMaxSize,
.auditBatchMaxWait,
.auditBatchThrottleBurst,
.auditBatchThrottleEnable,
.auditBatchThrottleQPS
] | {
auditLogPath: .[0],
auditPolicyFile: .[1],
auditWebhookConfigFile: .[2],
auditLogMaxAge: .[3],
auditLogMaxBackup: .[4],
auditLogMaxSize: .[5],
auditWebhookMode: .[6],
auditBatchMaxSize: .[7],
auditBatchMaxWait: .[8],
auditBatchThrottleBurst: .[9],
auditBatchThrottleEnable: .[10],
auditBatchThrottleQPS: .[11]
}' 2>/dev/null || echo "Could not parse kubeAPIServerConfig from /configz"
echo
echo "Review Guidance:"
echo " - In GKE, audit policy is managed by the platform and not user-editable."
echo " - Use the sections above to verify that:"
echo " * Control plane (master) logging is ENABLED."
echo " * Kubernetes API / audit logs (ADMIN_READ / DATA_WRITE / DATA_READ)"
echo " are present in Cloud Logging for your project(s)."
echo " - If master logging is DISABLED or relevant audit logs are NOT FOUND,"
echo " this indicates a potential gap in audit coverage that must be evaluated"
echo " against your organization’s logging and compliance requirements."
How to interpret the output
- Problems that need review:
- Any cluster where
MASTER_LOGSshowsDISABLED. - Any project where
NOT FOUNDappears forADMIN_READ,DATA_WRITE, orDATA_READin the last hour (may indicate missing or misrouted audit/API logs, or simply no activity).
- Any cluster where
- Because this control is MANUAL and GKE’s audit policy is not user-modifiable, these outputs are evidence for a human review, not an automatic pass/fail decision.