-
Notifications
You must be signed in to change notification settings - Fork 0
Security Architecture
NFTBan uses Polkit-based privilege separation, FHS auto-heal, and a 3-group access control model. No sudo required for service management.
- Reporting Vulnerabilities
- Access Control Model
- Polkit Integration
- FHS Auto-Heal
- FHS Directory Specification
- Security Testing
- Platform Security Tiers
- High-Risk Areas
- Migration from Previous Versions
- Troubleshooting
- References
Do NOT report security vulnerabilities through public GitHub issues.
Report via email: security@itcms.gr
See the Security Policy (SECURITY.md) for disclosure timeline and supported versions.
NFTBan uses a 3-group security model:
| Group | Purpose | Permissions |
|---|---|---|
nftban |
Admin/Operator - humans, CLI | Full access (config, actions, service management via Polkit) |
nftban-auditor |
Read-only - compliance, SOC, reports | View only (status, logs, reports - NO actions) |
nftban-panel |
Panel Operator - cPanel, DirectAdmin, Plesk | Limited actions (ban/unban, status - NO config, NO restart) |
CanConfig() → nftban only (configuration changes)
CanAct() → nftban OR nftban-panel (runtime actions: ban/unban)
CanRead() → nftban OR nftban-auditor OR nftban-panel (view logs/status)
Deprecated: The
nftban-cligroup is no longer used. Usenftbanfor admin access.
System group for daemon operations and Polkit-based service management.
Service management (no sudo via Polkit):
systemctl start nftban-core.service
systemctl stop nftban-login-monitor.service
systemctl restart nftban-core-feeds.service
systemctl enable nftban-core-geoip.timer
systemctl restart suricata.service
systemctl daemon-reloadAllowed units (whitelist in Polkit rule):
-
nftband.service,nftban-core.service,nftban-core-feeds.service/.timer -
nftban-core-geoip.service/.timer,nftban-login-monitor.service -
nftban-health.service/.timer,nftban-health-fix.service -
nftban-maintenance.service/.timer,nftban-watchdog.service/.timer -
nftban-queue.service/.timer,nftban-unified-exporter.service/.timer -
nftban-snapshot.service/.timer,nftban-rollback.service/.timer -
suricata.service,suricata-update.service/.timer
CLI commands:
nftban status
nftban health summary
nftban stats
nftban list
nftban search 1.2.3.4
nftban feeds list
nftban ban 1.2.3.4
nftban unban 1.2.3.4
nftban feeds enable
nftban geoban block CN
nftban whitelist add 10.0.0.1Historical note (≤ v1.100.0). Releases up to v1.100.0 also exposed a Web GUI (
https://SERVER:3940, PAM-authenticated) fornftbangroup members — dashboard, ban/unban, whitelist, module configuration, reports. The GUI was retired in v1.100.1b.A and removed entirely by v1.100.1b.D; current releases provide CLI access only. See archive/Web-GUI-and-nftban-ui-retired.
Security auditors and compliance officers with read-only access.
File access:
/var/log/nftban/*.log # read-only
/var/lib/nftban/* # read-only
/etc/nftban/*.conf # read-only
/var/lib/nftban/reports/auditors # write allowed (audit reports)Allowed systemd actions (via Polkit):
systemctl status nftban-core.service # service status
systemctl show nftban-core.service # service properties
systemctl is-active nftban-*.service # check if running
systemctl is-enabled nftban-*.service # check if enabled
systemctl is-failed nftban-*.service # check if failed
systemctl list-units 'nftban-*' # list units
systemctl list-unit-files 'nftban-*' # list unit filesAllowed CLI commands:
nftban status
nftban health
nftban list
nftban search IP
nftban stats
nftban report
nftban config --viewCannot: ban/unban, modify config, start/stop/restart services, enable/disable modules.
Hosting control panel service accounts (DirectAdmin, cPanel, Plesk, Webmin) with reload-only access.
Panel UI → nftban-panelctl wrapper → Polkit checks → systemctl reload
Security layers: Unix group membership → Polkit authorization (30-nftban-panel.rules) → nftban-panelctl wrapper (command registry RBAC).
Allowed operations:
systemctl status nftban-core.service
systemctl is-active nftban-*.service
systemctl reload nftban-core.service # reload ONLY
systemctl reload nftban-core-feeds.service # reload ONLYCannot: start/stop/restart, enable/disable, modify rules directly, ban/unban, use pkexec.
| Action | nftban | nftban-auditor | nftban-panel |
|---|---|---|---|
| Start/stop/restart services | Yes (Polkit) | No | No |
| Reload services | Yes (Polkit) | No | Yes (limited) |
| Enable/disable services | Yes (Polkit) | No | No |
| Query service status | Yes | Yes (Polkit) | Yes |
| Ban/unban IPs | Yes | No | Yes |
| Modify whitelist | Yes | No | Yes |
| Read configs | Yes | Yes | Yes |
| Read logs | Yes | Yes | Yes |
| Write configs | Yes | No | No |
| Write audit reports | No | Yes | No |
| Use nftban CLI | Yes | Yes (read-only) | Yes (limited) |
# Admin/Operators (CLI, full access)
sudo usermod -aG nftban username
# Auditors (read-only)
sudo usermod -aG nftban-auditor username
# Panel integration (operator actions only)
sudo usermod -aG nftban-panel panel_account
# Verify
id username
# User must re-login for groups to take effect, or:
newgrp nftbanFull administrator:
sudo usermod -aG nftban alice
# Alice: all CLI, manage services (Polkit), ban/unban, config changesRead-only auditor:
sudo usermod -aG nftban-auditor auditor
# auditor: read logs, view status, generate compliance reportsDirectAdmin panel integration:
sudo useradd -r -s /sbin/nologin nftban-da
sudo usermod -aG nftban-panel nftban-da
# nftban-da: ban/unban, status, reload - NO config changes, NO restartcPanel panel integration:
sudo useradd -r -s /sbin/nologin nftban-cp
sudo usermod -aG nftban-panel nftban-cp
# nftban-cp: operator actions via nftban-panelctl wrapperNFTBan uses Polkit instead of sudo for privilege separation. No root escalation, scoped permissions, full audit trail via systemd journal.
File: /etc/polkit-1/rules.d/10-nftban-systemd.rules
polkit.addRule(function(action, subject) {
if (!(action.id == "org.freedesktop.systemd1.manage-units" ||
action.id == "org.freedesktop.systemd1.manage-unit-files")) {
return polkit.Result.NOT_HANDLED;
}
if (!subject.isInGroup("nftban")) {
return polkit.Result.NOT_HANDLED;
}
var unit = action.lookup("unit");
var verb = action.lookup("verb");
// Only allow managing NFTBan units by prefix
if (!unit || unit.indexOf("nftban") !== 0) {
return polkit.Result.NOT_HANDLED;
}
var allowedVerbs = [
"start", "stop", "restart", "reload",
"enable", "disable", "try-restart",
"reload-or-restart", "try-reload-or-restart"
];
if (allowedVerbs.indexOf(verb) >= 0) {
return polkit.Result.YES;
}
return polkit.Result.NOT_HANDLED;
});File: /etc/polkit-1/rules.d/20-nftban-auditor.rules
Read-only systemd queries for nftban-auditor group members.
Allowed verbs: status, show, is-active, is-enabled, is-failed, list-units, list-unit-files
Denied verbs: start, stop, restart, reload, enable, disable, try-restart, reload-or-restart, kill, reset-failed
Denied actions: daemon-reload, pkexec (no command execution)
File: /etc/polkit-1/rules.d/30-nftban-panel.rules
Reload-only for nftban-panel group members. Start/stop/restart/enable/disable are denied.
Rule processing: Polkit evaluates rules in priority order (10 → 20 → 30).
Compromised nftban-group user can:
- Stop/start nftban-* and suricata services
- Read /etc/nftban/*.conf (group read)
Compromised nftban-group user CANNOT:
- Modify /usr/lib/nftban/* (owned by root, not writable)
- Modify /etc/nftban/* (owned by root, read-only for group)
- Manage other services (sshd, httpd, postgresql)
- Escalate to root
- Install backdoors in code
- Modify polkit rules
Mitigations:
- All actions logged to systemd journal
- Monitor for unexpected service restarts
- Review nftban group membership regularly
Automatic detection and correction of permission/ownership issues. Privilege-aware: fixes what the running user owns, reports what needs root.
- Check — Scan all FHS directories for missing dirs, wrong permissions, wrong ownership
-
Fix — If directory owned by nftban and parent is writable: create and chmod. If running as root: create, chown, chmod. Otherwise: report and suggest
sudo nftban health fix all - Report — Summary of fixed items and items needing root
- Log — All actions to systemd journal
# Check FHS compliance (no changes)
nftban fhs status
nftban fhs detailed # full table output
nftban fhs summary # one-line: "FHS: 28/28 OK (100%)"
nftban fhs json # JSON output for automation
nftban fhs html-report # HTML report with charts
nftban fhs mail-report admin@example.com
# Auto-fix (smart healing)
nftban health fix all # as nftban user (fixes owned dirs)
sudo nftban health fix all # as root (fixes everything)
nftban health fix directories # fix only directories
nftban health fix permissions # fix only permissionsExample output (as nftban user):
$ nftban health fix all
[1/2] Creating missing directories...
Created /var/lib/nftban/exports (750 nftban:nftban)
Created /var/log/nftban/reports (750 nftban:nftban)
Cannot create 1 directory (need root):
/usr/lib/nftban/bin → 755 root:root
[2/2] Fixing permissions and ownership...
Fixed /var/lib/nftban → perms: 750
Fixed /var/log/nftban → perms: 750
Cannot fix 2 issues (need root):
/etc/nftban: chmod 750 (currently 755, owned by root)
Summary: Fixed 5 items | Needs root: 3 items
→ sudo nftban health fix all
Timer: nftban-health.timer — runs daily at 03:00 with 30m jitter.
# /etc/systemd/system/nftban-health.service
[Service]
Type=oneshot
User=nftban
Group=nftban
ExecStart=/usr/bin/flock -n /var/cache/nftban/health.lock \
/usr/sbin/nftban health check --auto-heal --cache-status# View heal logs
journalctl -u nftban-health.service -n 50
journalctl -u nftban-health.service --since today$ nftban fhs status
┌──────────────────────────────────────────────────────────────────┐
│ Path │ Status │ Perms │ Owner:Group │
├──────────────────────────────────────────────────────────────────┤
│ /usr/lib/nftban │ ✓ │ 755 │ root:root │
│ /etc/nftban │ ✓ │ 750 │ root:nftban │
│ /var/lib/nftban │ ✓ │ 750 │ nftban:nftban │
│ /var/log/nftban │ ✓ │ 750 │ nftban:nftban │
│ /var/cache/nftban │ ✓ │ 755 │ nftban:nftban │
│ /run/nftban │ ✓ │ 755 │ nftban:nftban │
└──────────────────────────────────────────────────────────────────┘
Summary: 28/28 directories OK (100% compliant)HTML reports include compliance chart, full path table, issues highlighted, auto-fix suggestions.
Single source of truth: build/fhs-spec.yaml
# System directories (root:root, 755) — code
/usr/sbin 755|root|root
/usr/lib/nftban 755|root|root
/usr/lib/nftban/core 755|root|root
/usr/lib/nftban/cli 755|root|root
/usr/lib/nftban/bin 755|root|root
# Configuration (root:nftban, 750) — daemon reads via group
/etc/nftban 750|root|nftban
/etc/nftban/conf.d 750|root|nftban
# Runtime data (nftban:nftban, 750) — nftban user owns
/var/lib/nftban 750|nftban|nftban
/var/lib/nftban/reports 750|nftban|nftban
/var/lib/nftban/reports/auditors 770|root|nftban-auditor
/var/lib/nftban/metrics 750|nftban|nftban
/var/lib/nftban/snapshots 750|nftban|nftban
/var/lib/nftban/exports 750|nftban|nftban
/var/lib/nftban/geoip 750|nftban|nftban
# Logs (nftban:nftban, 750)
/var/log/nftban 750|nftban|nftban
/var/log/nftban/reports 750|nftban|nftban
# Cache and runtime (nftban:nftban, 755)
/var/cache/nftban 755|nftban|nftban
/run/nftban 755|nftban|nftban
# Shared data (root:root, 755) — read-only
/usr/share/nftban 755|root|root
/usr/share/nftban/templates 755|root|rootKey principle: root owns code and config, nftban user owns runtime data.
See FHS Compliance for the complete specification.
sudo rm -rf /var/lib/nftban/exports
sudo -u nftban nftban health fix all
# Expected: Creates /var/lib/nftban/exports successfullysudo rm -rf /usr/lib/nftban/bin
sudo nftban health fix all
# Expected: Creates /usr/lib/nftban/bin with root:root 755# As user in nftban group
systemctl restart nftban-core.service
# Expected: Succeeds without password promptsystemctl restart sshd
# Expected: Access deniedecho "test" >> /usr/lib/nftban/core/firewall.sh
# Expected: Permission denied
echo "test" >> /etc/nftban/nftban.conf
# Expected: Permission deniedSecurity fixes are prioritized by tier:
| Tier | Platforms | Priority |
|---|---|---|
| Tier 0 | Ubuntu 24.04, Debian 12, Rocky 9 | Immediate |
| Tier 1 | Debian 13, Rocky 10, Ubuntu 26.04 | High |
| Tier 2 | Ubuntu 22.04, Debian 11, Rocky 8 | Best-effort |
See Supported Platforms for the full platform contract.
NFTBan uses a defense-in-depth approach to prevent command injection in nftables operations.
User Input → CLI Validation → IPC Layer → Go Daemon Validation → nft Command
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
cmd_ban.sh cmd_validate_ip JSON/socket net.ParseIP() nft add element
| Layer | Location | Validation | Protection |
|---|---|---|---|
| CLI Entry | cmd_ban.sh:105 |
cmd_validate_ip() |
Rejects malformed input early |
| IPC Transport | nft_ipc.sh |
JSON protocol via jq
|
No shell interpolation in transport |
| Go Daemon | main.go:1466 |
net.ParseIP() + net.ParseCIDR()
|
Authoritative validation |
| Emergency Mode | nft_ipc.sh:357 |
_nft_ipc_validate_ip() |
Defense-in-depth for bypass path |
Even if the CLI validates input, the lower-level functions also validate because:
- Code paths change — Future code might call these functions directly
- Emergency mode bypass — Shell fallback when daemon is down
- Security boundary — Each layer assumes untrusted input
Shell layer (validation.sh):
nftban_validate_ip "$ip" # IPv4 or IPv6
nftban_validate_ipv4 "$ip" # Strict IPv4: 4 octets, 0-255
nftban_validate_ipv6 "$ip" # Hex + colons only
nftban_validate_cidr "$cidr" # IP/prefix formatIPC layer (nft_ipc.sh):
_nft_ipc_validate_ip "$ip" # Local validation for emergency modeGo layer (pkg/netutil/ip.go):
net.ParseIP(ip) // Returns nil if invalid
net.ParseCIDR(cidr) // Returns error if invalid
netutil.IsValidIP(ipStr) // Boolean wrapperAll validation uses strict character whitelists:
| Type | Allowed Characters | Example |
|---|---|---|
| IPv4 | 0-9. |
192.168.1.1 |
| IPv6 | 0-9a-fA-F: |
2001:db8::1 |
| CIDR | IPv4/IPv6 + /0-9
|
10.0.0.0/8 |
| Port | 0-9 |
8080 |
Rejected characters: ` $ " \ ; | & > < ( ) { } — prevents shell injection
| File | Function | Validation Added |
|---|---|---|
nft_ipc.sh |
nft_emergency_ban() |
_nft_ipc_validate_ip() |
nft_ipc.sh |
nft_emergency_unban() |
_nft_ipc_validate_ip() |
cmd_whitelist.sh |
nftban_whitelist_add_ip() |
nftban_validate_ip/cidr() |
cmd_whitelist.sh |
nftban_whitelist_remove_ip() |
nftban_validate_ip/cidr() |
maintenance.sh |
whitelist sync | Inline regex validation |
cmd_firewall.sh |
backup restore | Character whitelist regex |
cmd_flush.sh |
system IP restore | Character whitelist regex |
# Test CLI validation
nftban ban '; rm -rf /' # Expected: "Invalid IP address"
nftban ban '$(whoami)' # Expected: "Invalid IP address"
nftban ban '192.168.1.1; echo x' # Expected: "Invalid IP address"
# Test IPC validation (Go daemon logs)
journalctl -u nftband -f
# Then attempt:
echo '{"method":"ban","params":{"ip":"$(id)"}}' | socat - /run/nftban/nftband.sock
# Expected: {"success":false,"error":"invalid IP address"}
# Verify Go validation
grep "invalid IP" /var/log/nftban/daemon.logTo avoid rejecting legitimate IPs:
-
IPv6 compressed notation —
::1,fe80::1are valid -
CIDR notation —
10.0.0.0/8includes the slash -
IPv4-mapped IPv6 —
::ffff:192.168.1.1is handled
Validation functions are tested against RFC 5321 (IPv4) and RFC 4291 (IPv6) formats.
NFTBan uses a ban-first architecture where enforcement is immediate and enrichment is asynchronous. This ensures minimal latency in the hot path.
┌─────────────────────────────────────────────────────────────────────┐
│ BAN REQUEST PATH (SYNC) │
│ │
│ nftban ban <ip> ──► Whitelist Check ──► backend.Ban() ──► OK │
│ │ │ (BLOCKING) │ (IMMEDIATE) │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ CLI validation main.go:1474 main.go:1507-1512 │
│ │
└─────────────────────────────────────────────────────────────────────┘
│
│ (POST-BAN, NON-BLOCKING)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ ENRICHMENT PATH (INFORMATIONAL) │
│ │
│ GeoIP Lookup ──► Source Tagging ──► Log Entry ──► Metrics │
│ main.go:1544 main.go:1531 main.go:1545 │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ FEED SYNC PATH (ASYNC) │
│ │
│ feeds.conf ──► Timer Trigger ──► Download ──► nft load │
│ (enabled) (every 2 min) (curl) (atomic) │
│ │
│ Result: IPs added to blacklist_ipv4/ipv6 sets BEFORE requests │
│ │
└─────────────────────────────────────────────────────────────────────┘
| Lookup | Classification | Path | Justification |
|---|---|---|---|
| Input validation | Decision-critical | SYNC | Invalid IP cannot be banned |
| Whitelist check | Decision-critical | SYNC | Whitelisted IPs must never be banned |
| Set selection (IPv4/IPv6) | Decision-critical | SYNC | Determines target nftables set |
backend.Ban() |
Decision-critical | SYNC | Authoritative enforcement action |
| Feed lookup | Precomputed | ASYNC (timer) | Feeds loaded into sets beforehand |
| GeoIP lookup | Metadata only | Post-ban | Only affects logging, not ban decision |
| ASN/reverse DNS | Metadata only | Post-ban | Only affects analytics |
1. Feeds are Precomputed, Not Per-Ban
Feeds are synchronized asynchronously into nftables sets:
- Timer:
nftban-core-feeds.timer(every 2 minutes) - Files:
/var/lib/nftban/feeds/*.txt - Sets:
blacklist_ipv4,blacklist_ipv6
When a ban request arrives, the IP is added to the same sets. There is no per-ban feed lookup.
2. GeoIP is Post-Ban Informational
// main.go:1507-1512 — Ban executes FIRST
result, err := d.backend.Ban(d.ctx, nftbackend.BanRequest{
IP: ip, Timeout: timeout, Reason: reason, Source: source,
})
// main.go:1544 — GeoIP lookup AFTER ban (informational only)
country := lookupCountry(ip)
_ = banlog.LogBanWithReason(ip, banSource, country, reason)GeoIP affects:
- Log entry country field
- Prometheus metric labels
- Analytics aggregation
GeoIP does NOT affect:
- Whether to ban
- Ban TTL/timeout
- Target set selection
- Action type
3. Enrichment Never Blocks Ban
If any enrichment lookup fails:
- Ban still succeeds
- Log entry uses fallback values (e.g.,
country="unknown") - Metrics still recorded
| File | Line | Finding |
|---|---|---|
cmd/nftband/main.go |
1459-1571 |
handleBanRequest(): Whitelist check → Ban() → GeoIP |
cmd/nftband/main.go |
1507-1512 | Ban execution: No feed dependency |
cmd/nftband/main.go |
1544 | GeoIP lookup: Post-ban, informational |
pkg/nftbackend/backend.go |
224-229 | Set selection: Based on IP type only |
nftban_feeds.sh |
651-680 | Feeds: Timer-based sync, not per-request |
| Path | Target Latency | Blocking? |
|---|---|---|
| Ban execution | < 10ms P99 | Yes (minimal) |
| GeoIP lookup | < 5ms | Yes (post-ban) |
| Feed sync | 30-60s | No (timer-based) |
| Enrichment queue | N/A | No (future work) |
These are micro-optimizations, not architectural fixes:
- GeoIP async decoupling — Move to enrichment worker
-
Latency metrics — Add
nftban_ban_request_duration_seconds - Enrichment queue — Async metadata updates with BanID correlation
Note: The current architecture is already correct. These are polish items.
Changes to these require extra security review:
- systemd unit files, timers, sockets
- polkit rules or helpers
- nftables rule generation
- installer and maintainer scripts (deb/rpm)
- receipt schema or validation logic
- any code paths that modify
/etc/nftbanor firewall state
If upgrading from the old 4-group model (nftban, nftban-cli, nftban-web, nftban-auditors):
# Migrate nftban-cli and nftban-web users to nftban group
for old_group in nftban-cli nftban-web; do
if getent group "$old_group" >/dev/null 2>&1; then
for user in $(getent group "$old_group" | cut -d: -f4 | tr ',' ' '); do
echo "Migrating $user from $old_group to nftban"
sudo usermod -aG nftban "$user"
done
fi
done
# Migrate nftban-auditors (plural) to nftban-auditor (singular)
if getent group nftban-auditors >/dev/null 2>&1; then
for user in $(getent group nftban-auditors | cut -d: -f4 | tr ',' ' '); do
echo "Migrating $user from nftban-auditors to nftban-auditor"
sudo usermod -aG nftban-auditor "$user"
done
fi
# Clean up old groups after verification
# sudo groupdel nftban-cli
# sudo groupdel nftban-web
# sudo groupdel nftban-auditors# 1. Check group membership
id username | grep nftban
# 2. Add to group if missing
sudo usermod -aG nftban username
# 3. Re-login or activate immediately
newgrp nftban
# 4. Verify polkit rule exists
ls -la /etc/polkit-1/rules.d/10-nftban-systemd.rules- Only add users who actually manage the firewall to
nftban - Use
nftban-auditorfor read-only access — do not add auditors tonftban - Keep panel accounts in
nftban-panelonly — never innftban - Review group membership quarterly:
getent group nftban
getent group nftban-auditor
getent group nftban-panel- FHS Compliance — Filesystem hierarchy standard
- Security Operations Guide — Hardening, monitoring, emergency procedures
- CLI Commands Reference — All CLI commands
- Configuration Reference — Full configuration options
- Timer Schedule — Timer architecture
- SECURITY.md — Vulnerability reporting policy
NFTBan Wiki
Getting Started
Architecture
Modules
- BotGuard (HTTP L7)
- DDoS Protection (L3/L4)
- Portscan Detection
- Login Monitoring
- Blacklist & Threat Intelligence
- Suricata IDS Integration
- DNS Tunnel Suspicion
Operator Reference
- CLI Commands Reference
- Configuration Reference
- Systemd Units & Timers
- Optimization & Tuning
- Security Operations Guide
- GeoIP Database Guide
- FHS Compliance
- Troubleshooting: Smoke & Selftest
Verification & Trust
- Glossary & Vocabulary
- Known Limitations
- Metrics & Evidence Model
- Binary Verification (SLSA)
- Security Architecture
Reference
Legal