# GuardDuty EKS Protection: Detecting Control-Plane Threats (Anonymous Access, RBAC Tampering, Privilege Escalation) with Kubernetes Audit Logs

> A production-design guide to GuardDuty EKS Protection. We explain the mechanism of detecting control-plane threats — RBAC grants to system:anonymous, cluster-admin tampering, exec in kube-system, privileged-container launches — with EKS audit logs. Because GuardDuty ingests audit logs in an independent stream, enabling EKS control-plane logging (CloudWatch) is unneeded. We organize finding types by ThreatPurpose with MITRE ATT&CK mapping, and show per-ThreatPurpose response, Terraform (EKS_AUDIT_LOGS) enablement, RBAC hardening, and SOAR integration in code. We also clarify the difference from Runtime Monitoring (the data plane).

- Published: 2026-06-27
- Author: 友田 陽大
- Tags: セキュリティ, AWS, GuardDuty, EKS, Kubernetes
- URL: https://tomodahinata.com/en/blog/aws-guardduty-eks-protection-kubernetes-audit-logs-rbac-threats-guide
- Category: Amazon GuardDuty in production
- Pillar guide: https://tomodahinata.com/en/blog/aws-guardduty-threat-detection-multi-account-terraform-eventbridge-guide

## Key points

- EKS Protection analyzes EKS audit logs and detects 'which API calls happened' = control-plane threats. It's different from Runtime Monitoring (eBPF/data plane), which sees 'what actually ran inside the container' — only with both does the EKS attack sequence fully take effect
- The decisive fact: because GuardDuty ingests EKS audit logs in an 'independent stream,' no additional configuration is needed. You don't need to enable EKS control-plane logging (CloudWatch Logs) yourself (that's a separate thing, an optional feature for your own visibility)
- Findings have meaning encoded in the type by ThreatPurpose (anonymous access, RBAC/privilege escalation, recon, exec, secrets, impact). resource_type is EKSCluster. Representative examples: Policy:Kubernetes/AnonymousAccessGranted (High), Execution:Kubernetes/ExecInKubeSystemPod (Medium), PrivilegeEscalation:Kubernetes/AnomalousBehavior.RoleBindingCreated (Medium, High if cluster-admin is involved)
- Detection is the flip side of misconfiguration. Disabling anonymous authentication, least-privilege RBAC, Pod Security Standards/admission control, and avoiding granting admin to the default service account are themselves the hardening that erases the findings
- Terraform is aws_guardduty_detector_feature (EKS_AUDIT_LOGS) + for an org aws_guardduty_organization_configuration_feature (auto_enable=ALL). There's a 30-day free trial. Pricing is usage-based per million audit-log events (guideline, verify). Some regions aren't supported — verify in the official

---

"Your EKS cluster — can you currently track who gave `cluster-admin` to whom?" — it's a question I often throw out when consulted on container-platform security.

Usually, the answer falters. Adding one RoleBinding with `kubectl apply` is instant, and moreover **if you're not flowing audit logs to CloudWatch, there isn't even a record in the first place.** From an attacker's viewpoint, the EKS control plane is **a quiet path that advances with one API call** — "as long as you get a foothold (a leaked kubeconfig or IAM credentials), grant permission to `system:anonymous`, stand up a privileged container, and pull secrets." It leaves no trace on the network or host and advances as legitimate API calls to `kube-apiserver`.

This article is an implementation guide to **detecting and responding, at production quality**, to those **control-plane threats** — granting anonymous access, RBAC tampering, privilege escalation, `exec` in `kube-system`, secrets access — with **Amazon GuardDuty's EKS Protection.** As subject matter, I mix in the viewpoint of my experience cross-implementing IAM, observability, and DR on a [serverless payment platform](/case-studies/payment-platform-reliability) on multi-account AWS — where, **because it handles actual money, carbon credits, and local currencies, "who holds what permission" had to be monitored with a mechanism rather than operational carefulness.**

> **The rules of this article**: the specs, data sources, finding types and severities, and pricing scheme are based on the **AWS official documentation (as of June 2026).** Since finding types, severities, supported regions, and pricing are revised, always check the latest official information before going to production. And 2 iron rules — (1) **GuardDuty is detection, not prevention.** It's you, not GuardDuty, that fixes RBAC. (2) **EKS Protection is a feature that sees "audit logs = the control plane," and is different from [Runtime Monitoring](/blog/aws-guardduty-runtime-monitoring-eks-ecs-fargate-ec2-guide), which sees "what actually ran inside the container = the data plane."** Let me fix this difference between the two first.

---

## 0. Mental model: audit logs (the control plane), or runtime (the data plane)

Before starting the design, fix the GuardDuty around EKS as **2 eyes.** Confuse this and you open only one eye and "think you've defended."

> **EKS Protection = analyzes Kubernetes audit logs and detects "which API calls flew to `kube-apiserver`" = control-plane operations.** The eye that sees "who did `create rolebinding`" and "whether the API was hit as `system:anonymous`."
>
> **[Runtime Monitoring](/blog/aws-guardduty-runtime-monitoring-eks-ecs-fargate-ec2-guide) = with an eBPF security agent, detects "what process actually ran inside the container/host" = the data plane.** The eye that sees "whether a reverse shell launched" and "whether a container escape happened with `runc`."

The official definition is this — *"EKS Protection uses EKS audit logs to analyze activities of users and applications."* Audit logs record the **sequential actions** of users, applications (via the Kubernetes API), and the control plane. Runtime Monitoring, on the other hand, observes OS-level process execution, file access, and network connections **from inside the workload.**

From this contrast, 3 consequences follow.

1. **The place they look differs.** Audit logs are "**what was requested** to the API server" (the control plane). Runtime is "**what was executed** inside the container" (the data plane). For example, "a Deployment that launches a privileged container was `apply`'d" is EKS Protection (`create` remains in the audit log), but "inside that container it escaped to the host with `nsenter`" is captured by Runtime Monitoring. **Different cross-sections of the same attack.**

2. **The presence of an agent differs.** EKS Protection is **agentless.** Since GuardDuty just ingests audit logs in an independent stream, it puts nothing into the cluster (the next chapter's "killer fact"). Runtime Monitoring **needs an eBPF agent (the DaemonSet `aws-guardduty-agent`) on each node** — a separate design of distribution, coverage, and vCPU billing rides on it (details in the [data-plane article](/blog/aws-guardduty-runtime-monitoring-eks-ecs-fargate-ec2-guide)).

3. **Only with both does the attack sequence fully take effect.** GuardDuty's Extended Threat Detection correlates weak signals over a 24-hour window and bundles a multi-stage attack into `AttackSequence:EKS/CompromisedCluster` (always Critical 9.0–10.0). The official says the EKS attack sequence needs **EKS Protection or Runtime Monitoring**, and **having both covers the most broadly.** "Capture RBAC tampering with audit logs, and capture in-container execution with runtime" — only by looking with both eyes can you receive the chain extending from control plane to data plane as a single line (the reading of attack sequences goes to the [dedicated article](/blog/aws-guardduty-extended-threat-detection-attack-sequence-findings-guide)).

| Viewpoint | **EKS Protection (this article)** | **[Runtime Monitoring](/blog/aws-guardduty-runtime-monitoring-eks-ecs-fargate-ec2-guide)** |
| --- | --- | --- |
| The face it sees | The control plane (audit logs) | The data plane (OS-level behavior) |
| Data source | **EKS audit logs** (API calls) | The eBPF agent's runtime events |
| The question | "Which API calls happened" | "What ran inside the container" |
| Agent | **Unneeded (agentless)** | Needed (DaemonSet / SSM / sidecar) |
| feature name | `EKS_AUDIT_LOGS` | `RUNTIME_MONITORING` |
| resource type | `EKSCluster` | `Instance` / container-family |
| Representative finding | `Policy:Kubernetes/AnonymousAccessGranted` | `Execution:Runtime/ReverseShell` |
| Attack sequence | Contributes to the EKS sequence | Contributes to the EKS/ECS/EC2 sequence |

This article digs into the **left column** of this table. The right column is left to the [data-plane article](/blog/aws-guardduty-runtime-monitoring-eks-ecs-fargate-ec2-guide). The map of the whole of GuardDuty (foundational detection, protection-plan selection, organizational governance, EventBridge auto-response) is grounded in the [pillar article](/blog/aws-guardduty-threat-detection-multi-account-terraform-eventbridge-guide).

---

## 1. The killer fact: audit logs are ingested in an "independent stream" — no additional configuration

In designing EKS Protection, this is the **first fact to grasp.** Many get stuck here thinking "doesn't GuardDuty need EKS control-plane logging (CloudWatch Logs) enabled to see it?", but **the answer is No.**

Let me quote the official expression.

> *"When you enable EKS Protection, GuardDuty automatically starts monitoring your Amazon EKS clusters for potential security threats. GuardDuty uses its own independent stream to collect and analyze EKS audit logs – no additional configuration is required."*

That is — **GuardDuty directly collects and analyzes EKS audit logs in its "own independent stream."** You **don't need to enable EKS's control-plane logging (the feature that sends audit logs to CloudWatch Logs).** This is **exactly the same philosophy** as the "independent replica stream of foundational data sources (CloudTrail, VPC Flow Logs, DNS)" seen in the [pillar article](/blog/aws-guardduty-threat-detection-multi-account-terraform-eventbridge-guide). GuardDuty sees through its own pipe, not depending on "your log plumbing."

Get this wrong, and you fall to either paying **double cost** or **opening a hole.** The official is explicit to avoid confusion.

> *"To view EKS audit logs in your account (optional), you can configure Amazon EKS control plane logging to send audit logs to CloudWatch Logs. This configuration is separate from EKS Protection and is not required for security monitoring capability in GuardDuty."*

> *"GuardDuty doesn't manage your Amazon EKS control plane logging ... To manage access to and retention of your EKS audit logs, you must configure the Amazon EKS control plane logging feature."*

Let me drop the key points into a table.

| Feature | For what | Needed for GuardDuty | Billing |
| --- | --- | --- | --- |
| **GuardDuty EKS Protection** | GuardDuty analyzes audit logs for threat detection | — | Billed on the GuardDuty side per audit-log volume |
| **EKS control-plane logging (audit → CloudWatch Logs)** | For **you yourself** to view, store, and analyze audit logs | **Unneeded** (a separate thing) | The CloudWatch Logs ingestion/storage fee costs **separately** |

The design conclusion is simple.

- If you **just want GuardDuty to detect audit-log-derived threats**, it completes by just enabling `EKS_AUDIT_LOGS`. The EKS-side audit-log output (CloudWatch Logs) can stay off.
- If you **also want to view audit logs in a SIEM or CloudWatch yourself / want to chase raw logs during an incident**, separately and optionally enable EKS control-plane logging. This is an investment for your own observability, independent of GuardDuty.

> **A cost perspective (verify)**: turn "both on for now" and, in addition to GuardDuty's audit-log analysis fee, the CloudWatch Logs ingestion/storage fee rides doubly. The order more likely to win in cost-effectiveness is to **first run detection with just the GuardDuty side**, and judge the CloudWatch side after discerning whether always-on storage of raw logs is truly needed.

---

## 2. A catalog of finding types: read by ThreatPurpose

The findings EKS Protection emits all have **resource type `EKSCluster`** and a type name of the form `ThreatPurpose:Kubernetes/...`. As seen in [Chapter 5 of the pillar article](/blog/aws-guardduty-threat-detection-multi-account-terraform-eventbridge-guide), **meaning is encoded in the type name**, so look at the leading ThreatPurpose and you instantly know "which stage of the attack." Organize this by ThreatPurpose and the routing design of auto-response comes into view as-is.

### 2.1 The 2 lineages of detection mechanism

EKS findings split into 2 by the source of detection.

- **`.AnomalousBehavior`** = something **GuardDuty's ML model detected as a deviation from the baseline.** It captures "it's unusual for this user, from this place, in this namespace, to hit this API."
- **`.Custom`** = something that **matched an IP in a threat list (a custom threat list) you uploaded** (the threat list in [pillar article Chapter 7](/blog/aws-guardduty-threat-detection-multi-account-terraform-eventbridge-guide)).
- The rest (`MaliciousIPCaller` / `TorIPCaller` / `SuccessfulAnonymousAccess` / `Policy:*`) are explicit, non-ML signals like a known malicious IP, a Tor exit, anonymous access, or a config-policy violation.

> **The meaning of `SuccessfulAnonymousAccess` (important)**: a finding ending in `SuccessfulAnonymousAccess` indicates that **the `system:anonymous` user could hit the API "successfully."** The official says *"API calls made by `system:anonymous` are unauthenticated."* — that is, **the API went through without authentication.** This is, rather than an "attack," **evidence of a misconfiguration**, meaning anonymous access is permitted. Note that the `MaliciousIPCaller` family is, conversely, **not generated for unauthenticated access** (official: *"MaliciousIPCaller findings are not generated for unauthenticated access."*).

### 2.2 The per-ThreatPurpose catalog (severities per official 2026-06)

Severity is in the 4 bands of **Critical 9.0–10.0 / High 7.0–8.9 / Medium 4.0–6.9 / Low 1.0–3.9** ([pillar article 5.2](/blog/aws-guardduty-threat-detection-multi-account-terraform-eventbridge-guide)). The following transcribes the official **Default severity** as-is.

| ThreatPurpose (MITRE ATT&CK tactic) | finding type | Severity | What it means |
| --- | --- | --- | --- |
| **Policy** (config-policy violation) | `Policy:Kubernetes/AnonymousAccessGranted` | **High** | A RoleBinding/ClusterRoleBinding binding `system:anonymous` to a role was created = anonymous API access was permitted |
| | `Policy:Kubernetes/AdminAccessToDefaultServiceAccount` | **High** | The namespace's **default service account got admin permission** (a pod may unintentionally launch as admin) |
| | `Policy:Kubernetes/ExposedDashboard` | **Medium** | The Kubernetes Dashboard was exposed to the internet via an LB |
| | `Policy:Kubernetes/KubeflowDashboardExposed` | **Medium** | The Kubeflow Dashboard was exposed to the internet |
| **CredentialAccess** (credential/secrets theft) | `CredentialAccess:Kubernetes/AnomalousBehavior.SecretsAccessed` | **Medium** | A secrets-retrieval API was called in an **anomalous form** (ML deviation) |
| | `CredentialAccess:Kubernetes/SuccessfulAnonymousAccess` | **High** | A credential-family API could be hit by an **anonymous user** |
| | `CredentialAccess:Kubernetes/{MaliciousIPCaller, MaliciousIPCaller.Custom, TorIPCaller}` | **High** | A credential-family API was hit from a **malicious IP / Tor exit** |
| **PrivilegeEscalation** (privilege escalation) | `PrivilegeEscalation:Kubernetes/AnomalousBehavior.RoleBindingCreated` | **Medium** (**High** if `admin`/`cluster-admin` is involved) | A RoleBinding/ClusterRoleBinding to an over-privileged role was created/tampered in an **anomalous form** |
| | `PrivilegeEscalation:Kubernetes/AnomalousBehavior.RoleCreated` | **Low** | An over-privileged Role/ClusterRole was created in an **anomalous form** (evading detection while avoiding admin-family) |
| | `PrivilegeEscalation:Kubernetes/AnomalousBehavior.WorkloadDeployed!PrivilegedContainer` | **High** | A workload including a **privileged container** was launched in an anomalous form (can access the host as root) |
| **Persistence** (persistence) | `Persistence:Kubernetes/AnomalousBehavior.WorkloadDeployed!ContainerWithSensitiveMount` | **High** | A workload that **volumeMounts a sensitive host path** was launched in an anomalous form |
| | `Persistence:Kubernetes/SuccessfulAnonymousAccess` | **High** | A persistence-family high-privilege API could be hit by an **anonymous user** |
| | `Persistence:Kubernetes/{MaliciousIPCaller, MaliciousIPCaller.Custom, TorIPCaller}` | **Medium** | A persistence-family API was hit from a malicious IP / Tor |
| **Execution** (execution) | `Execution:Kubernetes/ExecInKubeSystemPod` | **Medium** | **A command was exec'd inside a pod in the `kube-system` namespace** (interference with system components) |
| | `Execution:Kubernetes/AnomalousBehavior.ExecInPod` | **Medium** | An in-pod exec was done in an **anomalous form** (the user/namespace/pod differ from usual) |
| | `Execution:Kubernetes/AnomalousBehavior.WorkloadDeployed` | **Low** (**Medium** for a suspicious image name/command) | A workload was created/tampered in an **anomalous form** |
| **Discovery** (recon) | `Discovery:Kubernetes/AnomalousBehavior.PermissionChecked` | **Low** | **Checked one's own permissions in an anomalous form** with `kubectl auth can-i` etc. (a scout before escalation) |
| | `Discovery:Kubernetes/SuccessfulAnonymousAccess` | **Medium** | A recon-family API could be hit by an **anonymous user** (health checks like `/healthz` are excluded) |
| | `Discovery:Kubernetes/{MaliciousIPCaller, MaliciousIPCaller.Custom, TorIPCaller}` | **Medium** | A recon-family API was hit from a malicious IP / Tor |
| **DefenseEvasion** (defense evasion) | `DefenseEvasion:Kubernetes/{MaliciousIPCaller, MaliciousIPCaller.Custom, SuccessfulAnonymousAccess, TorIPCaller}` | **High** | A defense-evasion-family API was hit from a malicious IP / Tor / anonymous |
| **Impact** (destruction/tampering) | `Impact:Kubernetes/{MaliciousIPCaller, MaliciousIPCaller.Custom, SuccessfulAnonymousAccess, TorIPCaller}` | **High** | A resource-tampering/destruction-family API was hit from a malicious IP / Tor / anonymous |

> **2 caveats (per official)**:
> (1) `PrivilegeEscalation:Kubernetes/AnomalousBehavior.RoleBindingCreated` is **normally Medium, but rises to High when the `admin` or `cluster-admin` ClusterRole is involved** (the official note as-is). Tampering that gives `cluster-admin` to someone raises exactly this High.
> (2) `Execution:Kubernetes/AnomalousBehavior.WorkloadDeployed` is **normally Low, but rises to Medium if it includes a known pentest tool name or a suspicious launch command like a reverse shell.**

### 2.3 Implication for design: the Policy family is not an "attack" but a "misconfiguration"

From this catalog, 3 readings that take effect on auto-response design come out.

- **`Policy:Kubernetes/*` is not a breach but "a fact of the config."** Granted anonymous access, gave the default SA admin, exposed a Dashboard — these aren't necessarily being exploited right now, but indicate **you opened the entrance to exploitation.** So the response is, more than "containment," **"fix the config (hardening)"** (Chapters 4 and 5).
- **Types ending in `SuccessfulAnonymousAccess` are crushed with top priority.** This is the fact that "the API **went through** anonymously." As a set with `Policy:Kubernetes/AnonymousAccessGranted` (the moment of granting), connect it directly to **disabling anonymous authentication.**
- **The `.AnomalousBehavior` family is a "different-from-usual" signal.** It can come out in legitimate operation too (CI/CD creating a new RoleBinding, etc.), so handle it basically with **notification-side triage, not suppression.** But as touched on in [pillar article Chapter 7](/blog/aws-guardduty-threat-detection-multi-account-terraform-eventbridge-guide), note that **erasing it with suppression erases the attack-sequence material too.**

---

## 3. Worked example ①: granting anonymous access → fix RBAC

The typical "a misconfiguration becomes a finding as-is" is **granting permission to `system:anonymous`.** Let me follow the attack chain in real code.

### 3.1 What happens to raise the finding

Suppose someone (maybe an attacker with a compromised kubeconfig, maybe a well-meaning but dangerous operator) `apply`'d a ClusterRoleBinding like this.

```yaml
# ❌ アンチパターン：system:anonymous に閲覧権限を「とりあえず」付ける。
# 「ヘルスチェックを通したかった」「動作確認を急いだ」——理由は何であれ、
# これは未認証ユーザーに cluster 全体の secrets 列挙を許す穴になり得る。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: anonymous-viewer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: view            # view でも secrets 以外を広く読める。cluster-admin なら全権
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: system:anonymous   # ← 未認証ユーザー。誰でもこの権限で API を叩ける
```

This `create clusterrolebinding` **remains in the audit log**, GuardDuty reads it in the independent stream, and generates **`Policy:Kubernetes/AnonymousAccessGranted` (High).** The official explanation:

> *"a user on your Kubernetes cluster successfully created a `ClusterRoleBinding` or `RoleBinding` to bind the user `system:anonymous` to a role. This enables unauthenticated access to the API operations permitted by the role."*

After that, if the attacker actually enumerates secrets with this anonymous permission, **`CredentialAccess:Kubernetes/SuccessfulAnonymousAccess` (High)** also follows. **The "grant (Policy)" and the "exercise (SuccessfulAnonymousAccess)" come out as separate findings** — line these 2 up in a time window and they become attack-sequence material.

### 3.2 Response: fix RBAC and cut off anonymous authentication

The official remediation is this — *"examine the permissions that have been granted to the `system:anonymous` user or `system:unauthenticated` group on your cluster and revoke unnecessary anonymous access."*

Let me drop it into a concrete procedure.

```bash
# ① 何が system:anonymous / system:unauthenticated に紐づいているかを棚卸しする。
#    「知らないうちに付いていた」binding をまず可視化するのが第一歩。
kubectl get clusterrolebindings,rolebindings -A -o json \
  | jq -r '.items[]
      | select(.subjects[]?
          | (.name=="system:anonymous") or (.name=="system:unauthenticated"))
      | "\(.kind)/\(.metadata.name) -> \(.roleRef.name)"'

# ② 不要な匿名 binding を削除する（root cause の除去）。
kubectl delete clusterrolebinding anonymous-viewer
```

> **A historical trap (explicitly stated by the official)**: *"Before Kubernetes version 1.14, the `system:unauthenticated` group was associated to `system:discovery` and `system:basic-user` ClusterRoles by default ... Cluster updates do not revoke these permissions."* — that is, **a cluster created in an old era may have anonymous permissions remaining even after upgrading to 1.14+.** Explicitly inventory and strip the bindings to `system:unauthenticated`.

And **the root cause fix is "don't let anonymous authentication through in the first place."** Lay down a setting equivalent to kube-apiserver's `--anonymous-auth=false`, or guards that "don't let dangerous RBAC be `apply`'d" with Pod Security Standards and admission control.

```yaml
# 予防：admission control で「危険な subject を持つ RoleBinding を拒否」する例(概念)。
# OPA/Gatekeeper や Kyverno で「subjects に system:anonymous/unauthenticated を
# 含む binding を deny」するポリシーを敷けば、finding が立つ前に apply を止められる。
# GuardDuty(検知) の手前に、admission(予防) を置く——多層防御。
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: deny-anonymous-binding
spec:
  validationFailureAction: Enforce   # 監査ではなく「拒否」。予防に振り切る
  rules:
    - name: block-anonymous-subjects
      match:
        any:
          - resources:
              kinds: ["RoleBinding", "ClusterRoleBinding"]
      validate:
        message: "system:anonymous / system:unauthenticated への binding は禁止"
        deny:
          conditions:
            any:
              - key: "{{ request.object.subjects[].name }}"
                operator: AnyIn
                value: ["system:anonymous", "system:unauthenticated"]
```

Note that **detection (GuardDuty) and prevention (admission control) have different roles.** Block the entrance with admission, and **detect** with GuardDuty the gaps it missed, existing holes, and grants by a compromised legitimate user. One alone isn't enough.

---

## 4. Worked example ②: exec in `kube-system`, and admin to the default SA

### 4.1 `Execution:Kubernetes/ExecInKubeSystemPod` (Medium)

The `kube-system` namespace is where **system components** like `kube-dns` and `kube-proxy` live. The official says *"It is very uncommon to execute commands inside pods or containers under `kube-system` namespace and may indicate suspicious activity."* — `kubectl exec` running here is almost always anomalous.

```bash
# 攻撃者(または侵害された認証情報)がこういう exec をすると finding が立つ。
# kube-system の pod は「触らない」のが正常。ここでのシェル取得は赤信号。
kubectl exec -it -n kube-system coredns-xxxx -- /bin/sh
```

GuardDuty reads this API call to the `exec` subresource from the audit log and raises **`Execution:Kubernetes/ExecInKubeSystemPod` (Medium).** The official response guide: *"the credentials of the user identity used to execute the command may be compromised. Revoke access of the user and reverse any changes made by an adversary to your cluster."*

In practice, identify "who exec'd" with the finding's `resource.kubernetesDetails.kubernetesUserDetails` (user name / uid / groups), and **immediately strip that identity's permission** — for an IAM-routed user, [rotate the access key](/blog/github-actions-oidc-keyless-cicd-aws-gcp-guide); for an OIDC user, revoke the credential on the IdP side; for a service account, **rotate the token** (Chapter 6).

### 4.2 `Policy:Kubernetes/AdminAccessToDefaultServiceAccount` (High)

Kubernetes auto-creates a **`default` service account** in every namespace and **auto-assigns it to pods that don't explicitly specify another SA.** So when the `default` SA gets admin permission, in the official's words *"it may result in pods being unintentionally launched with admin privileges."* — **a pod that specifies nothing runs as admin without your knowing.**

The official remediation is clear, and this becomes **the principle of hardening** as-is.

> *"You should not use the default service account to grant permissions to pods. Instead you should create a dedicated service account for each workload and grant permission to that account on a needs basis ... Then you should remove the admin permission from the default service account."*

```yaml
# ✅ 正しい形：ワークロードごとに専用 SA を作り、必要最小限だけ付ける。
# default SA は「何も権限を持たない」状態に保つ(automount も切る)。
apiVersion: v1
kind: ServiceAccount
metadata:
  name: payments-worker            # ワークロード専用。default を使い回さない
  namespace: payments
automountServiceAccountToken: false  # 必要な pod だけ明示的に mount する
---
# pod は専用 SA を「明示」して使う。default への暗黙依存をなくす。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payments-worker
  namespace: payments
spec:
  template:
    spec:
      serviceAccountName: payments-worker   # ← default ではなく専用 SA を明示
      automountServiceAccountToken: true     # この pod だけトークンを mount
```

And strip admin from the `default` SA, and guard with admission control so admin never gets attached again. **The work of erasing a finding becomes the thorough application of least privilege as-is** — this is the good thing about EKS Protection. Detection functions as a "config-health check."

---

## 5. Worked example ③: anomalous RBAC tampering (privilege escalation)

Tracking the grant of `cluster-admin` is the answer to the question at the start.

### 5.1 `PrivilegeEscalation:Kubernetes/AnomalousBehavior.RoleBindingCreated`

After an attacker gets a foothold, creating a RoleBinding to **secure persistent high privilege** is a classic move.

```yaml
# 攻撃者の典型ムーブ：侵害したサービスアカウントに cluster-admin を束ねる。
# 一度これが通れば、以後は「正規の権限」として全 API が叩けてしまう。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: backdoor-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin          # ← 全権。これが絡むと finding は Medium → High
subjects:
  - kind: ServiceAccount
    name: compromised-sa
    namespace: default
```

If GuardDuty's **ML model** judges this `create clusterrolebinding` as "it's unusual for this user, from this place, in this namespace, to create a binding," **`PrivilegeEscalation:Kubernetes/AnomalousBehavior.RoleBindingCreated`** is raised. What matters is the dynamic change in severity — **normally Medium, but it rises to High when `admin` or `cluster-admin` is involved** (the note in 2.2). The opening "who gave `cluster-admin` to whom" is exactly what this High answers.

The official remediation: *"Examine the permissions granted to the Kubernetes user. These permissions are defined in the role and subjects involved in `RoleBinding` and `ClusterRoleBinding`. If the permissions were granted mistakenly or maliciously, revoke user access and reverse any changes."*

```bash
# 対応：不正な binding を即削除し、「誰が」作ったかを監査ログ/finding で特定する。
kubectl delete clusterrolebinding backdoor-admin

# その binding を作った identity(finding の kubernetesUserDetails)の権限を剥がす。
# IAM 経由なら aws-auth ConfigMap から該当エントリを削除(6章)。
```

### 5.2 Related privilege-escalation/persistence types

Types to grasp in the same ThreatPurpose family:

- **`PrivilegeEscalation:Kubernetes/AnomalousBehavior.RoleCreated` (Low)**: the trick of **avoiding** the admin-family built-in roles and self-creating an over-privileged Role/ClusterRole. Official: *"Actors can use role creation with powerful permissions to avoid using built-in admin-like roles and avoid detection."* Low severity, but a signal where the **intent to evade detection** shows, so important in a correlation context.
- **`PrivilegeEscalation:Kubernetes/AnomalousBehavior.WorkloadDeployed!PrivilegedContainer` (High)**: the anomalous launch of a workload including a privileged container (`securityContext.privileged: true`). It can access the host as root and becomes a stepping stone for host takeover.
- **`Persistence:Kubernetes/AnomalousBehavior.WorkloadDeployed!ContainerWithSensitiveMount` (High)**: a workload that **volumeMounts a sensitive host path** like `/`, `/etc`, `/var/run/docker.sock`. It becomes a foothold to escape to the host's filesystem.

These "privileged container / sensitive mount" can be prevented with **Pod Security Standards' `restricted` profile.**

```yaml
# 予防：namespace に Pod Security Standards の restricted を適用。
# 特権コンテナ・hostPath マウント・root 実行を admission 段階で拒否する。
# WorkloadDeployed!PrivilegedContainer / !ContainerWithSensitiveMount は
# そもそも apply できなくなる(finding が立つ前に止まる)。
apiVersion: v1
kind: Namespace
metadata:
  name: payments
  labels:
    pod-security.kubernetes.io/enforce: restricted   # 危険な pod を拒否
    pod-security.kubernetes.io/enforce-version: latest
    pod-security.kubernetes.io/audit: restricted      # 監査ログにも残す
    pod-security.kubernetes.io/warn: restricted       # apply 時に警告
```

Here too the structure is the same — **block the entrance with admission (PSS), and detect with GuardDuty (audit logs) the gaps it missed and the anomalous operations of a compromised legitimate user.** Detection and prevention are not an alternative relationship but a complementary one.

---

## 6. Put the response on a pipeline: SOAR integration and the remedy for RBAC compromise

Even if you can detect, **if the response stays manual, the MTTR doesn't shrink.** Flow the EKS findings too into the **EventBridge → idempotent auto-response** plumbing built in [pillar article Chapter 6](/blog/aws-guardduty-threat-detection-multi-account-terraform-eventbridge-guide).

### 6.1 Route EKS findings with EventBridge

EKS findings too flow to EventBridge with `source: aws.guardduty` / `detail-type: GuardDuty Finding`. If you split routing from data-plane findings, filter by the **`detail.type` prefix (`*:Kubernetes/*`) and `resource.resourceType = EKSCluster`.**

```hcl
# EKS(監査ログ)由来の High 以上の finding を、専用の responder へ。
# type が ":Kubernetes/" を含むもの = コントロールプレーンの脅威に絞る。
resource "aws_cloudwatch_event_rule" "guardduty_eks_high" {
  name        = "guardduty-eks-control-plane-high"
  description = "Route EKS audit-log findings (severity >= 7) to the K8s responder"
  event_pattern = jsonencode({
    source        = ["aws.guardduty"]
    "detail-type" = ["GuardDuty Finding"]
    detail = {
      severity = [{ numeric = [">=", 7] }]
      # EKS Protection の finding は resource type が EKSCluster。
      resource = { resourceType = ["EKSCluster"] }
    }
  })
}

# 通知は広く(SNS→Slack)、封じ込めは「型の許可リスト」で狭く——という
# ピラー記事の原則は EKS でも同じ。EKS は破壊的操作(pod 削除・SA 失効)の
# ブラスト半径が大きいので、なおさら「人間のレビューを挟む」方に倒す。
resource "aws_cloudwatch_event_target" "eks_to_sns" {
  rule      = aws_cloudwatch_event_rule.guardduty_eks_high.name
  target_id = "notify-sns"
  arn       = aws_sns_topic.security_alerts.arn
}
```

> **EKS-specific response design**: unlike EC2's auto-isolation (swapping to an isolation SG), **Kubernetes containment has a hard-to-read blast radius.** "Delete a pod" or "revoke an SA token" can sweep in legitimate workloads. So the safe-side design for EKS auto-response is to **automate up to "notify, enrich, ticket," and lean RBAC revocation and pod isolation to an admission guard + human review** (apply [the pillar article's "interpose a human for destructive operations"](/blog/aws-guardduty-threat-detection-multi-account-terraform-eventbridge-guide) more strictly for EKS). The overall picture of SOAR integration goes to the [auto-response dedicated article](/blog/aws-guardduty-eventbridge-automated-remediation-incident-response-guide).

### 6.2 The remedy for RBAC compromise / credential leakage

EKS findings' response branches by the user kind. The official remediation, dropped into my operational procedure.

1. **Identify who hit it**: identify the IAM role/user with the finding's `resource.kubernetesDetails.kubernetesUserDetails` (user name / uid / groups) and, if IAM-routed, the `Access Key details`. You can investigate cross-cuttingly with **Amazon Detective's "Investigate in Detective."**
2. **Cut off credentials per identity kind**:
   - **IAM user/role**: [rotate the access key](/blog/github-actions-oidc-keyless-cicd-aws-gcp-guide) and delete the corresponding entry from the `aws-auth` ConfigMap (`kubectl edit configmaps aws-auth -n kube-system`).
   - **OIDC user**: rotate/revoke the credential on the IdP side.
   - **Service account**: **rotate the SA's token**, sweep out the pods using that SA, and remedy them.
3. **Rotate the secrets**: as the official repeatedly says, *"Rotate any secrets that user had access to."* — **rotate, in principle, all secrets that anonymous access or a compromised identity may have touched.** It's hard to prove "may have touched" as "didn't touch," so **when in doubt, rotate** is the safe side.
4. **Reverse the tampering**: delete the RoleBindings/Roles/workloads the attacker created and return the cluster to a known-good state.

> **A lesson from the payment platform**: when I designed IAM and observability on the [payment platform](/case-studies/payment-platform-reliability), what took effect most was "keeping **credential revocation and rotation in a state executable immediately by code/automation, not a runbook.**" Being able, during an incident, to chase "who touched which secrets" and **rotate with one command** — the presence of that preparation divides the MTTR. The same in EKS — "now, where are the secrets…" after receiving `Policy:Kubernetes/AnonymousAccessGranted` is too late. **Prepare the mapping of finding type → rotation targets in advance.**

---

## 7. Enable it with Terraform: `EKS_AUDIT_LOGS` and bulk org rollout

Once the design is solid, drop it into code. EKS Protection is, separate from the `aws_guardduty_detector` body, just adding feature name **`EKS_AUDIT_LOGS`** with the **`aws_guardduty_detector_feature`** resource. No agent or log plumbing is needed (Chapter 1's killer fact).

```hcl
# 前提：GuardDuty 本体(detector)は有効化済み。
# 基盤検知 + Extended Threat Detection はこの時点で既にオン(ピラー記事 2章)。
resource "aws_guardduty_detector" "this" {
  enable                       = true
  finding_publishing_frequency = "FIFTEEN_MINUTES" # 更新の遅延を縮める(ピラー記事 6章)
}

# EKS Protection = 監査ログ解析を有効化。
# これだけで GuardDuty が独立ストリームで EKS 監査ログを読み始める。
# EKS 側のコントロールプレーンログ(CloudWatch)は不要(1章)。
resource "aws_guardduty_detector_feature" "eks_audit_logs" {
  detector_id = aws_guardduty_detector.this.id
  name        = "EKS_AUDIT_LOGS"
  status      = "ENABLED"
}
```

If you govern an org in bulk, add `EKS_AUDIT_LOGS` auto-enablement to the delegated-administrator configuration of [pillar article Chapter 3](/blog/aws-guardduty-threat-detection-multi-account-terraform-eventbridge-guide).

```hcl
# ── 委任管理者(security-tooling)アカウントで実行 ──
# 組織に入っている/今後入る全アカウントで EKS Protection を自動 ON。
# 「EKS クラスタを持つアカウントだけ検知すればいい」が、
# どのアカウントが EKS を持つかは増減するので、ALL で漏れを構造的に消す。
# (EKS を持たないアカウントでは監査ログが流れず、課金もほぼ発生しない)
resource "aws_guardduty_organization_configuration_feature" "eks_audit_logs_org" {
  detector_id = aws_guardduty_detector.security.id
  name        = "EKS_AUDIT_LOGS"
  auto_enable = "ALL"   # ALL = 既存+新規すべて / NEW = 今後のみ / NONE = 無効
}
```

Design points:

- With **`auto_enable = "ALL"`**, create a state where "the EKS of newly-created accounts also enters the detection target without human intervention" (avoid the silent hole of `NEW` in [pillar article Chapter 3](/blog/aws-guardduty-threat-detection-multi-account-terraform-eventbridge-guide)). Because audit logs don't occur in accounts with no EKS cluster, **even org-wide ON incurs almost no wasted billing** — this is why, unlike Runtime Monitoring (which needs to "narrow to assets" due to vCPU billing), **EKS Protection is easy to make org-wide default ON.**
- **30-day free trial**: a newly-enabled account/region gets a 30-day free trial, and you can **grasp the projected bill based on audit-log volume before billing starts** (per official).

> **A region caveat (per official, verify)**: the official explicitly states *"EKS Protection may not be available in all the AWS Regions where GuardDuty is available."* — **even in a region where the GuardDuty body is usable, EKS Protection may not be.** Always confirm the availability in your region with the official Region-specific feature availability.

> **Pricing (guideline, verify)**: EKS Protection's billing is usage-based, based on **the volume of EKS audit logs analyzed (per million events).** Since the concrete unit price changes by region and revision, **the amount is to be verified in the official GuardDuty pricing** (this article doesn't assert a unit price). "Easy to make org-wide ON" is because audit logs don't occur in accounts with no EKS, and grasp that **the more API traffic the cluster has, the more billing increases.**

---

## 8. Summary: the GuardDuty EKS Protection cheat sheet

A quick reference for when you're unsure.

- **Don't confuse the 2 eyes**: **EKS Protection = audit logs = the control plane** ("which API calls happened," agentless, `EKS_AUDIT_LOGS`). **[Runtime Monitoring](/blog/aws-guardduty-runtime-monitoring-eks-ecs-fargate-ec2-guide) = eBPF = the data plane** ("what ran inside the container," needs an agent, `RUNTIME_MONITORING`). **Only with both does `AttackSequence:EKS/CompromisedCluster` (Critical) fully take effect.**
- **The killer fact**: GuardDuty ingests EKS audit logs in an **independent stream** — **no additional configuration.** Enabling EKS control-plane logging (CloudWatch Logs) is a **separate thing and unneeded** (an optional feature only for when you want to see raw logs yourself). "Both on" can be double cost.
- **Read findings by ThreatPurpose**: resource type is **`EKSCluster`.** A type ending in `SuccessfulAnonymousAccess` = **evidence of a misconfiguration where the API went through anonymously.** `Policy:Kubernetes/*` = **not a breach but "a fact of opening a hole"** (the response is hardening). `.AnomalousBehavior` = ML deviation, `.Custom` = a match with your own threat list.
- **Dynamic severity change (memorize)**: `PrivilegeEscalation:Kubernetes/AnomalousBehavior.RoleBindingCreated` is normally **Medium**, **High** when `admin`/`cluster-admin` is involved. `Execution:Kubernetes/AnomalousBehavior.WorkloadDeployed` is normally **Low**, **Medium** with a suspicious image/command.
- **Detection → hardening, directly connected**: anonymous access → **cut off anonymous authentication and strip RBAC.** `kube-system` exec → **revoke the identity's credential.** admin to the default SA → **a dedicated SA + least privilege.** privileged container / sensitive mount → **Pod Security Standards `restricted`.** **The work of erasing a finding becomes the thorough application of least privilege.**
- **Detection and prevention are complementary**: block the entrance with admission control (Kyverno/Gatekeeper/PSS), and **detect with GuardDuty (audit logs) the gaps it missed and the anomalous operations of a compromised legitimate user.** One alone isn't enough.
- **Terraform**: `aws_guardduty_detector_feature` (`name = "EKS_AUDIT_LOGS"`) + for an org `aws_guardduty_organization_configuration_feature` (`auto_enable = "ALL"`). **Easy to make org-wide default ON since there's no vCPU billing.** There's a 30-day free trial. **Region availability and pricing are to be verified in the official.**
- **Response**: route EKS findings by `resource.resourceType = EKSCluster` in EventBridge. **Notification automated, RBAC revocation and pod isolation human-reviewed** (large blast radius). For RBAC compromise, **delete the binding + revoke the identifier's credential + rotate all touched secrets + reverse the tampering.**

GuardDuty EKS Protection isn't "a box that protects you when enabled" — its value comes out only after designing it through to **"detecting control-plane threats with audit logs and turning those findings into RBAC hardening and SOAR response."** And **audit logs (this article) alone are half** — pair it with [runtime (the data plane)](/blog/aws-guardduty-runtime-monitoring-eks-ecs-fargate-ec2-guide) to see EKS attacks with both eyes for the first time. And if you're unsure about the foundational selection itself of EKS vs ECS, also see the [ECS vs EKS decision frame](/blog/aws-ecs-vs-eks-startup-decision-framework).

On a multi-account [serverless payment platform](/case-studies/payment-platform-reliability), I **cross-implemented the IAM, observability, and DR of a platform handling actual money, carbon credits, and local currencies**, and guaranteed "who holds what permission and which secrets they can touch" with **the structure of code and least privilege.** I design the introduction of GuardDuty EKS Protection with the same philosophy — **① enable audit-log detection org-wide without omission, ② connect finding types directly to RBAC hardening (disabling anonymous authentication, dedicated SAs, PSS, admission), and ③ prepare a response that can immediately execute credential revocation and secrets rotation for RBAC compromise.** I don't let detection end at a "red dashboard" but build it through to a mechanism that rides on operations.

**"How to design GuardDuty EKS Protection for your own EKS, how to connect audit-log detection with RBAC hardening, admission control, and SOAR response, and how to divide roles with Runtime Monitoring" — from control-plane threat detection to Terraform implementation, RBAC least-privileging, and incident response, I can accompany you fast and safely with one person × generative AI (Claude Code).** Feel free to consult us, even from the requirements-organizing stage.

---

## Reference (Official Documentation)

- [GuardDuty EKS Protection](https://docs.aws.amazon.com/guardduty/latest/ug/kubernetes-protection.html) — the definition of EKS Protection, **ingesting audit logs in an independent stream with no additional configuration**, the separation from control-plane logging, the 30-day free trial, region availability
- [EKS Protection finding types](https://docs.aws.amazon.com/guardduty/latest/ug/guardduty-finding-types-eks-audit-logs.html) — the list of `EKSCluster` finding types and Default severity (this article's severities follow here)
- [Remediating EKS Protection findings](https://docs.aws.amazon.com/guardduty/latest/ug/guardduty-remediate-kubernetes.html) — the official procedures for anonymous access, compromised user/pod/node, `aws-auth` ConfigMap editing, and secrets rotation
- [Security best practices for Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/security-best-practices.html) — stripping the permissions of `system:anonymous`/`system:unauthenticated`, RBAC hardening
- [GuardDuty finding format](https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_finding-format.html) — `ThreatPurpose:Resource/Family.Mechanism!Artifact` and the MITRE ATT&CK mapping of ThreatPurpose
- [Severity levels of GuardDuty findings](https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings-severity.html) — the numeric bands of Critical/High/Medium/Low (1.0–10.0)
- [GuardDuty Extended Threat Detection](https://docs.aws.amazon.com/guardduty/latest/ug/guardduty-extended-threat-detection.html) — `AttackSequence:EKS/CompromisedCluster`, the relation with EKS Protection / Runtime Monitoring
- [Amazon GuardDuty pricing](https://aws.amazon.com/guardduty/pricing/) — EKS Protection's audit-log-volume-based billing, the 30-day free trial, per-region (pricing to be verified)
