SysDeck/firewall/templates/cilium.sh

348 lines
12 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Name: cilium
# Description: Cilium eBPF datapath backend. Replaces nftables with eBPF programs attached at XDP and tc ingress/egress. Manages Cilium network policies via cilium-cli. Requires cilium + cilium-agent (or cilium-agent container). Installs cleanly via Helm or the official cilium-cli install script. Designed for eBPF-capable kernels (5.10+).
# Distro: arch,debian
# Services: cilium
#
# ============================================================================
# cilium.sh - Cilium eBPF Firewall Backend for SysDeck
# ============================================================================
#
# This template implements the standard SysDeck firewall template interface
# (start/stop/restart/detect/status/check) but the datapath is Cilium eBPF
# programs, NOT nftables rules. Cilium manages its own BPF maps and
# programs; nftables is left untouched (or explicitly flushed of any
# sysdeck-firewall table to avoid conflicts).
#
# Why Cilium over nftables for this backend:
# - eBPF programs run before the kernel networking stack (XDP) — packets
# are dropped before they consume socket buffers or conntrack entries.
# - Identity-based policy (CiliumIdentity labels) instead of IP-based.
# A pod/workload keeps its policy even when its IP changes.
# - L7 policy (HTTP/gRPC/Kafka) via Envoy sidecar — nftables cannot.
# - Observable via `cilium monitor`, `cilium metrics`, Hubble flow logs.
#
# Requirements:
# - Linux kernel 5.10+ (5.15+ recommended for latest BPF features)
# - CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_XDP_SOCKETS=y
# - cilium-cli >= 0.15 (or the `cilium` shell script — same binary)
# - For standalone (non-K8s) mode: cilium-agent binary installed at
# /usr/bin/cilium-agent (Arch: AUR cilium-agent; Debian: official repo
# or upstream .deb). Cilium 1.14+ supports standalone mode natively.
# - Helm 3 (optional — only if the operator chooses K8s-based install)
#
# Anti-requirements (why other backends are NOT this one):
# - UFW: frontend for nftables/iptables — does not use eBPF. Skipped
# per user directive.
# - fwbuilder: GUI rule generator — too complex for the average user.
# Skipped per user directive.
# - iptables-legacy / iptables-nft wrapper: legacy. The eBPF era has
# moved past it. Skipped per user directive.
#
# Subcommands (standard SysDeck firewall template interface):
# start install cilium (if missing) + apply the default policy
# stop delete all Cilium policies + disable cilium-agent
# restart stop + start
# detect print service detection summary (cilium version, kernel
# BPF features, agent status, endpoint count)
# status print cilium status + policy summary
# check cilium policy validate (syntax check)
#
# ============================================================================
set -euo pipefail
IFS=$'\n\t'
# ============================================================================
# CONFIGURATION
# ============================================================================
CILIUM_BIN="${CILIUM_BIN:-cilium}"
CILIUM_AGENT_BIN="${CILIUM_AGENT_BIN:-/usr/bin/cilium-agent}"
CILIUM_AGENT_SVC="${CILIUM_AGENT_SVC:-cilium-agent.service}"
HELM_BIN="${HELM_BIN:-helm}"
# The default Cilium policy shipped with this template. It defines:
# - default-deny ingress + egress for all endpoints
# - allow DNS (UDP/TCP 53) to kube-dns / systemd-resolved
# - allow SSH (TCP 22) from anywhere
# - allow HTTP/HTTPS (TCP 80/443) from anywhere
# The operator can drop a custom policy at
# /etc/sysdeck/firewall/cilium-policy.yaml to override.
POLICY_FILE="${POLICY_FILE:-/etc/sysdeck/firewall/cilium-policy.yaml}"
DEFAULT_POLICY_FILE="/usr/share/sysdeck/firewall/policies/cilium-default.yaml"
# ============================================================================
log_info() { printf '[cilium] [INFO] %s\n' "$*" >&2; }
log_warn() { printf '[cilium] [WARN] %s\n' "$*" >&2; }
log_error() { printf '[cilium] [ERROR] %s\n' "$*" >&2; }
die() {
log_error "$*"
exit 1
}
have() { command -v "$1" >/dev/null 2>&1; }
# ============================================================================
# PRE-FLIGHT
# ============================================================================
check_root() {
[[ $EUID -eq 0 ]] || die "This action requires root. The cockpit superuser channel should provide it."
}
check_cilium_installed() {
if ! have "$CILIUM_BIN"; then
cat >&2 <<EOF
[cilium] cilium-cli is not installed.
Install on Arch: sudo pacman -S cilium-cli (or AUR: cilium-cli-bin)
Install on Debian: sudo apt install cilium-cli (or upstream .deb)
Install via Helm: helm repo add cilium https://helm.cilium.io/ \\
helm install cilium cilium/cilium -n kube-system
Once installed, re-run this template. The bridge firewall.py
install-backend subcommand can also install it via the packages module.
EOF
return 1
fi
return 0
}
check_kernel_bpf() {
local kver
kver="$(uname -r)"
local major="${kver%%.*}"
if [[ "$major" -lt 5 ]]; then
log_warn "Kernel $kver is older than 5.10 — Cilium may not function. Recommended: 5.15+."
fi
if ! have bpftool; then
log_warn "bpftool not found — BPF feature probing will be skipped."
fi
}
# ============================================================================
# DETECT
# ============================================================================
fw_detect() {
local os_name="unknown"
if [[ -f /etc/os-release ]]; then
. /etc/os-release
os_name="${PRETTY_NAME:-${NAME:-unknown}}"
fi
local kernel_ver
kernel_ver="$(uname -r)"
local cilium_ver="not installed"
if have "$CILIUM_BIN"; then
cilium_ver="$("$CILIUM_BIN" version --short 2>/dev/null | head -1 || echo 'unknown')"
fi
local agent_status="not running"
if systemctl is-active --quiet "$CILIUM_AGENT_SVC" 2>/dev/null; then
agent_status="running"
fi
local endpoint_count="n/a"
if have "$CILIUM_BIN"; then
endpoint_count="$("$CILIUM_BIN" endpoint list -o json 2>/dev/null | \
python3 -c 'import json,sys; print(len(json.load(sys.stdin)))' 2>/dev/null || echo 'n/a')"
fi
local bpf_features="unknown"
if have bpftool; then
if bpftool feature probe kernel 2>/dev/null | grep -q 'eBPF program_type fentry'; then
bpf_features="fentry,fexit, LSM (modern)"
else
bpf_features="legacy (kprobe-based)"
fi
fi
cat <<EOF
+-----------------------------------------------------+
| Service Detection Summary |
+-----------------------------------------------------+
| OS: ${os_name}
| Kernel: ${kernel_ver}
| BPF features: ${bpf_features}
| Template: cilium (eBPF datapath)
| cilium-cli: ${cilium_ver}
| cilium-agent: ${agent_status}
| Endpoints: ${endpoint_count}
| Policy file: ${POLICY_FILE}
+-----------------------------------------------------+
EOF
}
# ============================================================================
# START
# ============================================================================
fw_start() {
log_info "Starting Cilium eBPF firewall backend..."
check_root
check_cilium_installed || return 1
check_kernel_bpf
# Flush any leftover nftables inet firewall table from a previous
# 'custom'/'sysdeck-fw' backend. Cilium manages its own
# datapath — a stale nftables table would conflict.
if have nft; then
nft delete table inet firewall 2>/dev/null || true
log_info "Cleared any stale nftables 'firewall' table."
fi
# Ensure cilium-agent is running (standalone mode). On K8s, cilium
# runs as a DaemonSet and this is a no-op.
if [[ -x "$CILIUM_AGENT_BIN" ]] && ! systemctl is-active --quiet "$CILIUM_AGENT_SVC" 2>/dev/null; then
log_info "Starting $CILIUM_AGENT_SVC ..."
systemctl start "$CILIUM_AGENT_SVC" || log_warn "cilium-agent did not start — assuming K8s DaemonSet mode."
fi
# Wait briefly for cilium API to be reachable.
local i
for i in 1 2 3 4 5; do
if "$CILIUM_BIN" status --brief >/dev/null 2>&1; then
break
fi
sleep 1
done
# Apply the default policy (or the operator's override).
local policy="$DEFAULT_POLICY_FILE"
if [[ -f "$POLICY_FILE" ]]; then
policy="$POLICY_FILE"
log_info "Using operator policy: $POLICY_FILE"
else
log_info "Using shipped default policy: $DEFAULT_POLICY_FILE"
fi
if [[ ! -f "$policy" ]]; then
die "Policy file not found: $policy. Reinstall the sysdeck package."
fi
log_info "Validating policy..."
if ! "$CILIUM_BIN" policy validate "$policy" 2>&1; then
die "Policy validation failed."
fi
log_info "Applying policy..."
if "$CILIUM_BIN" policy apply "$policy" 2>&1; then
log_info "Cilium policy applied successfully."
else
die "Policy apply failed."
fi
}
# ============================================================================
# STOP
# ============================================================================
fw_stop() {
log_info "Stopping Cilium eBPF firewall backend..."
check_root
if ! check_cilium_installed; then
log_warn "cilium-cli not installed — nothing to stop."
return 0
fi
# Delete all Cilium policies. Cilium's default posture without a
# policy is allow-all — stopping the agent opens the host; the
# operator hears that decision, not just "done".
# This does NOT unload the BPF programs — cilium-agent keeps running
# so the operator can re-apply a policy without reinstalling.
log_warn "stopping the cilium backend returns the host to allow-all — re-apply a policy or load an nftables template before exposing the host"
"$CILIUM_BIN" policy delete --all 2>/dev/null || log_warn "policy delete --all failed (no policies loaded?)."
if [[ -x "$CILIUM_AGENT_BIN" ]] && systemctl is-active --quiet "$CILIUM_AGENT_SVC" 2>/dev/null; then
log_info "Stopping $CILIUM_AGENT_SVC ..."
systemctl stop "$CILIUM_AGENT_SVC" || log_warn "could not stop cilium-agent."
fi
log_info "Cilium backend stopped (BPF programs removed when agent exits)."
}
# ============================================================================
# RESTART
# ============================================================================
fw_restart() {
fw_stop
sleep 1
fw_start
}
# ============================================================================
# STATUS
# ============================================================================
fw_status() {
if ! check_cilium_installed; then
echo "cilium-cli not installed."
return 0
fi
"$CILIUM_BIN" status 2>&1 || true
echo
echo "--- Policy summary ---"
"$CILIUM_BIN" policy get 2>&1 | head -40 || true
}
# ============================================================================
# CHECK
# ============================================================================
fw_check() {
check_cilium_installed || return 1
local policy="$DEFAULT_POLICY_FILE"
[[ -f "$POLICY_FILE" ]] && policy="$POLICY_FILE"
if [[ ! -f "$policy" ]]; then
die "Policy file not found: $policy"
fi
"$CILIUM_BIN" policy validate "$policy"
}
# ============================================================================
# DISPATCH
# ============================================================================
main() {
local command="${1:-help}"
case "$command" in
start) fw_start ;;
stop) fw_stop ;;
restart|reload) fw_restart ;;
detect) fw_detect ;;
status) fw_status ;;
check|validate) fw_check ;;
help|--help|-h)
cat <<EOF
cilium.sh — Cilium eBPF firewall backend for SysDeck
Usage: cilium.sh <start|stop|restart|detect|status|check>
Subcommands:
start install cilium (if missing) + apply the default policy
stop delete all Cilium policies + stop cilium-agent
restart stop + start
detect print detection summary (cilium version, kernel BPF features)
status print cilium status + policy summary
check validate the policy file
Environment variables:
CILIUM_BIN path to cilium CLI (default: cilium)
CILIUM_AGENT_BIN path to cilium-agent binary (default: /usr/bin/cilium-agent)
POLICY_FILE operator policy override (default: /etc/sysdeck/firewall/cilium-policy.yaml)
EOF
;;
*)
die "Unknown command: $command\nRun 'cilium.sh help' for usage."
;;
esac
}
main "$@"