Runtime Security for CKS¶
π Kubernetes Security Overview - Cloud native security and the 4C's model
Falco Runtime Security¶
π Falco Documentation - Cloud-native runtime security project π Falco Rules - Rule syntax and management
What is Falco?¶
Falco is a cloud-native runtime security tool that detects unexpected application behavior and alerts on threats at runtime. It monitors system calls using eBPF or a kernel module and matches them against a set of security rules.
Falco Architecture¶
- Kernel Module or eBPF probe - Captures system calls from the kernel
- Libraries - Filters and processes system call events
- Rules Engine - Matches events against security rules
- Alert Outputs - Sends alerts to stdout, files, syslog, HTTP endpoints, gRPC
Falco Rule Structure¶
- rule: Terminal Shell in Container
desc: A shell was used as the entrypoint/exec in a container
condition: >
spawned_process and container and
shell_procs and proc.tty != 0 and
container_entrypoint
output: >
A shell was spawned in a container with an attached terminal
(user=%user.name user_loginuid=%user.loginuid %container.info
shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline
terminal=%proc.tty container_id=%container.id
image=%container.image.repository)
priority: NOTICE
tags: [container, shell, mitre_execution]
Rule Components¶
- rule - The name of the rule
- desc - Description of what the rule detects
- condition - The filter expression using Falco fields (system call data)
- output - The alert message format with field substitutions
- priority - Severity level (EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG)
- tags - Categorization tags for the rule
Common Falco Macros and Lists¶
Macros (reusable condition snippets): - container - Event is inside a container - spawned_process - A new process was created - open_read - A file was opened for reading - open_write - A file was opened for writing - shell_procs - Process is a shell (bash, sh, zsh, etc.)
Lists (reusable value arrays): - shell_binaries - bash, sh, zsh, csh, dash - sensitive_file_names - /etc/shadow, /etc/sudoers, etc.
Custom Falco Rules for CKS¶
Detect shell spawning in containers:
- rule: Shell in Container
desc: Detect shell spawned inside a container
condition: >
spawned_process and container and
proc.name in (bash, sh, zsh, dash, csh)
output: >
Shell spawned in container (user=%user.name
container=%container.name image=%container.image.repository
shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline
pid=%proc.pid)
priority: WARNING
tags: [container, shell]
Detect sensitive file reads:
- rule: Read Sensitive File in Container
desc: Detect reading of sensitive files inside containers
condition: >
open_read and container and
fd.name in (/etc/shadow, /etc/sudoers, /etc/pam.d)
output: >
Sensitive file opened (user=%user.name file=%fd.name
container=%container.name image=%container.image.repository)
priority: WARNING
tags: [container, filesystem, sensitive_data]
Detect writing to /etc directory:
- rule: Write to etc in Container
desc: Detect file writes to /etc inside containers
condition: >
open_write and container and
fd.name startswith /etc
output: >
File written to /etc (user=%user.name file=%fd.name
container=%container.name image=%container.image.repository)
priority: ERROR
tags: [container, filesystem]
Falco Configuration¶
π Falco Configuration - Configuration options
# /etc/falco/falco.yaml (key settings)
rules_file:
- /etc/falco/falco_rules.yaml
- /etc/falco/falco_rules.local.yaml # Custom rules go here
- /etc/falco/rules.d # Additional rules directory
json_output: true # JSON format for log parsing
log_stderr: true
log_syslog: true
log_level: info
stdout_output:
enabled: true
file_output:
enabled: true
filename: /var/log/falco/events.log
Kubernetes Audit Logging¶
π Auditing - Kubernetes audit logging documentation π Audit Policy - Audit policy API reference
Audit Levels¶
- None - Do not log events matching this rule
- Metadata - Log request metadata (user, timestamp, resource, verb) but not body
- Request - Log metadata and request body, but not response body
- RequestResponse - Log metadata, request body, and response body
Audit Policy Structure¶
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
rules:
# Rule order matters - first match wins
# Don't log health checks and API discovery
- level: None
nonResourceURLs:
- "/healthz*"
- "/readyz*"
- "/livez*"
- "/api*"
# Don't log watch requests (too noisy)
- level: None
verbs: ["watch"]
# Log secret operations at maximum detail
- level: RequestResponse
resources:
- group: ""
resources: ["secrets"]
# Log RBAC changes at Request level
- level: Request
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
# Log pod creation/deletion at Request level
- level: Request
verbs: ["create", "delete"]
resources:
- group: ""
resources: ["pods"]
# Log everything else at Metadata level
- level: Metadata
Enabling Audit Logging on API Server¶
Add to the API server manifest (/etc/kubernetes/manifests/kube-apiserver.yaml):
spec:
containers:
- command:
- kube-apiserver
- --audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=10
- --audit-log-maxsize=100
volumeMounts:
- mountPath: /etc/kubernetes/audit
name: audit-policy
readOnly: true
- mountPath: /var/log/kubernetes/audit
name: audit-log
volumes:
- hostPath:
path: /etc/kubernetes/audit
type: DirectoryOrCreate
name: audit-policy
- hostPath:
path: /var/log/kubernetes/audit
type: DirectoryOrCreate
name: audit-log
Analyzing Audit Logs¶
# View recent audit events
tail -f /var/log/kubernetes/audit/audit.log | jq .
# Filter for secret access
cat /var/log/kubernetes/audit/audit.log | jq 'select(.objectRef.resource=="secrets")'
# Filter for specific user
cat /var/log/kubernetes/audit/audit.log | jq 'select(.user.username=="system:serviceaccount:default:suspicious-sa")'
# Filter for failed requests
cat /var/log/kubernetes/audit/audit.log | jq 'select(.responseStatus.code >= 400)'
Container Immutability¶
Read-Only Root Filesystem¶
apiVersion: v1
kind: Pod
metadata:
name: immutable-pod
spec:
containers:
- name: app
image: myapp:v1.0
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
- name: var-run
mountPath: /var/run
volumes:
- name: tmp
emptyDir: {}
- name: var-run
emptyDir:
medium: Memory # tmpfs - stored in memory
Why Immutability Matters¶
- Prevents attackers from modifying binaries or installing tools after compromise
- Detects unauthorized changes through filesystem errors
- Forces infrastructure-as-code practices
- Reduces the blast radius of container compromises
Implementing Immutability¶
- Set
readOnlyRootFilesystem: truein security context - Mount
emptyDirvolumes for directories that need writes (tmp, cache, logs) - Use
emptyDir.medium: Memoryfor sensitive temporary data - Avoid writable volumes mounted from the host
- Use immutable ConfigMaps and Secrets when possible
Threat Detection Patterns¶
Attack Lifecycle in Kubernetes¶
- Initial Access - Compromised container image, exposed API server, vulnerable application
- Execution - Shell spawning, script execution, binary download
- Persistence - New pods/deployments, modified RBAC, cronjobs
- Privilege Escalation - Privileged containers, hostPID, token theft
- Defense Evasion - Log deletion, pod deletion, timestamp manipulation
- Credential Access - Secret reading, service account token theft
- Discovery - API server enumeration, network scanning
- Lateral Movement - Pod-to-pod exploitation, service account abuse
- Impact - Crypto mining, data exfiltration, denial of service
Detection Strategies¶
- Falco rules for real-time syscall monitoring
- Audit logs for API-level activity tracking
- NetworkPolicies to detect (via denied connections) lateral movement attempts
- Resource monitoring for unexpected CPU/memory usage (crypto mining)
- File integrity monitoring for unauthorized changes
Forensic Investigation¶
Investigating a Compromised Container¶
# Check running processes
kubectl exec suspicious-pod -- ps aux
# Check network connections
kubectl exec suspicious-pod -- netstat -tlnp
# Check file system changes
kubectl exec suspicious-pod -- find / -newer /etc/hostname -type f 2>/dev/null
# Copy files from container for analysis
kubectl cp suspicious-pod:/var/log/suspicious.log ./evidence/
# Check container events
kubectl describe pod suspicious-pod | grep -A 20 Events
# Review container logs
kubectl logs suspicious-pod --previous
Containment Steps¶
- Isolate the pod with a deny-all NetworkPolicy
- Capture evidence (logs, filesystem state, network connections)
- Do not delete the pod immediately - preserve for forensics
- Check if the compromise has spread to other pods
- Review audit logs for the compromised pod's service account activity
- Rotate any credentials the compromised pod had access to
Key Takeaways¶
- Falco - Primary runtime detection tool; know rule syntax, macros, and common detection patterns
- Audit Logging - Configure with ordered rules; first match wins; always exclude health checks
- Immutability - Use readOnlyRootFilesystem and emptyDir for writable directories
- Defense in Depth - Combine Falco, audit logs, NetworkPolicies, and monitoring
- Investigation - Know how to examine a compromised container and preserve evidence
- Volume Mounts - Remember to add both volumes and volumeMounts when configuring audit logging on the API server