feat: add initial iptables rules via NetworkManager dispatcher#1213
feat: add initial iptables rules via NetworkManager dispatcher#1213mingshuoqiu wants to merge 3 commits intoharvester:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a NetworkManager dispatcher script to automatically apply iptables firewall rules when network interfaces come up, implementing network security controls for Harvester nodes. The solution creates custom iptables chains to manage both essential system rules and user-extensible rules.
Key changes:
- Adds a dispatcher script that runs on interface up events to configure iptables rules
- Creates two custom chains:
HARVESTER_ESSENTIAL(core Harvester/RKE2 rules) andHARVESTER_DYNAMIC(placeholder for external use) - Implements role-based firewall rules that differentiate between server and agent nodes, with default DROP policies for INPUT and FORWARD chains
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| add_rule INPUT -j HARVESTER_ESSENTIAL | ||
| add_rule INPUT -j HARVESTER_DYNAMIC |
There was a problem hiding this comment.
The jump rules to HARVESTER_ESSENTIAL and HARVESTER_DYNAMIC are added to INPUT chain before the HARVESTER_ESSENTIAL chain is populated with rules. Since add_rule uses iptables -A (append), if this script runs multiple times, the jump rules will be added again at the end of the INPUT chain, even though earlier jumps exist. This creates redundant chain jumps that will process the same packets multiple times unnecessarily.
| # Set default policies | ||
| iptables -P INPUT DROP | ||
| iptables -P FORWARD DROP |
There was a problem hiding this comment.
Setting default policies to DROP at the end of the script creates a critical race condition. If the script fails or is interrupted after line 78 but before completing, or if there's any error in rule application, the system will have DROP policies in place without the necessary ACCEPT rules, potentially locking out all network access including SSH. The default policies should only be set after confirming all rules are successfully applied, or should be set conditionally.
| # Set default policies | |
| iptables -P INPUT DROP | |
| iptables -P FORWARD DROP | |
| # Set default policies only if essential rules (e.g., SSH) are present | |
| if iptables -C HARVESTER_ESSENTIAL -p tcp --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT 2>/dev/null; then | |
| iptables -P INPUT DROP | |
| iptables -P FORWARD DROP | |
| fi |
|
|
||
| # Create custom chains | ||
| iptables -N HARVESTER_ESSENTIAL 2>/dev/null | ||
| iptables -N HARVESTER_DYNAMIC 2>/dev/null |
There was a problem hiding this comment.
The HARVESTER_DYNAMIC chain is created and referenced but never populated with any rules in this script. While the description mentions it's a "placeholder chain for users or external components," there's no documentation in the script itself explaining its purpose, how external components should use it, or any guarantees about its persistence. Add comments explaining the intended usage pattern for this chain.
|
|
||
| # Set default policies | ||
| iptables -P INPUT DROP | ||
| iptables -P FORWARD DROP |
There was a problem hiding this comment.
The default DROP policy for the FORWARD chain at line 79 may break container networking and VM traffic that relies on forwarding. Kubernetes and KVM virtual machines typically require packet forwarding to function. While there's a RELATED,ESTABLISHED rule at line 75, there's no rule to allow NEW connections for forwarded traffic, which could break legitimate traffic between containers/VMs and external networks.
| add_rule() { | ||
| iptables -C "$@" 2>/dev/null || iptables -A "$@" | ||
| } |
There was a problem hiding this comment.
The add_rule helper function adds rules using iptables -A (append), but after flushing HARVESTER_ESSENTIAL at line 21, all subsequent add_rule calls for this chain will succeed because the chain is empty. The check 'iptables -C' will never find existing rules in a just-flushed chain, making the check redundant for HARVESTER_ESSENTIAL. This pattern only provides protection for rules added to INPUT, FORWARD, and HARVESTER_DYNAMIC chains.
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 80 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT | ||
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 443 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT | ||
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT |
There was a problem hiding this comment.
Rules for ports 80 and 443 include explicit conntrack state matching with NEW,ESTABLISHED, while most other rules only use ACCEPT without state matching (or rely on the earlier ESTABLISHED,RELATED rule at line 35). This inconsistency is confusing - either all NEW connection rules should explicitly check state, or the redundant state checking for ports 80 and 443 should be removed since line 35 already handles ESTABLISHED connections.
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 80 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT | |
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 443 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT | |
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT | |
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 80 -j ACCEPT | |
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 443 -j ACCEPT | |
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 22 -j ACCEPT |
| # Create custom chains | ||
| iptables -N HARVESTER_ESSENTIAL 2>/dev/null | ||
| iptables -N HARVESTER_DYNAMIC 2>/dev/null | ||
|
|
||
| # Helper to add rule if missing | ||
| add_rule() { | ||
| iptables -C "$@" 2>/dev/null || iptables -A "$@" | ||
| } | ||
|
|
There was a problem hiding this comment.
The script creates custom chains with 'iptables -N' and redirects errors to /dev/null, which suppresses the error when chains already exist. However, there's no verification that the chains were successfully created or that they exist before attempting to use them. If iptables command fails for other reasons (e.g., missing iptables binary, permission issues), the script will continue silently and fail when trying to add rules to non-existent chains.
| # Create custom chains | |
| iptables -N HARVESTER_ESSENTIAL 2>/dev/null | |
| iptables -N HARVESTER_DYNAMIC 2>/dev/null | |
| # Helper to add rule if missing | |
| add_rule() { | |
| iptables -C "$@" 2>/dev/null || iptables -A "$@" | |
| } | |
| # Helper to ensure a chain exists (create it if missing) | |
| ensure_chain() { | |
| chain="$1" | |
| # Check if the chain already exists | |
| if ! iptables -L "$chain" >/dev/null 2>&1; then | |
| # Attempt to create the chain; if this fails, abort with an error | |
| if ! iptables -N "$chain" >/dev/null 2>&1; then | |
| echo "Failed to ensure iptables chain '$chain' exists" >&2 | |
| exit 1 | |
| fi | |
| fi | |
| } | |
| # Helper to add rule if missing | |
| add_rule() { | |
| iptables -C "$@" 2>/dev/null || iptables -A "$@" | |
| } | |
| # Ensure custom chains exist | |
| ensure_chain HARVESTER_ESSENTIAL | |
| ensure_chain HARVESTER_DYNAMIC |
| #!/bin/sh | ||
|
|
||
| if [ "$2" != "up" ]; then | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Create custom chains | ||
| iptables -N HARVESTER_ESSENTIAL 2>/dev/null | ||
| iptables -N HARVESTER_DYNAMIC 2>/dev/null | ||
|
|
||
| # Helper to add rule if missing | ||
| add_rule() { | ||
| iptables -C "$@" 2>/dev/null || iptables -A "$@" | ||
| } | ||
|
|
||
| # Ensure INPUT chain jumps to our custom chains | ||
| add_rule INPUT -j HARVESTER_ESSENTIAL | ||
| add_rule INPUT -j HARVESTER_DYNAMIC | ||
|
|
||
| # Flush HARVESTER_ESSENTIAL to ensure clean state | ||
| iptables -F HARVESTER_ESSENTIAL | ||
|
|
||
| # --- Rules for HARVESTER_ESSENTIAL --- | ||
|
|
||
| # Allow loopback | ||
| add_rule HARVESTER_ESSENTIAL -i lo -j ACCEPT | ||
|
|
||
| # Allow ICMP (Ping) | ||
| add_rule HARVESTER_ESSENTIAL -p icmp -j ACCEPT | ||
|
|
||
| # Allow VRRP (Keepalived) | ||
| add_rule HARVESTER_ESSENTIAL -p vrrp -j ACCEPT | ||
|
|
||
| # Allow Established/Related connections | ||
| add_rule HARVESTER_ESSENTIAL -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT | ||
|
|
||
| # Common Rules | ||
| # TCP 80, 443, 22 | ||
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 80 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT | ||
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 443 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT | ||
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT | ||
|
|
||
| # UDP 8472 (VXLAN) | ||
| add_rule HARVESTER_ESSENTIAL -p udp --dport 8472 -j ACCEPT | ||
|
|
||
| # K8s/RKE2 Common | ||
| add_rule HARVESTER_ESSENTIAL -p tcp -m multiport --dports 6443:6444 -j ACCEPT | ||
| add_rule HARVESTER_ESSENTIAL -p tcp -m multiport --dports 10248:10250 -j ACCEPT | ||
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 10010 -j ACCEPT | ||
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 9099 -j ACCEPT | ||
|
|
||
| # Determine role and apply specific rules | ||
| IS_SERVER=false | ||
| if systemctl is-enabled rke2-server.service 2>/dev/null | grep -q enabled; then | ||
| IS_SERVER=true | ||
| elif systemctl is-active rke2-server.service 2>/dev/null | grep -q active; then | ||
| IS_SERVER=true | ||
| fi | ||
|
|
||
| if [ "$IS_SERVER" = "true" ]; then | ||
| # Master/Server specific | ||
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 9345 -j ACCEPT | ||
| add_rule HARVESTER_ESSENTIAL -p tcp -m multiport --dports 10256:10260 -j ACCEPT | ||
| add_rule HARVESTER_ESSENTIAL -p tcp -m multiport --dports 2379:2382 -j ACCEPT | ||
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 9091 -j ACCEPT | ||
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 2112 -j ACCEPT | ||
| else | ||
| # Worker/Agent specific | ||
| add_rule HARVESTER_ESSENTIAL -p tcp --dport 10256 -j ACCEPT | ||
| fi | ||
|
|
||
| # --- End HARVESTER_ESSENTIAL --- | ||
|
|
||
| # FORWARD chain rules | ||
| add_rule FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT | ||
|
|
||
| # Set default policies | ||
| iptables -P INPUT DROP | ||
| iptables -P FORWARD DROP |
There was a problem hiding this comment.
The script doesn't handle IPv6 traffic at all. It only configures iptables (IPv4) rules but doesn't configure ip6tables. This means IPv6 traffic will follow default policies which might differ from the intended security posture. Either IPv6 should be explicitly disabled, or corresponding ip6tables rules should be configured, or this limitation should be documented.
| @@ -0,0 +1,79 @@ | |||
| #!/bin/sh | |||
There was a problem hiding this comment.
The script uses 'sh' as the interpreter but relies on shell features and patterns that, while POSIX-compliant, could benefit from bash-specific error handling. Consider adding 'set -e' at the beginning to exit on any command failure, or alternatively add proper error checking after critical operations to prevent the script from continuing after failures and potentially leaving the system in an inconsistent state.
| #!/bin/sh | |
| #!/bin/sh | |
| set -e |
| # Flush HARVESTER_ESSENTIAL to ensure clean state | ||
| iptables -F HARVESTER_ESSENTIAL |
There was a problem hiding this comment.
Only the HARVESTER_ESSENTIAL chain is flushed, but the HARVESTER_DYNAMIC chain is never flushed. This inconsistency could lead to duplicate rules accumulating in HARVESTER_DYNAMIC over time as the dispatcher script runs on every interface up event. Consider either flushing both chains or documenting why HARVESTER_DYNAMIC should preserve rules across interface events.
| # Flush HARVESTER_ESSENTIAL to ensure clean state | |
| iptables -F HARVESTER_ESSENTIAL | |
| # Flush custom chains to ensure clean state | |
| iptables -F HARVESTER_ESSENTIAL | |
| iptables -F HARVESTER_DYNAMIC |
2a331d5 to
118b041
Compare
118b041 to
8fd71ca
Compare
This commit introduces a NetworkManager dispatcher script to apply initial
iptables rules when network interfaces are brought up.
This commit introduces a NetworkManager dispatcher script to apply initial
iptables rules when network interfaces are brought up.
Creating two custom chains:
- `HARVESTER_ESSENTIAL`: Contains core rules required for Harvester and
RKE2 functionality (SSH, K8s API, VXLAN, etc.).
- `HARVESTER_DYNAMIC`: A placeholder chain for users or external
components
Signed-off-by: Chris Chiu <chris.chiu@suse.com>
8d95fc3 to
4b84b03
Compare
Check the kubeovn status to add the ports to the ACCEPT list It would help the kubeovn working accross boots. Signed-off-by: Chris Chiu <chris.chiu@suse.com>
4b84b03 to
526d4f8
Compare
Create a script in NetworkManager dispatcher to monitor the kubeovn status and add/remove rules accordingly Signed-off-by: Chris Chiu <chris.chiu@suse.com>
ea28645 to
2017c00
Compare
Problem:
There should be a way to blacklist external network or whitelist the Harvester node network only to protection from malicious scan and access.
Solution:
This commit introduces a NetworkManager dispatcher script to apply initial iptables rules when network interfaces are brought up.
This commit introduces a NetworkManager dispatcher script to apply initial iptables rules when network interfaces are brought up. Creating two custom chains:
HARVESTER_ESSENTIAL: Contains core rules required for Harvester and RKE2 functionality (SSH, K8s API, VXLAN, etc.).HARVESTER_DYNAMIC: A placeholder chain for users or external componentsRelated Issue(s):
harvester/harvester#5681
Test plan:
Should include all feature as follows which should be broken into details
Additional documentation or context