A carried peer is nameable, and the mesh answers for it (hq 112) #73
@@ -48,7 +48,7 @@ func overlayRange(ctx context.Context, inv *inventory.Inventory) (string, error)
|
|||||||
|
|
||||||
func overlayCommand(ctx context.Context, args []string) error {
|
func overlayCommand(ctx context.Context, args []string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return errors.New("overlay place <node> [flags], or overlay show")
|
return errors.New("overlay place <node> [flags], overlay name <address> <name>, or overlay show")
|
||||||
}
|
}
|
||||||
// Answered before anything is opened. A message about which command to use should not need a
|
// Answered before anything is opened. A message about which command to use should not need a
|
||||||
// database to say so, and needing one turns a redirect into a connection error.
|
// database to say so, and needing one turns a redirect into a connection error.
|
||||||
@@ -69,12 +69,29 @@ func overlayCommand(ctx context.Context, args []string) error {
|
|||||||
return overlayPlace(ctx, inv, args[1:])
|
return overlayPlace(ctx, inv, args[1:])
|
||||||
case "show":
|
case "show":
|
||||||
return overlayShow(ctx, open)
|
return overlayShow(ctx, open)
|
||||||
|
case "name":
|
||||||
|
return overlayName(ctx, inv, args[1:])
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("overlay has no %q; it has place and show", args[0])
|
return fmt.Errorf("overlay has no %q; it has place, name and show", args[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// overlayName is the operator saying which machine a carried address is (novox/hq issue 112),
|
||||||
|
// so the mesh answers for its name until the machine enrols and verifies it.
|
||||||
|
func overlayName(ctx context.Context, inv *inventory.Inventory, args []string) error {
|
||||||
|
if len(args) != 2 {
|
||||||
|
return errors.New("overlay name <carried-address> <node-name>")
|
||||||
|
}
|
||||||
|
address, name := args[0], args[1]
|
||||||
|
if err := inv.NamePeer(ctx, address, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("the peer at %s is %s until it enrols — the mesh answers for %s.<suffix> from the "+
|
||||||
|
"operator's word, and enrolment under this key must use this name\n", address, name, name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func overlayPlace(ctx context.Context, inv *inventory.Inventory, args []string) error {
|
func overlayPlace(ctx context.Context, inv *inventory.Inventory, args []string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return errors.New(
|
return errors.New(
|
||||||
@@ -588,6 +605,24 @@ func namesInTheMesh(ctx context.Context, inv *inventory.Inventory,
|
|||||||
for _, p := range places {
|
for _, p := range places {
|
||||||
out[overlay.InternalName(p.Name)] = p.Address
|
out[overlay.InternalName(p.Name)] = p.Address
|
||||||
}
|
}
|
||||||
|
// And the carried peers the operator has named (novox/hq issue 112): machines the
|
||||||
|
// predecessor's resolver answers for and the mesh routes to, known by name on the operator's
|
||||||
|
// word until they enrol — at which point enrolment verifies the name and the node's own
|
||||||
|
// entry takes over above. A name the predecessor answers for must keep resolving until the
|
||||||
|
// machine behind it is a node; without these, taking the resolver silences three machines.
|
||||||
|
carried, err := inv.CarriedPeers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, p := range carried {
|
||||||
|
if p.Named == "" || p.EnrolledAs != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, taken := out[overlay.InternalName(p.Named)]; taken {
|
||||||
|
continue // a node of the mesh owns the name; the stale statement loses
|
||||||
|
}
|
||||||
|
out[overlay.InternalName(p.Named)] = p.Address
|
||||||
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,17 @@ func (i *Inventory) AssignAddress(ctx context.Context, node, cidr string) (strin
|
|||||||
if key != nil && p.PublicKey == *key {
|
if key != nil && p.PublicKey == *key {
|
||||||
// The tunnel already routes to this key: the node keeps that address, and the peer
|
// The tunnel already routes to this key: the node keeps that address, and the peer
|
||||||
// notices nothing when its machine enrols.
|
// 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)
|
return i.place(ctx, node, p.Address)
|
||||||
}
|
}
|
||||||
taken[p.Address] = true
|
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
|
Address string
|
||||||
// EnrolledAs names the node that enrolled with this key, or is empty while none has.
|
// EnrolledAs names the node that enrolled with this key, or is empty while none has.
|
||||||
EnrolledAs string
|
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.
|
// 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.
|
// adopted no tunnel.
|
||||||
func (i *Inventory) CarriedPeers(ctx context.Context) ([]CarriedPeer, error) {
|
func (i *Inventory) CarriedPeers(ctx context.Context) ([]CarriedPeer, error) {
|
||||||
rows, err := i.store.Pool().Query(ctx,
|
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
|
from tunnel_peer p
|
||||||
join node hub on hub.id = p.node and hub.is_hub
|
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'
|
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
|
var out []CarriedPeer
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var p CarriedPeer
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
out = append(out, p)
|
out = append(out, p)
|
||||||
@@ -268,6 +271,46 @@ func (i *Inventory) CarriedPeers(ctx context.Context) ([]CarriedPeer, error) {
|
|||||||
return out, rows.Err()
|
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
|
// 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.
|
// declared to an adopted node only, since only there is a found unit kept to be stopped.
|
||||||
type FoundTunnel struct {
|
type FoundTunnel struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user