Read a ufw rule's direction: an outgoing rule answers no opening, and incoming is the default ufw merges on (hq ADR 0103)
This commit is contained in:
@@ -479,6 +479,9 @@ func words(rule string) []string {
|
|||||||
type ufwRule struct {
|
type ufwRule struct {
|
||||||
route bool
|
route bool
|
||||||
action, in, out string
|
action, in, out string
|
||||||
|
// dir is which way the rule matches: "" (ufw's default, incoming and forwarded), "in" or
|
||||||
|
// "out". A rule on the outgoing path admits nothing that arrives.
|
||||||
|
dir string
|
||||||
log string
|
log string
|
||||||
from, fromPort, to string
|
from, fromPort, to string
|
||||||
port, proto, app string
|
port, proto, app string
|
||||||
@@ -529,6 +532,7 @@ func parseRule(rule string) (ufwRule, bool) {
|
|||||||
iface = w[i+1]
|
iface = w[i+1]
|
||||||
i += 2
|
i += 2
|
||||||
}
|
}
|
||||||
|
r.dir = dir
|
||||||
if dir == "in" {
|
if dir == "in" {
|
||||||
r.in = iface
|
r.in = iface
|
||||||
} else {
|
} else {
|
||||||
@@ -586,6 +590,12 @@ func parseRule(rule string) (ufwRule, bool) {
|
|||||||
if r.proto == "any" {
|
if r.proto == "any" {
|
||||||
r.proto = ""
|
r.proto = ""
|
||||||
}
|
}
|
||||||
|
// Incoming is ufw's default direction, and it prints `allow in 9005/tcp` back as
|
||||||
|
// `allow 9005/tcp` and merges the two — captured in testdata/ufw-direction.txt. An outgoing
|
||||||
|
// rule is its own rule and stays one.
|
||||||
|
if r.dir == "in" && r.in == "" {
|
||||||
|
r.dir = ""
|
||||||
|
}
|
||||||
return r, true
|
return r, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,10 +626,15 @@ func (r ufwRule) sameAs(o ufwRule) bool {
|
|||||||
// protocol — and on any interface, or the private network's for an opening from it.
|
// protocol — and on any interface, or the private network's for an opening from it.
|
||||||
func (r ufwRule) admits(o *declaration.Opening) bool {
|
func (r ufwRule) admits(o *declaration.Opening) bool {
|
||||||
want, ok := parseRule(strings.Join(Rule(o), " "))
|
want, ok := parseRule(strings.Join(Rule(o), " "))
|
||||||
if !ok || r.route != want.route || r.action != "allow" || r.out != "" || r.app != "" ||
|
if !ok || r.route != want.route || r.action != "allow" || r.app != "" ||
|
||||||
r.from != "any" || r.fromPort != "" || r.to != "any" {
|
r.from != "any" || r.fromPort != "" || r.to != "any" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// A rule on the outgoing path lets this machine reach others; it admits nothing that arrives,
|
||||||
|
// so it never answers an opening.
|
||||||
|
if r.dir == "out" || r.out != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if r.proto != "" && r.proto != want.proto {
|
if r.proto != "" && r.proto != want.proto {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -604,11 +604,17 @@ func TestEveryCapturedRuleFormIsRead(t *testing.T) {
|
|||||||
"allow in on mesh0 to any port 5432 proto tcp": "tcp 5432 in=mesh0 from=any",
|
"allow in on mesh0 to any port 5432 proto tcp": "tcp 5432 in=mesh0 from=any",
|
||||||
"route allow 8080/tcp": "tcp 8080 in= from=any",
|
"route allow 8080/tcp": "tcp 8080 in= from=any",
|
||||||
"allow 9900/tcp": "tcp 9900 in= from=any",
|
"allow 9900/tcp": "tcp 9900 in= from=any",
|
||||||
|
"allow out 5671/tcp": "tcp 5671 in= from=any",
|
||||||
|
"deny out 5672/tcp": "tcp 5672 in= from=any",
|
||||||
|
"allow out on eth0 to any port 5673 proto tcp": "tcp 5673 in= from=any",
|
||||||
|
"allow log 9001/tcp": "tcp 9001 in= from=any",
|
||||||
|
"route allow log 8084/tcp": "tcp 8084 in= from=any",
|
||||||
|
"allow in on mesh0 log-all to any port 9002 proto tcp": "tcp 9002 in=mesh0 from=any",
|
||||||
}
|
}
|
||||||
rules, err := added(context.Background(), func(context.Context, string, ...string) (string, error) {
|
rules, err := added(context.Background(), func(context.Context, string, ...string) (string, error) {
|
||||||
return captured(t, "ufw-forms.txt"), nil
|
return captured(t, "ufw-forms.txt"), nil
|
||||||
})
|
})
|
||||||
if err != nil || len(rules) != 15 {
|
if err != nil || len(rules) != 21 {
|
||||||
t.Fatalf("read %d rules: %v", len(rules), err)
|
t.Fatalf("read %d rules: %v", len(rules), err)
|
||||||
}
|
}
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
@@ -716,3 +722,37 @@ func TestALogTypeIsReadInEitherPlace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAnOutgoingRuleNeverAnswersAnOpening(t *testing.T) {
|
||||||
|
// `ufw allow out 5671/tcp` lets this machine reach others; nothing arrives through it, and
|
||||||
|
// ufw keeps it as a rule of its own — captured in testdata/ufw-direction.txt.
|
||||||
|
raw := captured(t, "ufw-direction.txt")
|
||||||
|
if !strings.Contains(raw, "ufw allow out 9007/tcp\nufw allow 9007/tcp") {
|
||||||
|
t.Fatalf("the capture no longer shows an outgoing rule standing beside an incoming one:\n%s", raw)
|
||||||
|
}
|
||||||
|
for _, operators := range []string{"allow out 5671/tcp", "allow out on eth0 to any port 5671 proto tcp",
|
||||||
|
"deny out 5671/tcp"} {
|
||||||
|
f := &fakeUFW{installed: true, active: true, rules: []string{operators}}
|
||||||
|
done, err := Converge(context.Background(), f.run, opening("adoption.bus", 5671, "everywhere", "incoming", 0))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%q: an outgoing rule was taken for a conflict: %v", operators, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if done.Action != "created" || done.SatisfiedBy != "" {
|
||||||
|
t.Errorf("%q: an outgoing rule answered an incoming opening: %+v", operators, done)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncomingIsUfwsDefaultDirection(t *testing.T) {
|
||||||
|
// Captured: `deny in 9006/tcp` and `allow 9006/tcp` are one rule to ufw, so the mesh must read
|
||||||
|
// them as one too, or it would take an operator's refusal over.
|
||||||
|
raw := captured(t, "ufw-direction.txt")
|
||||||
|
if !strings.Contains(raw, "ufw allow 9005/tcp") || strings.Contains(raw, "ufw deny 9006/tcp") {
|
||||||
|
t.Fatalf("the capture no longer shows `in` as the default direction:\n%s", raw)
|
||||||
|
}
|
||||||
|
f := &fakeUFW{installed: true, active: true, rules: []string{"deny in to any port 5671 proto tcp"}}
|
||||||
|
if _, err := Converge(context.Background(), f.run, opening("adoption.bus", 5671, "everywhere", "incoming", 0)); err == nil {
|
||||||
|
t.Error("an incoming refusal ufw would merge was not refused")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
$ ufw allow in 9005/tcp
|
||||||
|
Rules updated
|
||||||
|
Rules updated (v6)
|
||||||
|
$ ufw deny in 9006/tcp
|
||||||
|
Rules updated
|
||||||
|
Rules updated (v6)
|
||||||
|
$ ufw allow 9006/tcp
|
||||||
|
Rules updated
|
||||||
|
Rules updated (v6)
|
||||||
|
$ ufw allow out 9007/tcp
|
||||||
|
Rules updated
|
||||||
|
Rules updated (v6)
|
||||||
|
$ ufw allow 9007/tcp
|
||||||
|
Rules updated
|
||||||
|
Rules updated (v6)
|
||||||
|
$ ufw show added
|
||||||
|
Added user rules (see 'ufw status' for running firewall):
|
||||||
|
ufw allow 9005/tcp
|
||||||
|
ufw allow 9006/tcp
|
||||||
|
ufw allow out 9007/tcp
|
||||||
|
ufw allow 9007/tcp
|
||||||
+6
@@ -14,3 +14,9 @@ ufw allow 9900/tcp
|
|||||||
ufw allow 80,443/tcp
|
ufw allow 80,443/tcp
|
||||||
ufw allow in on mesh0 to any port 5432 proto tcp
|
ufw allow in on mesh0 to any port 5432 proto tcp
|
||||||
ufw route allow 8080/tcp
|
ufw route allow 8080/tcp
|
||||||
|
ufw allow out 5671/tcp
|
||||||
|
ufw deny out 5672/tcp
|
||||||
|
ufw allow out on eth0 to any port 5673 proto tcp
|
||||||
|
ufw allow log 9001/tcp
|
||||||
|
ufw route allow log 8084/tcp
|
||||||
|
ufw allow in on mesh0 log-all to any port 9002 proto tcp
|
||||||
|
|||||||
Reference in New Issue
Block a user