The filter constrains what arrives from outside, and names no network

The forward chain blocked everything passing through the machine and then allowed
the machine's own containers back by naming their address ranges: 172.16.0.0/12 and
192.168.128.0/17 fixed here, the rest recorded per machine by 0043. Every way of
keeping that list correct fails — a constant describes one machine, a recorded range
goes stale in silence and cannot tell a network the mesh made from one a predecessor
left behind, and generating it from the modules would put half the rule set on the
machine.

The mesh has no position on a container reaching outward: that is not a port opened
to anybody. So both chains are written around the links traffic arrives on. What did
not arrive from outside is accepted in one line; what did meets the declared rules.
The tunnel is named beside the outward links rather than treated as inside, or a port
nothing declares would be reachable from every machine in the mesh.

A machine that has not reported an outward link is sent no filter and keeps the one
it has, refused where a person reads it rather than as a rule set that will not load.

Removes the two constants, `node networks`, and the column behind it. novox/hq ADR
0140, superseding 0137 and 0139.
This commit is contained in:
2026-09-29 01:01:29 +02:00
parent ed5d467d90
commit fe5988c536
15 changed files with 347 additions and 332 deletions
@@ -0,0 +1,26 @@
-- Which of a machine's links face outside it, replacing the networks it was told to say it routes.
--
-- novox/hq ADR 0140, superseding 0137 and 0139. The derived filter blocked everything passing
-- through a machine and then allowed the machine's own containers back by naming the address ranges
-- they sit on: two ranges fixed in the controller's source, the rest recorded by 0043's column.
--
-- Every route to a correct list fails. A constant describes one machine. A recorded range goes stale
-- in silence, and cannot tell a network the mesh made from one a predecessor left behind — measured
-- on the control-node, where six ranges fall outside the constants and two of the six belong to
-- services the mesh does not run. Generating the list from the modules put half the rule set on the
-- machine.
--
-- The list should not exist, because the mesh has no position on a container reaching outward: that
-- is not a port opened to anybody. The filter constrains what arrives from OUTSIDE the machine and
-- says nothing about what did not, which needs one fact instead of a list — which links "outside"
-- arrives on.
--
-- Reported by the machine on every apply, never recorded by hand, so it cannot go stale. Null for a
-- machine that has not reported yet; the mesh composes no filter for such a machine and leaves the
-- one it has, because a rule written around a link with no name is a rule set that does not load.
alter table node add column outward_links jsonb;
-- What 0043 recorded is not migrated into it. The ranges answered a question that no longer exists,
-- and every machine that named one keeps working without it: the traffic those ranges allowed is now
-- allowed by not having arrived from outside.
alter table node drop column routed_networks;
+25 -34
View File
@@ -9,7 +9,6 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"sort"
"strings"
"time"
@@ -519,37 +518,28 @@ func (i *Inventory) PublicDomainOf(ctx context.Context, name string) (string, er
return *domain, nil
}
// SetRoutedNetworks records the networks this machine routes for what it hosts, beyond the
// container runtime's own default pools.
// RecordOutwardLinks keeps the links a machine reported as facing outside it.
//
// A node-level fact (novox/hq ADR 0137), beside the node's public domain: the machine routes them,
// not whichever module loads the filter, so swapping that module must not lose them. Added to the
// runtime's defaults rather than replacing them, so a machine that says one range does not lose the
// ranges its containers were already using. An empty list clears it.
// A reported fact, not a setting (novox/hq ADR 0140). It replaces the networks a machine used to be
// told to say it routes: the filter blocked everything passing through and then allowed the machine's
// own containers back by naming their address ranges, and every way of keeping that list correct
// failed — a constant describes one machine, and a recorded range goes stale in silence. The filter
// now constrains what arrives from outside and says nothing about what did not, and the one thing it
// needs is which links "outside" arrives on. The machine reads that from its own routing table on
// every apply, so it cannot go stale and nobody types it.
//
// Each entry is checked as a CIDR here rather than at render time: an address that does not parse
// becomes a line nftables refuses, and a refused ruleset is a machine that filters nothing while
// its service reports a configuration fault.
func (i *Inventory) SetRoutedNetworks(ctx context.Context, name string, networks []string) error {
node, err := i.NodeByName(ctx, name)
if err != nil {
return err
}
// An empty list clears it, which is what a machine with no route off itself reports. The mesh then
// composes no filter for that machine at all.
func (i *Inventory) RecordOutwardLinks(ctx context.Context, id string, links []string) error {
var kept []string
for _, n := range networks {
n = strings.TrimSpace(n)
if n == "" {
continue
for _, name := range links {
if name = strings.TrimSpace(name); name != "" {
kept = append(kept, name)
}
if _, _, err := net.ParseCIDR(n); err != nil {
return fmt.Errorf("%q is not a network in CIDR form (10.0.0.0/8, 192.168.0.0/16): %w",
n, err)
}
kept = append(kept, n)
}
if len(kept) == 0 {
_, err = i.store.Pool().Exec(ctx,
`update node set routed_networks = null where id = $1`, node.ID)
_, err := i.store.Pool().Exec(ctx,
`update node set outward_links = null where id = $1`, id)
return err
}
body, err := json.Marshal(kept)
@@ -557,15 +547,16 @@ func (i *Inventory) SetRoutedNetworks(ctx context.Context, name string, networks
return err
}
_, err = i.store.Pool().Exec(ctx,
`update node set routed_networks = $2 where id = $1`, node.ID, string(body))
`update node set outward_links = $2 where id = $1`, id, string(body))
return err
}
// RoutedNetworksOf is the networks a machine routes for what it hosts, empty when it has named none.
func (i *Inventory) RoutedNetworksOf(ctx context.Context, name string) ([]string, error) {
// OutwardLinksOf is the links a machine reported as facing outside it, empty when it has reported
// none — which is a machine the mesh composes no filter for.
func (i *Inventory) OutwardLinksOf(ctx context.Context, name string) ([]string, error) {
var body []byte
err := i.store.Pool().QueryRow(ctx,
`select routed_networks from node where name = $1`, name).Scan(&body)
`select outward_links from node where name = $1`, name).Scan(&body)
if errors.Is(err, pgx.ErrNoRows) {
return nil, fmt.Errorf("%w: %s", ErrNoSuchNode, name)
}
@@ -575,11 +566,11 @@ func (i *Inventory) RoutedNetworksOf(ctx context.Context, name string) ([]string
if len(body) == 0 {
return nil, nil
}
var networks []string
if err := json.Unmarshal(body, &networks); err != nil {
return nil, fmt.Errorf("the networks recorded for %s are not a list: %w", name, err)
var links []string
if err := json.Unmarshal(body, &links); err != nil {
return nil, fmt.Errorf("the outward links recorded for %s are not a list: %w", name, err)
}
return networks, nil
return links, nil
}
// RecordOverlayKey keeps the public half a node generated.