A module can name the mesh's range: ${machine:mesh-range} (novox/hq ADR 0112)
A module cannot know the private network's CIDR — it is a per-mesh value chosen at genesis — but sometimes must name it: an intrusion filter that must never ban a tunnel peer. Carry the overlay range on the Rendering and offer it as the machine fact mesh-range, the same way a machine's own address is offered, so the module names it rather than hardcoding a value (data is the mesh's). Absent when the mesh has no range. Enables the fail2ban ignoreip fix.
This commit is contained in:
@@ -488,6 +488,13 @@ func renderingFor(ctx context.Context, open *stores, node string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return catalogue.Rendering{}, inventory.Node{}, err
|
return catalogue.Rendering{}, inventory.Node{}, err
|
||||||
}
|
}
|
||||||
|
// The private network's range, offered to a module as ${machine:mesh-range} — a module that must
|
||||||
|
// name the whole mesh (an intrusion filter that must never ban a tunnel peer) names it here
|
||||||
|
// rather than hardcoding a value it cannot know.
|
||||||
|
meshRange, err := overlayRange(ctx, inv)
|
||||||
|
if err != nil {
|
||||||
|
return catalogue.Rendering{}, inventory.Node{}, err
|
||||||
|
}
|
||||||
|
|
||||||
// The artifact store as this node reaches it now — the address every image and archive the
|
// The artifact store as this node reaches it now — the address every image and archive the
|
||||||
// mesh built is fetched through, composed here and recorded nowhere — with what the mesh has
|
// mesh built is fetched through, composed here and recorded nowhere — with what the mesh has
|
||||||
@@ -597,7 +604,8 @@ func renderingFor(ctx context.Context, open *stores, node string,
|
|||||||
Settings: settings, Generators: gens, Grants: grants, Needed: needed, Ports: ports,
|
Settings: settings, Generators: gens, Grants: grants, Needed: needed, Ports: ports,
|
||||||
Certificate: certificate, Authority: authority, Mesh: private, Names: names,
|
Certificate: certificate, Authority: authority, Mesh: private, Names: names,
|
||||||
Machines: machines,
|
Machines: machines,
|
||||||
Suffix: overlay.Suffix(), Foundation: foundation, Kept: kept, Adopted: record.Adopted,
|
Suffix: overlay.Suffix(), MeshRange: meshRange, Foundation: foundation, Kept: kept,
|
||||||
|
Adopted: record.Adopted,
|
||||||
Given: given, Taken: taken, Seats: seats, ArtifactStore: artifactStore, Built: built,
|
Given: given, Taken: taken, Seats: seats, ArtifactStore: artifactStore, Built: built,
|
||||||
}, record, nil
|
}, record, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ type Rendering struct {
|
|||||||
// compose it a second time.
|
// compose it a second time.
|
||||||
Suffix string
|
Suffix string
|
||||||
|
|
||||||
|
// MeshRange is the private network's CIDR (the range node addresses are allocated from), for a
|
||||||
|
// module that must name the whole mesh rather than one machine — an intrusion filter that must
|
||||||
|
// never ban a tunnel peer, say. A per-mesh value the module cannot know, so it is carried here
|
||||||
|
// and offered as ${machine:mesh-range}, the same way one machine's address is.
|
||||||
|
MeshRange string
|
||||||
|
|
||||||
// Kept is every operator-sealed secret in the mesh, for a module that `keeps` them. Nil when
|
// Kept is every operator-sealed secret in the mesh, for a module that `keeps` them. Nil when
|
||||||
// nothing on this node keeps them, or the mesh has no operator key.
|
// nothing on this node keeps them, or the mesh has no operator key.
|
||||||
Kept *KeptExport
|
Kept *KeptExport
|
||||||
@@ -545,7 +551,7 @@ func (r Resolution) compose(with Rendering, owner map[string]string) ([]map[stri
|
|||||||
// (novox/hq ADR 0112) — resolved once per module, named by ${dir:…} from any resource.
|
// (novox/hq ADR 0112) — resolved once per module, named by ${dir:…} from any resource.
|
||||||
dirs := dirsFor(m, with)
|
dirs := dirsFor(m, with)
|
||||||
// And the machine underneath, which no binding of its own can tell it.
|
// And the machine underneath, which no binding of its own can tell it.
|
||||||
thisMachine := machineFacts(r, with.Names)
|
thisMachine := machineFacts(r, with.Names, with.MeshRange)
|
||||||
|
|
||||||
// Which of this module's files carry a secret, for the rule that a container may not read
|
// Which of this module's files carry a secret, for the rule that a container may not read
|
||||||
// one of them as its environment without saying so (ADR 0086, issue 041).
|
// one of them as its environment without saying so (ADR 0086, issue 041).
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func machineUsed(content string) []string {
|
|||||||
// the hosts file and the resolver's wildcards are written from, so a file naming the machine's
|
// the hosts file and the resolver's wildcards are written from, so a file naming the machine's
|
||||||
// address and the file every other machine reaches it by cannot disagree. Absent, like `at`, when
|
// address and the file every other machine reaches it by cannot disagree. Absent, like `at`, when
|
||||||
// the machine is off the network or the mesh has not placed it.
|
// the machine is off the network or the mesh has not placed it.
|
||||||
func machineFacts(r Resolution, names map[string]string) map[string]string {
|
func machineFacts(r Resolution, names map[string]string, meshRange string) map[string]string {
|
||||||
out := map[string]string{"name": r.Node}
|
out := map[string]string{"name": r.Node}
|
||||||
if r.At != "" {
|
if r.At != "" {
|
||||||
out["at"] = r.At
|
out["at"] = r.At
|
||||||
@@ -65,6 +65,12 @@ func machineFacts(r Resolution, names map[string]string) map[string]string {
|
|||||||
out["address"] = address
|
out["address"] = address
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The private network's whole range — a mesh-wide fact, not this machine's, but named here
|
||||||
|
// because a module cannot know it and sometimes must (an intrusion filter that must never ban a
|
||||||
|
// tunnel peer). Absent when the mesh has no range to give.
|
||||||
|
if meshRange != "" {
|
||||||
|
out["mesh-range"] = meshRange
|
||||||
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,3 +103,26 @@ func TestAModuleNamesTheAddressBehindItsMachinesName(t *testing.T) {
|
|||||||
t.Fatalf("a machine off the network was given an address, or refused for another reason: %v", err)
|
t.Fatalf("a machine off the network was given an address, or refused for another reason: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The mesh's private range is offered as ${machine:mesh-range}, so a module names it rather than
|
||||||
|
// hardcoding a value it cannot know (novox/hq ADR 0112) — the fail2ban ignoreip is the case.
|
||||||
|
func TestAModuleNamesTheMeshRange(t *testing.T) {
|
||||||
|
facts := machineFacts(Resolution{Node: "anchor", At: "anchor.internal"},
|
||||||
|
map[string]string{"anchor.internal": "10.10.0.1"}, "10.10.0.0/24")
|
||||||
|
if facts["mesh-range"] != "10.10.0.0/24" {
|
||||||
|
t.Fatalf("the mesh range is not a machine fact: %v", facts)
|
||||||
|
}
|
||||||
|
res := map[string]any{"type": "file", "id": "jail", "content": "ignoreip = 127.0.0.1/8 ${machine:mesh-range}\n"}
|
||||||
|
if err := machineInto(res, facts, "fail2ban"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := res["content"].(string); !strings.Contains(got, "10.10.0.0/24") || strings.Contains(got, "${machine:") {
|
||||||
|
t.Fatalf("the mesh range was not written in: %q", got)
|
||||||
|
}
|
||||||
|
// A mesh with no range gives no such fact, and a file that names it is refused rather than
|
||||||
|
// left with a literal placeholder in it.
|
||||||
|
none := machineFacts(Resolution{Node: "anchor"}, nil, "")
|
||||||
|
if _, has := none["mesh-range"]; has {
|
||||||
|
t.Fatal("a mesh with no range still offered one")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user