#!/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 </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 </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 < 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 "$@"