Adoption mode: a node in use is adopted before it is converged (hq ADR 0100–0103) #20
@@ -63,19 +63,31 @@ func Detect(ctx context.Context, run Runner) (Kind, string, error) {
|
||||
ufwActive = statusActive(out)
|
||||
}
|
||||
|
||||
noNft := false
|
||||
out, err := run(ctx, "nft", "list", "ruleset")
|
||||
switch {
|
||||
case err == nil:
|
||||
if refusing := Refusing(out, ufwActive); len(refusing) > 0 {
|
||||
return Unsupported, "nftables rules that refuse traffic, in " + strings.Join(refusing, ", "), nil
|
||||
}
|
||||
case !missing(err):
|
||||
case missing(err):
|
||||
// **No nft on this machine does not mean no rules.** iptables-nft writes tables nft would
|
||||
// have shown, and a machine whose only tool is iptables answers about them through that.
|
||||
// Read as "nothing filters here", a machine with an iptables firewall would be adopted
|
||||
// with no openings and nothing would reach the mesh (novox/hq ADR 0100).
|
||||
noNft = true
|
||||
default:
|
||||
return "", "", fmt.Errorf("cannot read this machine's packet filter to know what it has: %w", err)
|
||||
}
|
||||
|
||||
if !ufwActive {
|
||||
// iptables with the legacy backend is invisible to nft.
|
||||
for _, legacy := range []string{"iptables-legacy", "ip6tables-legacy"} {
|
||||
// iptables with the legacy backend is invisible to nft; and where nft is not installed,
|
||||
// the iptables command is the only way to see anything at all.
|
||||
tools := []string{"iptables-legacy", "ip6tables-legacy"}
|
||||
if noNft {
|
||||
tools = append(tools, "iptables", "ip6tables")
|
||||
}
|
||||
for _, legacy := range tools {
|
||||
out, err := run(ctx, legacy, "-S")
|
||||
if err != nil {
|
||||
continue
|
||||
|
||||
@@ -119,11 +119,20 @@ type fakeUFW struct {
|
||||
// after it is disabled; empty is a machine without iptables. forward is a policy set since.
|
||||
iptablesActive, iptablesInactive string
|
||||
forward string
|
||||
// noNft is a machine with no nft binary; iptablesRules is what `iptables -S` prints there.
|
||||
noNft bool
|
||||
iptablesRules string
|
||||
}
|
||||
|
||||
// iptables answers `iptables -S FORWARD` from the captured output for ufw's state, and records
|
||||
// a forward policy set with -P.
|
||||
func (f *fakeUFW) iptables(name string, args []string) (string, error) {
|
||||
if f.iptablesRules != "" && len(args) == 1 && args[0] == "-S" {
|
||||
if name == "ip6tables" {
|
||||
return "", nil
|
||||
}
|
||||
return f.iptablesRules, nil
|
||||
}
|
||||
if f.iptablesActive == "" {
|
||||
return "", &exec.Error{Name: name, Err: exec.ErrNotFound}
|
||||
}
|
||||
@@ -189,6 +198,9 @@ func (f *fakeUFW) run(_ context.Context, name string, args ...string) (string, e
|
||||
}
|
||||
return "", &exec.Error{Name: name, Err: exec.ErrNotFound}
|
||||
case "nft":
|
||||
if f.noNft {
|
||||
return "", &exec.Error{Name: name, Err: exec.ErrNotFound}
|
||||
}
|
||||
return f.ruleset, nil
|
||||
case "iptables-legacy", "ip6tables-legacy":
|
||||
return "", &exec.Error{Name: name, Err: exec.ErrNotFound}
|
||||
@@ -756,3 +768,22 @@ func TestIncomingIsUfwsDefaultDirection(t *testing.T) {
|
||||
t.Error("an incoming refusal ufw would merge was not refused")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAMachineWithIptablesRulesAndNoNftIsNotReadAsUnfiltered(t *testing.T) {
|
||||
// nft is not installed, and iptables-nft holds a firewall of somebody's. Read as "nothing
|
||||
// filters here" the mesh would adopt it, open nothing, and be unreachable (novox/hq ADR 0100).
|
||||
rules := "-P INPUT DROP\n-P FORWARD DROP\n-P OUTPUT ACCEPT\n-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT\n"
|
||||
f := &fakeUFW{noNft: true, iptablesRules: rules}
|
||||
kind, what, err := Detect(context.Background(), f.run)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if kind != Unsupported {
|
||||
t.Errorf("a machine filtered by iptables with no nft read as %s (%s)", kind, what)
|
||||
}
|
||||
// And a machine with nothing but the runtime's own rules and no nft is still unfiltered.
|
||||
docker := "-P INPUT ACCEPT\n-P FORWARD DROP\n-P OUTPUT ACCEPT\n-N DOCKER\n-A DOCKER -i docker0 -j DROP\n"
|
||||
if kind, _, err := Detect(context.Background(), (&fakeUFW{noNft: true, iptablesRules: docker}).run); err != nil || kind != None {
|
||||
t.Errorf("a machine with only the runtime's rules read as %s: %v", kind, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user