A cross-node module reaches the broker by the hub's overlay name, not the public address
The broker credential handed to a module named the genesis MESH_BROKER_ADDRESS — the broker's public endpoint. That is reachable from the control-node itself but not routed to another node, whose firewall admits only the overlay (from:mesh); a consumer on a joined node timed out fetching the broker's certificate and never connected. brokerReachableAt returns the broker's address as the given node can reach it: a node on the overlay gets the hub's `.internal` name (which every node resolves and the firewall admits, the fingerprint pin making the host swap safe for TLS); a node not yet on the overlay — at genesis, before any `overlay place`, when the builder's account is issued — keeps the genesis address, so bring-up is unchanged. Both credential paths (module issue and builder issue) use it. This is the reachability half of novox/hq issue 055. https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -283,6 +284,10 @@ func moduleCommand(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot deliver a credential without knowing where the broker is: %w", err)
|
||||
}
|
||||
brokerAddr, err := brokerReachableAt(ctx, inv, known, *forNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The URL and what verifies the broker, together — a mesh's broker presents its own
|
||||
// certificate, in no public trust store, so a URL alone fails at TLS (as `builder issue`).
|
||||
held, err := json.Marshal(struct {
|
||||
@@ -291,7 +296,7 @@ func moduleCommand(ctx context.Context, args []string) error {
|
||||
Node string `json:"node"`
|
||||
Module string `json:"module"`
|
||||
}{
|
||||
URL: fmt.Sprintf("amqps://%s:%s@%s/", account, password, known.Address),
|
||||
URL: fmt.Sprintf("amqps://%s:%s@%s/", account, password, brokerAddr),
|
||||
Fingerprint: known.Fingerprint,
|
||||
// The node and module the account is for, so the runtime names its queue as the mesh
|
||||
// scoped it (<node>.<module>.events) without a manifest having to interpolate a node.
|
||||
@@ -461,3 +466,37 @@ func pinCommand(ctx context.Context, args []string, setting bool) error {
|
||||
fmt.Printf(" run `push %s` to send it\n", args[0])
|
||||
return nil
|
||||
}
|
||||
|
||||
// brokerReachableAt is the broker's address as the given node can reach it.
|
||||
//
|
||||
// The genesis address (MESH_BROKER_ADDRESS) is the broker's public endpoint — reachable from the
|
||||
// control-node itself, but not routed to another node, whose firewall admits only the overlay
|
||||
// (from:mesh). The foundation, and so the broker, sits on the control-node, which is the overlay
|
||||
// hub; a node that is on the overlay reaches the broker by the hub's `.internal` name, which the
|
||||
// firewall admits and every node resolves. A node not yet on the overlay — at genesis, before any
|
||||
// `overlay place`, which is when the builder's account is issued — keeps the genesis address it
|
||||
// was given, so nothing about bring-up changes. This is issue 055.
|
||||
func brokerReachableAt(ctx context.Context, inv *inventory.Inventory, known broker.Broker, node string) (string, error) {
|
||||
overlays, err := inv.Overlays(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var hub string
|
||||
onOverlay := false
|
||||
for _, o := range overlays {
|
||||
if o.Hub && o.Address != "" {
|
||||
hub = o.Name
|
||||
}
|
||||
if o.Name == node && o.Address != "" {
|
||||
onOverlay = true
|
||||
}
|
||||
}
|
||||
if hub == "" || !onOverlay {
|
||||
return known.Address, nil
|
||||
}
|
||||
_, port, err := net.SplitHostPort(known.Address)
|
||||
if err != nil {
|
||||
return known.Address, nil
|
||||
}
|
||||
return net.JoinHostPort(overlay.InternalName(hub), port), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user