I've played around with this and I've come up with this:

# /usr/lib/systemd/system/ferm-early.service
[Unit]
Description=Early firewall configuration with ferm
Documentation=man:ferm(1)
Before=network-pre.target
Wants=network-pre.target
After=local-fs.target
ConditionFileIsExecutable=/usr/sbin/ferm
ConditionPathExists=/etc/ferm/ferm.conf

[Service]
Type=oneshot
RemainAfterExit=yes

# Set defaults for variables not in environment file
# (EnvironmentFile takes precedence, see systemd.exec(5)
Environment="CACHE=no"
Environment="OPTIONS="

EnvironmentFile=-/etc/default/ferm

# Execute wrapper
ExecStart=-/bin/sh -c '/usr/libexec/ferm/ferm-systemd activate && touch 
/run/ferm-early-success'

UMask=0077

# Security hardening
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/cache/ferm /run
NoNewPrivileges=no

# Required capabilities for firewall management
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_SYS_MODULE

[Install]
WantedBy=multi-user.target



The important parts being:
  Before=network-pre.target
  Wants=network-pre.target
  After=local-fs.target
  ConditionFileIsExecutable=/usr/sbin/ferm

(Note, ConditionFileIsExecutable, not ConditionPathIsExecutable,
see systemd.directives)


And, more importantly:
  ExecStart=-/bin/sh -c '/usr/libexec/ferm/ferm-systemd activate && touch 
/run/ferm-early-success'

The "=-" means this will be a best-effort attempt and won't
error out on failure (like if hostnames have been used in the
ferm scripts, not something I'd endorse, but I know there's
people who have different opinions).


Then:

# /usr/lib/systemd/system/ferm.service
[Unit]
Description=Firewall configuration with ferm
Documentation=man:ferm(1)
After=network-online.target nss-lookup.target
Wants=network-online.target
ConditionFileIsExecutable=/usr/sbin/ferm
ConditionPathExists=/etc/ferm/ferm.conf

[Service]
Type=oneshot
RemainAfterExit=yes

# Set defaults for variables not in environment file
# (EnvironmentFile takes precedence, see systemd.exec(5)
Environment="CACHE=no"
Environment="OPTIONS="

EnvironmentFile=-/etc/default/ferm

# Execute wrapper
ExecCondition=/bin/sh -c 'if [ -f /run/ferm-early-success ]; then rm -f 
/run/ferm-early-success; exit 1; else exit 0; fi'
ExecStart=/usr/libexec/ferm/ferm-systemd activate
ExecReload=/usr/libexec/ferm/ferm-systemd activate
ExecStop=/usr/libexec/ferm/ferm-systemd deactivate

UMask=0077

# Security hardening
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/cache/ferm /run
NoNewPrivileges=no

# Required capabilities for firewall management
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_SYS_MODULE

[Install]
WantedBy=multi-user.target



Rationale:
ferm.service is the *real* service that'll run unless ferm-early.service
succeeded. It'll run at a point where the network is up and nss-lookup
is finished. It'll only run if ferm-early.service failed, and do another
attempt. If it fails, that'll be a hard fail in the output from e.g.
"systemctl status". ferm-early.service errors will be ignored.

Also, this ensures that people who are used to "ferm.service" being the
"real" service can do things like "systemctl restart ferm.service" long
after the system has booted, and it'll do the right thing.

Finally, it removes the need for separate "early" and "late" ferm scripts.
Instead, we do a best-effort early firewall setup (will work for ferm
configurations without hostnames), and then an optional second pass
(where DNS should be available).



Things left to consider:
* Should ferm-early.service have DefaultDependencies=no?
* This might still not be enough on servers providing DNS services, since
  I don't think it'll ensure the DNS server is up before ferm.service kicks in
  (I'd argue that DNS servers should not have hostnames in ferm scripts).




(Sorry if the formatting is weird, had to do this via a web client).

Reply via email to