FirewalldLesson 2 of 2
Sources, rich rules and port forwarding
Allow or block traffic by the address it comes from, write rich rules with logging and rate limits, and forward one port to another.
--add-source--add-rich-rule--list-rich-rules--remove-rich-rule--add-forward-portlog prefixrejectdrop
After this lesson you can
- Assign a source address or network to a zone so its traffic gets different rules.
- Write rich rules in the correct syntax to accept, reject, drop or log traffic from specific sources to a service or port.
- Restrict a service to one network, forward a port, and test the result from the allowed and from the blocked host.
The idea
Allowing a service in a zone lets in everyone that zone covers. To say who may reach it, firewalld gives you two tools.
Sources
Assign an address or network to a zone with --add-source, and traffic from it uses that zone’s rules, instead of the zone for the interface. This is the simplest way to handle “this host is trusted” or “this network can use NFS”:
sudo firewall-cmd --permanent --zone=trusted --add-source=192.168.122.51
sudo firewall-cmd --permanent --zone=internal --add-source=192.168.122.0/24
Rich rules
A rich rule is a one-line sentence with four parts:
rule family="ipv4" source address="192.168.122.0/24" service name="http" accept
│ │ │ └─ action
│ │ └─ what: service or port
│ └─ who: source or destination address
└─ IP version
| Part | Options |
|---|---|
| Family | ipv4, ipv6 (required when you give an address) |
| Source | source address="ADDR[/MASK]", can be negated with invert="True" |
| Target | service name="ssh", or port port="8081" protocol="tcp" |
| Action | accept, reject, drop, or mark |
| Logging | log prefix="TEXT " level="info" limit value="3/m" before the action |
reject answers the sender with an error so they know at once; drop is silent, so they wait for a timeout. Use reject inside a trusted environment and drop towards the internet.
Manage them with --add-rich-rule='...', --remove-rich-rule='...' (give the identical text) and --list-rich-rules. Wrap the whole rule in single quotes so the double quotes inside survive the shell.
The common trap
If you add service name="http" accept for a network while the plain http service is still allowed in the zone, nothing is restricted: the service rule already lets everyone in. To restrict, remove the zone-wide service and keep only the rich rule.
Forwarding
--add-forward-port=port=80:proto=tcp:toport=8080 redirects incoming connections on port 80 to 8080 on the same machine (add :toaddr=ADDRESS to send them elsewhere, which also needs masquerading).
Try it
sudo firewall-cmd --permanent --zone=trusted --add-source=192.168.122.51
sudo firewall-cmd --reload
firewall-cmd --zone=trusted --list-sources
firewall-cmd --get-active-zones
success
success
192.168.122.51
trusted
sources: 192.168.122.51
public
interfaces: eth0
Restrict a service to a network
sudo firewall-cmd --permanent --remove-service=http
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.122.0/24" service name="http" accept'
sudo firewall-cmd --reload
firewall-cmd --list-services
firewall-cmd --list-rich-rules
success
success
success
cockpit dhcpv6-client ssh
rule family="ipv4" source address="192.168.122.0/24" service name="http" accept
Test both sides: from a host in 192.168.122.0/24 the page loads, from a host outside it curl -m3 times out. A rule is proven only when the unwanted access fails too.
Reject and log
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.122.99" service name="ssh" reject'
sudo firewall-cmd --reload
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.122.0/24" service name="ssh" log prefix="ssh-from-lab " level="info" limit value="3/m" accept'
sudo firewall-cmd --reload
ssh node1 true # from node2
sudo journalctl -k --grep ssh-from-lab --no-pager | tail -1
Nov 03 14:02:11 node1 kernel: ssh-from-lab IN=eth0 OUT= MAC=52:54:00:12:34:56:52:54:00:aa:bb:cc:08:00 SRC=192.168.122.51 DST=192.168.122.50 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=24680 DF PROTO=TCP SPT=52114 DPT=22 WINDOW=64240 SYN
Rich rules take precedence over the zone’s allowed services in the order log, then deny, then allow. A reject for one address therefore works even though ssh is allowed for everyone.
Forward a port
sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080
sudo firewall-cmd --reload
firewall-cmd --list-forward-ports
curl -s http://node1/ # from node2
success
success
port=80:proto=tcp:toport=8080:toaddr=
app
Going further
Rich rules also accept destination address=, protocol value="icmp", masquerade, forward-port and source-port. --add-icmp-block=echo-request stops ping replies. firewall-cmd --get-policies lists policies (firewalld 1.0+), which filter traffic between zones and are the right tool for forwarding or routing setups; rich rules remain the standard answer for exam-style “allow from this network” tasks. firewall-cmd --query-service=http and --query-port=8080/tcp return yes or no with an exit status, useful in scripts and checks.
Practice
0 of 5 done
Do these on your own system. They get harder. Check each result, then tick it off.
- Warm-up
Trust one host completely
Make everything from
node2(192.168.122.51) accepted bynode1, permanently, by assigning that address to thetrustedzone. Prove it by reaching a port that is not open for anyone else, then undo it.Check your result
firewall-cmd --zone=trusted --list-sourcesshows192.168.122.51. node2 can connect to a closed port, e.g.8081, while another host cannot.Hint
--permanent --zone=trusted --add-source=192.168.122.51, then--reload. A source is matched before the interface.Solution
(cd /tmp && python3 -m http.server 8081 >/dev/null 2>&1 &) sudo firewall-cmd --permanent --zone=trusted --add-source=192.168.122.51 sudo firewall-cmd --reload firewall-cmd --zone=trusted --list-sources # from node2: curl -sI http://node1:8081 | head -1 sudo firewall-cmd --permanent --zone=trusted --remove-source=192.168.122.51 sudo firewall-cmd --reload - Practice
Web server for one network only
httpdmust be reachable only from192.168.122.0/24, and from nowhere else. Currentlyhttpis allowed in thepubliczone for everyone. Fix this permanently using a rich rule, and make sure the plainhttpservice is no longer open to all.Check your result
--list-servicesno longer listshttp.--list-rich-rulesshows a rule withsource address="192.168.122.0/24"andservice name="http"andaccept. node2 (in that network) gets the page; a host outside it times out.Hint
Allowing a service in the zone applies to everyone; a rich rule with a source limits it. So remove the service first, then add the rich rule.
Solution
sudo firewall-cmd --permanent --remove-service=http sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.122.0/24" service name="http" accept' sudo firewall-cmd --reload firewall-cmd --list-services firewall-cmd --list-rich-rules - Practice
Block one troublesome host
Make node1 refuse SSH connections from
192.168.122.99only, with an explicit rejection (so the client sees the refusal immediately), while others can still use SSH. Verify, then remove the rule.Check your result
--list-rich-rulesshowssource address="192.168.122.99" service name="ssh" reject. That host receivesConnection refused; node2 still connects.Hint
rejectsends an ICMP error back;dropdiscards silently. Rich rules are evaluated before the zone's allowed services.Solution
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.122.99" service name="ssh" reject' sudo firewall-cmd --reload firewall-cmd --list-rich-rules sudo firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" source address="192.168.122.99" service name="ssh" reject' sudo firewall-cmd --reload - Practice
Allow, but log and limit
Allow SSH from
192.168.122.0/24with a log entry for each new connection, limited to 3 log entries per minute. Make a connection from node2 and find the log line.Check your result
journalctl -k --grep 'ssh-from-lab'(ordmesg) shows a line starting with your prefix. The rule includeslog prefix="ssh-from-lab " level="info" limit value="3/m".Hint
rule family="ipv4" source address="..." service name="ssh" log prefix="ssh-from-lab " level="info" limit value="3/m" accept. Logging goes to the kernel log.Solution
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.122.0/24" service name="ssh" log prefix="ssh-from-lab " level="info" limit value="3/m" accept' sudo firewall-cmd --reload # from node2: ssh node1 true sudo journalctl -k --grep ssh-from-lab --no-pager | tail -2 - Challenge
Forward port 80 to 8080
An application listens on TCP 8080, but clients must use port 80. Forward incoming TCP 80 to 8080 on the same host permanently, and test it from node2 with the port 80 URL. State what you must also check about the application, and the zone.
Check your result
--list-forward-portsshowsport=80:proto=tcp:toport=8080:toaddr=.curl http://node1/from node2 returns the application's page.Hint
--add-forward-port=port=80:proto=tcp:toport=8080. The forwarded packets still pass the zone's rules for port 8080 after translation.Solution
(cd /tmp && echo app > index.html && python3 -m http.server 8080 >/dev/null 2>&1 &) sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080 sudo firewall-cmd --reload firewall-cmd --list-forward-ports # from node2: curl -s http://node1/