mesh-enrolment: keep the tunnel key in delivery.Enrolment's own field

mesh-sdk 22c03af carries the machine's tunnel key on the enrolment (OverlayKey, overlay-key, required at every stage), so the
holder's wrapper that kept it beside the contract is gone; the wire is unchanged. Test fixtures give the seeded enrolments a
real tunnel key, which the contract now requires.

(issue 774)

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 01:45:53 +01:00
parent d1a256b429
commit cbcbb9644b
5 changed files with 13 additions and 40 deletions
@@ -50,18 +50,11 @@ const (
StageRedeeming = delivery.EnrolmentRedeeming
)
// Reservation is the seat's state contract for an address key (delivery.EnrolmentAddress).
type Reservation = delivery.EnrolmentAddress
// Enrolment is a node key: the seat's state contract (delivery.Enrolment) and the machine's tunnel key given at issue, which
// the node enters placement with on the operator's yes and which redeem must present again (the operator's decision of
// 2026-11-02 01:05). The contract does not carry the tunnel key yet: it is kept as one more member of the same JSON object,
// which every reader of the contract decodes as before (delivery's decode ignores a member it does not know). Once
// delivery.Enrolment carries `overlay-key` this wrapper goes.
type Enrolment struct {
delivery.Enrolment
OverlayKey string `json:"overlay-key,omitempty"`
}
// Enrolment and Reservation are the seat's state contract (delivery.Enrolment, delivery.EnrolmentAddress).
type (
Enrolment = delivery.Enrolment
Reservation = delivery.EnrolmentAddress
)
const (
nodePrefix = "node."
@@ -85,33 +78,12 @@ func CheckNode(node string) error {
return nil
}
func decodeEnrolment(raw []byte) (Enrolment, error) {
e, err := delivery.DecodeEnrolment(raw)
if err != nil {
return Enrolment{}, err
}
var tunnel struct {
OverlayKey string `json:"overlay-key"`
}
_ = json.Unmarshal(raw, &tunnel)
return Enrolment{Enrolment: e, OverlayKey: tunnel.OverlayKey}, nil
}
func decodeEnrolment(raw []byte) (Enrolment, error) { return delivery.DecodeEnrolment(raw) }
// Encode is a value of the bucket as the seat's contract writes it: an Enrolment or a Reservation checked and versioned.
func Encode(v any) (json.RawMessage, error) {
switch x := v.(type) {
case Enrolment:
raw, err := delivery.EncodeEnrolment(x.Enrolment)
if err != nil || x.OverlayKey == "" {
return raw, err
}
var obj map[string]json.RawMessage
if err := json.Unmarshal(raw, &obj); err != nil {
return nil, err
}
obj["overlay-key"], _ = json.Marshal(x.OverlayKey)
return json.Marshal(obj)
case delivery.Enrolment:
return delivery.EncodeEnrolment(x)
case Reservation:
return delivery.EncodeEnrolmentAddress(x.Node)
@@ -149,7 +149,8 @@ func (h *Holder) warrantOf(e Enrolment) (delivery.AddNodeWarrant, error) {
return delivery.AddNodeWarrant{Node: e.Node, Address: e.Address, Ask: e.Ask, Warrant: warrant}, nil
}
// checkTunnelKey says a tunnel key is a WireGuard public key, as `nox-mesh-host key` prints it: 32 bytes in standard base64.
// checkTunnelKey says a tunnel key is a WireGuard public key, as `nox-mesh-host key` prints it: 32 bytes in standard base64
// (as delivery.Enrolment requires it too); said here so issue names it among its findings.
func checkTunnelKey(k string) error {
raw, err := base64.StdEncoding.DecodeString(strings.TrimSpace(k))
if err != nil || len(raw) != 32 {
@@ -98,8 +98,8 @@ func (h *Holder) Issue(caller, node, why, overlayKey string, lasts time.Duration
return IssueAnswer{}, fmt.Errorf("no enrolment of %q is issued: %s", node, strings.Join(findings, "; "))
}
now := h.now()
e := Enrolment{Enrolment: delivery.Enrolment{Node: node, Why: strings.TrimSpace(why), Caller: caller, Stage: StageAsked,
Issued: now, Expires: now.Add(lasts)}, OverlayKey: overlayKey}
e := Enrolment{Node: node, Why: strings.TrimSpace(why), Caller: caller, Stage: StageAsked, Issued: now, Expires: now.Add(lasts),
OverlayKey: overlayKey}
e.Ask = "enrol-" + node + "-" + now.UTC().Format("20060102t150405")
skip := map[string]bool{}
for {
@@ -12,7 +12,7 @@ import (
// redeemArgs are a node's redeem call with a proof signed by the key seeded, over the keys given.
func redeemArgs(node string, seed byte, serving string) delivery.Redeem {
return redeemArgsWith(node, seed, serving, "overlay-"+node)
return redeemArgsWith(node, seed, serving, tunnelKey)
}
// redeemArgsWith is redeemArgs presenting the tunnel key given.
@@ -13,8 +13,8 @@ import (
// seed writes an open enrolment of node at stage, with its address key, as the holder would have.
func (r *rig) seed(node, address string, stage string) {
e := Enrolment{Enrolment: delivery.Enrolment{Node: node, Address: address, Why: "a new machine", Caller: "person.operator", Ask: "ask-" + node,
Stage: stage, Issued: r.now.Add(-time.Minute), Expires: r.now.Add(time.Hour)}, OverlayKey: "overlay-" + node}
e := Enrolment{Node: node, Address: address, Why: "a new machine", Caller: "person.operator", Ask: "ask-" + node,
Stage: stage, Issued: r.now.Add(-time.Minute), Expires: r.now.Add(time.Hour), OverlayKey: tunnelKey}
if stage == StageRedeeming {
e.KeysDigest = delivery.KeysDigest("pk", "ov", "se", "sv")
}