The machine that most needed a firewall was the one that could not have one. A hub is dialled by every node at other sites and needs its port open; a machine that is not a hub dials out and needs nothing open. They are the same module, and `listens` in a manifest is one answer for every machine that runs it — so the machine a static answer gets wrong is the one facing the public internet. A generator can now say what it opens, in a second interface rather than a method on every generator: most have nothing to say here, and requiring an empty method of each would be a cost paid everywhere for one caller. The port is the one in the endpoint, which is where the interface takes its ListenPort from. One source, so a rule set cannot open a port the interface is not on. Open to everywhere and deliberately: a node at another site is not on the private network until this port lets it on, so restricting it to the mesh would be a rule that can never be satisfied by the thing it exists for. And a generator that cannot say is refused rather than read as silence. Closing a port on the evidence of a failure to look is how a machine is severed by a fault somewhere else — and the machine it would sever is the hub, whose only route to being fixed is the network it just closed.
114 lines
4.0 KiB
Go
114 lines
4.0 KiB
Go
package overlay
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-control/internal/catalogue"
|
|
)
|
|
|
|
func networkOf(t *testing.T, nodes []Node) *Generator {
|
|
t.Helper()
|
|
made, err := From(nodes, "10.42.0.0/16", "/var/lib/mesh-host/overlay.key")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return made
|
|
}
|
|
|
|
// A hub is dialled by every node at other sites, and needs its port open. A machine that is not a
|
|
// hub dials out and needs nothing open at all.
|
|
//
|
|
// They are the same module, which is why a static field in a manifest cannot say it — and the
|
|
// machine it gets wrong is the one facing the public internet, which is the machine that most
|
|
// needs filtering.
|
|
func TestOnlyAMachineThatCanBeDialledOpensTheOverlaysPort(t *testing.T) {
|
|
network := networkOf(t, []Node{
|
|
{Name: "anchor", Key: "a", Endpoint: "198.51.100.10:51820", Site: "one", Hub: true},
|
|
{Name: "laptop", Key: "b", Site: "one"},
|
|
})
|
|
|
|
opens, err := network.Listens("anchor")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(opens) != 1 {
|
|
t.Fatalf("the machine every other one dials opens %d ports", len(opens))
|
|
}
|
|
if opens[0].Port != 51820 || opens[0].At() != "udp" {
|
|
t.Fatalf("the port is not the one the interface listens on: %+v", opens[0])
|
|
}
|
|
// From everywhere, and deliberately: a node at another site is not on the private network
|
|
// until this port lets it on, so restricting it to the mesh would be a rule that can never be
|
|
// satisfied by the thing it exists for.
|
|
if opens[0].From != catalogue.FromEverywhere {
|
|
t.Fatalf("the way onto the private network is restricted to the private network: %+v", opens[0])
|
|
}
|
|
if opens[0].Why == "" {
|
|
t.Fatal("a port is opened and nothing says why, which is what makes a rule set unreadable")
|
|
}
|
|
|
|
// And the machine nothing dials opens nothing. Not harmless to get wrong: a rule with no
|
|
// reason is one somebody later has to work out the reason for.
|
|
quiet, err := network.Listens("laptop")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(quiet) != 0 {
|
|
t.Fatalf("a machine nothing dials opened %d port(s)", len(quiet))
|
|
}
|
|
}
|
|
|
|
// The port comes from the endpoint, which is where the interface takes its ListenPort from. One
|
|
// source, so a rule set cannot open a port the interface is not on.
|
|
func TestTheOpenedPortIsTheOneTheInterfaceListensOn(t *testing.T) {
|
|
network := networkOf(t, []Node{
|
|
{Name: "anchor", Key: "a", Endpoint: "198.51.100.10:60000", Site: "one", Hub: true},
|
|
})
|
|
opens, err := network.Listens("anchor")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(opens) != 1 || opens[0].Port != 60000 {
|
|
t.Fatalf("the rule set would open a port the interface is not on: %+v", opens)
|
|
}
|
|
|
|
written, err := Declaration(Node{Name: "anchor", Key: "a", Address: "10.42.0.1",
|
|
Endpoint: "198.51.100.10:60000", Hub: true}, nil, "/k")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(string(written), "ListenPort = 60000") {
|
|
t.Fatalf("the interface and the rule set disagree about the port:\n%s", written)
|
|
}
|
|
}
|
|
|
|
// An endpoint with no port is refused rather than treated as a machine that opens nothing.
|
|
//
|
|
// A generator that cannot say what a machine opens is not one that says it opens nothing, and
|
|
// closing a port on the evidence of a failure to look is how a machine is severed by a fault
|
|
// somewhere else entirely.
|
|
func TestAnEndpointWithNoPortIsRefusedRatherThanTakenAsSilence(t *testing.T) {
|
|
network := networkOf(t, []Node{
|
|
{Name: "anchor", Key: "a", Endpoint: "198.51.100.10", Site: "one", Hub: true},
|
|
})
|
|
if _, err := network.Listens("anchor"); err == nil {
|
|
t.Fatal("a machine whose port could not be read was treated as opening nothing")
|
|
}
|
|
}
|
|
|
|
// A machine the network has never heard of opens nothing, and that is an answer rather than an
|
|
// error: a node assigned the module before it is placed is in exactly that state.
|
|
func TestAMachineNotOnTheNetworkOpensNothing(t *testing.T) {
|
|
network := networkOf(t, []Node{
|
|
{Name: "anchor", Key: "a", Endpoint: "198.51.100.10:51820", Site: "one", Hub: true},
|
|
})
|
|
opens, err := network.Listens("a-stranger")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(opens) != 0 {
|
|
t.Fatalf("a machine not on the network opened %d port(s)", len(opens))
|
|
}
|
|
}
|