Refuse an opening ufw would merge into a found rule that does other than a plain allow, and read log types in either place (hq ADR 0103)

This commit is contained in:
2026-09-22 18:29:06 +02:00
parent facf6af46a
commit b531c47486
2 changed files with 74 additions and 4 deletions
+38 -3
View File
@@ -479,6 +479,7 @@ func words(rule string) []string {
type ufwRule struct {
route bool
action, in, out string
log string
from, fromPort, to string
port, proto, app string
comment string
@@ -488,8 +489,23 @@ type ufwRule struct {
// in on mesh0 to any port 5432 proto tcp` — into the fields ufw compares. Not ok for anything it
// does not recognise, which is then never taken to answer an opening.
func parseRule(rule string) (ufwRule, bool) {
w := words(rule)
r := ufwRule{from: "any", to: "any", comment: comment(rule)}
// A log type may stand after the action or after the direction; either way it is a property
// of the rule, not of where it matches. The word after `comment` is the comment, whatever it
// says.
var w []string
all := words(rule)
for i := 0; i < len(all); i++ {
switch {
case all[i] == "comment" && i+1 < len(all):
w = append(w, all[i], all[i+1])
i++
case all[i] == "log" || all[i] == "log-all":
r.log = all[i]
default:
w = append(w, all[i])
}
}
i := 0
if i < len(w) && w[i] == "route" {
r.route = true
@@ -585,9 +601,13 @@ func isPorts(s string) bool {
return true
}
// sameAs is whether ufw would take two rules for one — everything but the comment equal.
// sameAs is whether ufw would take two rules for one: everything equal but the comment, the
// action and the log type. Adding one beside the other updates it in place — its comment, and its
// action or log type — rather than adding a second.
func (r ufwRule) sameAs(o ufwRule) bool {
r.comment, o.comment = "", ""
r.action, o.action = "", ""
r.log, o.log = "", ""
return r == o
}
@@ -658,6 +678,7 @@ func Converge(ctx context.Context, run Runner, o *declaration.Opening) (Converge
present := false
var stale []string
satisfiedBy := ""
want, _ := parseRule(strings.Join(Rule(o), " "))
for _, rule := range rules {
c := comment(rule)
switch {
@@ -666,7 +687,21 @@ func Converge(ctx context.Context, run Runner, o *declaration.Opening) (Converge
case markedFor(c, o.ID):
stale = append(stale, rule)
default:
if parsed, ok := parseRule(rule); ok && satisfiedBy == "" && parsed.admits(o) {
parsed, ok := parseRule(rule)
if !ok {
continue
}
// ufw would take the mesh's rule for this one and rewrite its action or log type:
// an operator's refusal, or a limit, would silently become an allow — and removing the
// opening would then delete it. A plain allow answers the opening; anything else is a
// conflict the operator decides (novox/hq ADR 0103).
if parsed.sameAs(want) && (parsed.action != "allow" || parsed.log != "") {
return Converged{}, fmt.Errorf("ufw holds %q, which ufw takes for the same rule as the "+
"mesh's opening for %s, differing in what it does; adding the opening would change "+
"it, so nothing was added. Change or remove that rule, or have the mesh stop "+
"declaring the opening", rule, o.Target())
}
if satisfiedBy == "" && parsed.admits(o) {
satisfiedBy = rule
}
}