Whitelist source IP addresses in CentOS 7

I'd accomplish this by adding sources to a zone. First checkout which sources there are for your zone:

firewall-cmd --permanent --zone=public --list-sources

If there are none, you can start to add them, this is your "whitelist"

firewall-cmd --permanent --zone=public --add-source=192.168.100.0/24
firewall-cmd --permanent --zone=public --add-source=192.168.222.123/32

(That adds a whole /24 and a single IP, just so you have a reference for both a subnet and a single IP)

Set the range of ports you'd like open:

firewall-cmd --permanent --zone=public --add-port=1-22/tcp
firewall-cmd --permanent --zone=public --add-port=1-22/udp

This just does ports 1 through 22. You can widen this, if you'd like.

Now, reload what you've done.

firewall-cmd --reload

And check your work:

 firewall-cmd --zone=public --list-all

Side note / editorial: It doesn't matter but I like the "trusted" zone for a white-listed set of IPs in firewalld. You can make a further assessment by reading redhat's suggestions on choosing a zone.

See also:

  • RHEL 7 using Firewalls article
  • Fedora FirewallD docs (fairly good, fedora's been using firewalld for some while)

If you'd like to DROP packets outside this source, here's an example for dropping those outside the /24 I used as an example earlier, you can use rich rules for this, I believe. This is conceptual, I have not tested it (further than seeing that centos 7 accepts the command), but, should be easy enough to do a pcap and see if it behaves how you'd expect

firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.100.0/24" invert="True" drop'

Even if an answer has been accepted and up-voted, I do not think it is a correct one. I fail to find clear explanation in the documentation, but from the implemented behaviour it looks like that:

  1. interface and source are used as selectors - which zone(s) to activate
  2. both are ignored for the default zone (always active)

So the answer would be:

  1. lock down the default zone, say "public" - no ports open or services available
  2. in another zone, say "work" - define source and open ports

For example, assuming default zone is public and has no open ports, add source and port range to "work" zone:

$ sudo firewall-cmd --zone=work --add-source=192.168.0.0/24
$ sudo firewall-cmd --zone=work --add-port=8080-8090/tcp

now check the active zones (default zone is always active):

$ sudo firewall-cmd --get-active-zones

you'll get:

work
  sources: 192.168.0.0/24

so "work" zone rules will apply to the particular subnet. You will have a range of open ports for the "whitelist" = subnet as requested. And of course use --permanent option in --add-xxx statements to make the behaviour stick.

In turn any ports or services you have in "public" (default) zone will apply to all interfaces and source addresses.

$ sudo firewall-cmd --list-all-zones

public (default)
interfaces:
sources:
services:
ports: 
masquerade: no
forward-ports:
icmp-blocks:
rich rules:

work (active)
interfaces: 
sources: 192.168.0.0/24
services: dhcpv6-client ipp-client ssh
ports: 8080-8090/tcp
masquerade: no
forward-ports:
icmp-blocks:
rich rules:

The same system works for interfaces. Say by adding interface "ens3" to "work" zone:

$ sudo firewall-cmd --zone=work --add-interface=ens3

you will use the "work" zone rules to any requests from the particular interface - more rough selector than "source".


Disclaimer: I haven't actually tried what I'm suggesting, here, but it's fairly close to the last firewalld setup I did, so I'm going off of that. Firewalld provides you with a few pre-configured zones, just for this purpose. There's one called "drop", which drops anything coming in, and one called "trusted", which allows any connection (ie, so you shouldn't even need to open individual ports, I think). The trick is getting the right zone to trigger for what you want.

Firewalld will apply the rules for a zone based upon the following precedence:

  • If the source IP matches a source IP bound to a zone, it uses that.
  • If the source IP doesn't match any particular zone, it checks to see if there's a zone configured for the interface the packet came in on. If there is one, it uses that.
  • Lastly, if nothing else matches, it uses the default zone.

So, first off, you want to bind your trusted IP's to the "trusted" zone:

firewall-cmd --permanent --zone=trusted --add-source=1.2.3.4

Then, either set your default zone to "drop" or bind your interface to it:

firewall-cmd --permanent --set-default-zone=drop
firewall-cmd --permanent --zone=drop --change-interface=eth0

and then make the changes take effect (warning: this will probably drop your connection if you're doing this over the network and you didn't add your source IP to the trusted zone):

firewall-cmd --reload

Of course, you can also just test these temporarily by omitting the "--permanent" (and then you don't have to --reload, either).