A carried peer is nameable, and the mesh answers for it (hq 112)
The tunnel the hub took over routes to machines the predecessor knows by name and the mesh knew only by address — taking the resolver in that state silences three machines at once. Now the operator states which machine a carried address is (overlay name <address> <name>), the statement rides tunnel_peer.named, and namesInTheMesh answers for named not-yet-enrolled peers — one reading, so the hosts fact, a container's hosts and the resolver cannot disagree. Enrolment verifies the word: a machine enrolling under a named peer's key with a different name is refused where the operator can read it, the stated name keeps the carried address, and an enrolled peer's name is the node's — naming it again refuses. The issue's rule holds: a name the predecessor answers for keeps resolving until the machine behind it is a node.
This commit is contained in:
@@ -69,6 +69,17 @@ func (i *Inventory) AssignAddress(ctx context.Context, node, cidr string) (strin
|
||||
if key != nil && p.PublicKey == *key {
|
||||
// The tunnel already routes to this key: the node keeps that address, and the peer
|
||||
// notices nothing when its machine enrols.
|
||||
//
|
||||
// **Unless the operator named it something else** (novox/hq issue 112). The name is
|
||||
// what the mesh has been answering for this address in the meantime; a machine
|
||||
// enrolling under a different one would silently split the two — the name resolving
|
||||
// here, the node known as that — so it is refused where the operator can read it.
|
||||
if p.Named != "" && p.Named != name {
|
||||
return "", fmt.Errorf(
|
||||
"the carried peer at %s was named %q, and %q is enrolling under its key — "+
|
||||
"enrol it as %q, or rename the peer first (`overlay name`)",
|
||||
p.Address, p.Named, name, p.Named)
|
||||
}
|
||||
return i.place(ctx, node, p.Address)
|
||||
}
|
||||
taken[p.Address] = true
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
-- A carried peer may be named before it enrols (novox/hq issue 112).
|
||||
--
|
||||
-- The tunnel the hub took over routes to machines the predecessor knows by name and the mesh
|
||||
-- knows only by address. A name the predecessor answers for must keep resolving until the
|
||||
-- machine behind it is a node — so the operator may state which machine a carried address is,
|
||||
-- and everything derived from "the machines the mesh knows" (a container's hosts, the hosts
|
||||
-- fact, the resolver) answers for it in the meantime. The mesh records the statement as the
|
||||
-- operator's, unverified: enrolment is what verifies it, and enrolling under a different name
|
||||
-- than the one stated is refused rather than silently renamed.
|
||||
alter table tunnel_peer add column named text;
|
||||
@@ -0,0 +1,92 @@
|
||||
package inventory
|
||||
|
||||
// A carried peer is nameable (novox/hq issue 112): the operator states which machine a carried
|
||||
// address is, the mesh answers for the name until the machine enrols, and enrolment verifies the
|
||||
// statement rather than silently renaming it.
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestACarriedPeerIsNamedAndTheRegistrySaysSo(t *testing.T) {
|
||||
inv := fresh(t)
|
||||
anAdoptedHub(t, inv)
|
||||
|
||||
if err := inv.NamePeer(t.Context(), "192.0.2.2", "home-server"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
carried, err := inv.CarriedPeers(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
byKey := map[string]CarriedPeer{}
|
||||
for _, c := range carried {
|
||||
byKey[c.PublicKey] = c
|
||||
}
|
||||
if byKey[peerTwo].Named != "home-server" || byKey[peerThree].Named != "" {
|
||||
t.Fatalf("the statement was not recorded where it was made: %+v", carried)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamingRefusesWhatWouldCollide(t *testing.T) {
|
||||
inv := fresh(t)
|
||||
anAdoptedHub(t, inv)
|
||||
|
||||
if err := inv.NamePeer(t.Context(), "192.0.2.9", "ghost"); err == nil ||
|
||||
!strings.Contains(err.Error(), "no carried peer") {
|
||||
t.Fatalf("naming an address nothing carries must refuse; got %v", err)
|
||||
}
|
||||
if err := inv.NamePeer(t.Context(), "192.0.2.2", "anchor"); err == nil ||
|
||||
!strings.Contains(err.Error(), "node of this mesh") {
|
||||
t.Fatalf("naming a peer after a node must refuse; got %v", err)
|
||||
}
|
||||
if err := inv.NamePeer(t.Context(), "192.0.2.2", "home-server"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := inv.NamePeer(t.Context(), "192.0.2.3", "home-server"); err == nil ||
|
||||
!strings.Contains(err.Error(), "already named") {
|
||||
t.Fatalf("one machine per name; got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnrolmentUnderANamedKeyMustUseTheName(t *testing.T) {
|
||||
inv := fresh(t)
|
||||
anAdoptedHub(t, inv)
|
||||
if err := inv.NamePeer(t.Context(), "192.0.2.3", "home-server"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// The wrong name is refused where the operator can read it…
|
||||
imposter, err := inv.AddNode(t.Context(), "some-other-name")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := inv.RecordOverlayKey(t.Context(), imposter.ID, peerThree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := inv.AssignAddress(t.Context(), imposter.ID, "192.0.2.0/24"); err == nil ||
|
||||
!strings.Contains(err.Error(), `named "home-server"`) {
|
||||
t.Fatalf("enrolling a named peer under another name must refuse; got %v", err)
|
||||
}
|
||||
|
||||
// …and the stated name enrols cleanly, keeping the carried address.
|
||||
named, err := inv.AddNode(t.Context(), "home-server")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := inv.RecordOverlayKey(t.Context(), named.ID, peerThree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
address, err := inv.AssignAddress(t.Context(), named.ID, "192.0.2.0/24")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if address != "192.0.2.3" {
|
||||
t.Fatalf("the named peer keeps its carried address; got %s", address)
|
||||
}
|
||||
if err := inv.NamePeer(t.Context(), "192.0.2.3", "renamed"); err == nil ||
|
||||
!strings.Contains(err.Error(), "enrolled as") {
|
||||
t.Fatalf("an enrolled peer's name is the node's; got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,9 @@ type CarriedPeer struct {
|
||||
Address string
|
||||
// EnrolledAs names the node that enrolled with this key, or is empty while none has.
|
||||
EnrolledAs string
|
||||
// Named is what the operator said this peer is, before it enrolled (novox/hq issue 112) —
|
||||
// a statement the mesh records and cannot verify, which is why enrolment checks it.
|
||||
Named string
|
||||
}
|
||||
|
||||
// ErrNoTunnel is asking about a tunnel on a node that presented none.
|
||||
@@ -247,7 +250,7 @@ func (i *Inventory) AdoptedTunnel(ctx context.Context) (Tunnel, string, bool, er
|
||||
// adopted no tunnel.
|
||||
func (i *Inventory) CarriedPeers(ctx context.Context) ([]CarriedPeer, error) {
|
||||
rows, err := i.store.Pool().Query(ctx,
|
||||
`select p.public_key, host(p.address), coalesce(n.name, '')
|
||||
`select p.public_key, host(p.address), coalesce(n.name, ''), coalesce(p.named, '')
|
||||
from tunnel_peer p
|
||||
join node hub on hub.id = p.node and hub.is_hub
|
||||
and hub.tunnel is not null and hub.overlay_key = hub.tunnel->>'public_key'
|
||||
@@ -260,7 +263,7 @@ func (i *Inventory) CarriedPeers(ctx context.Context) ([]CarriedPeer, error) {
|
||||
var out []CarriedPeer
|
||||
for rows.Next() {
|
||||
var p CarriedPeer
|
||||
if err := rows.Scan(&p.PublicKey, &p.Address, &p.EnrolledAs); err != nil {
|
||||
if err := rows.Scan(&p.PublicKey, &p.Address, &p.EnrolledAs, &p.Named); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, p)
|
||||
@@ -268,6 +271,46 @@ func (i *Inventory) CarriedPeers(ctx context.Context) ([]CarriedPeer, error) {
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// NamePeer records the operator's statement that a carried address is a particular machine
|
||||
// (novox/hq issue 112). Refused when nothing carried has that address, when a node of the mesh
|
||||
// already has the name — the statement would collide with something verified — and when another
|
||||
// peer was already named it. Naming an enrolled peer is refused too: its name is the node's now.
|
||||
func (i *Inventory) NamePeer(ctx context.Context, address, name string) error {
|
||||
peers, err := i.CarriedPeers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var at *CarriedPeer
|
||||
for idx := range peers {
|
||||
if peers[idx].Address == address {
|
||||
at = &peers[idx]
|
||||
continue
|
||||
}
|
||||
if peers[idx].Named == name {
|
||||
return fmt.Errorf("the carried peer at %s is already named %q — one machine per name",
|
||||
peers[idx].Address, name)
|
||||
}
|
||||
}
|
||||
if at == nil {
|
||||
return fmt.Errorf("no carried peer has the address %s — `overlay show` lists them", address)
|
||||
}
|
||||
if at.EnrolledAs != "" {
|
||||
return fmt.Errorf("the peer at %s enrolled as %q — its name is the node's now", address, at.EnrolledAs)
|
||||
}
|
||||
if _, err := i.NodeByName(ctx, name); err == nil {
|
||||
return fmt.Errorf("%q is a node of this mesh — a carried peer cannot be named after one", name)
|
||||
}
|
||||
tag, err := i.store.Pool().Exec(ctx,
|
||||
`update tunnel_peer set named = $2 where host(address) = $1`, address, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return fmt.Errorf("no carried peer has the address %s", address)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FoundTunnel is a node's found tunnel with the node's mode, for composing: the takeover is
|
||||
// declared to an adopted node only, since only there is a found unit kept to be stopped.
|
||||
type FoundTunnel struct {
|
||||
|
||||
Reference in New Issue
Block a user