Converge openings through the firewall an adopted node was found with, and retire it only when the node converges (hq ADR 0100)

This commit is contained in:
2026-09-22 17:19:49 +02:00
parent 3a613113be
commit 3c90d155b3
10 changed files with 1522 additions and 7 deletions
+204
View File
@@ -0,0 +1,204 @@
package apply
import (
"context"
"errors"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/novox/mesh-host/internal/declaration"
"github.com/novox/mesh-host/internal/store"
)
// Defends novox/hq ADR 0100: the firewall found on an adopted node stays in force; converging the
// node retires it by disabling it, and returning the node to adopted enables it again.
type ufwMachine struct {
installed, active bool
rules []string
ruleset string
asked []string
}
func (u *ufwMachine) run(_ context.Context, name string, args ...string) (string, error) {
u.asked = append(u.asked, name+" "+strings.Join(args, " "))
switch name {
case "nft":
return u.ruleset, nil
case "ufw":
if !u.installed {
return "", &exec.Error{Name: name, Err: exec.ErrNotFound}
}
default:
return "", &exec.Error{Name: name, Err: exec.ErrNotFound}
}
switch args[0] {
case "status":
if u.active {
return "Status: active\n", nil
}
return "Status: inactive\n", nil
case "show":
out := "Added user rules (see 'ufw status' for running firewall):\n"
for _, r := range u.rules {
out += "ufw " + r + "\n"
}
return out, nil
case "--force":
u.active = true
return "", nil
case "disable":
u.active = false
return "", nil
case "delete":
want := strings.Join(args[1:], " ")
for i, r := range u.rules {
if strings.ReplaceAll(r, "'", "") == want {
u.rules = append(u.rules[:i], u.rules[i+1:]...)
return "", nil
}
}
return "", errors.New("Could not delete non-existent rule")
default:
// Printed back the way it was given, with the comment quoted as ufw does.
line := strings.Join(args[:len(args)-1], " ") + " '" + args[len(args)-1] + "'"
u.rules = append(u.rules, line)
return "", nil
}
}
func (u *ufwMachine) index(prefix string) int {
for i, a := range u.asked {
if strings.HasPrefix(a, prefix) {
return i
}
}
return -1
}
const busOpening = `{"id":"adoption.opening-tcp-5671-incoming","type":"opening","port":5671,"protocol":"tcp","from":"everywhere","path":"incoming"}`
func withConf(dir string) string {
return `{"id":"x.conf","type":"file","path":"` + filepath.Join(dir, "x.conf") + `","content":"x\n"}`
}
func applyWith(t *testing.T, d *declaration.Declaration, known store.State, run Runner) (Report, store.State, error) {
t.Helper()
return ApplyKeeping(context.Background(), archHost(t), d, known, store.OriginDeclared, run, nil, nil,
KeepIn(t.TempDir()))
}
func TestAnOpeningOnAMachineWithNoFirewallChangesNothing(t *testing.T) {
dir := t.TempDir()
u := &ufwMachine{}
report, state, err := applyWith(t, adopted(t, `{"taken":[]}`, busOpening+","+withConf(dir)), store.State{}, u.run)
if err != nil {
t.Fatal(err)
}
o := outcomeOf(report, "adoption.opening-tcp-5671-incoming")
if o.Action != "unchanged" || !strings.Contains(o.Detail, "nothing filters this port") {
t.Errorf("an opening with no firewall: %+v", o)
}
if state.Firewall == nil || state.Firewall.Kind != "none" {
t.Errorf("the firewall found was not recorded: %+v", state.Firewall)
}
}
func TestAnUnsupportedFirewallRefusesTheWholeDeclaration(t *testing.T) {
dir := t.TempDir()
u := &ufwMachine{ruleset: "table inet filter {\n\tchain input {\n\t\ttype filter hook input priority filter; policy drop;\n\t}\n}\n"}
_, state, err := applyWith(t, adopted(t, `{"taken":[]}`, busOpening+","+withConf(dir)), store.State{}, u.run)
if err == nil || !strings.Contains(err.Error(), "no host speaks that firewall") {
t.Fatalf("an unsupported firewall was not refused: %v", err)
}
if _, statErr := os.Stat(filepath.Join(dir, "x.conf")); !errors.Is(statErr, os.ErrNotExist) {
t.Error("part of a refused declaration was applied")
}
if state.Firewall != nil {
t.Errorf("an unsupported firewall was recorded: %+v", state.Firewall)
}
}
func TestConvergingRetiresTheFoundFirewallAndReturningRestoresIt(t *testing.T) {
dir := t.TempDir()
u := &ufwMachine{installed: true, active: true, rules: []string{"allow 22/tcp"}}
// Adopted: the opening goes through ufw.
_, state, err := applyWith(t, adopted(t, `{"taken":[]}`, busOpening+","+withConf(dir)), store.State{}, u.run)
if err != nil {
t.Fatal(err)
}
if len(u.rules) != 2 || !u.active {
t.Fatalf("adopted: rules %v, active %v", u.rules, u.active)
}
if state.Firewall == nil || state.Firewall.Kind != "ufw" || !state.Firewall.WasActive {
t.Fatalf("adopted: firewall recorded as %+v", state.Firewall)
}
// Converged: the opening's rule goes, and only then is ufw disabled — never reset.
u.asked = nil
converged := parse(t, `{"declaration":1,"resources":[`+withConf(dir)+`]}`)
_, state, err = applyWith(t, converged, state, u.run)
if err != nil {
t.Fatal(err)
}
if u.active || !state.Firewall.DisabledByMesh {
t.Fatalf("converged: ufw still active (%v) or not recorded as retired (%+v)", u.active, state.Firewall)
}
if len(u.rules) != 1 || u.rules[0] != "allow 22/tcp" {
t.Errorf("converged: the operator's rules were touched, or the mesh's left: %v", u.rules)
}
if del, dis := u.index("ufw delete"), u.index("ufw disable"); del < 0 || dis < del {
t.Errorf("converged: the opening was not removed before ufw was disabled: %v", u.asked)
}
for _, a := range u.asked {
if strings.Contains(a, "reset") {
t.Errorf("converged: ufw was reset: %s", a)
}
}
// Converged again: nothing more to retire.
u.asked = nil
if _, state, err = applyWith(t, converged, state, u.run); err != nil {
t.Fatal(err)
}
if u.index("ufw") >= 0 {
t.Errorf("a converged node kept talking to a retired ufw: %v", u.asked)
}
// Returned to adopted: ufw is enabled before the opening is converged through it.
u.asked = nil
_, state, err = applyWith(t, adopted(t, `{"taken":[]}`, busOpening+","+withConf(dir)), state, u.run)
if err != nil {
t.Fatal(err)
}
if !u.active || state.Firewall.DisabledByMesh {
t.Fatalf("returned: ufw active %v, record %+v", u.active, state.Firewall)
}
if en, add := u.index("ufw --force enable"), u.index("ufw allow"); en < 0 || add < en {
t.Errorf("returned: ufw was not enabled before the opening was added: %v", u.asked)
}
if len(u.rules) != 2 {
t.Errorf("returned: the opening was not converged again: %v", u.rules)
}
}
func TestAConvergedNodeThatWasNeverAdoptedNeverAsksAboutAFirewall(t *testing.T) {
dir := t.TempDir()
u := &ufwMachine{installed: true, active: true}
if _, _, err := applyWith(t, parse(t, `{"declaration":1,"resources":[`+withConf(dir)+`]}`), store.State{}, u.run); err != nil {
t.Fatal(err)
}
if len(u.asked) != 0 {
t.Errorf("a converged apply asked the machine about its firewall: %v", u.asked)
}
}
func TestAnOpeningOnAConvergedNodeIsRefused(t *testing.T) {
if _, err := declaration.Parse([]byte(`{"declaration":1,"resources":[` + busOpening + `]}`)); err == nil {
t.Error("an opening was accepted on a node the declaration does not say is adopted")
}
}