Review of the ADR 0105 build (hq ADR 0105). The takeover stopped the found unit and then found out whether the mesh's interface would do; a start that failed left the machine with no tunnel at all. Now nothing is stopped until the declared interface listens on the found port at the found address and the key file it names holds the found key — the refusal names the remedy — and a mesh interface that fails to start after the takeover has the found unit started again, with the account saying so. The account has three states (not taken, taken, down) and is given on every takeover, failure included. An interface raised by hand is looked at again for a moment and then refused naming `wg-quick down`. A found unit started again by hand beside the mesh's is said, not stopped: on the hub it cannot hold the port, and on a spoke two interfaces with one key would fight. `mesh-host overlay take --tunnel <iface>` is the path for a node that enrolled before the mesh knew to take a tunnel over: the found key becomes its overlay key — identity, sealing and serving keys untouched, so nothing sealed to the node is remade — and the mesh is told with a rekey signed by the identity key, over the key left, the key taken and the tunnel. Told first, written second, so a run again puts right whichever half did not happen.
95 lines
3.4 KiB
Go
95 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-host/internal/identity"
|
|
"github.com/novox/mesh-host/internal/link"
|
|
"github.com/novox/mesh-host/internal/tunnel"
|
|
)
|
|
|
|
// novox/hq ADR 0105: `overlay take` moves this node's overlay key onto the found tunnel's and
|
|
// nothing else — identity, sealing and serving keys stay as they were — and tells the mesh with a
|
|
// proof signed by the identity key, over the previous key, the new one and the tunnel.
|
|
|
|
func anEnrolledNode(t *testing.T) identity.Identity {
|
|
t.Helper()
|
|
mine, err := identity.Generate("anchor")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mine.Overlay, err = identity.GenerateOverlayKey()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mine.Membership = identity.Membership{Broker: "198.51.100.1:5671", Fingerprint: "sha256:aa",
|
|
Signer: make([]byte, ed25519.PublicKeySize), Password: "p"}
|
|
return mine
|
|
}
|
|
|
|
func aFoundTunnel(t *testing.T) tunnel.Found {
|
|
t.Helper()
|
|
private, err := identity.GenerateOverlayKey()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found, err := tunnel.Parse([]byte("[Interface]\nPrivateKey = " + private.Private + "\nListenPort = 51900\n" +
|
|
"Address = 192.0.2.1/24\n[Peer]\nPublicKey = PEER-A=\nAllowedIPs = 192.0.2.2/32\n"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found.Interface, found.Unit, found.Config = "wg0", "wg-quick@wg0", "/etc/wireguard/wg0.conf"
|
|
return found
|
|
}
|
|
|
|
func TestTakingATunnelMovesOnlyTheOverlayKeyAndSignsForIt(t *testing.T) {
|
|
mine := anEnrolledNode(t)
|
|
found := aFoundTunnel(t)
|
|
before := mine.Overlay.Public
|
|
|
|
taken, rekey, err := rekeyOnto(mine, found)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if taken.Overlay.Public != found.PublicKey || taken.Overlay.Private != found.PrivateKey() {
|
|
t.Fatal("the overlay key is not the tunnel's")
|
|
}
|
|
if string(taken.Public) != string(mine.Public) || string(taken.Private) != string(mine.Private) ||
|
|
taken.Node != mine.Node || taken.Membership.Password != mine.Membership.Password ||
|
|
taken.Membership.Broker != mine.Membership.Broker {
|
|
t.Fatal("something other than the overlay key moved")
|
|
}
|
|
if taken.OverlayBefore != before {
|
|
t.Errorf("the key before the take was not kept: %q", taken.OverlayBefore)
|
|
}
|
|
if rekey.Previous != before || rekey.OverlayKey != found.PublicKey || rekey.Tunnel == nil ||
|
|
rekey.Tunnel.PublicKey != found.PublicKey || len(rekey.Tunnel.Peers) != 1 {
|
|
t.Fatalf("the rekey does not say what moved: %+v", rekey)
|
|
}
|
|
if !ed25519.Verify(ed25519.PublicKey(mine.Public),
|
|
link.RekeyProof("anchor", before, found.PublicKey, rekey.Tunnel), rekey.Proof) {
|
|
t.Fatal("the rekey is not signed by this node's identity key over what it says")
|
|
}
|
|
if ed25519.Verify(ed25519.PublicKey(mine.Public),
|
|
link.RekeyProof("laptop", before, found.PublicKey, rekey.Tunnel), rekey.Proof) {
|
|
t.Fatal("the proof is not bound to the node")
|
|
}
|
|
for _, said := range []string{rekey.Previous, rekey.OverlayKey, rekey.Tunnel.Interface} {
|
|
if strings.Contains(said, found.PrivateKey()) {
|
|
t.Fatal("the private key travels")
|
|
}
|
|
}
|
|
|
|
// Run again after the take — the mesh not yet told, or told and refused — the previous key it
|
|
// names is still the one before the take, so the mesh can tell a repeat from a replay.
|
|
again, second, err := rekeyOnto(taken, found)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if second.Previous != before || again.OverlayBefore != before || again.Overlay.Public != found.PublicKey {
|
|
t.Fatalf("a take run again does not name the key before the first: %+v", second)
|
|
}
|
|
}
|