nftgen 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. nftgen-1.0.0/.gitignore +15 -0
  2. nftgen-1.0.0/CHANGELOG.md +123 -0
  3. nftgen-1.0.0/LICENSE +21 -0
  4. nftgen-1.0.0/PKG-INFO +149 -0
  5. nftgen-1.0.0/README.md +115 -0
  6. nftgen-1.0.0/example/definitions/interfaces.yaml +7 -0
  7. nftgen-1.0.0/example/definitions/networks.yaml +25 -0
  8. nftgen-1.0.0/example/definitions/services.yaml +14 -0
  9. nftgen-1.0.0/example/policies/hosts/gateway.yaml +141 -0
  10. nftgen-1.0.0/example/policies/hosts/router1.yaml +65 -0
  11. nftgen-1.0.0/example/policies/hosts/router2.yaml +56 -0
  12. nftgen-1.0.0/example/policies/includes/common-forward.yaml +8 -0
  13. nftgen-1.0.0/example/policies/includes/common-input.yaml +13 -0
  14. nftgen-1.0.0/example/policies/includes/common-sets.yaml +12 -0
  15. nftgen-1.0.0/example/policies/includes/local-egress.yaml +12 -0
  16. nftgen-1.0.0/example/policies/includes/scrub.yaml +25 -0
  17. nftgen-1.0.0/example/sites/site1.yaml +10 -0
  18. nftgen-1.0.0/example/sites/site2.yaml +5 -0
  19. nftgen-1.0.0/example-fleet/README.md +126 -0
  20. nftgen-1.0.0/example-fleet/definitions/interfaces.yaml +13 -0
  21. nftgen-1.0.0/example-fleet/definitions/networks.yaml +72 -0
  22. nftgen-1.0.0/example-fleet/definitions/services.yaml +18 -0
  23. nftgen-1.0.0/example-fleet/generated/br1-r.nft +228 -0
  24. nftgen-1.0.0/example-fleet/generated/br2-r.nft +228 -0
  25. nftgen-1.0.0/example-fleet/generated/hq-r.nft +228 -0
  26. nftgen-1.0.0/example-fleet/policies/hosts/br1-r.yaml +159 -0
  27. nftgen-1.0.0/example-fleet/policies/hosts/br2-r.yaml +159 -0
  28. nftgen-1.0.0/example-fleet/policies/hosts/hq-r.yaml +159 -0
  29. nftgen-1.0.0/example-fleet/policies/includes/common/bad-tcp.yaml +34 -0
  30. nftgen-1.0.0/example-fleet/policies/includes/common/blocklist.yaml +8 -0
  31. nftgen-1.0.0/example-fleet/policies/includes/common/conntrack.yaml +15 -0
  32. nftgen-1.0.0/example-fleet/policies/includes/common/fwd-catchall.yaml +16 -0
  33. nftgen-1.0.0/example-fleet/policies/includes/common/icmp.yaml +30 -0
  34. nftgen-1.0.0/example-fleet/policies/includes/common/scrub-wan.yaml +13 -0
  35. nftgen-1.0.0/example-fleet/policies/includes/sets/scrub-sets.yaml +15 -0
  36. nftgen-1.0.0/example-fleet/policies/includes/zones/dmz-inet.yaml +26 -0
  37. nftgen-1.0.0/example-fleet/policies/includes/zones/input-local.yaml +25 -0
  38. nftgen-1.0.0/example-fleet/policies/includes/zones/nwm-services.yaml +31 -0
  39. nftgen-1.0.0/example-fleet/policies/includes/zones/nwm-transit.yaml +20 -0
  40. nftgen-1.0.0/example-fleet/policies/includes/zones/services-inet.yaml +26 -0
  41. nftgen-1.0.0/example-fleet/policies/includes/zones/transit-dmz.yaml +19 -0
  42. nftgen-1.0.0/example-fleet/policies/includes/zones/transit-services.yaml +33 -0
  43. nftgen-1.0.0/example-fleet/policies/includes/zones/users-dmz.yaml +19 -0
  44. nftgen-1.0.0/example-fleet/policies/includes/zones/users-inet.yaml +30 -0
  45. nftgen-1.0.0/example-fleet/policies/includes/zones/users-services.yaml +30 -0
  46. nftgen-1.0.0/example-fleet/policies/includes/zones/users-transit.yaml +21 -0
  47. nftgen-1.0.0/example-fleet/policies/includes/zones/wan-dmz.yaml +20 -0
  48. nftgen-1.0.0/example-fleet/sites/br1.yaml +8 -0
  49. nftgen-1.0.0/example-fleet/sites/br2.yaml +8 -0
  50. nftgen-1.0.0/example-fleet/sites/hq.yaml +11 -0
  51. nftgen-1.0.0/nftgen/__init__.py +13 -0
  52. nftgen-1.0.0/nftgen/__main__.py +6 -0
  53. nftgen-1.0.0/nftgen/cli.py +209 -0
  54. nftgen-1.0.0/nftgen/definitions.py +180 -0
  55. nftgen-1.0.0/nftgen/generate.py +334 -0
  56. nftgen-1.0.0/nftgen/ir.py +588 -0
  57. nftgen-1.0.0/nftgen/protocols.py +38 -0
  58. nftgen-1.0.0/nftgen/rules.py +1191 -0
  59. nftgen-1.0.0/nftgen/schema.py +317 -0
  60. nftgen-1.0.0/nftgen/validate.py +89 -0
  61. nftgen-1.0.0/pyproject.toml +86 -0
  62. nftgen-1.0.0/tests/behavioral/__init__.py +0 -0
  63. nftgen-1.0.0/tests/behavioral/agent.py +568 -0
  64. nftgen-1.0.0/tests/behavioral/fixtures/basic/definitions/defs.yaml +12 -0
  65. nftgen-1.0.0/tests/behavioral/fixtures/basic/policies/hosts/router.yaml +50 -0
  66. nftgen-1.0.0/tests/behavioral/fixtures/bogon-counter/definitions/defs.yaml +12 -0
  67. nftgen-1.0.0/tests/behavioral/fixtures/bogon-counter/policies/hosts/router.yaml +27 -0
  68. nftgen-1.0.0/tests/behavioral/fixtures/concat-flows/definitions/defs.yaml +14 -0
  69. nftgen-1.0.0/tests/behavioral/fixtures/concat-flows/policies/hosts/router.yaml +25 -0
  70. nftgen-1.0.0/tests/behavioral/fixtures/live-blocklist/definitions/defs.yaml +7 -0
  71. nftgen-1.0.0/tests/behavioral/fixtures/live-blocklist/policies/hosts/router.yaml +25 -0
  72. nftgen-1.0.0/tests/behavioral/fixtures/nat/definitions/defs.yaml +20 -0
  73. nftgen-1.0.0/tests/behavioral/fixtures/nat/policies/hosts/router.yaml +81 -0
  74. nftgen-1.0.0/tests/behavioral/fixtures/nflog/definitions/defs.yaml +7 -0
  75. nftgen-1.0.0/tests/behavioral/fixtures/nflog/policies/hosts/router.yaml +32 -0
  76. nftgen-1.0.0/tests/behavioral/fixtures/rpfilter/definitions/defs.yaml +5 -0
  77. nftgen-1.0.0/tests/behavioral/fixtures/rpfilter/policies/hosts/router.yaml +29 -0
  78. nftgen-1.0.0/tests/behavioral/fixtures/service-pairs/definitions/defs.yaml +9 -0
  79. nftgen-1.0.0/tests/behavioral/fixtures/service-pairs/policies/hosts/router.yaml +23 -0
  80. nftgen-1.0.0/tests/behavioral/fixtures/set-member/definitions/defs.yaml +9 -0
  81. nftgen-1.0.0/tests/behavioral/fixtures/set-member/policies/hosts/router.yaml +23 -0
  82. nftgen-1.0.0/tests/behavioral/fixtures/statements/definitions/defs.yaml +16 -0
  83. nftgen-1.0.0/tests/behavioral/fixtures/statements/policies/hosts/router.yaml +87 -0
  84. nftgen-1.0.0/tests/behavioral/fixtures/tcp-flags/definitions/defs.yaml +8 -0
  85. nftgen-1.0.0/tests/behavioral/fixtures/tcp-flags/policies/hosts/router.yaml +43 -0
  86. nftgen-1.0.0/tests/behavioral/fixtures/vmap-group/definitions/defs.yaml +7 -0
  87. nftgen-1.0.0/tests/behavioral/fixtures/vmap-group/policies/hosts/router.yaml +27 -0
  88. nftgen-1.0.0/tests/behavioral/fixtures/vmap-input/definitions/defs.yaml +10 -0
  89. nftgen-1.0.0/tests/behavioral/fixtures/vmap-input/policies/hosts/router.yaml +35 -0
  90. nftgen-1.0.0/tests/behavioral/fixtures/vmap-pairs/definitions/defs.yaml +9 -0
  91. nftgen-1.0.0/tests/behavioral/fixtures/vmap-pairs/policies/hosts/router.yaml +27 -0
  92. nftgen-1.0.0/tests/behavioral/netns.py +185 -0
  93. nftgen-1.0.0/tests/golden/gateway.nft +99 -0
  94. nftgen-1.0.0/tests/golden/router1.nft +108 -0
  95. nftgen-1.0.0/tests/golden/router2.nft +107 -0
  96. nftgen-1.0.0/tests/test_annotate.py +122 -0
  97. nftgen-1.0.0/tests/test_behavioral.py +901 -0
  98. nftgen-1.0.0/tests/test_build.py +226 -0
  99. nftgen-1.0.0/tests/test_comments.py +169 -0
  100. nftgen-1.0.0/tests/test_concat.py +350 -0
  101. nftgen-1.0.0/tests/test_counters.py +39 -0
  102. nftgen-1.0.0/tests/test_definitions.py +235 -0
  103. nftgen-1.0.0/tests/test_fleet.py +73 -0
  104. nftgen-1.0.0/tests/test_fleet_behavioral.py +341 -0
  105. nftgen-1.0.0/tests/test_flowtables.py +89 -0
  106. nftgen-1.0.0/tests/test_generate.py +231 -0
  107. nftgen-1.0.0/tests/test_ir_sets.py +200 -0
  108. nftgen-1.0.0/tests/test_keyword_guard.py +67 -0
  109. nftgen-1.0.0/tests/test_resolve.py +54 -0
  110. nftgen-1.0.0/tests/test_rules.py +687 -0
  111. nftgen-1.0.0/tests/test_schema.py +335 -0
  112. nftgen-1.0.0/tests/test_smoke.py +21 -0
  113. nftgen-1.0.0/tests/test_statements.py +169 -0
  114. nftgen-1.0.0/tests/test_table_shapes.py +68 -0
  115. nftgen-1.0.0/tests/test_tcp_flags.py +74 -0
  116. nftgen-1.0.0/tests/test_validate.py +46 -0
  117. nftgen-1.0.0/tests/test_vmaps.py +575 -0
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .venv/
8
+ venv/
9
+ .pytest_cache/
10
+ # generated rulesets are transient, EXCEPT the drift-pinned example projects
11
+ # whose committed render is the golden their drift tests compare against.
12
+ **/generated/
13
+ !example-fleet/generated/
14
+ # local Claude Code config (agents, settings) stays local
15
+ .claude/
@@ -0,0 +1,123 @@
1
+ # Changelog
2
+
3
+ What changed per release, newest first. Details are in the commit messages;
4
+ the *why* behind design choices is in [DECISIONS.md](DECISIONS.md).
5
+
6
+ Versioning: [semantic versioning](https://semver.org) from 1.0.0 on. A new
7
+ check that rejects input which rendered a wrong or broken ruleset is a fix,
8
+ not a breaking change.
9
+
10
+ ## 1.0.0 — 2026-10-18
11
+
12
+ The first release on PyPI (`pip install nftgen`).
13
+
14
+ ### Breaking
15
+
16
+ - Rule values are schema-checked: an `action:` word outside `accept`, `drop`,
17
+ `reject`, `return`, `continue`, `masquerade` or `reject with …` is an error
18
+ (use `raw:` for others).
19
+ - Python 3.10 dropped; `requires-python >=3.11`.
20
+
21
+ ### New
22
+
23
+ - `comment:` on rules, chains, set entries and tables — native nft comments.
24
+ - One value or a list on every matching key (`dport: [ssh, 8080]`,
25
+ `saddr: [mgmt, 10.0.0.5]`); `ct: established` takes one state plain.
26
+ - `proto:` is optional when the ports come from services: each port keeps
27
+ its own protocol (`meta l4proto . th dport { tcp . 53, udp . 53 }`).
28
+ - Concat vmap rows take `action:` (accept, drop, return, continue, jump, goto).
29
+ - `family: ip | ip6` for concat sets filled at runtime.
30
+ - Protocol numbers (`17`, `112`) are checked as their names everywhere.
31
+ - `render_tables(…, annotate=fn)`: an optional per-rule hook whose note is
32
+ appended as a `# <note>` file comment (used by ansible-firewall's blame
33
+ notes); output is unchanged without it.
34
+
35
+ ### Fixed
36
+
37
+ Mistakes that rendered a wrong or broken ruleset now fail the
38
+ build with a clear error:
39
+
40
+ - `nftgen build <typo>` with no host policies; `--host` with a path or a
41
+ pattern; two files for one host (`fw1.yaml` + `fw1.yml`).
42
+ - `type:`/`policy:`/`priority:` on a chain without `hook:`.
43
+ - `iif`/`oif` with a named interface set; `flow-offload:` to an undeclared
44
+ flowtable; mark, `set-mss`, log level/group/prefix values.
45
+ - Duplicate map keys; a `sport`+`dport` vmap without a `proto` column; a port
46
+ on a protocol without ports; reject/masquerade inside a vmap.
47
+ - `icmp`/`icmpv6` with dual-stack addresses render only their own family.
48
+ - `log:` with both `level` and `group` (nft refuses the pair).
49
+ - Flowtable `devices:` takes one group plain (`devices: wan` was read letter
50
+ by letter); a device shared by two groups is listed once.
51
+ - Hand-written sets: `flags:` must be nft set flags (`auto-merge` isn't one);
52
+ `flags:`/`elements:` take one value plain instead of letter by letter.
53
+ - Table `counters:`/`raw:` take one value plain (`counters: bad` rendered
54
+ counters `b`, `a`, `d`); a blank `chains:` is empty instead of a crash;
55
+ wrong shapes (`chains:` as a mapping, a non-text name, `concat:` as a
56
+ string) are clean errors.
57
+ - `log: {}` logs; OS errors are clean errors; a closed stdout pipe exits
58
+ quietly (141).
59
+
60
+ ### Infra
61
+
62
+ Renovate (auto-merges minor updates after CI), weekly CI, a lint
63
+ job plus tests on Python 3.11–3.14.
64
+
65
+ ## 0.7.0 — 2026-10-11
66
+
67
+ **Breaking:** named service sets are `(proto . port)` pair sets
68
+ (`type inet_proto . inet_service`); `dport`/`sport` vmaps are
69
+ `meta l4proto . th dport` pair lookups, and a bare port number there needs
70
+ `/tcp`.
71
+
72
+ - A named service set or a port vmap no longer opens a port on a protocol the
73
+ service doesn't have (udp/80 for a tcp-only service) — proven on the wire.
74
+ - Include files are validated: a typo'd section (`rule:`), a file under the
75
+ wrong list, or a non-list fragment is an error instead of contributing
76
+ nothing (an empty `policy: drop` chain = a lockout on deploy).
77
+
78
+ ## 0.6.0 — 2026-07-14
79
+
80
+ - `render_tables()` core + `PathResolver` / `DictResolver`: nftgen can be
81
+ embedded as a render backend (sessrumnir's Ansible vars mode uses it).
82
+ - `Definitions.from_named_mappings()`, with both origins named in duplicate
83
+ errors; malformed definition layers are clean errors.
84
+
85
+ ## 0.5.0 — 2026-07-13
86
+
87
+ - `nftgen build <root> --host X --stdout`: render one host to stdout, write
88
+ nothing.
89
+
90
+ ## 0.4.0 — 2026-07-11
91
+
92
+ **Breaking:** interface keys renamed `iif`/`oif` → `iifname`/`oifname`
93
+ (matching nft); object names that are reserved nft keywords are an error.
94
+
95
+ - `iif:`/`oif:` index matching; `meter:` per-key rate limiting.
96
+ - `example-fleet/`: a realistic 3-site reference, replacing `example-poc/`.
97
+ - Behavioral (network-namespace) tests: the B01–B26 primitive matrix and the
98
+ P01–P22 example-fleet matrix — which found that `flow-offload:` + `action:`
99
+ in one rule drops the handshake; that form is now an error.
100
+ - CI on GitHub Actions: lint, tests incl. `nft -c` and netns, drift gate.
101
+
102
+ ## 0.3.0 — 2026-07-05
103
+
104
+ - Large set/map literals render one element per line (one-line git diffs).
105
+ - Bare `dnat:`/`snat:` targets resolve single-address network groups.
106
+ - The netns behavioral test harness (rootless).
107
+
108
+ ## 0.2.0 — 2026-07-05
109
+
110
+ **Breaking:** a strict authoring surface — unknown keys at every level,
111
+ undefined names, empty groups, include and definition cycles, and a missing
112
+ `tables:` fail the build instead of rendering a weaker or empty ruleset;
113
+ `build --check` fails loudly when `nft -c` can't run.
114
+
115
+ ## 0.1.0 — 2026-06-28
116
+
117
+ The first release: definitions (networks, services, interfaces) with
118
+ composition and per-site overlays; tables, base and regular chains, named
119
+ sets, includes; rule keys for matches, statements (limit, quota, log,
120
+ set-mark, set-mss, counters, flow-offload), tcp flags, icmp types, NAT
121
+ including a dnat port map; inline vmaps (incl. concatenated keys) and
122
+ concatenation sets; `nftgen build <root>` producing one complete
123
+ `flush ruleset` deploy file per host; `nft -c` validation.
nftgen-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 aeglanee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
nftgen-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.4
2
+ Name: nftgen
3
+ Version: 1.0.0
4
+ Summary: nftables firewall generator: YAML definitions and host policies → a native nftables ruleset
5
+ Project-URL: Source, https://github.com/aeglanee/nftgen
6
+ Project-URL: Documentation, https://github.com/aeglanee/nftgen/blob/main/docs/authoring.md
7
+ Project-URL: Changelog, https://github.com/aeglanee/nftgen/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/aeglanee/nftgen/issues
9
+ Author: aeglanee
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: ansible,firewall,firewall-as-code,linux,netfilter,nft,nftables
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: System Administrators
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: System :: Networking :: Firewalls
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: pyyaml>=6.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pre-commit>=4.0; extra == 'dev'
28
+ Requires-Dist: pytest>=8.0; extra == 'dev'
29
+ Requires-Dist: ruff==0.15.20; extra == 'dev'
30
+ Requires-Dist: yamllint==1.38.0; extra == 'dev'
31
+ Provides-Extra: test
32
+ Requires-Dist: pytest>=8.0; extra == 'test'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # nftgen
36
+
37
+ [![ci](https://github.com/aeglanee/nftgen/actions/workflows/ci.yml/badge.svg)](https://github.com/aeglanee/nftgen/actions/workflows/ci.yml)
38
+
39
+ A small, **nftables-only** firewall-as-code generator: YAML definitions +
40
+ host policies → one native `.nft` ruleset per host, applied with `nft -f`.
41
+
42
+ - **It mirrors nftables.** Tables → chains → rules, sets, maps: you write
43
+ your own chains, and the YAML reads like the ruleset it becomes.
44
+ - **Definitions** (`networks`, `services`, `interfaces`) compose by name,
45
+ per site; nftgen derives set types, splits rules per address family and
46
+ keeps each port with its protocol.
47
+ - **Strict.** Unknown keys, undefined names and values of the wrong shape
48
+ fail the build; nothing is guessed, optimized or injected.
49
+ - **Deterministic.** The same input gives the same bytes, so a diff of the
50
+ output is a real rule change.
51
+ - **`raw:`** reaches anything nft can do that has no key yet.
52
+
53
+ ## Example
54
+
55
+ ```yaml
56
+ # definitions/networks.yaml
57
+ networks:
58
+ mgmt: [192.168.9.0/24, fd00:9::/64]
59
+ # definitions/services.yaml
60
+ services:
61
+ ssh: [22/tcp]
62
+ # policies/hosts/gw1.yaml
63
+ tables:
64
+ - family: inet
65
+ name: filter
66
+ chains:
67
+ - name: input
68
+ hook: input
69
+ rules:
70
+ - ct: [established, related]
71
+ action: accept
72
+ - saddr: mgmt
73
+ proto: tcp
74
+ dport: ssh
75
+ action: accept
76
+ ```
77
+
78
+ `nftgen build .` → `generated/gw1.nft`:
79
+
80
+ ```nft
81
+ table inet filter {
82
+ chain input {
83
+ type filter hook input priority 0; policy drop;
84
+ ct state established,related accept
85
+ ip saddr 192.168.9.0/24 tcp dport 22 accept
86
+ ip6 saddr fd00:9::/64 tcp dport 22 accept
87
+ }
88
+ }
89
+ ```
90
+
91
+ ## Use
92
+
93
+ ```bash
94
+ pip install nftgen
95
+
96
+ nftgen build <root> # every host → <root>/generated/
97
+ nftgen build <root> --host gw1 --stdout # one host to stdout, write nothing
98
+ nftgen build <root> --check # also validate each file with nft -c
99
+ ```
100
+
101
+ Each output is a complete file (`flush ruleset` + the host's tables) that
102
+ ships as `/etc/nftables.conf`. With Ansible, the
103
+ [ansible-firewall](https://github.com/sessrumnir/ansible-firewall)
104
+ collection renders from inventory variables instead and applies behind a
105
+ rollback timer ([DEPLOYMENT.md][deployment]).
106
+
107
+ ## Docs
108
+
109
+ - [docs/authoring.md][authoring] — **start here**: firewall
110
+ structure, adding a rule, which tool to use.
111
+ - [DESIGN.md][design] — the reference: every key and what it renders to.
112
+ - [DECISIONS.md][decisions] — why it's built this way.
113
+ - [docs/best-practices.md][best-practices],
114
+ [docs/sets-and-performance.md][sets-and-performance],
115
+ [docs/maps.md][maps], [docs/raw.md][raw] — nftables
116
+ cookbooks, every example checked with `nft -c`.
117
+ - [example-fleet/][example-fleet] — a realistic 3-site reference;
118
+ [example/][example] — a small one.
119
+ - [CHANGELOG.md][changelog] · [TODO.md][todo] ·
120
+ [docs/testing.md][testing].
121
+
122
+ ## Develop
123
+
124
+ ```bash
125
+ make install-dev # .venv with nftgen (editable), linters, pre-commit hook
126
+ make verify # lint + tests — put nft and ip on PATH, see docs/testing.md
127
+ ```
128
+
129
+ ## License
130
+
131
+ MIT (see [LICENSE][license]). The definitions model reimplements concepts
132
+ from [Aerleon](https://github.com/aerleon/aerleon) (Apache-2.0) clean-room —
133
+ no Aerleon code is used; the acknowledgment is courtesy, not a license
134
+ obligation.
135
+
136
+ [authoring]: https://github.com/aeglanee/nftgen/blob/main/docs/authoring.md
137
+ [best-practices]: https://github.com/aeglanee/nftgen/blob/main/docs/best-practices.md
138
+ [changelog]: https://github.com/aeglanee/nftgen/blob/main/CHANGELOG.md
139
+ [decisions]: https://github.com/aeglanee/nftgen/blob/main/DECISIONS.md
140
+ [deployment]: https://github.com/aeglanee/nftgen/blob/main/DEPLOYMENT.md
141
+ [design]: https://github.com/aeglanee/nftgen/blob/main/DESIGN.md
142
+ [example]: https://github.com/aeglanee/nftgen/tree/main/example/
143
+ [example-fleet]: https://github.com/aeglanee/nftgen/tree/main/example-fleet/
144
+ [license]: https://github.com/aeglanee/nftgen/blob/main/LICENSE
145
+ [maps]: https://github.com/aeglanee/nftgen/blob/main/docs/maps.md
146
+ [raw]: https://github.com/aeglanee/nftgen/blob/main/docs/raw.md
147
+ [sets-and-performance]: https://github.com/aeglanee/nftgen/blob/main/docs/sets-and-performance.md
148
+ [testing]: https://github.com/aeglanee/nftgen/blob/main/docs/testing.md
149
+ [todo]: https://github.com/aeglanee/nftgen/blob/main/TODO.md
nftgen-1.0.0/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # nftgen
2
+
3
+ [![ci](https://github.com/aeglanee/nftgen/actions/workflows/ci.yml/badge.svg)](https://github.com/aeglanee/nftgen/actions/workflows/ci.yml)
4
+
5
+ A small, **nftables-only** firewall-as-code generator: YAML definitions +
6
+ host policies → one native `.nft` ruleset per host, applied with `nft -f`.
7
+
8
+ - **It mirrors nftables.** Tables → chains → rules, sets, maps: you write
9
+ your own chains, and the YAML reads like the ruleset it becomes.
10
+ - **Definitions** (`networks`, `services`, `interfaces`) compose by name,
11
+ per site; nftgen derives set types, splits rules per address family and
12
+ keeps each port with its protocol.
13
+ - **Strict.** Unknown keys, undefined names and values of the wrong shape
14
+ fail the build; nothing is guessed, optimized or injected.
15
+ - **Deterministic.** The same input gives the same bytes, so a diff of the
16
+ output is a real rule change.
17
+ - **`raw:`** reaches anything nft can do that has no key yet.
18
+
19
+ ## Example
20
+
21
+ ```yaml
22
+ # definitions/networks.yaml
23
+ networks:
24
+ mgmt: [192.168.9.0/24, fd00:9::/64]
25
+ # definitions/services.yaml
26
+ services:
27
+ ssh: [22/tcp]
28
+ # policies/hosts/gw1.yaml
29
+ tables:
30
+ - family: inet
31
+ name: filter
32
+ chains:
33
+ - name: input
34
+ hook: input
35
+ rules:
36
+ - ct: [established, related]
37
+ action: accept
38
+ - saddr: mgmt
39
+ proto: tcp
40
+ dport: ssh
41
+ action: accept
42
+ ```
43
+
44
+ `nftgen build .` → `generated/gw1.nft`:
45
+
46
+ ```nft
47
+ table inet filter {
48
+ chain input {
49
+ type filter hook input priority 0; policy drop;
50
+ ct state established,related accept
51
+ ip saddr 192.168.9.0/24 tcp dport 22 accept
52
+ ip6 saddr fd00:9::/64 tcp dport 22 accept
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## Use
58
+
59
+ ```bash
60
+ pip install nftgen
61
+
62
+ nftgen build <root> # every host → <root>/generated/
63
+ nftgen build <root> --host gw1 --stdout # one host to stdout, write nothing
64
+ nftgen build <root> --check # also validate each file with nft -c
65
+ ```
66
+
67
+ Each output is a complete file (`flush ruleset` + the host's tables) that
68
+ ships as `/etc/nftables.conf`. With Ansible, the
69
+ [ansible-firewall](https://github.com/sessrumnir/ansible-firewall)
70
+ collection renders from inventory variables instead and applies behind a
71
+ rollback timer ([DEPLOYMENT.md][deployment]).
72
+
73
+ ## Docs
74
+
75
+ - [docs/authoring.md][authoring] — **start here**: firewall
76
+ structure, adding a rule, which tool to use.
77
+ - [DESIGN.md][design] — the reference: every key and what it renders to.
78
+ - [DECISIONS.md][decisions] — why it's built this way.
79
+ - [docs/best-practices.md][best-practices],
80
+ [docs/sets-and-performance.md][sets-and-performance],
81
+ [docs/maps.md][maps], [docs/raw.md][raw] — nftables
82
+ cookbooks, every example checked with `nft -c`.
83
+ - [example-fleet/][example-fleet] — a realistic 3-site reference;
84
+ [example/][example] — a small one.
85
+ - [CHANGELOG.md][changelog] · [TODO.md][todo] ·
86
+ [docs/testing.md][testing].
87
+
88
+ ## Develop
89
+
90
+ ```bash
91
+ make install-dev # .venv with nftgen (editable), linters, pre-commit hook
92
+ make verify # lint + tests — put nft and ip on PATH, see docs/testing.md
93
+ ```
94
+
95
+ ## License
96
+
97
+ MIT (see [LICENSE][license]). The definitions model reimplements concepts
98
+ from [Aerleon](https://github.com/aerleon/aerleon) (Apache-2.0) clean-room —
99
+ no Aerleon code is used; the acknowledgment is courtesy, not a license
100
+ obligation.
101
+
102
+ [authoring]: https://github.com/aeglanee/nftgen/blob/main/docs/authoring.md
103
+ [best-practices]: https://github.com/aeglanee/nftgen/blob/main/docs/best-practices.md
104
+ [changelog]: https://github.com/aeglanee/nftgen/blob/main/CHANGELOG.md
105
+ [decisions]: https://github.com/aeglanee/nftgen/blob/main/DECISIONS.md
106
+ [deployment]: https://github.com/aeglanee/nftgen/blob/main/DEPLOYMENT.md
107
+ [design]: https://github.com/aeglanee/nftgen/blob/main/DESIGN.md
108
+ [example]: https://github.com/aeglanee/nftgen/tree/main/example/
109
+ [example-fleet]: https://github.com/aeglanee/nftgen/tree/main/example-fleet/
110
+ [license]: https://github.com/aeglanee/nftgen/blob/main/LICENSE
111
+ [maps]: https://github.com/aeglanee/nftgen/blob/main/docs/maps.md
112
+ [raw]: https://github.com/aeglanee/nftgen/blob/main/docs/raw.md
113
+ [sets-and-performance]: https://github.com/aeglanee/nftgen/blob/main/docs/sets-and-performance.md
114
+ [testing]: https://github.com/aeglanee/nftgen/blob/main/docs/testing.md
115
+ [todo]: https://github.com/aeglanee/nftgen/blob/main/TODO.md
@@ -0,0 +1,7 @@
1
+ ---
2
+ # Interface groups. Device names (or other groups). Used in rules as iifname / oifname.
3
+ # Note: named distinctly from network groups (lan_if vs the network `lan`) — nft
4
+ # set names are unique per table.
5
+ interfaces:
6
+ wan: [wan0, wwan0]
7
+ lan_if: [lan0]
@@ -0,0 +1,25 @@
1
+ ---
2
+ # Network groups. A value is a CIDR/IP literal, or the name of another group
3
+ # (composition). Used in rules as saddr / daddr.
4
+ networks:
5
+ # ----- site subnets (the real addresses; all sites known everywhere) -----
6
+ users_site1: [192.168.10.0/24]
7
+ users_site2: [192.168.20.0/24]
8
+ servers_site1: [192.168.11.0/24]
9
+ servers_site2: [192.168.21.0/24]
10
+ mgmt: [192.168.9.0/24]
11
+ webhosts: [192.0.2.10, 192.0.2.11]
12
+
13
+ # ----- bogons / special-use (RFC 6890), for scrubbing -----
14
+ bogon_ipv4:
15
+ - 192.0.0.0/24
16
+ - 192.0.2.0/24
17
+ - 198.51.100.0/24
18
+ - 203.0.113.0/24
19
+ - 240.0.0.0/4
20
+ no_forward_ipv4:
21
+ - 0.0.0.0/8
22
+ - 127.0.0.0/8
23
+ - 169.254.0.0/16
24
+ - 224.0.0.0/4
25
+ - 255.255.255.255/32
@@ -0,0 +1,14 @@
1
+ ---
2
+ # Service groups. Each port carries its protocol (22/tcp). They emit as a
3
+ # proto-agnostic nft set of ports; the rule states the protocol (tcp dport @web).
4
+ # A group can compose other groups.
5
+ services:
6
+ ssh: [22/tcp]
7
+ http: [80/tcp]
8
+ https: [443/tcp]
9
+ dns: [53/tcp, 53/udp]
10
+
11
+ # composition: a bundle of other services
12
+ web:
13
+ - http
14
+ - https
@@ -0,0 +1,141 @@
1
+ ---
2
+ # gateway.yaml — a showcase exercising every nftgen capability (Phases 0–6).
3
+ # generate: nftgen example/policies/hosts/gateway.yaml
4
+ site: site1 # per-site overlay: local_users -> site1
5
+
6
+ tables:
7
+ - family: inet
8
+ name: filter
9
+
10
+ # 6B — named counters (read with: nft list counter inet filter <name>)
11
+ counters:
12
+ - bad_tcp
13
+ - web_hits
14
+ - ssh_excess
15
+
16
+ # 6C — flowtable; devices resolved from the wan / lan_if interface groups
17
+ flowtables:
18
+ - name: ft
19
+ hook: ingress
20
+ priority: filter
21
+ devices: [wan, lan_if]
22
+
23
+ # named sets (declared) + a bare, live-populated blocklist
24
+ sets:
25
+ - mgmt
26
+ - webhosts
27
+ - web
28
+ - ssh
29
+ - wan
30
+ - lan_if
31
+ - name: blocklist
32
+ type: ipv4_addr
33
+ flags: [interval, timeout]
34
+
35
+ chains:
36
+ - name: input
37
+ hook: input
38
+ priority: filter
39
+ policy: drop
40
+ rules:
41
+ - ct: [established, related] # 6C — offload established flows...
42
+ flow-offload: ft
43
+ - ct: [established, related] # ...then accept them (separate
44
+ action: accept # rule: `flow add` breaks the
45
+ # rule when it can't offload)
46
+ - saddr: "@blocklist"
47
+ counter: true
48
+ action: drop
49
+ - vmap: # 6D — dispatch by interface group
50
+ key: iifname
51
+ map:
52
+ wan: # expands to wan0 + wwan0
53
+ jump: input_wan
54
+ lan_if:
55
+ jump: input_lan
56
+
57
+ - name: input_wan # regular chain (reached by the vmap)
58
+ rules:
59
+ - flags: # 6E — drop invalid TCP flag combos
60
+ - match: none
61
+ mask: all
62
+ - match: [fin, syn]
63
+ mask: [fin, syn]
64
+ - match: [fin]
65
+ mask: [fin, ack]
66
+ counter: bad_tcp
67
+ action: drop
68
+ - proto: tcp # 6A — rate-limit new SSH...
69
+ dport: ssh
70
+ ct: [new]
71
+ limit: 4/minute
72
+ action: accept
73
+ - proto: tcp # ...log a sample of the excess...
74
+ dport: ssh
75
+ limit: 6/minute
76
+ log:
77
+ prefix: "ssh-excess "
78
+ level: info
79
+ - proto: tcp # ...and drop all of it, counted
80
+ dport: ssh
81
+ counter: ssh_excess
82
+ action: drop
83
+ - proto: tcp
84
+ dport: web
85
+ counter: web_hits
86
+ action: accept
87
+
88
+ - name: input_lan
89
+ rules:
90
+ - proto: icmp
91
+ action: accept
92
+ - saddr: mgmt
93
+ proto: tcp
94
+ dport: ssh
95
+ action: accept
96
+
97
+ - name: forward
98
+ hook: forward
99
+ priority: filter
100
+ policy: drop
101
+ rules:
102
+ - ct: [established, related]
103
+ action: accept
104
+ - proto: tcp
105
+ set-mss: pmtu # 6A — MSS clamp (statement-only)
106
+ - iifname: lan_if
107
+ oifname: wan
108
+ set-mark: "0x1" # 6A — fwmark (statement-only)
109
+ - iifname: lan_if
110
+ oifname: wan
111
+ saddr: local_users # per-site overlay -> users_site1
112
+ action: accept
113
+ - saddr: webhosts # 6A — quota: cut off after 10 GiB
114
+ quota: over 10240 mbytes
115
+ counter: true
116
+ action: drop
117
+
118
+ - family: inet
119
+ name: nat
120
+ chains:
121
+ - name: prerouting
122
+ hook: prerouting
123
+ type: nat
124
+ priority: dstnat
125
+ policy: accept
126
+ rules:
127
+ - iifname: wan # dnat port-forward: family qualifier
128
+ proto: tcp # is inferred from the target (inet
129
+ dport: 443 # tables require `dnat ip to`)
130
+ action:
131
+ dnat: "192.168.10.50:443"
132
+
133
+ - name: postrouting
134
+ hook: postrouting
135
+ type: nat
136
+ priority: srcnat
137
+ policy: accept
138
+ rules:
139
+ - oifname: wan
140
+ saddr: local_users
141
+ action: masquerade
@@ -0,0 +1,65 @@
1
+ ---
2
+ # router1 — site1 edge router.
3
+ # Layers: COMMON (shared includes) + LOCAL (site1 via `site:`) + UNIQUE (this host).
4
+ # `site:` selects the per-site definitions the generator merges over def/ (later
5
+ # phase) so `local_users` resolves to site1's group.
6
+ site: site1
7
+
8
+ tables:
9
+ # ----- raw: early scrubbing (policy accept; only bad traffic dropped) -----
10
+ - family: inet
11
+ name: raw
12
+ sets:
13
+ - bogon_ipv4
14
+ chains:
15
+ - name: prerouting
16
+ hook: prerouting
17
+ type: filter
18
+ priority: raw
19
+ policy: accept
20
+ rules:
21
+ - include: includes/scrub.yaml # COMMON: bogons + bad-tcp
22
+
23
+ # ----- filtering -----
24
+ - family: inet
25
+ name: filter
26
+ sets:
27
+ - include: includes/common-sets.yaml # shared named sets
28
+ chains:
29
+ - name: input
30
+ hook: input
31
+ priority: filter
32
+ policy: drop
33
+ rules:
34
+ - ct: [established, related]
35
+ action: accept
36
+ - include: includes/common-input.yaml # COMMON: icmp + ssh-from-mgmt
37
+ - saddr: webhosts # UNIQUE: this host serves web
38
+ proto: tcp
39
+ dport: web
40
+ counter: true
41
+ action: accept
42
+
43
+ - name: forward
44
+ hook: forward
45
+ priority: filter
46
+ policy: drop
47
+ rules:
48
+ - ct: [established, related]
49
+ action: accept
50
+ - include: includes/common-forward.yaml # COMMON: no-forward + mss
51
+ - include: includes/local-egress.yaml # LOCAL: local_users -> users_site1
52
+
53
+ # ----- NAT -----
54
+ - family: inet
55
+ name: nat
56
+ chains:
57
+ - name: postrouting
58
+ hook: postrouting
59
+ type: nat
60
+ priority: srcnat
61
+ policy: accept
62
+ rules:
63
+ - oifname: wan
64
+ saddr: local_users # LOCAL: site1 users
65
+ action: masquerade