update-firewall-source
Update a firewall rule that relies on dynamic DNS names
What
-
This script assumes exclusive ownership of the
firewallddirect rules file/etc/firewalld/direct.xmlor whereever configured -
List of address can be empty, direct file will then be removed
-
After every execution script will trigger systemd firewalld service restart
-
No subnet, will simply not be validated
-
Include example systemd unit file and install instructions
-
firewall-cmd --check-config
-
default location for config file and default name for config file
-
we should deduplicate list
-
is intended to help with just docker-user
-
man page https://ipset.netfilter.org/iptables.man.html we do iptables
-
added in order
-
related, established? needed?
-
section names as comments?
Config structure
Package configuration happens via a config.ini file that follows INI-style syntax. Copy examples/config.ini.example to config.ini to get started:
[DEFAULT]
target = ACCEPT
addr =
ports = 80, 443
proto = tcp
state = NEW
do_ipv6 = false
firewalld_direct_file_abs = /etc/firewalld/direct.xml
restart_firewalld_after_change = true
[anyone-can-access-website]
# Unsetting 'proto' while having a 'ports' value results in an invalid section
# [these-guys-can-dns]
# addr = google.li, 142.251.36.195, lowendbox.com, 2606:4700:20::ac43:4775
# ports = 53
# proto =
# do_ipv6 = true
[maybe-a-webserver]
addr = 2606:4700:20::681a:804, lowendtalk.com
ports = 80, 443
do_ipv6 = true
[allow-anyone-to-access-mail-services]
ports = 143, 993, 110, 995, 25, 465, 587
[deny-all]
target = DROP
addr =
ports =
proto =
state =
do_ipv6 = true
Layout
A config file can have an optional [DEFAULT] section and must have at least one [section] other than [DEFAULT]. Any [DEFAULT] option that's undefined retains its default value. Feel free to delete the entire [DEFAULT] section from your file. A setting changed in [DEFAULT] section affects all sections. A setting changed only in a custom [section] overwrites it for only the section.
Custom sections such as [maybe-a-webserver] in above example file are treated as organizational helper constructs. You can but don't have to group IP address rules by sections. Technically nothing's stopping you from adding all IP allow list entries into a single section.
Example explanation
With config_check_after_change
Setting restart_firewalld_after_change controls if you want the firewalld systemd unit to be restarted
In above example file note that [anyone-can-access-website] makes [maybe-a-webserver] irrelevant, the latter one could easily be deleted. [anyone-can-access-website] does not overwrite defaults, it's an empty section. With it firewalld will create a rule that - following all default settings - allows access from any source address on TCP ports 80 and 443. In section [maybe-a-webserver] we do the same and additionally limit source addresses. Rules are added in config.ini order so the first rule permitting access to TCP ports 80 and 443 from anywhere makes the second one irrelevant.
We strongly recommend you do keep the very last example section:
[deny-all]
target = DROP
addr =
ports =
proto =
state =
do_ipv6 = true
If a packet has traversed rules this far without being accepted it will be dropped. Note that if any of your custom [sections] use do_ipv6 = true your final DROP rule should do the same. Otherwise you'll just get DROP rule in iptables but not in ip6tables.
Options
Globals
In [DEFAULT] section the following settings are called globals. They're only valid in [DEFAULT] context. Adding them to a custom [section] (see Locals below) won't do anything, in a custom [section] the following settings are ignored.
-
firewalld_direct_file_abs, optional, defaults to/etc/firewalld/direct.xml: Location offirewalld's direct rules file. This is where new XML rule content is written. -
restart_firewalld_after_change, optional, defaults totrue: After putting a new/etc/firewalld/direct.xmlfile in place restart thefirewalldsystemd service unit.
Locals
A custom [section] has the following options. We're calling them locals most of which are optional.
-
target, mandatory, defaults toACCEPT, can be any validiptablestarget. Must not be empty nor unset. A string specifying the fate of a packet that matched this rule. See "TARGETS" section in iptables man page. You're most likely going to want to stick to eitherACCEPTorDROP. By default matching packets are accepted. We do not do our own validation of what you write here. By default (see Globals)do_config_checkequals to true in which case we letfirewallddo a config check to catch nonsense rules. -
addr, optional, defaults to an empty string: A comma-separated list of any combination of IPv4 addresses, IPv6 addresses and domain names. Whenupdate-firewall-source.pyconstructsfirewalldrules these addresses are allowed to access the server. If left undefinedaddrdefaults to an empty list meaning rules apply to any and all source address.Subnets are unsupported, both as subnet masks (
142.251.36.195/255.255.255.248) and in CIDR notation (142.251.36.195/29). Do not single- nor double-quote list entries. Do feel free to separate entries with comma-space instead of just a comma. -
ports, optional, defaults to80, 443: A comma-separated list of ports that should be accessible fromaddr. If emptyaddrmay access all ports. See iptables-extensions man page, section "multiport" for syntax reference. All port-based rules useiptables ... --match multiporteven if you're only allowing access to a single port. In essence construct your ports list with any combination of single ports (80, 443, 8080) and port ranges (6660:7000, 61000:65535).# Valid example: ports = 80, 443, 6660:7000, 8080 -
proto, optional, defaults totcp: A singular protocol that should be allowed foraddronports. Can be set to an empty value in which case all protocols are allowed. Sincefirewallddirect rules useiptablessyntax the list of possible protocol names is largely identical to what the iptables man page says about its--protocolargument:The specified protocol can be one of
tcp,udp,udplite,icmp,icmpv6,esp,ah,sctp,mhor the special keywordall, or it can be a numeric value, representing one of these protocols or a different one. A protocol name from/etc/protocolsis also allowed. A!argument before the protocol inverts the test. The number zero is equivalent toall.Your mileage may vary depending on which specific OS flavor and version you're running.
Implementation details:
-
protois treated as a string, not a list. To for example allow access via both TCP and UDP create two[sections]like so:[tcp-rule] addr = 1.1.1.1 ports = proto = tcp [udp-rule] addr = 1.1.1.1 ports = proto = udpSince
proto = tcpis default you can leave it out of the top section. Side note, in this specific example you would want to set the[DEFAULT]valueports =instead of repeating it in each[section]. Alternatively setproto =to allow all protocols in which case a single[section]is enough to cover that use case:[permit-port-53-via-all-protocols] addr = 1.1.1.1 ports = proto =Make sure that when
protois unset you also unsetports. See next bullet point for details on that. -
Unsetting
protowhile at the same time leaving at least oneportsvalue in place (which is the default withports = 80, 443) is an error.It will result in a rule that
firewalldcannot load intoip(6)tables. It will report it as such in its systemd journal visible e.g. viajournalctl -fu firewalld.service. This is because having at least one port configured will always result in adding a--match multiportwhich is only valid when also giving a--protocolsuch as--protocol tcp.[DEFAULT] ports = 80, 443 proto = tcp [valid] addr = example.net ports = 22, 80, 443 [invalid] addr = example.com proto = # Without 'ports' there will be no '--match multiport' # and without /that/ you can safely unset 'proto': [also-valid] addr = example.org ports = proto =
-
-
state, optional, defaults toNEW: Comma-separated list of connection tracking states against which a packet is matched. Most of the time your rules will want to use the defaultNEW. The finalDROPrule present in the exampleconfig.inifile at examples/config.ini.example is one occasion where you'll want to deviate and unsetstateto an empty value. See "state" extension man page in iptables docs for reference. -
do_ipv6, optional, defaults tofalse: Decide if you wantfirewalldto generateip6tablesrules in addition toiptablesrules. A default install of Docker Engine will have its IPv6 support disabled in/etc/docker/daemon.jsonin which caseip6tableswill not have aDOCKER-USERor similar Docker-related chains. In this default setup havingupdate-firewall-source.pygenerate an otherwise unusedDOCKER-USERchain and adding rules to it clutters your rule set. Consider setting this totrueif and when your Docker install uses IPv6.If this is
trueIPv6 addresses found or resolved inaddrin a[section]will be discarded.
Development
Conventional Commits
We use Conventional Commits.
Scopes
The following scopes are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none:
config: Structure or content of aconfig.inifiledbus: Deals with functionality to restart thefirewalld.serviceunitsystemd: Deals with lifecycle as a systemd unitmeta: Affects the project's repo layout, readme content, file names etc.dns: Resolution of DNS recordsxml: XML content handling forfirewallddirect rules, includes segues intoip(6)tablesterritorynetdev: Network devicesdebug: Deals with debuggability, concise messages to end user
Types
The following types are known for this project in addition to Conventional Commits default types fix and feat. A Conventional Commits commit message must use either one of the two default types or optionally a type from this list:
build: Project structure, directory layout, build instructions for roll-outrefactor: Keeping functionality while streamlining or otherwise improving function flowtest: Working on test coveragedocs: Documentation for project or components