A token with all four parts
Given the broker's address and its certificate, mesh-control now issues a token carrying everything ADR 0004 asks for: where to connect, what to expect there, whose signature to believe afterwards, and a one-time right to join. Verified by decoding one and checking the fingerprint against `openssl x509 | sha256sum` -- they match. The fingerprint is derived from the certificate on disk and never configured. A configured pin can drift from the certificate it describes, and a drifted pin is worse than none: every node issued a token during the drift refuses to connect, and the failure looks like an attack rather than a mistake. Computed over DER, which is what a client sees on the wire. Hashing the PEM text instead would mean the same certificate, re-wrapped with different line endings, produced a different pin -- there is a test for exactly that, and one for pointing this at tls.key by mistake, which would otherwise produce a confident pin over the wrong file. Having no broker stays a state rather than a failure: a control plane holds records and a signing key without one. Having half a broker is refused, because a token with an address and nothing to check it against invites a node to trust whatever answers. Fault injection caught the same weak test I wrote earlier in the day -- asking whether something failed rather than why, so deleting the guard changed nothing because it failed one line later anyway. Both are now asserted on the reason.
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/novox/mesh-control/internal/broker"
|
||||
"github.com/novox/mesh-control/internal/identity"
|
||||
"github.com/novox/mesh-control/internal/inventory"
|
||||
"github.com/novox/mesh-control/internal/store"
|
||||
@@ -64,6 +65,8 @@ func run() error {
|
||||
return tokenCommand(ctx, args[1:])
|
||||
case "identity":
|
||||
return identityCommand(ctx, args[1:])
|
||||
case "broker":
|
||||
return brokerCommand(args[1:])
|
||||
case "version":
|
||||
fmt.Println(version)
|
||||
return nil
|
||||
@@ -85,6 +88,7 @@ func usage() {
|
||||
token issue --node <name> a one-time right to join, for an existing record
|
||||
token issue --new <name> create the record and issue for it
|
||||
identity show this control plane's signing key
|
||||
broker show where the broker is, and what to expect there
|
||||
version what this binary is
|
||||
|
||||
Each context reaches its own store through its own credential (novox/hq ADR 0008), named
|
||||
@@ -250,6 +254,17 @@ func tokenCommand(ctx context.Context, args []string) error {
|
||||
}
|
||||
|
||||
made := token.Token{Signer: key.Public, Secret: issued.Secret}
|
||||
|
||||
// Absent is a state, not a failure: a control plane can hold records and a key before it has
|
||||
// a broker. What it cannot do is issue a token anybody could use, and Missing() says so.
|
||||
known, err := broker.FromEnvironment()
|
||||
switch {
|
||||
case err == nil:
|
||||
made.Broker, made.Fingerprint = known.Address, known.Fingerprint
|
||||
case errors.Is(err, broker.ErrNotConfigured):
|
||||
default:
|
||||
return err
|
||||
}
|
||||
encoded, err := made.Encode()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -264,8 +279,8 @@ func tokenCommand(ctx context.Context, args []string) error {
|
||||
for _, m := range missing {
|
||||
fmt.Printf(" - %s\n", m)
|
||||
}
|
||||
fmt.Println("\nThe broker and its certificate are step 5 of the substrate bootstrap and " +
|
||||
"do not exist yet\n(novox/hq 07-the-substrate). The signing key above is real.")
|
||||
fmt.Printf("\nSet %s and %s once the broker is raised.\n",
|
||||
broker.AddressVar, broker.CertificateVar)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -305,3 +320,25 @@ func identityCommand(ctx context.Context, args []string) error {
|
||||
"declaration because it carries a signature this key made (novox/hq ADR 0004).\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func brokerCommand(args []string) error {
|
||||
if len(args) == 0 || args[0] != "show" {
|
||||
return errors.New("broker show")
|
||||
}
|
||||
known, err := broker.FromEnvironment()
|
||||
if errors.Is(err, broker.ErrNotConfigured) {
|
||||
fmt.Printf("no broker configured. Set %s and %s.\n\n"+
|
||||
"Until then tokens carry the signing key and the one-time secret, and say what they\n"+
|
||||
"are missing. They cannot be used to join.\n",
|
||||
broker.AddressVar, broker.CertificateVar)
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("address %s\n", known.Address)
|
||||
fmt.Printf("fingerprint %s\n", known.Fingerprint)
|
||||
fmt.Print("\nThe fingerprint is computed from the certificate on disk, never configured. A\n" +
|
||||
"node checks it before sending anything (novox/hq ADR 0004).\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user