Skip to main content

Cilium Host-Firewall Policy

KubeAid ships a CiliumClusterwideNetworkPolicy that locks down the host endpoint on every node. Enable it on bare-metal clusters where the public NIC needs an ingress filter; leave it disabled on cloud clusters that rely on cloud-provider security groups instead.

Overview​

When enabled, the policy:

  • Allows cluster-internal traffic by Cilium identity (node-to-node etcd/kubelet, pods, apiserver, health probes, localhost)
  • Allows admin SSH only from specified CIDRs
  • Restricts the Kubernetes API server (port 6443) to explicit source CIDRs (the node public IPs - never the open internet)
  • Allows DHCP replies (68/UDP) only from the listed DHCP servers
  • Allows HTTP (80) and HTTPS (443) plus ICMP from the world

The policy is disabled by default. There is no standalone kubeaid-cli cluster lockdown subcommand; you enable it by setting hostNetworkPolicy.enabled: true in your per-cluster Cilium values overlay (see Configuration below) and rolling it out via ArgoCD.

Configuration​

Set the following values in your per-cluster Cilium values overlay:

hostNetworkPolicy:
enabled: false # Set to true to activate the host-firewall
allowSshFrom: [] # CIDRs or bare IPs (treated as /32); empty = SSH open to world
publicPorts: [80, 443] # Service ports allowed from the world
apiserverSourceCIDRs: [] # CIDRs/IPs allowed to reach 6443/TCP; empty = no 6443 rule emitted
dhcpServerCIDRs: [] # DHCP servers whose replies (68/UDP) are allowed; empty = no DHCP rule emitted

Example: Bare-Metal Production Cluster​

hostNetworkPolicy:
enabled: true
allowSshFrom:
- "203.0.113.0/24" # Office VPN
- "198.51.100.42" # Jump host (treated as /32)
publicPorts: [80, 443]
apiserverSourceCIDRs:
- "203.0.113.0/24" # Office VPN
- "10.0.0.0/8" # Internal network

When to Enable​

Cluster TypeEnable?Reason
Bare Metal✅ YesNo cloud security group; the NIC is directly exposed
Hetzner Hybrid✅ YesBare-metal workers have exposed NICs
HCloud-only❌ NoHetzner Cloud firewall / security groups handle this
AWS / Azure❌ NoCloud-provider security groups handle this

Nodes That Get Their Address by DHCP​

The policy applies to every node, so it also filters DHCP. A DHCP client renews its lease by unicast, and the reply matches conntrack and passes. When systemd-networkd restarts (for example needrestart after unattended-upgrades), the client starts over with a broadcast. The server's unicast reply then matches no conntrack entry and is dropped. The node keeps its old address only until the lease runs out (24 hours on Hetzner Cloud), then loses it: kubelet, etcd and SSH all stop at once. Nodes created together run their first unattended-upgrade in the same window, so a whole control plane can lose its addresses within minutes of each other.

If any node gets its address by DHCP, list the DHCP servers in dhcpServerCIDRs. On a Hetzner Cloud network this is the network gateway, the first IP of the network's IP range:

hostNetworkPolicy:
enabled: true
dhcpServerCIDRs:
- "10.0.0.1" # Gateway of the 10.0.0.0/16 Hetzner Cloud network

kubeaid-cli does not set this value; add it to your per-cluster Cilium values overlay. To confirm the rule works, restart systemd-networkd on one node and check that the journal logs DHCPv4 address ... acquired within seconds. Without the rule, Hubble shows the replies as POLICY_DENIED ingress drops from the DHCP server's port 67 to UDP port 68 on the node.

How It Works​

The policy is templated at cilium/templates/host-firewall-policy.yaml and creates a CiliumClusterwideNetworkPolicy targeting the host endpoint. Traffic not matching any allow rule is dropped by default (Cilium's host-firewall operates in default-deny mode when a policy is attached to the host endpoint).

Enabling After Pivot​

After the cluster is bootstrapped and the management cluster has pivoted to the workload cluster, apply the same hostNetworkPolicy.enabled: true values change and let ArgoCD sync it. Note that kubeaid-cli's interactive config prompt separately exposes a lockdown field that gets rendered into the generated cluster.lockdown setting — that's unrelated config plumbing, not a trigger for this policy.

See Also​