Skip to content

Commit ab36f50

Browse files
committed
mimas: migrate to nftable and configure whole as blocks
1 parent 31a774b commit ab36f50

File tree

8 files changed

+385
-0
lines changed

8 files changed

+385
-0
lines changed

build/mimas/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
../hydra.nix
55
../hydra-proxy.nix
66
./boot.nix
7+
./firewall.nix
78
./network.nix
89
];
910

build/mimas/firewall.nix

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
pkgs,
3+
lib,
4+
...
5+
}:
6+
7+
let
8+
nft-asblock = pkgs.callPackage ./nft-asblock { };
9+
10+
asblocks = [
11+
45102 # ALIBABA-CN-NET
12+
132203 # TENCENT-NET-AP-CN
13+
];
14+
in
15+
16+
{
17+
networking.nftables = {
18+
enable = true;
19+
tables."abuse" = {
20+
family = "inet";
21+
content = ''
22+
set blocked4 {
23+
type ipv4_addr;
24+
flags interval, timeout;
25+
auto-merge;
26+
timeout 6h;
27+
}
28+
set blocked6 {
29+
type ipv6_addr;
30+
auto-merge;
31+
flags interval, timeout;
32+
timeout 6h;
33+
}
34+
chain input-abuse {
35+
type filter hook input priority filter - 5;
36+
37+
ip saddr @blocked4 counter drop;
38+
ip6 saddr @blocked6 counter drop;
39+
}
40+
'';
41+
};
42+
};
43+
44+
systemd.services.nft-asblock = {
45+
path = with pkgs; [ nftables ];
46+
serviceConfig = {
47+
AmbientCapabilities = [ "CAP_NET_ADMIN" ];
48+
DynamicUser = true;
49+
User = "nft-asblock";
50+
Group = "nft-asblock";
51+
ExecStart = toString ([
52+
(lib.getExe nft-asblock)
53+
] ++ asblocks);
54+
StateDirectory = "nft-asblock";
55+
};
56+
};
57+
58+
}

build/mimas/nft-asblock/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dist
2+
.ruff_cache
3+
.venv
4+
__pycache__
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{ python3Packages }:
2+
3+
with python3Packages;
4+
5+
buildPythonApplication {
6+
pname = "nft-asblock";
7+
version = "0.0";
8+
format = "pyproject";
9+
10+
src = ./.;
11+
12+
build-system = [ uv-build ];
13+
14+
dependencies = [
15+
httpx
16+
typer
17+
];
18+
19+
meta.mainProgram = "nft-asblock";
20+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[build-system]
2+
requires = ["uv-build"]
3+
build-backend = "uv_build"
4+
5+
[project]
6+
name = "nft-asblock"
7+
version = "0.1.0"
8+
description = "Block prefixes for whole Autonomus Systems with nftable sets"
9+
requires-python = ">=3.12"
10+
dependencies = [
11+
"httpx",
12+
"typer",
13+
]
14+
15+
[project.scripts]
16+
nft-asblock = "nft_asblock.__main__:cli"
17+
18+
[tool.ruff.lint]
19+
select = ["I"]
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import sys
2+
import time
3+
from ipaddress import ip_network
4+
from pathlib import Path
5+
from subprocess import CalledProcessError, run
6+
from typing import Final
7+
8+
import httpx
9+
import typer
10+
11+
RTTABLE_CACHE: Final = Path("/var/lib/nft-asblock/table.txt")
12+
NFT_TABLE: Final = "abuse"
13+
NFT_IPV4_SET: Final = "blocked4"
14+
NFT_IPV6_SET: Final = "blocked6"
15+
16+
17+
def get_rttable() -> str:
18+
def fetch() -> str:
19+
url = "https://bgp.tools/table.txt"
20+
headers = {"User-Agent": "nixos.org infra - [email protected]"}
21+
response = httpx.get(url, headers=headers)
22+
assert response.status_code == 200
23+
return response.text
24+
25+
try:
26+
mtime = RTTABLE_CACHE.stat().st_mtime
27+
except FileNotFoundError:
28+
mtime = None
29+
30+
# don't pull more often than every two hours
31+
if not mtime or time.time() - mtime > 2 * 3600:
32+
print("Pulling routing table from bgp.tools", file=sys.stderr)
33+
data = fetch()
34+
with RTTABLE_CACHE.open("w") as fd:
35+
fd.write(data)
36+
else:
37+
print("Loading routing table from cache", file=sys.stderr)
38+
with RTTABLE_CACHE.open() as fd:
39+
data = fd.read()
40+
41+
return data
42+
43+
44+
def nft_block(prefixes: set[str]) -> None:
45+
networks = map(ip_network, prefixes)
46+
47+
for network in networks:
48+
print(f"\nBlocking {network}...", file=sys.stderr, end="")
49+
try:
50+
run(
51+
[
52+
"nft",
53+
"add",
54+
"element",
55+
"inet",
56+
NFT_TABLE,
57+
NFT_IPV4_SET if network.version == 4 else NFT_IPV6_SET,
58+
"{",
59+
str(network),
60+
"}",
61+
],
62+
check=True,
63+
)
64+
except CalledProcessError:
65+
continue
66+
67+
68+
def main(autnums: list[str]) -> None:
69+
rttable = get_rttable()
70+
71+
prefixes = set()
72+
for line in rttable.splitlines():
73+
try:
74+
prefix, autnum = line.split()
75+
except ValueError:
76+
continue
77+
if autnum in autnums:
78+
prefixes.add(prefix)
79+
nft_block(prefixes)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import typer
2+
3+
from nft_asblock import main
4+
5+
6+
def cli():
7+
typer.run(main)
8+
9+
10+
if __name__ == "__main__":
11+
cli()

build/mimas/nft-asblock/uv.lock

Lines changed: 193 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)