host_firewall: refresh the Spamhaus sets by rule set reload, flush and refill let unlisted addresses match
This commit is contained in:
@@ -106,11 +106,6 @@ def nft(*args):
|
||||
return subprocess.run(["nft", *args], capture_output=True, text=True)
|
||||
|
||||
|
||||
def write(path, text):
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
|
||||
|
||||
def main():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
drop4 = [r["cidr"] for r in spamhaus_records("drop_v4", tmp)]
|
||||
@@ -131,36 +126,37 @@ def main():
|
||||
return ",\n\t\t".join(str(n) for n in networks) + ","
|
||||
|
||||
persisted = ["# Generated by nft-host-spamhaus, do not edit.", "table inet host {"]
|
||||
update = []
|
||||
for name, kind in (("spamhaus_v4", "ipv4_addr"), ("spamhaus_v6", "ipv6_addr")):
|
||||
persisted.append(f"\tset {name} {{ type {kind}; flags interval; auto-merge; elements = {{\n"
|
||||
f"\t\t{elements(sets[name])}\n\t}} }}")
|
||||
update.append(f"flush set inet host {name}\n"
|
||||
f"add element inet host {name} {{\n\t{elements(sets[name])}\n}}")
|
||||
persisted.append("}\n")
|
||||
|
||||
persisted_path = os.path.join(tmp, "host-spamhaus.nft")
|
||||
update_path = os.path.join(tmp, "update.nft")
|
||||
write(persisted_path, "\n".join(persisted))
|
||||
write(update_path, "\n".join(update) + "\n")
|
||||
|
||||
# The running table gets the new elements in one transaction; without
|
||||
# the sets (boot, or a rule set rendered without them) the persisted
|
||||
# file is the only copy.
|
||||
loaded = all(nft("list", "set", "inet", "host", n).returncode == 0
|
||||
for n in ("spamhaus_v4", "spamhaus_v6"))
|
||||
for path in (persisted_path,) + ((update_path,) if loaded else ()):
|
||||
checked = nft("-c", "-f", path)
|
||||
candidate = os.path.join(tmp, "host-spamhaus.nft")
|
||||
with open(candidate, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(persisted))
|
||||
checked = nft("-c", "-f", candidate)
|
||||
if checked.returncode != 0:
|
||||
fail(f"nft rejects {os.path.basename(path)}: {checked.stderr.strip()}")
|
||||
if loaded:
|
||||
applied = nft("-f", update_path)
|
||||
if applied.returncode != 0:
|
||||
fail(f"loading the sets failed: {applied.stderr.strip()}")
|
||||
shutil.copyfile(persisted_path, PERSISTED + ".new")
|
||||
fail(f"nft rejects the sets: {checked.stderr.strip()}")
|
||||
|
||||
previous = None
|
||||
if os.path.exists(PERSISTED):
|
||||
previous = os.path.join(tmp, "previous.nft")
|
||||
shutil.copyfile(PERSISTED, previous)
|
||||
shutil.copyfile(candidate, PERSISTED + ".new")
|
||||
os.chmod(PERSISTED + ".new", 0o644)
|
||||
os.replace(PERSISTED + ".new", PERSISTED)
|
||||
print("loaded into the running table" if loaded else "persisted only")
|
||||
|
||||
# A reload replaces the whole table, sets included, in one transaction.
|
||||
# Flushing and refilling a running interval set instead lets packets of
|
||||
# unlisted addresses match while the transaction commits.
|
||||
active = subprocess.run(["systemctl", "-q", "is-active", "nft-host.service"]).returncode == 0
|
||||
if active:
|
||||
reloaded = subprocess.run(["systemctl", "reload", "nft-host.service"], capture_output=True, text=True)
|
||||
if reloaded.returncode != 0:
|
||||
if previous:
|
||||
shutil.copyfile(previous, PERSISTED)
|
||||
fail(f"reloading the rule set failed, kept the previous sets: {reloaded.stderr.strip()}")
|
||||
print("reloaded the rule set" if active else "persisted only")
|
||||
|
||||
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user