Kubernetes Pod Security Policies (PSPs) are gone, and you’re looking to replace them with Falco rules. This isn’t just a simple substitution; it’s a shift from a declarative, Kubernetes-native admission controller to a dynamic, runtime security tool.
Let’s see Falco in action. Imagine a pod trying to execute a shell inside a sensitive directory like /etc.
# In a test pod:
ls /etc
# Falco Alert: Unexpected shell in sensitive directory
This is what we’re aiming for: catching deviations from expected behavior as they happen.
The core problem PSPs solved was enforcing security best practices at admission time. They prevented pods from being created with dangerous configurations, like running as root, having privileged access, or mounting sensitive host paths. Falco, on the other hand, operates at runtime. It watches kernel system calls and Kubernetes audit logs to detect suspicious activity after a pod has been scheduled. This means Falco doesn’t prevent a bad pod from starting, but it alerts you immediately when that pod does something it shouldn’t.
The mental model for Falco is a continuous, real-time security auditor. It’s constantly listening for events and comparing them against a set of rules. These rules are written in a specific YAML format, defining conditions based on event types, process arguments, file access, network connections, and more. When a rule’s conditions are met, Falco triggers an alert.
Here’s a breakdown of how you’d translate common PSP restrictions into Falco rules:
-
Disallowing Privileged Containers (
privileged: true):- Falco Rule Concept: Alert when a container starts with the
privilegedcapability enabled. - Falco Rule:
- rule: Run privileged container desc: A privileged container can bypass most security restrictions. condition: container.privileged=true output: Privileged container detected (user: %(user.name) user_groups: %(user.groups) pod: %(k8s.pod.name) namespace: %(k8s.namespace.name) container_name: %(container.name)) priority: critical tags: [kubernetes, cis, mitre_execution] - Why it works: Falco directly inspects container metadata derived from Kubernetes API events.
container.privileged=trueis a direct flag indicating the container is running with elevated host privileges.
- Falco Rule Concept: Alert when a container starts with the
-
Disallowing Host Network (
hostNetwork: true):- Falco Rule Concept: Alert when a pod is created using the host’s network namespace.
- Falco Rule:
- rule: Use of hostNetwork desc: Pod using host networking is a security risk. condition: k8s.pod.hostnetwork=true output: Pod using hostNetwork detected (pod: %(k8s.pod.name) namespace: %(k8s.namespace.name)) priority: critical tags: [kubernetes, cis] - Why it works: Similar to privileged containers,
k8s.pod.hostnetwork=trueis a field in the Kubernetes audit event that Falco can access.
-
Disallowing Host PID Namespace (
hostPID: true):- Falco Rule Concept: Alert when a pod is created using the host’s PID namespace.
- Falco Rule:
- rule: Use of hostPID desc: Pod using host PID namespace can see all processes on the host. condition: k8s.pod.hostpid=true output: Pod using hostPID detected (pod: %(k8s.pod.name) namespace: %(k8s.namespace.name)) priority: critical tags: [kubernetes, cis] - Why it works: This again leverages the
k8s.pod.hostpid=truefield from Kubernetes audit events.
-
Disallowing Host IPC Namespace (
hostIPC: true):- Falco Rule Concept: Alert when a pod is created using the host’s IPC namespace.
- Falco Rule:
- rule: Use of hostIPC desc: Pod using host IPC namespace can access host IPC resources. condition: k8s.pod.hostipc=true output: Pod using hostIPC detected (pod: %(k8s.pod.name) namespace: %(k8s.namespace.name)) priority: critical tags: [kubernetes, cis] - Why it works: Relies on the
k8s.pod.hostipc=truefield.
-
Restricting Read-Only Root Filesystem (
readOnlyRootFilesystem: true):- Falco Rule Concept: Alert when a process attempts to write to a file in a container that is expected to have a read-only root filesystem. This is trickier as Falco doesn’t know the intent of the pod spec directly. Instead, you infer it from behavior.
- Falco Rule:
- rule: Write to read-only root filesystem desc: Attempting to write to a read-only root filesystem. condition: > writable_fs != "none" and openat.flags contains "O_WRONLY" and openat.flags contains "O_CREAT" and fd.typechar = 'f' and container.mounts.readonly = true output: Attempt to write to a read-only root filesystem (executable: %(proc.name) cmdline: %(proc.cmdline) file: %(openat.pathname) flags: %(openat.flags)) priority: warning tags: [kubernetes, defense-in-depth] - Why it works: This rule monitors file write operations (
openat.flags contains "O_WRONLY" and openat.flags contains "O_CREAT") on file descriptors (fd.typechar = 'f') where the container’s mount is explicitly marked as read-only (container.mounts.readonly = true). It requires Falco to be configured to parse container mount information.
-
Restricting Volume Mounts (e.g.,
/var/run/docker.sock):- Falco Rule Concept: Alert when a container mounts a sensitive host path, like the Docker socket.
- Falco Rule:
- rule: Mount sensitive host directory desc: Mounting sensitive host directories can lead to host compromise. condition: > k8s.audit.requestObject.spec.volumes[*] contains "hostPath.path=/var/run/docker.sock" output: Sensitive host directory mounted (pod: %(k8s.pod.name) namespace: %(k8s.namespace.name) path: %(k8s.audit.requestObject.spec.volumes[*].hostPath.path)) priority: critical tags: [kubernetes, cis] - Why it works: This rule directly inspects the Kubernetes audit log for
Podcreation or update events (k8s.audit.requestObject). It checks if any volume defined in the pod’s spec has ahostPathwith the specific path/var/run/docker.sock.
-
Restricting Capabilities (e.g.,
CAP_SYS_ADMIN):- Falco Rule Concept: Alert when a container is started with excessive or dangerous Linux capabilities.
- Falco Rule:
- rule: Add dangerous capability desc: Adding dangerous capabilities like CAP_SYS_ADMIN can lead to privilege escalation. condition: > k8s.audit.requestObject.spec.containers[*].securityContext.capabilities.add contains "SYS_ADMIN" output: Dangerous capability added to container (pod: %(k8s.pod.name) namespace: %(k8s.namespace.name) capability: SYS_ADMIN) priority: critical tags: [kubernetes, cis, mitre_privilege_escalation] - Why it works: This rule also inspects Kubernetes audit logs, specifically looking at the
securityContext.capabilities.addfield for containers within a pod’s spec. It flags the addition ofSYS_ADMINor other specified dangerous capabilities.
The shift to Falco means you’re moving from a preventative model (PSPs) to a detective model (Falco). This is a more robust security posture because it catches all suspicious activity, not just what you explicitly configured to block. However, it also means you need to be diligent about tuning your Falco rules to avoid alert fatigue. You’ll want to start with a strong baseline of rules (like those derived from CIS Kubernetes benchmarks) and then layer on custom rules for your specific application needs.
One aspect often overlooked is how Falco correlates events. While a single system call might seem benign, Falco can chain multiple events together. For instance, it can detect a process spawning a shell (execve) and then that shell attempting to modify a sensitive system file (openat with write flags). This ability to understand sequences of actions is where Falco truly shines beyond simple configuration checks.
The next hurdle you’ll likely face is managing the lifecycle of these Falco rules, especially as your Kubernetes environment evolves.