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.
- nftgen-1.0.0/.gitignore +15 -0
- nftgen-1.0.0/CHANGELOG.md +123 -0
- nftgen-1.0.0/LICENSE +21 -0
- nftgen-1.0.0/PKG-INFO +149 -0
- nftgen-1.0.0/README.md +115 -0
- nftgen-1.0.0/example/definitions/interfaces.yaml +7 -0
- nftgen-1.0.0/example/definitions/networks.yaml +25 -0
- nftgen-1.0.0/example/definitions/services.yaml +14 -0
- nftgen-1.0.0/example/policies/hosts/gateway.yaml +141 -0
- nftgen-1.0.0/example/policies/hosts/router1.yaml +65 -0
- nftgen-1.0.0/example/policies/hosts/router2.yaml +56 -0
- nftgen-1.0.0/example/policies/includes/common-forward.yaml +8 -0
- nftgen-1.0.0/example/policies/includes/common-input.yaml +13 -0
- nftgen-1.0.0/example/policies/includes/common-sets.yaml +12 -0
- nftgen-1.0.0/example/policies/includes/local-egress.yaml +12 -0
- nftgen-1.0.0/example/policies/includes/scrub.yaml +25 -0
- nftgen-1.0.0/example/sites/site1.yaml +10 -0
- nftgen-1.0.0/example/sites/site2.yaml +5 -0
- nftgen-1.0.0/example-fleet/README.md +126 -0
- nftgen-1.0.0/example-fleet/definitions/interfaces.yaml +13 -0
- nftgen-1.0.0/example-fleet/definitions/networks.yaml +72 -0
- nftgen-1.0.0/example-fleet/definitions/services.yaml +18 -0
- nftgen-1.0.0/example-fleet/generated/br1-r.nft +228 -0
- nftgen-1.0.0/example-fleet/generated/br2-r.nft +228 -0
- nftgen-1.0.0/example-fleet/generated/hq-r.nft +228 -0
- nftgen-1.0.0/example-fleet/policies/hosts/br1-r.yaml +159 -0
- nftgen-1.0.0/example-fleet/policies/hosts/br2-r.yaml +159 -0
- nftgen-1.0.0/example-fleet/policies/hosts/hq-r.yaml +159 -0
- nftgen-1.0.0/example-fleet/policies/includes/common/bad-tcp.yaml +34 -0
- nftgen-1.0.0/example-fleet/policies/includes/common/blocklist.yaml +8 -0
- nftgen-1.0.0/example-fleet/policies/includes/common/conntrack.yaml +15 -0
- nftgen-1.0.0/example-fleet/policies/includes/common/fwd-catchall.yaml +16 -0
- nftgen-1.0.0/example-fleet/policies/includes/common/icmp.yaml +30 -0
- nftgen-1.0.0/example-fleet/policies/includes/common/scrub-wan.yaml +13 -0
- nftgen-1.0.0/example-fleet/policies/includes/sets/scrub-sets.yaml +15 -0
- nftgen-1.0.0/example-fleet/policies/includes/zones/dmz-inet.yaml +26 -0
- nftgen-1.0.0/example-fleet/policies/includes/zones/input-local.yaml +25 -0
- nftgen-1.0.0/example-fleet/policies/includes/zones/nwm-services.yaml +31 -0
- nftgen-1.0.0/example-fleet/policies/includes/zones/nwm-transit.yaml +20 -0
- nftgen-1.0.0/example-fleet/policies/includes/zones/services-inet.yaml +26 -0
- nftgen-1.0.0/example-fleet/policies/includes/zones/transit-dmz.yaml +19 -0
- nftgen-1.0.0/example-fleet/policies/includes/zones/transit-services.yaml +33 -0
- nftgen-1.0.0/example-fleet/policies/includes/zones/users-dmz.yaml +19 -0
- nftgen-1.0.0/example-fleet/policies/includes/zones/users-inet.yaml +30 -0
- nftgen-1.0.0/example-fleet/policies/includes/zones/users-services.yaml +30 -0
- nftgen-1.0.0/example-fleet/policies/includes/zones/users-transit.yaml +21 -0
- nftgen-1.0.0/example-fleet/policies/includes/zones/wan-dmz.yaml +20 -0
- nftgen-1.0.0/example-fleet/sites/br1.yaml +8 -0
- nftgen-1.0.0/example-fleet/sites/br2.yaml +8 -0
- nftgen-1.0.0/example-fleet/sites/hq.yaml +11 -0
- nftgen-1.0.0/nftgen/__init__.py +13 -0
- nftgen-1.0.0/nftgen/__main__.py +6 -0
- nftgen-1.0.0/nftgen/cli.py +209 -0
- nftgen-1.0.0/nftgen/definitions.py +180 -0
- nftgen-1.0.0/nftgen/generate.py +334 -0
- nftgen-1.0.0/nftgen/ir.py +588 -0
- nftgen-1.0.0/nftgen/protocols.py +38 -0
- nftgen-1.0.0/nftgen/rules.py +1191 -0
- nftgen-1.0.0/nftgen/schema.py +317 -0
- nftgen-1.0.0/nftgen/validate.py +89 -0
- nftgen-1.0.0/pyproject.toml +86 -0
- nftgen-1.0.0/tests/behavioral/__init__.py +0 -0
- nftgen-1.0.0/tests/behavioral/agent.py +568 -0
- nftgen-1.0.0/tests/behavioral/fixtures/basic/definitions/defs.yaml +12 -0
- nftgen-1.0.0/tests/behavioral/fixtures/basic/policies/hosts/router.yaml +50 -0
- nftgen-1.0.0/tests/behavioral/fixtures/bogon-counter/definitions/defs.yaml +12 -0
- nftgen-1.0.0/tests/behavioral/fixtures/bogon-counter/policies/hosts/router.yaml +27 -0
- nftgen-1.0.0/tests/behavioral/fixtures/concat-flows/definitions/defs.yaml +14 -0
- nftgen-1.0.0/tests/behavioral/fixtures/concat-flows/policies/hosts/router.yaml +25 -0
- nftgen-1.0.0/tests/behavioral/fixtures/live-blocklist/definitions/defs.yaml +7 -0
- nftgen-1.0.0/tests/behavioral/fixtures/live-blocklist/policies/hosts/router.yaml +25 -0
- nftgen-1.0.0/tests/behavioral/fixtures/nat/definitions/defs.yaml +20 -0
- nftgen-1.0.0/tests/behavioral/fixtures/nat/policies/hosts/router.yaml +81 -0
- nftgen-1.0.0/tests/behavioral/fixtures/nflog/definitions/defs.yaml +7 -0
- nftgen-1.0.0/tests/behavioral/fixtures/nflog/policies/hosts/router.yaml +32 -0
- nftgen-1.0.0/tests/behavioral/fixtures/rpfilter/definitions/defs.yaml +5 -0
- nftgen-1.0.0/tests/behavioral/fixtures/rpfilter/policies/hosts/router.yaml +29 -0
- nftgen-1.0.0/tests/behavioral/fixtures/service-pairs/definitions/defs.yaml +9 -0
- nftgen-1.0.0/tests/behavioral/fixtures/service-pairs/policies/hosts/router.yaml +23 -0
- nftgen-1.0.0/tests/behavioral/fixtures/set-member/definitions/defs.yaml +9 -0
- nftgen-1.0.0/tests/behavioral/fixtures/set-member/policies/hosts/router.yaml +23 -0
- nftgen-1.0.0/tests/behavioral/fixtures/statements/definitions/defs.yaml +16 -0
- nftgen-1.0.0/tests/behavioral/fixtures/statements/policies/hosts/router.yaml +87 -0
- nftgen-1.0.0/tests/behavioral/fixtures/tcp-flags/definitions/defs.yaml +8 -0
- nftgen-1.0.0/tests/behavioral/fixtures/tcp-flags/policies/hosts/router.yaml +43 -0
- nftgen-1.0.0/tests/behavioral/fixtures/vmap-group/definitions/defs.yaml +7 -0
- nftgen-1.0.0/tests/behavioral/fixtures/vmap-group/policies/hosts/router.yaml +27 -0
- nftgen-1.0.0/tests/behavioral/fixtures/vmap-input/definitions/defs.yaml +10 -0
- nftgen-1.0.0/tests/behavioral/fixtures/vmap-input/policies/hosts/router.yaml +35 -0
- nftgen-1.0.0/tests/behavioral/fixtures/vmap-pairs/definitions/defs.yaml +9 -0
- nftgen-1.0.0/tests/behavioral/fixtures/vmap-pairs/policies/hosts/router.yaml +27 -0
- nftgen-1.0.0/tests/behavioral/netns.py +185 -0
- nftgen-1.0.0/tests/golden/gateway.nft +99 -0
- nftgen-1.0.0/tests/golden/router1.nft +108 -0
- nftgen-1.0.0/tests/golden/router2.nft +107 -0
- nftgen-1.0.0/tests/test_annotate.py +122 -0
- nftgen-1.0.0/tests/test_behavioral.py +901 -0
- nftgen-1.0.0/tests/test_build.py +226 -0
- nftgen-1.0.0/tests/test_comments.py +169 -0
- nftgen-1.0.0/tests/test_concat.py +350 -0
- nftgen-1.0.0/tests/test_counters.py +39 -0
- nftgen-1.0.0/tests/test_definitions.py +235 -0
- nftgen-1.0.0/tests/test_fleet.py +73 -0
- nftgen-1.0.0/tests/test_fleet_behavioral.py +341 -0
- nftgen-1.0.0/tests/test_flowtables.py +89 -0
- nftgen-1.0.0/tests/test_generate.py +231 -0
- nftgen-1.0.0/tests/test_ir_sets.py +200 -0
- nftgen-1.0.0/tests/test_keyword_guard.py +67 -0
- nftgen-1.0.0/tests/test_resolve.py +54 -0
- nftgen-1.0.0/tests/test_rules.py +687 -0
- nftgen-1.0.0/tests/test_schema.py +335 -0
- nftgen-1.0.0/tests/test_smoke.py +21 -0
- nftgen-1.0.0/tests/test_statements.py +169 -0
- nftgen-1.0.0/tests/test_table_shapes.py +68 -0
- nftgen-1.0.0/tests/test_tcp_flags.py +74 -0
- nftgen-1.0.0/tests/test_validate.py +46 -0
- nftgen-1.0.0/tests/test_vmaps.py +575 -0
nftgen-1.0.0/.gitignore
ADDED
|
@@ -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
|
+
[](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
|
+
[](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,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
|