"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 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, 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 didcreate rolebinding" and "whether the API was hit assystem:anonymous."Runtime Monitoring = 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.
-
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 (createremains in the audit log), but "inside that container it escaped to the host withnsenter" is captured by Runtime Monitoring. Different cross-sections of the same attack. -
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). -
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).
| Viewpoint | EKS Protection (this article) | Runtime Monitoring |
|---|---|---|
| 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. The map of the whole of GuardDuty (foundational detection, protection-plan selection, organizational governance, EventBridge auto-response) is grounded in the pillar article.
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. 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, 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).- 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 inSuccessfulAnonymousAccessindicates that thesystem:anonymoususer could hit the API "successfully." The official says "API calls made bysystem:anonymousare 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 theMaliciousIPCallerfamily 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). 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.RoleBindingCreatedis normally Medium, but rises to High when theadminorcluster-adminClusterRole is involved (the official note as-is). Tampering that givescluster-adminto someone raises exactly this High. (2)Execution:Kubernetes/AnomalousBehavior.WorkloadDeployedis 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
SuccessfulAnonymousAccessare crushed with top priority. This is the fact that "the API went through anonymously." As a set withPolicy:Kubernetes/AnonymousAccessGranted(the moment of granting), connect it directly to disabling anonymous authentication. - The
.AnomalousBehaviorfamily 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, 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.
# ❌ アンチパターン: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
ClusterRoleBindingorRoleBindingto bind the usersystem:anonymousto 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.
# ① 何が 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:unauthenticatedgroup was associated tosystem:discoveryandsystem:basic-userClusterRoles 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 tosystem: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.
# 予防: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.
# 攻撃者(または侵害された認証情報)がこういう 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; 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."
# ✅ 正しい形:ワークロードごとに専用 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.
# 攻撃者の典型ムーブ:侵害したサービスアカウントに 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."
# 対応:不正な 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.
# 予防: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.
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.
# 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" more strictly for EKS). The overall picture of SOAR integration goes to the auto-response dedicated article.
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.
- Identify who hit it: identify the IAM role/user with the finding's
resource.kubernetesDetails.kubernetesUserDetails(user name / uid / groups) and, if IAM-routed, theAccess Key details. You can investigate cross-cuttingly with Amazon Detective's "Investigate in Detective." - Cut off credentials per identity kind:
- IAM user/role: rotate the access key and delete the corresponding entry from the
aws-authConfigMap (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.
- IAM user/role: rotate the access key and delete the corresponding entry from the
- 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.
- 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, 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/AnonymousAccessGrantedis 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).
# 前提: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.
# ── 委任管理者(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 ofNEWin pillar article Chapter 3). 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 = eBPF = the data plane ("what ran inside the container," needs an agent,RUNTIME_MONITORING). Only with both doesAttackSequence: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 inSuccessfulAnonymousAccess= 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.RoleBindingCreatedis normally Medium, High whenadmin/cluster-adminis involved.Execution:Kubernetes/AnomalousBehavior.WorkloadDeployedis normally Low, Medium with a suspicious image/command. - Detection → hardening, directly connected: anonymous access → cut off anonymous authentication and strip RBAC.
kube-systemexec → revoke the identity's credential. admin to the default SA → a dedicated SA + least privilege. privileged container / sensitive mount → Pod Security Standardsrestricted. 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 orgaws_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 = EKSClusterin 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) 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.
On a multi-account serverless payment platform, 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 — 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 — the list of
EKSClusterfinding types and Default severity (this article's severities follow here) - Remediating EKS Protection findings — the official procedures for anonymous access, compromised user/pod/node,
aws-authConfigMap editing, and secrets rotation - Security best practices for Amazon EKS — stripping the permissions of
system:anonymous/system:unauthenticated, RBAC hardening - GuardDuty finding format —
ThreatPurpose:Resource/Family.Mechanism!Artifactand the MITRE ATT&CK mapping of ThreatPurpose - Severity levels of GuardDuty findings — the numeric bands of Critical/High/Medium/Low (1.0–10.0)
- GuardDuty Extended Threat Detection —
AttackSequence:EKS/CompromisedCluster, the relation with EKS Protection / Runtime Monitoring - Amazon GuardDuty pricing — EKS Protection's audit-log-volume-based billing, the 30-day free trial, per-region (pricing to be verified)