A machine's filtering is computed from what it was assigned

A rule nobody derives is a rule somebody keeps in step by hand, and five HAL
manifests carry a `scope:` key that reads as a restriction and restricts
nothing. Both halves are closed here.

Manifests are parsed strictly. An unknown key is refused, which is the
discipline the host's declaration parser has always had; `scope:` survived
because nothing rejected it.

A module says what it listens on and who may reach it, and saying from where is
required — a rule with no source is open, and must say so rather than appear to
restrict something. The mesh gathers every assigned module's ports, widens
where two overlap, names every module that wanted each one, and renders one
nftables file per node. What no module declared is closed.

Three things it deliberately does not do: it writes no forward policy, because
what a machine routes is the container runtime's business and dropping there
stops every container on the node; it never flushes the whole ruleset, only
its own table; and it carries no command to load itself, because the link may
not carry an action. A service declares `restart-on` the file instead, which is
the shape that rule leaves.

Also fixes a fault the lab found: certificateFor asked where every node is
without the catalogue, so nothing resolved, every machine looked like it was on
no private network, and every certificate the mesh was asked for was refused
with a reason that was not true. Asking that question without the catalogue is
now refused rather than answered wrongly.
This commit is contained in:
2026-08-31 00:25:12 +02:00
parent 646609c1b2
commit 48735171eb
5 changed files with 577 additions and 3 deletions
+15
View File
@@ -53,6 +53,11 @@ type Rendering struct {
// name the module gave it.
Needed map[string]map[string]string
// Mesh is every node's address on the private network, which is what a rule saying "from the
// mesh" resolves to. Passed in for the same reason grants are: who else is on the network is
// a fact about the mesh, and resolution answers questions about one machine.
Mesh []string
Settings SettingsBy
Generators map[string]Generator
// Grants are the credentials this node must create, for the provisions it offers. Passed in
@@ -80,9 +85,19 @@ func (r Resolution) Declaration(with Rendering) ([]map[string]any, error) {
return nil, err
}
// Once, from every module's listens -- not per module. A module receiving only its own ports
// would write a rule set that closed every other module on the machine.
filtering := AsNftables(r.Filtering(), with.Mesh)
var out []map[string]any
for _, m := range r.Modules {
resources := m.Resources
if f := m.Filtering; f != nil {
resources = append(append([]map[string]any{}, resources...), map[string]any{
"id": FilteringID(), "type": "file", "path": f.Into,
"content": filtering, "mode": "0600",
})
}
if c := m.Certificate; c != nil {
if with.Certificate == "" {
// Asked for and not issued. Refused rather than skipped: a module that serves TLS