A machine joins the mesh it raised
The last step of the first-node path, and the bundle now carries all of it: a container runtime, the store, a database per context, their schemas, the broker with a certificate it generated itself, and the control plane running. Then the machine enrols against the mesh on its own disk. It dials the broker over TLS, refuses anything but the pinned certificate, presents the one-time secret with a public key it generated, and is told the name the mesh has for it. Its specialness lasted two commands, which is what ADR 0004 asked for. The identity is saved only after the mesh says it knows this node. A node holding an identity the mesh never recorded would believe it had joined and be believed by nobody, which is worse than not joining because nothing looks wrong. An already-enrolled machine refuses a valid token rather than quietly acquiring a second identity, and a spent token is refused by the mesh. Both checked. Containers gained a network field. The control plane must reach the store and the broker on the machine it was raised on, before there is any mesh to arrange that; the alternative was publishing ports and guessing an address that works from inside a container, which fails in a worse way. The control plane talks to the broker over loopback in plaintext, deliberately. The TLS on 5671 exists so a node crossing a network can pin a certificate, not for a hop that never leaves the machine. Verified on a sealed lab machine: eleven resources applied from bare, the control plane consuming, a token issued from inside it, and the machine enrolled -- with the recorded public key matching what the host printed, the token marked spent, and the profile stored.
This commit is contained in:
+36
-5
@@ -222,7 +222,7 @@ func run(ctx context.Context, command string, opts options) error {
|
||||
return w.Flush()
|
||||
|
||||
case "enrol", "enroll":
|
||||
return enrol(opts)
|
||||
return enrol(ctx, opts)
|
||||
|
||||
case "version":
|
||||
fmt.Println(version)
|
||||
@@ -386,7 +386,7 @@ func runApply(ctx context.Context, opts options, d *declaration.Declaration, sou
|
||||
// the one-time secret together with a public key it generated itself.
|
||||
//
|
||||
// The mesh issues no identity. This machine arrives holding one; what it receives is being known.
|
||||
func enrol(opts options) error {
|
||||
func enrol(ctx context.Context, opts options) error {
|
||||
tokenText, name := &opts.token, &opts.nodeName
|
||||
if strings.TrimSpace(*tokenText) == "" {
|
||||
return errors.New("enrol --token <token>: the token is carried to this machine by a " +
|
||||
@@ -429,13 +429,44 @@ func enrol(opts options) error {
|
||||
defer conn.Close()
|
||||
fmt.Println("\nthe broker presented the certificate this token pins")
|
||||
|
||||
conn.Close()
|
||||
|
||||
mine, err := identity.Generate(*name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("generated this node's identity: %s\n", mine.PublicBase64())
|
||||
|
||||
return errors.New("the link is not built: this machine has verified the broker and made its " +
|
||||
"identity, and there is nothing yet to present them to.\n" +
|
||||
"Nothing has been saved, so this can be run again unchanged")
|
||||
// What this machine can be asked to do, gathered before joining rather than after. The
|
||||
// control plane cannot decide what a node should run without it, so it travels with the
|
||||
// request instead of being asked for in a second round trip.
|
||||
detected := profile.Detect(ctx, profile.Default(nil), opts.timeout)
|
||||
reported := map[string]any{}
|
||||
if raw, err := json.Marshal(detected); err == nil {
|
||||
_ = json.Unmarshal(raw, &reported)
|
||||
}
|
||||
|
||||
reply, err := link.Enrol(ctx, token.Broker, token.Fingerprint, *name, token.Secret,
|
||||
mine.Public, reported, opts.timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The mesh's name for this node wins over what the machine called itself: the token was
|
||||
// issued for a node record, and that record is what the identity binds to.
|
||||
mine.Node = reply.Node
|
||||
|
||||
// Saved only now, and only once the mesh has said it knows this node. A node holding an
|
||||
// identity the mesh has never recorded would believe it had joined and be believed by
|
||||
// nobody — worse than not having joined, because nothing would look wrong.
|
||||
if err := identity.Save(identityPath, mine); err != nil {
|
||||
return fmt.Errorf(
|
||||
"the mesh accepted this node as %q and its identity could not be saved: %w\n"+
|
||||
"That token is spent, so getting back needs a new one", reply.Node, err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nenrolled as %s\n", reply.Node)
|
||||
fmt.Printf(" identity %s\n", identityPath)
|
||||
fmt.Printf(" queue %s\n", reply.Queue)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user