CSF-inspired firewall module for NixOS, built around nftables with declarative policy, mutable runtime overlays, and operator-focused tooling.
Works in both modes:
- flake input (
inputs.nix-csf.url = "github:cheirekov/nix-csf") - non-flake import (
imports = [ /path/to/nix-csf ];)
Current module version source of truth: VERSION.
- Start here:
- Flake example:
examples/flake/test-server-bg-netdata-lfd/flake.nix - Non-flake example:
examples/non-flake/test-server-bg-netdata-import.nix
- Flake example:
- Day-2 operations:
- Scripts/runbook index:
docs/SCRIPTS_RUNBOOK.md - Security validation runbook:
docs/SECURITY_VALIDATION_RUNBOOK.md - Troubleshooting:
docs/TROUBLESHOOTING.md - Use-case catalog:
docs/USE_CASES.md - Deployment blueprints:
docs/DEPLOYMENT_BLUEPRINTS.md - Authoritative DNS blueprint:
docs/BIND_PRODUCTION_BLUEPRINT.md
- Scripts/runbook index:
- Security and architecture:
- Architecture:
docs/ARCHITECTURE.md - Cluster control-plane POC:
docs/CLUSTER_CONTROL_PLANE_POC.md - Control-plane TLS proxy POC:
docs/CONTROL_PLANE_TLS_PROXY_POC.md - Cluster auth token runbook:
docs/CLUSTER_AUTH_TOKENS.md - Dynamic/cluster recommendation:
docs/DYNAMIC_CLUSTER_POC.md
- Architecture:
- Monitoring:
- Prometheus/Grafana:
docs/MONITORING.md - Netdata integration:
docs/NETDATA.md
- Prometheus/Grafana:
- Migration:
- Legacy CSF import:
docs/CSF_IMPORT.md - Release hardening gate:
docs/RELEASE_CANDIDATE_HARDENING.md - RC decision package:
docs/RELEASE_CANDIDATE_DECISION.md
- Legacy CSF import:
- Core module:
services.nixCsf - Stateful baseline firewall (
nftables) with strict apply/refresh pipeline - Static local policy sets:
allow*,deny*, open ports, ICMP profiles - DNS-focused flood meters with optional trusted-source bypass selectors (
rateLimits.dnsFlood.*) - Country controls:
- full-country deny/allow modes,
- per-port country deny (
CC_DENY_PORTSstyle), - per-port country allow (
CC_ALLOW_PORTSstyle)
- Feed-backed deny overlays (catalog + source governance)
- Hybrid local file overlays (
localFiles.allow|deny|ignore) - Legacy CSF import tool (
nix-csf-import-csf) - Cluster policy + dynamic offender snapshots (TTL-aware)
- Optional local control-plane +
nix-csfctlmutation workflow - Escalation engine v2 for temp->perm promotion (
controlPlane.escalation.*: threshold/window/cooldown/reasonClasses + audit IDs) - Cluster propagation semantics v2 (
controlPlane.propagation.*: local vs cluster scope defaults, provenance metadata, replay marker) - Nix-native LFD-like detector framework v2 (explicit
lfdDetector.detectorsor curatedlfdDetector.detectorPack) and fail2ban adapter- built-in templates:
ssh-auth,nginx-auth,dovecot-auth, plus opt-incaddy-auth,postfix-sasl,control-plane-auth, andapi-proxy-auth
- built-in templates:
- Auth token rotation (
*.authTokenFiles) for remote snapshots - Docker coexistence profile (
coexistence.profile = "docker-coexist") - NAT datapath foundation (
nat.*: IPv4 masquerade + explicit port forwards) - Forwarding policy matrix (
forwarding.zones+forwarding.rules) - Optional egress controls (
egress.*: output policy + destination/interface allow/deny selectors) - Structured logs + Prometheus textfile metrics + Netdata integration
- Validation lanes split for agent/operator workflows
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nix-csf.url = "github:<org>/nix-csf?ref=vX.Y.Z";
};
outputs = { nixpkgs, nix-csf, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
nix-csf.nixosModules.default
({ ... }: {
services.nixCsf = {
enable = true;
openTCPPorts = [ 22 80 443 ];
};
})
];
};
};
}{ ... }:
{
imports = [
/path/to/nix-csf
];
services.nixCsf = {
enable = true;
openTCPPorts = [ 22 443 ];
};
}imports = [
"${builtins.fetchTarball "https://github.com/<org>/nix-csf/archive/refs/tags/vX.Y.Z.tar.gz"}"
];services.nixCsf = {
enable = true;
threatProfile = "server"; # custom | server | workstation | edge
openTCPPorts = [ 22 80 443 ];
openUDPPorts = [ 53 ];
icmp = {
profile = "safe"; # legacy | off | safe | diagnostic | open
rateLimit = {
enable = true;
rate = "30/second";
burst = 120;
};
};
observability.metrics = {
enable = true;
outputFile = "/var/lib/node_exporter/textfile_collector/nix-csf.prom";
};
autoRefresh.onCalendar = "hourly";
};For production-style complete examples (global ports + country-restricted SSH + imported legacy lists + local control-plane + LFD detector), use:
examples/flake/test-server-bg-netdata-lfd/flake.nixexamples/non-flake/test-server-bg-netdata-import.nix
services.nixCsf.country = {
enable = true;
mode = "deny"; # deny | allow
countries = [ "RU" "CN" ];
portDeny = {
enable = true;
countries = [ "RU" "CN" ];
tcpPorts = [ 443 ];
};
portAllow = {
enable = true;
countries = [ "BG" ];
tcpPorts = [ 112 ];
};
};services.nixCsf = {
localFiles = {
enable = true;
allow = [ "/var/lib/nix-csf/lists/allow.local" ];
deny = [ "/var/lib/nix-csf/lists/deny.local" ];
ignore = [ "/var/lib/nix-csf/lists/ignore.local" ];
};
clusterPolicy = {
enable = true;
url = "https://policy.example.org/nix-csf/prod.json";
failOpen = true;
};
dynamicOffenders = {
enable = true;
url = "https://policy.example.org/nix-csf/dynamic-offenders.json";
defaultEntryTTLSeconds = 900;
maxEntries = 20000;
failOpen = true;
};
};services.nixCsf = {
controlPlane = {
enable = true;
bindAddress = "127.0.0.1";
port = 18081;
environment = "lab";
requireAuth = false; # lab only
propagation = {
policyDefaultScope = "cluster";
dynamicDefaultScope = "cluster";
escalationPromotionScope = "cluster";
requireNodeForLocalScope = true;
includeProvenanceMetadata = true;
};
};
lfdDetector = {
enable = true;
detectorPack = {
enable = true;
profile = "server-web"; # ssh-auth + nginx-auth
sshAuth.threshold = 5;
nginxAuth.threshold = 10;
caddyAuth.enable = true; # optional template
postfixSasl.enable = true; # optional template
controlPlaneAuth.enable = true; # optional template
apiProxyAuth.enable = true; # optional template
};
refreshAfterBan = true;
};
};Offline policy authoring/compile path:
nix-csfctl --output pretty policy compile --input ./policy-source.json --cluster-output ./cluster-policy.json --dynamic-output ./dynamic-offenders.jsonservices.nixCsf = {
netdata = {
enable = true;
# Reduce non-critical default charts.d module checks while keeping nix_csf charts.
noiseProfile = "chartsd-minimal"; # off | chartsd-minimal
};
};services.nixCsf = {
coexistence.profile = "docker-coexist";
forwardPolicy = "accept";
};services.nixCsf = {
nat = {
enable = true;
externalInterface = "eth0";
masquerade = {
enable = true;
sourceIPv4 = [ "10.42.0.0/16" ];
};
portForwards = [
{
protocol = "tcp";
externalPort = 8080;
destinationAddress = "10.42.0.10";
destinationPort = 80;
sourceIPv4 = [ "198.51.100.0/24" ];
}
];
};
};services.nixCsf = {
forwardPolicy = "drop";
forwarding = {
zones = {
lan = {
interfaces = [ "br-lan" ];
cidrIPv4 = [ "10.42.0.0/16" ];
};
wan = {
interfaces = [ "eth0" ];
};
};
rules = [
{
fromZone = "lan";
toZone = "wan";
protocol = "tcp";
destinationPorts = [ 80 443 ];
}
];
};
};services.nixCsf = {
egress = {
enable = true;
defaultPolicy = "drop";
trustedInterfaces = [ "wg0" ];
allowIPv4 = [ "198.51.100.0/24" ];
denyIPv4 = [ "203.0.113.0/24" ];
allowTCPPorts = [ 53 443 ];
allowUDPPorts = [ 53 ];
};
};nix-csf-import-csf \
--allow-file /etc/csf/csf.allow \
--deny-file /etc/csf/csf.deny \
--ignore-file /etc/csf/csf.ignore \
--output-dir /var/lib/nix-csf/imported \
--prefix legacy-csfnix-csfctl policy add deny 203.0.119.9/32
nix-csfctl --node-id edge-us-01 policy add deny 203.0.119.140/32 --scope local --source lfd
nix-csfctl ban-temp 203.0.119.10/32 --ttl 900 --reason syn_flood
nix-csfctl ban-temp 203.0.119.142/32 --ttl 600 --reason lfd:ssh_auth --scope local --node-id edge-us-01 --source lfd
nix-csfctl promotions --limit 20
sudo systemctl start nix-csf-refresh.servicesudo nix-csf-triage --output /tmp/nix-csf-triage-$(date -u +%Y%m%dT%H%M%SZ).log./scripts/validate-agent.shAlias:
./scripts/validate-fast.sh./scripts/validate.shWith captured summary/log handoff:
./scripts/validate-capture.sh- Module asserts
networking.firewall.enable = false. - Apply service runs before network stack (
network-pre.target). - Refresh service runs after network is online.
failOpen = falsefor feeds/snapshots requires valid cache and can fail closed.- Dynamic bans are evaluated after explicit allow/ignore overlays.
- Stage-1 NAT is IPv4-focused and not combined with
coexistence.profile = "docker-coexist". forwarding.rulesrequiresforwardPolicy = "drop"and is not combined withcoexistence.profile = "docker-coexist"in Stage 1.egress.enable = falsekeeps output policy lockout-safe (accept).- When
egress.enable = trueandegress.defaultPolicy = "drop", define explicit allow selectors (trustedInterfaces, allow CIDRs, or allow ports). - Local list conflict audit artifacts are written to:
/var/lib/nix-csf/local-list-audit-summary.tsv/var/lib/nix-csf/local-list-conflicts.tsv
- Runtime-generated state is under
/var/lib/nix-csfand/var/lib/nix-csf-control-plane.
VERSIONis source of truth.- Tags follow
v<semver>. MAJOR: breaking changes.MINOR: backward-compatible features.PATCH: backward-compatible fixes.
Release commands:
./scripts/release.sh --version 1.0.3 --dry-run
./scripts/release.sh --version 1.0.3
./scripts/release.sh --version 1.0.3 --pushdocs/ARCHITECTURE.mddocs/USE_CASES.mddocs/TROUBLESHOOTING.mddocs/SCRIPTS_RUNBOOK.md
docs/LFD_NIX_WAY_POC.mddocs/EPIC_FIREWALL_LFD_EXPANSION.mddocs/DYNAMIC_CLUSTER_POC.mddocs/CLUSTER_CONTROL_PLANE_POC.md
docs/MONITORING.mddocs/NETDATA.mddocs/FAIL2BAN_ADAPTER.mddocs/LFD_DETECTOR.md
docs/CSF_IMPORT.mddocs/RELEASE.md
docs/DELIVERY_BOARD.mddocs/SESSION_BRIEF.mddocs/PM_BA_CHANGELOG.mddocs/TEAM_OPERATING_RULES.mddocs/ROADMAP.md
MIT (LICENSE)