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. Review EKS control-plane logging configuration (CloudWatch integration)

    • On any machine with AWS CLI configured, list current control plane log types:
      aws eks describe-cluster \
      --name YOUR_CLUSTER_NAME \
      --query 'cluster.logging.clusterLogging'
    • Confirm that "audit" is present in the enabled list. If not, plan to enable it.
  2. Enable/ensure EKS audit logging to CloudWatch Logs

    • Using AWS CLI on any machine with appropriate IAM permissions, enable audit logging:
      aws eks update-cluster-config \
      --name YOUR_CLUSTER_NAME \
      --logging '{
      "clusterLogging": [
      {
      "types": ["audit"],
      "enabled": true
      }
      ]
      }'
    • Alternatively in the AWS console: EKS → your cluster → Configuration → Logging → Manage logging → check Audit → Save changes.
  3. Verify audit events are being produced

    • In the AWS console, open CloudWatch Logs → Log groups, find the group named like:
      /aws/eks/YOUR_CLUSTER_NAME/cluster
    • Open the log group and check that a stream with recent timestamps contains Kubernetes API audit records (JSON entries with fields like verb, user, objectRef).
    • From CLI, confirm the log group exists and has recent events:
      aws logs describe-log-groups \
      --log-group-name-prefix "/aws/eks/YOUR_CLUSTER_NAME/cluster"

      aws logs filter-log-events \
      --log-group-name "/aws/eks/YOUR_CLUSTER_NAME/cluster" \
      --limit 5
  4. Configure and verify off-cluster, tamper-resistant retention of audit logs

    • Decide on an external store (commonly: S3 + KMS + write-only IAM, or a third-party SIEM).
    • For S3 via CloudWatch Logs subscription (any machine with AWS CLI):
      1. Create or identify a target S3 bucket with appropriate bucket policy preventing non-audited deletion/modification.
      2. Create a CloudWatch Logs subscription filter to a Kinesis stream, Firehose, or Lambda that writes to that S3 bucket. Example (Firehose):
        aws logs put-subscription-filter \
        --log-group-name "/aws/eks/YOUR_CLUSTER_NAME/cluster" \
        --filter-name eks-audit-to-firehose \
        --filter-pattern "" \
        --destination-arn arn:aws:firehose:REGION:ACCOUNT_ID:deliverystream/YOUR_FIREHOSE_STREAM
    • In your chosen destination (e.g., S3 bucket, SIEM console), verify new Kubernetes audit records are appearing.
  5. Harden retention and access controls for the external store

    • For S3:
      • Configure bucket versioning and lifecycle policies to meet your retention requirements.
      • Use a KMS CMK and limit key and bucket access to a small, audited set of roles.
      • Optionally configure Object Lock (Compliance or Governance mode) to prevent tampering if compliant with your requirements.
    • For SIEM/other: ensure write-only or tightly controlled delete privileges and auditable access logs.
  6. Re-verify configuration and document the control

    • Re-run:
      aws eks describe-cluster \
      --name YOUR_CLUSTER_NAME \
      --query 'cluster.logging.clusterLogging'
      Confirm "audit" is enabled.
    • Capture evidence (screenshots/CLI output) of:
      • EKS audit logging enabled.
      • CloudWatch log group receiving audit records.
      • Off-cluster destination receiving the same records and showing appropriate retention/immutability controls.
Using kubectl

kubectl cannot enable or configure Kubernetes API audit logging on Amazon EKS because this is controlled by the EKS/CloudTrail/CloudWatch/S3 configuration in AWS, not by Kubernetes API objects. To address this finding, configure audit logging and off-cluster forwarding in the AWS console, CLI, or IaC as described in the Manual Steps section.

Automation
#!/usr/bin/env bash
#
# EKS API audit logging state reporter
# Requirements:
# - aws CLI configured with credentials
# - jq installed
#
# Usage:
# ./eks_audit_logging_report.sh <aws-region>
#
# Output:
# One line per cluster with:
# - Cluster name
# - Cluster ARN
# - Logging types and whether they are enabled
# - Whether audit logging is enabled
# - Whether an external, tamper-resistant sink is detected (CloudTrail + CloudWatch Logs / S3)
#
# NOTES:
# - This script assesses control-plane API audit logging at the EKS level.
# - It does NOT change any configuration.
# - “Problem” means: audit logging is disabled OR logs are not going to a durable,
# off-cluster destination.

set -euo pipefail

REGION="${1:-}"
if [ -z "$REGION" ]; then
echo "Usage: $0 <aws-region>" >&2
exit 1
fi

command -v aws >/dev/null 2>&1 || { echo "aws CLI not found in PATH" >&2; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "jq not found in PATH" >&2; exit 1; }

echo "Region: $REGION"
echo "Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo

# Get all EKS clusters in the region
CLUSTERS_JSON="$(aws eks list-clusters --region "$REGION" --output json)"
CLUSTER_NAMES=($(echo "$CLUSTERS_JSON" | jq -r '.clusters[]?'))

if [ "${#CLUSTER_NAMES[@]}" -eq 0 ]; then
echo "No EKS clusters found in region $REGION."
exit 0
fi

printf '%-30s %-60s %-30s %-15s %-25s\n' \
"CLUSTER_NAME" "CLUSTER_ARN" "AUDIT_LOG_ENABLED" "PROBLEM" "NOTES"
printf '%*s\n' 168 | tr ' ' '-'

for CLUSTER in "${CLUSTER_NAMES[@]}"; do
DESC="$(aws eks describe-cluster --name "$CLUSTER" --region "$REGION" --output json)"

CLUSTER_ARN=$(echo "$DESC" | jq -r '.cluster.arn')
LOGGING_JSON=$(echo "$DESC" | jq -r '.cluster.logging.clusterLogging')

# Determine if audit logging is enabled
AUDIT_LOG_ENABLED=$(echo "$LOGGING_JSON" \
| jq -r '.[] | select(.types[]? == "audit") | .enabled' 2>/dev/null || true)

if [ -z "$AUDIT_LOG_ENABLED" ]; then
AUDIT_LOG_ENABLED="false"
fi

PROBLEM="false"
NOTES=""

if [ "$AUDIT_LOG_ENABLED" != "true" ]; then
PROBLEM="true"
NOTES="audit logging disabled"
fi

# Check CloudTrail and its destinations as a proxy for "shipped off-cluster"
# This is heuristic and must be reviewed.
TRAILS_JSON="$(aws cloudtrail describe-trails --region "$REGION" --output json)"
CT_HAS_ORG_OR_MULTI_REGION=false
CT_HAS_S3=false
CT_HAS_CLOUDWATCH=false

if echo "$TRAILS_JSON" | jq -e '.trailList | length > 0' >/dev/null 2>&1; then
# Any trail with IsOrganizationTrail or IsMultiRegionTrail set?
if echo "$TRAILS_JSON" | jq -e '.trailList[] | select(.IsOrganizationTrail == true or .IsMultiRegionTrail == true)' >/dev/null 2>&1; then
CT_HAS_ORG_OR_MULTI_REGION=true
fi
# Any trail with S3BucketName set?
if echo "$TRAILS_JSON" | jq -e '.trailList[] | select(.S3BucketName != null and .S3BucketName != "")' >/dev/null 2>&1; then
CT_HAS_S3=true
fi
# Any trail with CloudWatchLogsLogGroupArn set?
if echo "$TRAILS_JSON" | jq -e '.trailList[] | select(.CloudWatchLogsLogGroupArn != null and .CloudWatchLogsLogGroupArn != "")' >/dev/null 2>&1; then
CT_HAS_CLOUDWATCH=true
fi
fi

# Heuristic: if audit logging is enabled AND there is at least one
# CloudTrail trail sending to S3 and/or CloudWatch Logs, we consider logs
# "likely" shipped off-cluster. This still needs human review.
if [ "$AUDIT_LOG_ENABLED" = "true" ]; then
if [ "$CT_HAS_S3" = "true" ] || [ "$CT_HAS_CLOUDWATCH" = "true" ]; then
if [ -z "$NOTES" ]; then
NOTES="audit logging enabled; CloudTrail with S3/CloudWatch detected (review configuration)"
else
NOTES="$NOTES; CloudTrail with S3/CloudWatch detected (review configuration)"
fi
else
PROBLEM="true"
if [ -z "$NOTES" ]; then
NOTES="audit logging enabled but no CloudTrail trail with S3/CloudWatch destination detected"
else
NOTES="$NOTES; no CloudTrail trail with S3/CloudWatch destination detected"
fi
fi
fi

printf '%-30s %-60s %-30s %-15s %-25s\n' \
"$CLUSTER" "$CLUSTER_ARN" "$AUDIT_LOG_ENABLED" "$PROBLEM" "$NOTES"
done

How to run (any machine with AWS CLI access):

  1. Save as eks_audit_logging_report.sh and make executable:
    chmod +x eks_audit_logging_report.sh
  2. Run for a region:
    ./eks_audit_logging_report.sh us-east-1

Interpreting output (what indicates a problem):

  • AUDIT_LOG_ENABLED is false → audit logging not enabled for that EKS cluster (non-compliant).
  • PROBLEM is true and NOTES contains any of:
    • audit logging disabled
    • audit logging enabled but no CloudTrail trail with S3/CloudWatch destination detected

These clusters require manual review and, if appropriate, enabling of API audit logging and configuration of a durable, off-cluster log destination.