> ## 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. **Review EKS control-plane logging configuration (CloudWatch integration)**
           * On any machine with AWS CLI configured, list current control plane log types:
             ```bash theme={null}
             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:
             ```bash theme={null}
             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:
             ```bash theme={null}
             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):
                ```bash theme={null}
                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:
             ```bash theme={null}
             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.
      </Accordion>

      <Accordion title="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.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/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:
           ```bash theme={null}
           chmod +x eks_audit_logging_report.sh
           ```
        2. Run for a region:
           ```bash theme={null}
           ./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.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
