The bus is never public: the broker port is no longer a foundation opening (hq ADR 0169) #229
@@ -1,33 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// The broker opening belongs only on the node that listens on it (novox/hq: it leaked onto
|
|
||||||
// every enrolled node's declaration, opening a from-anywhere hole for a port nothing there
|
|
||||||
// serves). foundationPortsFor is the scope.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/novox/mesh-controller/internal/catalogue"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTheBrokerHostGetsTheFoundationOpening(t *testing.T) {
|
|
||||||
broker := catalogue.Manifest{Module: "lavinmq", Listens: []catalogue.Listening{
|
|
||||||
{Port: 5671, Protocol: "tcp", From: "mesh"},
|
|
||||||
{Port: 5672, Protocol: "tcp", From: "mesh"},
|
|
||||||
}}
|
|
||||||
got := foundationPortsFor(5671, []catalogue.Manifest{broker})
|
|
||||||
if len(got) != 1 || got[0] != 5671 {
|
|
||||||
t.Fatalf("the node that listens on the broker port keeps it; got %v", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestANodeThatOnlyDialsTheBrokerGetsNoOpening(t *testing.T) {
|
|
||||||
// ace's set: things that reach the broker as a client, none listening on 5671.
|
|
||||||
ace := []catalogue.Manifest{
|
|
||||||
{Module: "plex", Listens: []catalogue.Listening{{Port: 32400, Protocol: "tcp", From: "anywhere"}}},
|
|
||||||
{Module: "postgres", Listens: []catalogue.Listening{{Port: 5432, Protocol: "tcp", From: "mesh"}}},
|
|
||||||
}
|
|
||||||
if got := foundationPortsFor(5671, ace); got != nil {
|
|
||||||
t.Fatalf("a node that only dials out opens nothing for the broker; got %v", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,8 +16,6 @@ import (
|
|||||||
"github.com/novox/mesh-controller/internal/inventory"
|
"github.com/novox/mesh-controller/internal/inventory"
|
||||||
"github.com/novox/mesh-controller/internal/licences"
|
"github.com/novox/mesh-controller/internal/licences"
|
||||||
"github.com/novox/mesh-controller/internal/overlay"
|
"github.com/novox/mesh-controller/internal/overlay"
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// working out what one machine should be.
|
// working out what one machine should be.
|
||||||
@@ -648,25 +646,12 @@ func renderingFor(ctx context.Context, open *stores, node string,
|
|||||||
names[name] = at
|
names[name] = at
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ports the mesh itself needs open, which no module declares. Read from the broker this
|
// **The bus is never public** (novox/hq ADR 0169). It was a foundation port — widened from the
|
||||||
// control plane was told about rather than written down twice: the address a node is handed in
|
// broker's own `from: mesh` to from-anywhere on the broker's host, so a machine could enrol
|
||||||
// its token and the port its machine must accept on are the same fact.
|
// before it had an address on the private network. A machine joins through the tunnel now, and
|
||||||
//
|
// every link to the bus crosses it, so its reach is what the `nats` module declares: the mesh.
|
||||||
// **Only on the node that listens on it** (novox/hq issue: the broker opening leaked onto
|
// Nothing the mesh itself needs is opened beyond what a module declares.
|
||||||
// every node). The opening exists to WIDEN the broker's port to from-anywhere — a machine
|
|
||||||
// enrolling is not on the mesh yet, so the broker's own `from: mesh` listen would refuse its
|
|
||||||
// first dial. That widening belongs on the broker's host and nowhere else: a node that only
|
|
||||||
// dials out needs no incoming rule, and an opening for a port nothing here listens on is a
|
|
||||||
// from-anywhere hole for a dead port. So the foundation port is kept only when a module
|
|
||||||
// resolved onto THIS node actually listens on it.
|
|
||||||
var foundation []int
|
var foundation []int
|
||||||
if b, err := broker.FromEnvironment(); err == nil {
|
|
||||||
if _, port, err := net.SplitHostPort(b.Address); err == nil {
|
|
||||||
if n, err := strconv.Atoi(port); err == nil {
|
|
||||||
foundation = foundationPortsFor(n, plan.Modules)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// And, for a module that keeps them, every operator-sealed secret in the mesh — the vault's
|
// And, for a module that keeps them, every operator-sealed secret in the mesh — the vault's
|
||||||
// copy, outside the store (novox/hq ADR 0085, amended). Read only; nothing here mints. The
|
// copy, outside the store (novox/hq ADR 0085, amended). Read only; nothing here mints. The
|
||||||
@@ -1380,23 +1365,6 @@ func composeBusUsers(ctx context.Context, inv *inventory.Inventory,
|
|||||||
return broker.ComposeAccounts(filled)
|
return broker.ComposeAccounts(filled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// foundationPortsFor is the broker port, kept only when a module resolved onto this node listens
|
|
||||||
// on it (novox/hq issue: the broker opening leaked onto every node). The foundation opening
|
|
||||||
// exists to WIDEN the broker's `from: mesh` port to from-anywhere, because a machine enrolling is
|
|
||||||
// not on the mesh yet and its first dial would be refused. That widening belongs on the broker's
|
|
||||||
// host alone: a node that only dials out needs no incoming rule, and an opening for a port
|
|
||||||
// nothing here listens on is a from-anywhere hole for a dead port.
|
|
||||||
func foundationPortsFor(brokerPort int, modules []catalogue.Manifest) []int {
|
|
||||||
for _, m := range modules {
|
|
||||||
for _, l := range m.Listens {
|
|
||||||
if l.Port == brokerPort {
|
|
||||||
return []int{brokerPort}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// providerModuleOf is which module answers a need on the providing node: the one in this node's
|
// providerModuleOf is which module answers a need on the providing node: the one in this node's
|
||||||
// own set when the provider is here, else the one the catalogue says offers it.
|
// own set when the provider is here, else the one the catalogue says offers it.
|
||||||
func providerModuleOf(resolved catalogue.Resolution, open *stores, ctx context.Context, n catalogue.Needed) string {
|
func providerModuleOf(resolved catalogue.Resolution, open *stores, ctx context.Context, n catalogue.Needed) string {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package catalogue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The bus is never public (novox/hq ADR 0169). Its port is what the bus module declares, the mesh,
|
||||||
|
// and the control plane adds no opening of its own: a machine joins through the tunnel, so the
|
||||||
|
// broker's host is filtered like any other. Before this, the broker port was a foundation port and
|
||||||
|
// rendered from anywhere beside its from-the-mesh rule.
|
||||||
|
func TestTheBusPortIsReachedFromTheMeshAlone(t *testing.T) {
|
||||||
|
rules := []Rule{{Port: 4222, Protocol: "tcp", From: FromMesh, Because: []string{"nats"},
|
||||||
|
Why: []string{"the mesh bus"}}}
|
||||||
|
out := AsNftables(rules, []string{"10.10.0.1", "10.10.0.2"}, true, nil, []string{"eth0"}, "mesh0")
|
||||||
|
|
||||||
|
if !regexp.MustCompile(`ip saddr \{ 10\.10\.0\.1, 10\.10\.0\.2 \} tcp dport 4222 accept`).MatchString(out) {
|
||||||
|
t.Fatalf("the bus is not reachable from the mesh:\n%s", out)
|
||||||
|
}
|
||||||
|
if regexp.MustCompile(`(?m)^\s*tcp dport 4222 accept`).MatchString(out) {
|
||||||
|
t.Fatalf("the bus is reachable from anywhere:\n%s", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user