Falco macros and lists let you define reusable snippets of logic and sets of values to keep your Falco rules DRY (Don’t Repeat Yourself).
Let’s see Falco in action. Imagine you have a bunch of rules that all care about specific sensitive directories. Instead of listing them out every time, you can create a list:
lists:
- name: sensitive_dirs
items:
- /etc/ssl
- /etc/kubernetes
- /var/lib/etcd
- /var/run/secrets/kubernetes.io/serviceaccount
Now, you can use this list in a rule. Let’s say you want to alert if any process writes to these directories:
rules:
- rule: Write to sensitive directory
desc: Alert on any process writing to a sensitive directory.
condition: evt.type = write and fd.directory in (sensitive_dirs)
output: Write to sensitive directory (user=%user.name command=%proc.cmdline fd.name=%fd.name)
priority: ERROR
This is already much cleaner. But what if you want to combine conditions or create more complex reusable logic? That’s where macros come in.
Consider a macro that checks if a process is running as root and if it’s a shell:
macros:
- name: is_root_shell
condition: user.uid = 0 and proc.name in (bash, sh, zsh)
You can then use this macro in a rule:
rules:
- rule: Root shell spawned
desc: Alert if a root shell is spawned by a non-shell process.
condition: evt.type = execve and proc.name != bash and proc.name != sh and proc.name != zsh and is_root_shell
output: Root shell spawned by unexpected process (parent=%proc.name command=%proc.cmdline)
priority: HIGH
Notice how is_root_shell simplifies the condition significantly.
You can also combine macros and lists. Let’s say you want to alert on network connections to external IPs from processes running as root:
lists:
- name: sensitive_ports
items:
- 22 # SSH
- 6443 # Kubernetes API
- 2379 # etcd client
macros:
- name: is_privileged_process
condition: user.uid = 0
rules:
- rule: Privileged process external connection
desc: Alert on privileged processes connecting to external IPs on sensitive ports.
condition: evt.type = connect and is_privileged_process and fd.cip != "127.0.0.1" and fd.cip != "::1" and fd.sport in (sensitive_ports)
output: Privileged process external connection (user=%user.name command=%proc.cmdline dest_ip=%fd.cip dest_port=%fd.sport)
priority: CRITICAL
Here, is_privileged_process is a simple macro, and sensitive_ports is a list. The rule combines them to create a very specific alert.
The power of macros and lists isn’t just in reducing character count; it’s in creating a shared vocabulary for your security posture. When you need to update what constitutes a "sensitive directory" or a "privileged process," you change it in one place (the list or macro definition), and all rules referencing it are updated automatically. This significantly reduces the chance of misconfiguration and makes your Falco rule management much more robust and maintainable.
You can also define macros that return a boolean expression. For instance, a macro to check if a process is a known web server:
macros:
- name: is_web_server
condition: proc.name in (nginx, apache2, httpd, caddy, traefik)
This macro can then be used in conditions like evt.type = network and is_web_server.
When defining macros, the condition field expects a valid Falco rule condition string. This means you can use all the standard Falco operators (and, or, not, in, !=, =, etc.) and field comparisons within your macro definitions. The same applies to list items, which can be strings, numbers, or even IP addresses/CIDRs.
One thing most people don’t realize is that you can define macros that are other macros. This allows for a hierarchical composition of logic. For example, you could have a is_network_process macro and then build an is_sensitive_network_process macro that requires both is_network_process to be true and the destination port to be in a specific list of ports. This is a powerful way to build up complex security checks from simpler, more manageable building blocks.
The next step in mastering Falco rules is understanding how to use the append operator within macros, which allows you to dynamically build conditions based on multiple values.