discover · compare · collections · requirements · contribute · feed · watching · stats · help with your tokens
dannysheehan/nft-blocklist · source
Snapshot 0 · 2026-11-04T12:47:23Z · Go · MIT
Content fingerprint
454c22439cc81552a095e5eb10e2a79888641b6f5c3d35afe315f328b2820132GitHub description at capture
Atomic nftables IP blocklist manager in Go — curated threat feeds, allowlist protection, shrink guard, systemd timers, fail2ban-friendly dynamic sets, pluggable notifications. Successor to the iptables/ipset blacklists script.
README at capture
# nft-blocklist
Atomic **nftables IP blocklist manager** for Linux servers. Fetches curated
threat-intelligence feeds, validates and merges them, and applies the
result as native nftables sets — in one kernel transaction, so your
firewall is never half-updated and never unprotected during a refresh.
This is the v2 rewrite of `iptables-ipset-blacklists` (2014). The legacy
iptables/ipset Bash script lives frozen in [legacy/](legacy/);
[docs/migration-from-v1.md](docs/migration-from-v1.md) shows how to switch.
> **Scope honestly stated:** IP blocklists are a noise-reduction control,
> not a security boundary. They cut log spam, scanner traffic and known-bad
> actors so your IDS/monitoring can see real attacks — they do not replace
> patching, MFA, SSH hardening, or a WAF.
## How it works
```mermaid
flowchart LR
F[curated feeds\nSpamhaus DROP, FireHOL L1,\nblocklist.de, CINS, ipsum...] -->|ETag-cached HTTP| P[parse + validate\nreject bogons & poison]
L[your allowlist\n+ local blocklist] --> M
P --> M[dedupe + CIDR merge\nallowlist subtracted]
M --> G{shrink guard}
G -->|ok| N["one atomic nft -f transaction\ntable inet blocklist"]
G -->|feed collapsed| K[refuse + alert\nold rules stay active]
N --> S[(last-known-good\nrestored at boot)]
```
Key properties, each enforced by tests:
- **Atomic**: the generated ruleset applies entirely or not at all
(`nft -f` transaction). No exposure window during updates.
- **Fail closed**: any fetch/parse/apply failure leaves the previous rules
active; a **shrink guard** refuses updates that would silently drop most
of your protection when an upstream feed dies.
- **Allowlist wins, always**: allowlisted IPs are subtracted from every
feed *and* accepted by an early rule; if one shows up in a feed you get
an `allowlist_collision` alert instead of an outage.
- **Feed hygiene**: bogons, private ranges, default routes and absurdly
broad prefixes are rejected; scored feeds (ipsum) filter by confidence;
HTML error pages don't pass as empty lists.
- **Plays well with others**: its own `table inet blocklist` at priority
-150 coexists with firewalld and UFW (never edits their tables), and a
separate `dynamic` set integrates [fail2ban](fail2ban/) bans that feed
updates can never clobber.
- **Kind to providers**: per-feed minimum fetch intervals, conditional
requests (ETag/Last-Modified), and timer jitter.
- **Observable**: journald logs, `status` against the live kernel, and
pluggable notifications (webhook → Discord/Slack/Telegram today,
command → signal-cli/anything; see
[docs/notifications.md](docs/notifications.md)).
## Quick start
```sh
# install the package (Releases page), then:
sudo vi /etc/nft-blocklist/allowlist.txt # 1. add YOUR management IPs first
sudo nft-blocklist update --dry-run | less # 2. review the exact ruleset
sudo nft-blocklist update # 3. apply
sudo systemctl enable --now nft-blocklist.timer # daily + jitter
sudo systemctl enable nft-blocklist-restore.service # protection at boot
nft-blocklist status
```
Distro guides: [Ubuntu/Debian](docs/debian-ubuntu.md) ·
[RHEL/Rocky/Alma/Fedora](docs/rhel-family.md) · [SLES/openSUSE](docs/suse.md)
## Commands
| Command | Purpose |
|--------------------------------|-----------------------------------------------|
| `update` `--dry-run` `--force` | fetch, merge, atomically apply |
| `validate` | check config + feeds.d without touching state |
| `status` | last run summary + live kernel set counts |
| `rollback` | re-apply the last-known-good ruleset |
## Configuration
`/etc/nft-blocklist/config.yaml` plus one YAML per feed in `feeds.d/`
(strictly validated — typos are errors, not surprises). Shipped defaults:
Spamhaus DROP (v4+v6), FireHOL level1, blocklist.de, CINS Army, and ipsum
with a ≥3-lists confidence threshold; abuse.ch C2 feeds and Tor exit nodes
are included but disabled (aggressive feeds require an explicit
`acknowledge_risk: true`). See [configs/](configs/) — every option is
documented in place.
## Development
Go 1.26+, table-driven unit tests, golden-file ruleset tests, real-kernel
integration tests in unprivileged network namespaces (`make integration` —
no sudo needed), and a five-distro Vagrant e2e matrix (`make e2e`).
Architecture and design rationale: [docs/architecture.md](docs/architecture.md).
Contributing and task-brief conventions: [CONTRIBUTING.md](CONTRIBUTING.md).
## License
MIT — see [LICENSE](LICENSE).