ufw: add UfwStatus + UfwRules facts and service/default/logging/rule ops#1671
Open
wowi42 wants to merge 5 commits into
Open
ufw: add UfwStatus + UfwRules facts and service/default/logging/rule ops#1671wowi42 wants to merge 5 commits into
wowi42 wants to merge 5 commits into
Conversation
Facts:
- UfwStatus parses `ufw status verbose` into {active, logging, default,
new_profiles}. Handles the inactive case (no verbose body).
- UfwRules parses `ufw show added` into a list of canonical rule dicts
(action, direction, interface, from_, to, port, proto, app, log,
comment).
Operations (all idempotent via the facts above):
- service(enabled, reload): toggles ufw via `ufw --force enable` /
`ufw disable`; reload=True triggers `ufw reload` when already active.
- default(policy, direction): noop when the existing default already
matches, else `ufw default <policy> <direction>`.
- logging(level): accepts bool or one of off/on/low/medium/high/full.
- rule(action, port, proto, from_ip, to_ip, direction, interface, app,
log, comment, present): builds a canonical dict, compares against
UfwRules membership and emits `ufw ...` or `ufw delete ...`.
A shared `_canonical_rule`/`_render_rule` pair in facts/ufw.py is used
by both the fact parser and the operation renderer so that parse(render
(rule)) == rule; that invariant is what makes idempotency hold.
Verified live on ubuntu 10.0.10.23 (`ufw 0.36.2`): parsing the actual
status/show-added output and running a rule render+reparse roundtrip
both succeed. Alpine and FreeBSD do not ship ufw, so the
`requires_command = "ufw"` declaration short-circuits cleanly there.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a new
ufwmodule (facts + operations) so pyinfra can manage Uncomplicated Firewall state and rules on Debian/Ubuntu hosts idempotently, matching the conventions of the existingiptablesmodule.Facts (
pyinfra.facts.ufw)UfwStatusparsesufw status verboseinto a structured dict:{ "active": True, "logging": "low", "default": {"incoming": "deny", "outgoing": "allow", "routed": "disabled"}, "new_profiles": "skip", }The
inactivecase returns just{"active": False}.UfwRulesparsesufw show addedinto a list of canonical rule dicts with keysaction,direction,interface,from_,to,port,proto,app,log,comment. Non-ufw …lines (the header, blanks) are skipped.Both facts declare
requires_command = "ufw"so hosts without UFW (Alpine / FreeBSD in the test fleet) short-circuit cleanly.Operations (
pyinfra.operations.ufw)All operations read one fact up front and call
host.noop()when the host is already in the desired state.service(enabled=True, reload=False):ufw --force enable/ufw disable.reload=Truetriggersufw reloadwhen already active (no idempotency check on reload — it's an action).default(policy, direction="incoming"):policyin {allow,deny,reject},directionin {incoming,outgoing,routed}.logging(level): acceptsTrue/Falseoroff/on/low/medium/high/full.rule(action, port=None, proto=None, from_ip=None, to_ip=None, direction=None, interface=None, app=None, log=False, comment=None, present=True): structured kwargs → canonical rule dict → membership check againstUfwRules; emitsufw …orufw delete ….Idempotency guarantee
A shared
_canonical_rule/_render_rulepair infacts/ufw.pyis used by both the fact parser and the operation renderer, so the invariantparse(render(rule)) == ruleholds. That is what makesrule(...)noop on re-runs even though UFW itself will happily create duplicate rules.Tests
tests/facts/ufw.UfwStatus/{active,inactive}.json,tests/facts/ufw.UfwRules/mixed.json(limit, allow with no proto, full direction+interface+from+to+proto, deny-by-source).tests/operations/ufw.{service,default,logging,rule}/…covering mutation + noop + complex rule cases.Full suite
pytest tests/is green (1563 passing),ruff checkandruff format --checkclean.Scope not included (deferred)
Application profiles (
ufw app list/info),reset, numbered-insert, delete-by-number. Can ship in a follow-up if desired.Live verification
Tested on Ubuntu 0.36.2: real
ufw status verboseandufw show addedoutput parses correctly, and the render-then-reparse roundtrip returns identical canonical rule dicts.3.xat this time)scripts/dev-test.sh)scripts/dev-lint.sh)