eBPF, short for Extended Berkeley Packet Filter, is a kernel technology that allows programs to run without requiring changes to the kernel source code or the addition of new modules. eBPF was built on top of the Berkeley Packet Filter (cBPF). Notable milestones in its development include the first in-kernel Linux JIT compiler in April 2011 and the first non-networking use case of the classic Berkeley Packet Filter, seccomp-bpf, appearing in January 2012.
It can be used for a variety of purposes. In this blog post, we will explore how eBPF can be used for runtime security, network security, container security, and CI/CD security.
Runtime security involves protecting a running system or application from unauthorized access, malicious activities, or vulnerabilities. By leveraging eBPF, we can monitor and enforce security policies at a very low level, providing granular control over system behavior. Let's take a look at an example of how eBPF can be utilized for runtime security.
Consider a scenario where we want to detect and prevent the execution of a specific system call by a particular process. We can achieve this with an eBPF program that attaches to the kernel and intercepts the system calls made by the process. If the intercepted system call matches the one we want to block, the eBPF program can take action, such as terminating the process or logging the event for further investigation.
Here is a simplified example of an eBPF program that blocks the execve system call made by a process with a specific process ID (PID):
#include <linux/bpf.h>
#include <linux/types.h>
#include <linux/ptrace.h>
SEC("kprobe/security_sys_execve")
int bpf_sys_execve(struct pt_regs *ctx) {
pid_t pid = bpf_get_current_pid_tgid() >> 32;
if (pid == target_pid) {
bpf_kern_panic();
}
return 0;
}
With this eBPF program in place, any attempt by the specified process to execute the execve
system call will result in a kernel panic, effectively preventing the execution.
eBPF provides powerful capabilities for monitoring and controlling network traffic, allowing us to enforce security policies in real-time.
Suppose we want to block all incoming network connections except for those coming from a specific IP address range. We can achieve this with an eBPF program that attaches to the network stack and filters incoming packets based on their source IP addresses. If the source IP address is not in the allowed range, the eBPF program can drop the packet.
Here is a simplified example of an eBPF program that enforces this network security policy:
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
SEC("filter")
int bpf_filter(struct __sk_buff *skb) {
struct ethhdr *eth = bpf_hdr_pointer(skb);
struct iphdr *ip = (struct iphdr *)(eth + 1);
if (ip->saddr < start_ip || ip->saddr > end_ip) {
return XDP_DROP;
}
return XDP_PASS;
}
By attaching this eBPF program to an appropriate network interface, we can effectively block incoming network connections from IP addresses outside the specified range.
Container security is of paramount importance in today's cloud-native landscape. eBPF can help enforce security policies within containers, providing visibility and control at the kernel level. Let's explore a practical example of using eBPF for container security.
Assume we want to monitor all outgoing network connections from a container and alert on any connections to a suspicious IP address. With eBPF, we can write a program that attaches to the container's network namespace and inspects outgoing packets. If the destination IP address matches the suspicious IP address, the eBPF program can generate an alert.
Here is a simplified example of an eBPF program for monitoring container network connections:
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
SEC("kprobe/tcp_v4_connect")
int bpf_tcp_connect(struct pt_regs *ctx) {
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
struct iphdr *iph = (struct iphdr *)skb_network_header(sk->sk_skb);
if (ntohl(iph->daddr) == suspicious_ip) {
bpf_printk("Suspicious outgoing connection detected from container!");
}
return 0;
}
By attaching this eBPF program to the container's network namespace, we can monitor all outgoing TCP connections and alert on any connections to the specified suspicious IP address.
In the context of CI/CD Security, eBPF can be used to monitor and block system calls made by the pipeline. This can be done by attaching an eBPF program to the kernel that monitors the system calls made by the pipeline and blocks any unauthorized or malicious activity.
The eBPF program can be designed to monitor specific system calls made by the pipeline such as execve
, open
, write
, read
, and connect
. The program can also be designed to monitor the arguments passed to these system calls and block any unauthorized or malicious activity.
For example, if a pipeline is attempting to execute a command that is not authorized, the eBPF program can block the execve
system call and prevent the command from being executed. Similarly, if a pipeline is attempting to write to a file that is not authorized, the eBPF program can block the write
system call and prevent the write operation from being performed.
eBPF can also be used to monitor network traffic generated by the pipeline and block any unauthorized or malicious activity. For example, if a pipeline is attempting to connect to a malicious IP address, the eBPF program can block the connect
system call and prevent the connection from being established.
To secure your CI/CD pipelines using eBPF, you can leverage its capabilities to monitor and enforce security policies on outgoing network connections. Let's take an example where you want to block any outbound network connections from your CI/CD pipeline, except for specific whitelisted endpoints.
First, you need to write an eBPF program that matches outbound traffic from your CI/CD pipeline and compares the destination IP address against a whitelist. If the destination IP address is not in the whitelist, the eBPF program drops the packet.
Below is a simplified example of an eBPF program that demonstrates this behavior:
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
SEC("filter")
int block_outbound_network(struct __sk_buff *skb) {
struct ethhdr *eth = bpf_hdr_pointer(skb);
struct iphdr *ip = (struct iphdr *)(eth + 1);
if (ip->daddr != whitelist_ip) {
bpf_skb_drop(skb);
return XDP_DROP;
}
return XDP_PASS;
}
By attaching this eBPF program to the appropriate network interface used by your CI/CD pipeline, you can effectively block any outbound network connections that are not whitelisted.
Interacting with eBPF programs like this typically requires using a applications like bcc, bpftrace, Cilium or Falco. These applications provides high-level functions to simplify the process of enabling and managing eBPF programs for CI/CD security. They provide convenient APIs to load and attach eBPF programs to specific network interfaces or containers, effectively enforcing the defined security policies.
For a more complete and production-ready solution, consider leveraging existing eBPF-based security frameworks like Cilium or Falco. These frameworks offer additional features such as network visibility, deep packet inspection, and integration with orchestration platforms.
In this blog post, we explored how eBPF can be utilized for runtime security, network security, container security, and CI/CD security. We provided detailed explanations and practical examples for each use case, showcasing the capabilities of eBPF in enhancing security at a granular level. By leveraging eBPF, you can enforce custom security policies, monitor network traffic, and protect your systems from malicious activities, ultimately strengthening the overall security of your applications.