ferm 0.1.0a1__py3-none-any.whl

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.
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.4
2
+ Name: ferm
3
+ Version: 0.1.0a1
4
+ Summary: For Easy Rule Making - a frontend for iptables (Python port)
5
+ Project-URL: Homepage, https://github.com/6RUN0/ferm
6
+ Project-URL: Repository, https://github.com/6RUN0/ferm
7
+ Project-URL: Changelog, https://github.com/6RUN0/ferm/blob/main/CHANGELOG.md
8
+ Author: Auke Kok, Max Kellermann
9
+ Maintainer-email: Boris Talovikov <boris@talovikov.ru>
10
+ License-Expression: GPL-2.0-or-later
11
+ License-File: COPYING
12
+ Keywords: firewall,iptables,netfilter,nftables
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: System Administrators
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: System :: Networking :: Firewalls
23
+ Requires-Python: >=3.11
24
+ Provides-Extra: dns
25
+ Requires-Dist: dnspython>=2.2.1; extra == 'dns'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # ferm (Python port)
29
+
30
+ `ferm` ("For Easy Rule Making", pronounced "firm") is a frontend for
31
+ `iptables`. It reads firewall rules from a structured, high-level
32
+ configuration language — with variables, functions, arrays, blocks and
33
+ includes — and installs them into the running kernel by calling
34
+ `iptables(8)` / `iptables-restore`. It also drives the `ip6tables`,
35
+ `arptables` and `ebtables` families.
36
+
37
+ The goal is to make rules easy to write *and* easy to read, so the
38
+ administrator spends time designing good rules rather than transcribing
39
+ them.
40
+
41
+ ## Project status
42
+
43
+ This repository is **mid-migration** from the original Perl
44
+ implementation to Python:
45
+
46
+ - **`src/pyferm/`** — the Python port. **Phase 1 is complete**: it parses
47
+ the full ferm configuration language and emits `iptables` rulesets
48
+ (`ip`, `ip6`, `arp`, `eb` families), with both the fast
49
+ (`iptables-restore`) and slow (per-rule) execution paths, the
50
+ `--interactive` rollback safety net, and the `import-ferm` save-file
51
+ round-trip. Output is validated **byte-for-byte against the Perl
52
+ oracle**.
53
+ - **`reference/`** — the original Perl implementation, kept verbatim as
54
+ the semantic oracle. Run its own test suite with
55
+ `make -C reference check`.
56
+
57
+ **Phase 2 (opt-in / experimental)**: a native `nftables` backend behind
58
+ `--nft` translates the same configuration into a native nft ruleset and
59
+ applies it atomically via `nft -f -`. The default backend stays
60
+ `iptables`, so existing configurations and output are unchanged unless
61
+ `--nft` is passed. `--nft` is opt-in and experimental: it carries
62
+ documented semantic differences (a `policy DROP` shift under the own-table
63
+ model, `@preserve` unsupported) and has golden + `nft -c` coverage but no
64
+ Perl-oracle differential test. The roadmap lives in
65
+ [`docs/ROADMAP.md`](docs/ROADMAP.md).
66
+
67
+ ### Branches
68
+
69
+ - **`main`** — the new default branch (release line).
70
+ - **`develop`** — active development; branch your work from here.
71
+ - **`python-port`** — the porting-process branch.
72
+ - **`master`** — frozen; kept for historical reference only.
73
+
74
+ ## Requirements
75
+
76
+ - Python **3.11–3.14**
77
+ - `iptables` (including `iptables-save` / `iptables-restore`) and a
78
+ netfilter-capable kernel, at runtime
79
+ - [`uv`](https://docs.astral.sh/uv/) for development
80
+
81
+ There are no required runtime dependencies. `@resolve()` uses
82
+ [`dnspython`](https://www.dnspython.org/) when it is installed (full record
83
+ vocabulary, including `NS`/`MX`); otherwise it falls back to the system stub
84
+ resolver (`getaddrinfo`, honouring `/etc/nsswitch.conf`), which answers only
85
+ `A`/`AAAA` records — other types then raise a clear error. Install the `dns`
86
+ extra (`pip install pyferm[dns]`) for the full set of record types. The stub
87
+ resolver consults system sources such as `/etc/hosts` and mDNS that dnspython
88
+ bypasses, so the two backends can diverge when those local sources differ from
89
+ authoritative DNS.
90
+
91
+ ## Installation
92
+
93
+ Three distribution forms are available. All carry the same version derived
94
+ from the same git tag.
95
+
96
+ ### WARNING: the default configuration blocks all inbound traffic
97
+
98
+ Before enabling the `ferm` systemd service, read this section carefully.
99
+
100
+ The starter `/etc/ferm/ferm.conf` installed by the `.deb` package applies a
101
+ **DROP policy to the `INPUT` chain**. Only two narrow exceptions are open by
102
+ default: SSH on port 22 (identified by the service name `ssh`) and the
103
+ ICMPv6 essentials required by RFC 4890 (neighbour discovery, etc.). Any
104
+ other inbound traffic is dropped immediately on service start.
105
+
106
+ **Before running `systemctl enable --now ferm`:**
107
+
108
+ - Add every service you need to reachable through your firewall as a
109
+ drop-in fragment in `/etc/ferm/ferm.d/` (for example, HTTP/HTTPS,
110
+ custom application ports).
111
+ - If SSH runs on a port other than 22, edit `$SSH_PORT` in
112
+ `/etc/ferm/ferm.conf` before enabling. Enabling the service with the
113
+ wrong SSH port will lock you out of a remote machine.
114
+ - Fragments in `/etc/ferm/ferm.d/*.conf` are executed as root. Keep them
115
+ owned by `root:root` with permissions `0644` or stricter. A
116
+ world-writable fragment is a privilege-escalation vector.
117
+
118
+ **Migrating from the Perl `ferm` package:** the `pyferm` `.deb` installs
119
+ over the Perl package via `Provides/Conflicts/Replaces: ferm`, but it does
120
+ **not** automatically enable or start `ferm.service`. If you relied on the
121
+ Perl package having the service enabled, you must re-enable it explicitly
122
+ after installing `pyferm`. This is intentional: the default configuration
123
+ above would otherwise lock you out on first boot.
124
+
125
+ **Third-party packages that declare `Depends: ferm`** (automation tooling,
126
+ configuration managers) will have their dependency satisfied by `pyferm`,
127
+ but any `systemctl enable ferm` those tools may run will apply the default
128
+ DROP configuration. Audit what your automation does before installing.
129
+
130
+ ### PyPI (pip)
131
+
132
+ ```sh
133
+ pip install ferm
134
+ ```
135
+
136
+ For full DNS record-type support in `@resolve()` (including `NS`/`MX`),
137
+ install the `dns` extra:
138
+
139
+ ```sh
140
+ pip install ferm[dns]
141
+ ```
142
+
143
+ The `ferm` and `import-ferm` console scripts are placed on `PATH` by pip.
144
+
145
+ ### Native .deb package
146
+
147
+ Download `pyferm_<version>_all.deb` from the
148
+ [GitHub Releases](https://github.com/6RUN0/ferm/releases) page and install
149
+ it:
150
+
151
+ ```sh
152
+ sudo apt install ./pyferm_<version>_all.deb
153
+ ```
154
+
155
+ `apt install ./...` (with the explicit `./` path) resolves dependencies
156
+ automatically. Do not use `dpkg -i` directly unless you handle dependencies
157
+ yourself.
158
+
159
+ The package name is `pyferm` but it declares `Provides: ferm`,
160
+ `Conflicts: ferm`, and `Replaces: ferm`. Installing it removes any
161
+ existing Perl `ferm` package and satisfies packages that depend on `ferm`.
162
+
163
+ When migrating from the Perl `ferm`, your edited `/etc/ferm/ferm.conf` is
164
+ kept: an interactive `apt` prompts you to keep it (the default), and for an
165
+ unattended upgrade pass `-o Dpkg::Options::=--force-confold` to keep it
166
+ without prompting.
167
+
168
+ After installation the service is **not** enabled. Review the warning above,
169
+ customise `/etc/ferm/ferm.conf` and add fragments to `/etc/ferm/ferm.d/`,
170
+ then opt in:
171
+
172
+ ```sh
173
+ systemctl enable --now ferm
174
+ ```
175
+
176
+ ### Installation (standalone binary)
177
+
178
+ A self-contained binary is published for **Linux x86_64** (glibc **2.28**
179
+ or newer). It carries its own Python runtime and a bundled `dnspython`,
180
+ so no Python installation is needed on the target host. It does **not**
181
+ bundle `iptables` or `nft` — those must already be present on the system,
182
+ because ferm calls them to install the rules.
183
+
184
+ Download the release tarball `ferm-<version>-linux-x86_64.tar.gz` and
185
+ unpack it, preserving symlinks:
186
+
187
+ ```sh
188
+ tar xzf ferm-<version>-linux-x86_64.tar.gz
189
+ ```
190
+
191
+ This produces a `ferm.dist/` directory containing the `ferm` binary and,
192
+ next to it, an `import-ferm` symlink.
193
+
194
+ ### Keep the binary inside its directory
195
+
196
+ The `ferm` binary loads its bundled shared objects from its own directory
197
+ (via an `$ORIGIN`-relative runtime path). **Do not move or copy the bare
198
+ `ferm` binary out of `ferm.dist/`** — a lone copy can no longer find its
199
+ libraries and will fail to start. To run it from a directory on `PATH`,
200
+ create a **symlink** to the binary instead of copying it; `$ORIGIN` still
201
+ resolves through the symlink:
202
+
203
+ ```sh
204
+ ln -s /opt/ferm/ferm.dist/ferm /usr/local/bin/ferm
205
+ ```
206
+
207
+ ### Unpack into a root-owned directory
208
+
209
+ ferm runs as root. Because the binary loads shared objects from its own
210
+ directory, a world- or group-writable dist directory lets a local
211
+ attacker plant a malicious shared object next to the binary that then
212
+ runs with root privileges. **Unpack into a directory owned by root and
213
+ not writable by other users** (for example `/opt/ferm`, mode `0755`,
214
+ owner `root`), and verify the permissions *before* the first run as root:
215
+
216
+ ```sh
217
+ sudo install -d -o root -g root -m 0755 /opt/ferm
218
+ sudo tar xzf ferm-<version>-linux-x86_64.tar.gz -C /opt/ferm
219
+ ls -ld /opt/ferm /opt/ferm/ferm.dist
220
+ ```
221
+
222
+ ### Verifying the download
223
+
224
+ The release ships a `SHA256SUMS` file. It guards against accidental
225
+ corruption in transfer — **integrity, not authenticity.** A matching
226
+ checksum does **not** prove the file was not maliciously substituted,
227
+ because an attacker who can replace the tarball can replace the checksum
228
+ file too.
229
+
230
+ For authenticity, verify the build provenance attestation with the GitHub
231
+ CLI:
232
+
233
+ ```sh
234
+ gh attestation verify ferm-<version>-linux-x86_64.tar.gz --repo 6RUN0/ferm
235
+ ```
236
+
237
+ Attestation only protects those who actually run the check, so verify
238
+ every download rather than trusting the file blindly.
239
+
240
+ ### `@resolve()` and the host resolver
241
+
242
+ `@resolve()` looks names up through the host's `/etc/resolv.conf`, and
243
+ ferm runs as root. If a rule's correctness depends on the resolved
244
+ address (for example, restricting access to a named host), a tampered or
245
+ spoofed DNS answer can change which addresses the installed ruleset
246
+ trusts. For security-significant rules, use a trusted or local resolver,
247
+ or write static addresses directly.
248
+
249
+ ## Usage
250
+
251
+ ```sh
252
+ # Inspect the generated rules without touching the kernel (the safe way):
253
+ uv run ferm --noexec --lines /etc/ferm/ferm.conf
254
+
255
+ # Install the ruleset into the running kernel (needs root):
256
+ uv run ferm /etc/ferm/ferm.conf
257
+
258
+ # Convert an existing firewall into a ferm config:
259
+ uv run import-ferm > /etc/ferm/ferm.conf
260
+ ```
261
+
262
+ Be careful not to lock yourself out of a remote machine — use the
263
+ interactive mode (`--interactive`, `-i`) often. It installs the new
264
+ ruleset, then rolls back to the previous one unless you confirm in time.
265
+
266
+ The `ferm(1)` man page (authored in `reference/doc/ferm.pod`) is the
267
+ extensive reference for the configuration syntax.
268
+
269
+ ## Development
270
+
271
+ The project is managed entirely with `uv` and orchestrated with `nox`:
272
+
273
+ ```sh
274
+ uv sync # create .venv from uv.lock
275
+ uv run nox -s lint tests typecheck # the everyday inner loop
276
+ uv run nox -s preflight # the full binding gate
277
+ ```
278
+
279
+ Selected `nox` sessions:
280
+
281
+ | Session | Purpose |
282
+ | --------------- | --------------------------------------------------------------- |
283
+ | `lint` | `ruff` lint + format check |
284
+ | `tests` | unit + golden-file suite |
285
+ | `typecheck` | `mypy` + `pyright` (`verifytypes` 100%) |
286
+ | `golden_oracle` | golden output diffed against the Perl oracle + differential fuzz |
287
+ | `coverage` | coverage with an enforced floor |
288
+ | `matrix` | the suite across Python 3.11–3.14 |
289
+ | `fuzz` | Hypothesis differential fuzzing vs. the oracle |
290
+ | `crashfuzz` | `atheris` crash fuzzing of both parsers (opt-in) |
291
+ | `mutation` | `mutmut` mutation testing (opt-in, nightly) |
292
+ | `lockout` | containerised anti-lockout `--interactive` e2e (opt-in) |
293
+
294
+ ### Testing
295
+
296
+ The suite is golden-file ("expected output") based: each fixture pairs a
297
+ `.ferm` input with a checked-in expected output, and ferm's output is
298
+ diffed after canonicalisation (tables/chains emit in non-deterministic
299
+ order). On top of that, the port is continuously checked **differentially
300
+ against the Perl oracle** — both on a corpus of real-world configs and on
301
+ Hypothesis-generated inputs — so divergences are caught automatically.
302
+
303
+ ## License
304
+
305
+ GPL-2.0-or-later. See `reference/COPYING`.
306
+
307
+ Original authors: Auke Kok and Max Kellermann. Python port maintained by
308
+ Boris Talovikov.
@@ -0,0 +1,26 @@
1
+ pyferm/__init__.py,sha256=JanXy9P2H2q6i9HWvd6zgbdnquCDrQ2mLMg9xEpTquQ,478
2
+ pyferm/__main__.py,sha256=I-t3PLy8gr7IF47yGt7JDlqZtYuLRHJoP1_ucL4X4QU,162
3
+ pyferm/cli.py,sha256=tXD7iSuM4sk3AUm1uCZgpNtcoP4EHPYxuYTmeK4CxS4,28197
4
+ pyferm/config.py,sha256=hib0EoIESS7lxEKkWMo9IZ6K9L75Pq2yfEHmMeRNze8,2839
5
+ pyferm/domains.py,sha256=Yd_7H-iO7zq1e87phkNj28clZRjW8ODJcpTekYNq3QE,11766
6
+ pyferm/errors.py,sha256=Jq-TrR2xnhAeca0XKXhBGKgsTzl5izagKPTARusMoyc,5346
7
+ pyferm/functions.py,sha256=IQHlvm8ii3G_K418zVJGDqtT7r-Vgi8zVsO8X_HV-yA,30363
8
+ pyferm/import_ferm.py,sha256=q0MUtnLH-8KSDhx5iT27AIqI8uuvmxdlUioVzAnpIHw,32537
9
+ pyferm/modules.py,sha256=cB53rigqXAWAtvRjXehhG42HzZtM-tcrjBf553zPlwI,20639
10
+ pyferm/parser.py,sha256=4epz-xy0Mf4GVqrh4ow7SVNevR4rSx-0WmXJimjMCMU,50708
11
+ pyferm/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ pyferm/resolver.py,sha256=nck5bUX771Dly9XCfJW5kZ-rWtQTqNt2irqHw2-ssyM,15907
13
+ pyferm/rules.py,sha256=3hvGRTuHGyskNuRnxi_IpI78wa-_8QIPNHHfWWfwmGo,8915
14
+ pyferm/scope.py,sha256=2ts2ORL0naLXxuXFLjdD6ZNqcdinf11CUGljfzZx55E,10440
15
+ pyferm/streams.py,sha256=8JLV-uIv5toPi5UNNor_4P80f4BMKRCg86woiNmxQTM,2752
16
+ pyferm/tokenizer.py,sha256=JmzEbWUlE3szfzXPYo4ncVIjIDs0_BY719kPdbUT5ek,11570
17
+ pyferm/values.py,sha256=WmuBrOt_MVtmPChqBwR4fbqUyEfVGRWGA6S9lGcfEDw,8235
18
+ pyferm/backend/__init__.py,sha256=Rgb-imQ2q1GHMQs1v9qdKHgoleHaDPgwlhuCk-bGzbA,75
19
+ pyferm/backend/base.py,sha256=B1_czmgC9ln3qSL5V38bVC1GSlMCecq6SiE_5SxLBRE,9104
20
+ pyferm/backend/iptables.py,sha256=mGXbXc_h79YhpzmAfAYeaRbqUlTUUXjVKhOWstiqP3M,29437
21
+ pyferm/backend/nft.py,sha256=4ceQpGWTjDDApb2zaQANdljWX5CZj4A5TUL8a7S9rBE,38463
22
+ ferm-0.1.0a1.dist-info/METADATA,sha256=aeAfinVQstSZlfTkZRKIx6TPVrIdn_QOathx3JryMKc,12642
23
+ ferm-0.1.0a1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
24
+ ferm-0.1.0a1.dist-info/entry_points.txt,sha256=Ceo1Tb3G1DMFaCqlWqXjR3kZdi_GC0pOSLs8Vnuem0g,79
25
+ ferm-0.1.0a1.dist-info/licenses/COPYING,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
26
+ ferm-0.1.0a1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ ferm = pyferm.cli:main
3
+ import-ferm = pyferm.import_ferm:main