Guide · AWS GuardDuty · EKS Runtime Monitoring
GuardDuty EKS Runtime Monitoring for MCP Servers — Kubernetes Threat Detection
GuardDuty EKS protection has two independent layers: EKS Audit Log Monitoring (analyzes Kubernetes control-plane API calls — who created which pods, RBAC changes, exec commands) and EKS Runtime Monitoring (installs a DaemonSet security agent on each node to monitor process execution, network connections, and file system access inside containers at runtime). Audit log monitoring covers RBAC attacks, dashboard exposure, and Kubernetes API abuse — threats that happen at the control plane level. Runtime monitoring covers post-exploitation: what happens inside a container after an attacker gains entry. For MCP servers running on EKS, both layers are needed: audit logs catch the initial compromise vector (malicious kubectl exec, privileged container deployment), while runtime monitoring catches what the attacker does next (downloading malware, establishing reverse shell, lateral movement).
TL;DR
Enable both EKS Audit Log Monitoring and EKS Runtime Monitoring as separate GuardDuty features. Runtime monitoring deploys a DaemonSet (aws-guardduty-agent) in the amazon-guardduty namespace — use managed add-on mode so GuardDuty handles agent lifecycle. Audit log findings cover RBAC and API abuse; runtime findings cover in-container process/network behavior. Create suppression filters for kubectl exec performed by your CI/CD principal and container image scanning tools to avoid alert fatigue. See the main GuardDuty guide for detector setup and the remediation guide for automated pod isolation patterns.
Enable EKS audit log monitoring
EKS Audit Log Monitoring enables GuardDuty to analyze the EKS control-plane audit log stream. EKS must have control-plane logging enabled — specifically the audit log type — for GuardDuty to receive the audit events. GuardDuty uses its own internal log delivery path (not your CloudWatch Logs delivery), so you do not need to route EKS audit logs to CloudWatch for GuardDuty to work, but if you want both, enable both independently.
# Enable EKS Audit Log Monitoring in GuardDuty
aws guardduty update-detector \
--detector-id abc1234567890abcdef1234567890 \
--features '[
{
"Name": "EKS_AUDIT_LOGS",
"Status": "ENABLED"
}
]'
# Verify EKS audit logs are enabled on the EKS cluster itself
# (GuardDuty uses its own delivery, but good practice to also enable CloudWatch delivery)
aws eks update-cluster-config \
--name mcp-production-cluster \
--logging '{
"clusterLogging": [{
"types": ["api", "audit", "authenticator", "controllerManager", "scheduler"],
"enabled": true
}]
}'
# Check GuardDuty feature status
aws guardduty get-detector \
--detector-id abc1234567890abcdef1234567890 \
--query 'Features[?Name==`EKS_AUDIT_LOGS`]'
EKS Audit Log Monitoring is priced per EKS vCPU-hour across all clusters in the account — not per finding or per event volume. Cost scales with the number of worker nodes running, not with cluster activity level.
Enable EKS runtime monitoring
EKS Runtime Monitoring adds the in-container agent layer. It deploys the aws-guardduty-agent DaemonSet in the amazon-guardduty namespace on every node in selected (or all) clusters. You can deploy via managed add-on (GuardDuty manages the agent lifecycle, updates, and version compatibility) or self-managed DaemonSet (you manage the manifest). Managed add-on is strongly preferred for production MCP clusters.
# Enable EKS Runtime Monitoring in GuardDuty
aws guardduty update-detector \
--detector-id abc1234567890abcdef1234567890 \
--features '[
{
"Name": "RUNTIME_MONITORING",
"Status": "ENABLED",
"AdditionalConfiguration": [
{
"Name": "EKS_ADDON_MANAGEMENT",
"Status": "ENABLED"
}
]
}
]'
# EKS_ADDON_MANAGEMENT = ENABLED means GuardDuty auto-deploys and manages
# the aws-guardduty-agent add-on on all opted-in clusters
# Opt specific clusters into runtime monitoring (SELECTIVE mode)
# By default all clusters are monitored; use selective if you have dev clusters
# to exclude from runtime monitoring cost
aws guardduty update-detector \
--detector-id abc1234567890abcdef1234567890 \
--features '[
{
"Name": "RUNTIME_MONITORING",
"Status": "ENABLED",
"AdditionalConfiguration": [
{
"Name": "EKS_ADDON_MANAGEMENT",
"Status": "ENABLED"
}
]
}
]'
# After enabling, GuardDuty creates the add-on on opted-in clusters
# Verify the add-on is running:
aws eks list-addons --cluster-name mcp-production-cluster \
--query 'addons[?contains(@, `aws-guardduty`)]'
# Check DaemonSet status on the cluster (kubectl)
# kubectl get daemonset aws-guardduty-agent \
# --namespace amazon-guardduty \
# -o wide
# Verify all nodes have the agent pod running:
# kubectl get pods --namespace amazon-guardduty \
# --field-selector spec.nodeName=
If the GuardDuty agent DaemonSet is not running on a node, runtime findings are not generated for containers on that node — there is no error or warning, just silent missing coverage. After enabling runtime monitoring, always verify the DaemonSet pod count matches the number of nodes in your cluster. On Fargate nodes, runtime monitoring is not supported (Fargate does not support DaemonSets); Fargate-based MCP servers are protected only by EKS Audit Log Monitoring.
EKS Audit Log finding types
Audit log findings analyze the Kubernetes control-plane event stream. These findings represent RBAC changes, unauthorized API access, and suspicious pod configurations detected at schedule/deploy time — before any container code runs.
| Finding type | What triggered it | MCP server implication |
|---|---|---|
Policy:Kubernetes/AdminAccessToDefaultServiceAccount | ClusterRoleBinding grants cluster-admin to default service account in any namespace | Any pod in that namespace can make arbitrary Kubernetes API calls — escape path for compromised MCP container |
Policy:Kubernetes/AnonymousAccessGranted | RBAC binding grants permissions to system:anonymous or system:unauthenticated | Unauthenticated access to Kubernetes API — check if your MCP server's service account needs RBAC tightening |
Policy:Kubernetes/KubeflowDashboardExposed | Kubeflow or other dashboard service exposed via LoadBalancer or NodePort without auth | Not directly MCP-related but indicates a misconfigured cluster that may expose other services |
Persistence:Kubernetes/ContainerWithSensitiveMount | Container deployed with hostPath mount to sensitive paths (/etc/passwd, /etc/shadow, /proc) | Container can read/write host credentials — check if MCP server deployment uses any hostPath mounts |
PrivilegeEscalation:Kubernetes/PrivilegedContainer | Container deployed with securityContext.privileged: true | Container has full root access to host — MCP server deployment should never need privileged mode |
Execution:Kubernetes/ExecIntoContainer | kubectl exec or API equivalent executed into a running container | Legitimate for debugging; suppress for your CI/CD principal; investigate if principal is unexpected |
Discovery:Kubernetes/MaliciousIPCaller | Kubernetes API calls from a known-malicious IP | Someone with stolen credentials calling K8s API from compromised host — rotate kubeconfig and IRSA tokens |
# Check finding details for an Execution:Kubernetes/ExecIntoContainer finding
# Key fields: who ran exec, into which pod, from where
aws guardduty get-findings \
--detector-id abc1234567890abcdef1234567890 \
--finding-ids "exec-finding-id" \
--query 'Findings[0].{
Type:Type,
Severity:Severity,
Principal:Resource.KubernetesDetails.KubernetesUserDetails,
Target:Resource.KubernetesDetails.KubernetesWorkloadDetails,
Action:Service.Action.KubernetesApiCallAction
}'
# KubernetesUserDetails:
# {
# "Username": "arn:aws:sts::123456789012:assumed-role/developer-role/session",
# "Uid": "...",
# "Groups": ["system:masters", "system:authenticated"]
# }
# If Username is your CI/CD role → suppress
# If Username is system:anonymous or unexpected → investigate immediately
EKS runtime finding types
Runtime findings require the DaemonSet agent and capture in-container behavior: process execution, library loading, network connections, and file system events inside running MCP server containers.
| Finding type | What triggered it | MCP server context |
|---|---|---|
Execution:Kubernetes/NewBinaryExecutedInContainer | A binary was executed inside a container that was not present in the original image | Indicates post-compromise binary download and execution; also fires for container startup scripts that download tools at runtime |
Execution:Kubernetes/NewLibraryLoadedInContainer | A shared library was loaded that was not present in the original image | Indicates dynamic library injection; common in memory-based attacks |
PrivilegeEscalation:Kubernetes/ContainerMountsHostDirectory | At runtime, a process inside the container accessed a host directory mount that includes sensitive paths | Different from the audit-log finding — this detects actual access of the mount, not just configuration |
Backdoor:Kubernetes/C&CActivity.B | Container process making network connections to known C2 server IPs/domains | Post-compromise communication from within MCP server container — immediate isolation required |
DefenseEvasion:Kubernetes/ProcessInjected | Process injection detected inside a container (e.g., ptrace, /proc/mem write) | Sophisticated in-container attack; attacker has code execution in MCP server process |
Impact:Kubernetes/CryptoCurrencyMining | Container process executing known cryptocurrency miner binary | Container compromised and used for crypto mining; likely via code injection in MCP tool handler |
# Get runtime finding details — note containerDetails in the resource
aws guardduty get-findings \
--detector-id abc1234567890abcdef1234567890 \
--finding-ids "runtime-finding-id" \
--query 'Findings[0].Resource.KubernetesDetails.KubernetesWorkloadDetails'
# KubernetesWorkloadDetails:
# {
# "Name": "mcp-server-deployment-abc123",
# "Type": "Pod",
# "Uid": "pod-uid",
# "Namespace": "production",
# "Containers": [{
# "Name": "mcp-server",
# "Image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/mcp-server:v1.2.3",
# "ContainerId": "containerd://abc...",
# "SecurityContext": {
# "Privileged": false,
# "AllowPrivilegeEscalation": false
# }
# }]
# }
# Service.RuntimeDetails provides the process that triggered the finding:
aws guardduty get-findings \
--detector-id abc1234567890abcdef1234567890 \
--finding-ids "runtime-finding-id" \
--query 'Findings[0].Service.RuntimeDetails'
# RuntimeDetails:
# {
# "Process": {
# "Name": "curl",
# "ExecutablePath": "/tmp/curl", ← /tmp is suspicious for a compiled binary
# "ExecutableSha256": "abc...",
# "Pid": 12345,
# "Uuid": "process-uuid",
# "ParentUuid": "parent-uuid",
# "User": "root",
# "UserUid": "0"
# },
# "Context": {
# "ModifyingProcess": {...}
# }
# }
Suppression patterns for EKS workloads
EKS environments generate several legitimate activities that GuardDuty flags as suspicious. Create suppression filters before your first deployment cycle to prevent alert fatigue.
# Suppress Execution:Kubernetes/ExecIntoContainer for CI/CD principal
# (e.g., Jenkins, GitHub Actions runner IAM role running kubectl exec for health checks)
aws guardduty create-filter \
--detector-id abc1234567890abcdef1234567890 \
--name "cicd-kubectl-exec-suppression" \
--action ARCHIVE \
--rank 1 \
--finding-criteria '{
"Criterion": {
"type": {
"Equals": ["Execution:Kubernetes/ExecIntoContainer"]
},
"resource.kubernetesDetails.kubernetesUserDetails.username": {
"Prefix": ["arn:aws:sts::123456789012:assumed-role/github-actions-role/"]
}
}
}'
# Suppress NewBinaryExecutedInContainer for container image scanner (Trivy, etc.)
# Scanner may execute binaries during scan; filter by the scanner pod name
aws guardduty create-filter \
--detector-id abc1234567890abcdef1234567890 \
--name "image-scanner-suppression" \
--action ARCHIVE \
--rank 2 \
--finding-criteria '{
"Criterion": {
"type": {
"Equals": ["Execution:Kubernetes/NewBinaryExecutedInContainer"]
},
"resource.kubernetesDetails.kubernetesWorkloadDetails.name": {
"Prefix": ["trivy-scan-", "snyk-scan-"]
}
}
}'
# Suppress PrivilegedContainer for known infrastructure pods
# (node-local-dns, kube-proxy, vpc-cni require privileged mode in some configurations)
aws guardduty create-filter \
--detector-id abc1234567890abcdef1234567890 \
--name "infra-privileged-container-suppression" \
--action ARCHIVE \
--rank 3 \
--finding-criteria '{
"Criterion": {
"type": {
"Equals": ["PrivilegeEscalation:Kubernetes/PrivilegedContainer"]
},
"resource.kubernetesDetails.kubernetesWorkloadDetails.namespace": {
"Equals": ["kube-system"]
}
}
}'
Failure modes reference
| Failure | Symptom | Fix |
|---|---|---|
| Runtime findings not generated despite runtime monitoring enabled | No Execution:Kubernetes/* findings from known container events | Check DaemonSet pod count equals node count — agent not running on a node means no coverage for that node; also check agent version is compatible with kernel version |
| Fargate pods not covered by runtime monitoring | EKS Fargate workloads never generate runtime findings | Expected — GuardDuty runtime agent requires DaemonSet which is not supported on Fargate; only audit log findings cover Fargate pods |
| EKS_AUDIT_LOGS enabled but no audit findings | kubectl exec and RBAC changes not generating findings | Verify EKS cluster has audit control-plane logging enabled (aws eks describe-cluster --query cluster.logging); GuardDuty requires audit log type to be enabled |
| Excessive NewBinaryExecutedInContainer findings from init containers | Every pod startup generates a finding | Init containers that download dependencies at startup trigger this; build dependencies into the image instead; or suppress by namespace/workload name |
| GuardDuty agent DaemonSet in CrashLoopBackOff | Pods in amazon-guardduty namespace failing | Check node kernel version compatibility (agent requires kernel 4.14+); check node IAM role has necessary permissions for GuardDuty agent; review pod logs |