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)
|
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():
|
def main():
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
drop4 = [r["cidr"] for r in spamhaus_records("drop_v4", 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) + ","
|
return ",\n\t\t".join(str(n) for n in networks) + ","
|
||||||
|
|
||||||
persisted = ["# Generated by nft-host-spamhaus, do not edit.", "table inet host {"]
|
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")):
|
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"
|
persisted.append(f"\tset {name} {{ type {kind}; flags interval; auto-merge; elements = {{\n"
|
||||||
f"\t\t{elements(sets[name])}\n\t}} }}")
|
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.append("}\n")
|
||||||
|
|
||||||
persisted_path = os.path.join(tmp, "host-spamhaus.nft")
|
candidate = os.path.join(tmp, "host-spamhaus.nft")
|
||||||
update_path = os.path.join(tmp, "update.nft")
|
with open(candidate, "w", encoding="utf-8") as f:
|
||||||
write(persisted_path, "\n".join(persisted))
|
f.write("\n".join(persisted))
|
||||||
write(update_path, "\n".join(update) + "\n")
|
checked = nft("-c", "-f", candidate)
|
||||||
|
|
||||||
# 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)
|
|
||||||
if checked.returncode != 0:
|
if checked.returncode != 0:
|
||||||
fail(f"nft rejects {os.path.basename(path)}: {checked.stderr.strip()}")
|
fail(f"nft rejects the sets: {checked.stderr.strip()}")
|
||||||
if loaded:
|
|
||||||
applied = nft("-f", update_path)
|
previous = None
|
||||||
if applied.returncode != 0:
|
if os.path.exists(PERSISTED):
|
||||||
fail(f"loading the sets failed: {applied.stderr.strip()}")
|
previous = os.path.join(tmp, "previous.nft")
|
||||||
shutil.copyfile(persisted_path, PERSISTED + ".new")
|
shutil.copyfile(PERSISTED, previous)
|
||||||
|
shutil.copyfile(candidate, PERSISTED + ".new")
|
||||||
os.chmod(PERSISTED + ".new", 0o644)
|
os.chmod(PERSISTED + ".new", 0o644)
|
||||||
os.replace(PERSISTED + ".new", PERSISTED)
|
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:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user