The roster is the machines: each node's internal domain covers its routes (hq ADR 0191) #238
@@ -1,27 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The mesh's resolver holds only the mesh's own names (novox/hq ADR 0191): a routed public name is
|
|
||||||
// never given a private answer, and a route's internal name is.
|
|
||||||
func TestOnlyTheMeshsOwnNamesAreAnsweredPrivately(t *testing.T) {
|
|
||||||
routes := map[string]string{
|
|
||||||
"git.example.tld": "10.77.0.1",
|
|
||||||
"example.tld": "10.77.0.1",
|
|
||||||
"media.home.example": "10.77.0.2",
|
|
||||||
"git.anchor.internal": "10.77.0.1",
|
|
||||||
"media.homeserver.internal": "10.77.0.2",
|
|
||||||
"internal.example.tld": "10.77.0.1", // the suffix as a label, not as the zone
|
|
||||||
}
|
|
||||||
got := meshOwnNames(routes, "internal")
|
|
||||||
want := map[string]string{
|
|
||||||
"git.anchor.internal": "10.77.0.1",
|
|
||||||
"media.homeserver.internal": "10.77.0.2",
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(got, want) {
|
|
||||||
t.Fatalf("names answered privately: %v\nwant only the mesh's own: %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -645,9 +645,9 @@ func renderingFor(ctx context.Context, open *stores, node string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// And every routed name under the mesh's own suffix → the node that serves it, alongside the
|
// And every route's internal name → the node that serves it, alongside the `<node>.internal`
|
||||||
// `<node>.internal` names above (novox/hq ADR 0066, narrowed by ADR 0191). A public name is not
|
// names above (novox/hq ADR 0066, narrowed by ADR 0191). A route's public name is not among
|
||||||
// among them: the mesh gives no private answer for a name public DNS answers.
|
// them: the mesh gives no private answer for a name public DNS answers.
|
||||||
// Kept apart from the machines, because a fact about the machines must not be handed the names
|
// Kept apart from the machines, because a fact about the machines must not be handed the names
|
||||||
// the mesh merely serves (novox/hq 04-ISSUES/111).
|
// the mesh merely serves (novox/hq 04-ISSUES/111).
|
||||||
machines := make(map[string]string, len(names))
|
machines := make(map[string]string, len(names))
|
||||||
@@ -658,7 +658,7 @@ 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
|
||||||
}
|
}
|
||||||
for name, at := range meshOwnNames(routes, overlay.Suffix()) {
|
for name, at := range routes {
|
||||||
names[name] = at
|
names[name] = at
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -739,25 +739,6 @@ func renderingFor(ctx context.Context, open *stores, node string,
|
|||||||
}, record, nil
|
}, record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// meshOwnNames is the routed names the mesh may answer privately: those under its own suffix.
|
|
||||||
//
|
|
||||||
// **The mesh's resolver holds only the mesh's own names** (novox/hq ADR 0191). A routed public name
|
|
||||||
// was once published here at its serving node's private address, so an internal authority could
|
|
||||||
// reach it to certify it (ADR 0066). Every machine's resolver then answered public names with
|
|
||||||
// addresses only members can reach — and a resolver that also serves a LAN handed them to a phone
|
|
||||||
// on it, which could not reach the mail server while every check, run from a member, passed. A
|
|
||||||
// route is reached and certified inside the mesh by its internal name (ADR 0151); its public name
|
|
||||||
// resolves publicly, for members and everyone else alike.
|
|
||||||
func meshOwnNames(routes map[string]string, suffix string) map[string]string {
|
|
||||||
out := map[string]string{}
|
|
||||||
for name, at := range routes {
|
|
||||||
if strings.HasSuffix(name, "."+suffix) {
|
|
||||||
out[name] = at
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// routeNamesInTheMesh is every routed name and the address of the node that serves it (novox/hq
|
// routeNamesInTheMesh is every routed name and the address of the node that serves it (novox/hq
|
||||||
// ADR 0066).
|
// ADR 0066).
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -22,8 +22,15 @@ import (
|
|||||||
// modules declared. Deterministic: names, requirements and nodes are walked in order, so two
|
// modules declared. Deterministic: names, requirements and nodes are walked in order, so two
|
||||||
// plans of one mesh yield one region.
|
// plans of one mesh yield one region.
|
||||||
|
|
||||||
// NamesServed is every routed name across the mesh and the node that serves it, from every node's
|
// **The name published is the one the mesh composed for itself** (novox/hq ADR 0191). A route carries
|
||||||
// resolution and settings. A name several termini claim goes to the first node in name order, so
|
// two: `name`, composed from the node's public domain, and `internal-name`, composed from the mesh's
|
||||||
|
// own domain under the serving node (ADR 0151). Only the second is the mesh's to answer. The public
|
||||||
|
// one is the operator's, answered by public DNS — publishing it here gave every machine's resolver a
|
||||||
|
// private answer for a public name, and a resolver that also serves a LAN handed it to a phone that
|
||||||
|
// could not reach it. Which is which is known from where each was composed, not read off its spelling.
|
||||||
|
//
|
||||||
|
// NamesServed is every routed internal name across the mesh and the node that serves it, from every
|
||||||
|
// node's resolution and settings. A name several termini claim goes to the first node in name order, so
|
||||||
// the answer is stable; a name nothing terminal claims is left out.
|
// the answer is stable; a name nothing terminal claims is left out.
|
||||||
func NamesServed(plans map[string]Resolution, settings map[string]SettingsBy) (map[string]string, error) {
|
func NamesServed(plans map[string]Resolution, settings map[string]SettingsBy) (map[string]string, error) {
|
||||||
nodes := make([]string, 0, len(plans))
|
nodes := make([]string, 0, len(plans))
|
||||||
@@ -56,7 +63,9 @@ func NamesServed(plans map[string]Resolution, settings map[string]SettingsBy) (m
|
|||||||
if _, labelled := given.Values["label"]; !labelled {
|
if _, labelled := given.Values["label"]; !labelled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
name, _ := given.Values["name"].(string)
|
// A route that asked for no internal name — its reach is public only — has none to
|
||||||
|
// publish, and its public name is not the mesh's to answer.
|
||||||
|
name, _ := given.Values["internal-name"].(string)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,14 +55,14 @@ func TestANameResolvesToTheNodeWhoseProxyServesIt(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if served["grafana.home.example"] != "home-server" {
|
if served["grafana.home-server.internal"] != "home-server" {
|
||||||
t.Fatalf("run %d: the dashboard's name is served by %q, and its proxy is on the home server: %v",
|
t.Fatalf("run %d: the dashboard's name is served by %q, and its proxy is on the home server: %v",
|
||||||
i, served["grafana.home.example"], served)
|
i, served["grafana.home-server.internal"], served)
|
||||||
}
|
}
|
||||||
if served["login.control.example"] != "anchor" {
|
if served["login.anchor.internal"] != "anchor" {
|
||||||
t.Fatalf("run %d: the identity provider's own name is served by its proxy on the control node: %v", i, served)
|
t.Fatalf("run %d: the identity provider's own name is served by its proxy on the control node: %v", i, served)
|
||||||
}
|
}
|
||||||
if _, leaked := served["grafana.control.example"]; leaked {
|
if _, leaked := served["grafana.anchor.internal"]; leaked {
|
||||||
t.Fatalf("a name composed for the identity provider's benefit is not one it serves: %v", served)
|
t.Fatalf("a name composed for the identity provider's benefit is not one it serves: %v", served)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,9 +91,25 @@ func TestEveryRouteOfAModuleWithSeveralIsANameServed(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, name := range []string{"photos.control.example", "photos-api.control.example"} {
|
for _, name := range []string{"photos.anchor.internal", "photos-api.anchor.internal"} {
|
||||||
if served[name] != "anchor" {
|
if served[name] != "anchor" {
|
||||||
t.Fatalf("%s is not served by its proxy: %v", name, served)
|
t.Fatalf("%s is not served by its proxy: %v", name, served)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A route's public name is the operator's and answered by public DNS; the mesh publishes only the
|
||||||
|
// internal name it composed for the same route (novox/hq ADR 0191) — told apart by where each was
|
||||||
|
// composed, never by how it is spelled.
|
||||||
|
func TestAPublicNameIsNeverAMeshName(t *testing.T) {
|
||||||
|
plans, settings := twoNodesOneName(t)
|
||||||
|
served, err := NamesServed(plans, settings)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, public := range []string{"grafana.home.example", "login.control.example"} {
|
||||||
|
if node, published := served[public]; published {
|
||||||
|
t.Fatalf("the public name %s is published at %s; public DNS answers it: %v", public, node, served)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user