|
|
|
@@ -3,6 +3,7 @@ package link
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
@@ -63,8 +64,15 @@ func presentNats(_ context.Context, to Approach, node, secret string,
|
|
|
|
|
// subscribe its own inbox and nothing else (design 25 §6). The secret is its password, the same
|
|
|
|
|
// string the request claims, so the server proves somebody holds the token and the request
|
|
|
|
|
// proves the same thing to the controller without it having to ask the server who connected.
|
|
|
|
|
// **Its own inbox space, because that is the only one it may listen in** (novox/hq
|
|
|
|
|
// 04-ISSUES/146). A JetStream publish waits for the stream's acknowledgement on an inbox the
|
|
|
|
|
// client picks, and the client's default is `_INBOX.<random>` — which this user may not
|
|
|
|
|
// subscribe to, so the enrolment failed with a permissions violation on a subject nobody had
|
|
|
|
|
// chosen. The permission is `_INBOX.enrol.<node>.>` (design 25 §6), so the client is told to
|
|
|
|
|
// pick its inboxes there; the reply address below is in the same space for the same reason.
|
|
|
|
|
conn, err := nats.Connect(natsURL(to.Address),
|
|
|
|
|
nats.Secure(config),
|
|
|
|
|
nats.CustomInboxPrefix("_INBOX.enrol."+node),
|
|
|
|
|
nats.UserInfo("enrol."+node, secret),
|
|
|
|
|
nats.Name("mesh-host/enrol/"+node),
|
|
|
|
|
nats.Timeout(timeout),
|
|
|
|
@@ -124,7 +132,19 @@ func (a *natsAsking) Ask(ctx context.Context, request []byte, wait time.Duration
|
|
|
|
|
defer cancel()
|
|
|
|
|
// Into the stream and awaited: an enrolment the bus never accepted must fail here rather than be
|
|
|
|
|
// assumed, because the node has nothing else to go on.
|
|
|
|
|
if _, err := a.js.Publish(EnrolSubject, addressed, nats.Context(publish)); err != nil {
|
|
|
|
|
//
|
|
|
|
|
// **Once, however many times it is sent** (novox/hq 04-ISSUES/146). The client re-publishes when
|
|
|
|
|
// an acknowledgement is slow, and the mesh enrolled the machine on each copy — minting a second
|
|
|
|
|
// credential, which replaced the first, which is the one the node had already been given. The
|
|
|
|
|
// machine then reconnected for ever as a user whose password the mesh had rotated out from under
|
|
|
|
|
// it, and the controller's log said "enrolled anchor" twice in the same second.
|
|
|
|
|
//
|
|
|
|
|
// The id is the message: the same bytes carry the same id, so the stream discards the client's
|
|
|
|
|
// own retry, and a genuine second attempt — which carries a new reply address — is a different
|
|
|
|
|
// message and is let through.
|
|
|
|
|
sum := sha256.Sum256(addressed)
|
|
|
|
|
if _, err := a.js.Publish(EnrolSubject, addressed,
|
|
|
|
|
nats.MsgId(hex.EncodeToString(sum[:])), nats.Context(publish)); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("cannot ask the mesh to enrol this node: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|