package main import ( "context" "errors" "flag" "fmt" "os" "strings" "github.com/novox/mesh-controller/internal/secrets" ) // operatorCommand is the mesh's one holder of secrets that is not a machine. // // **Every secret a module holds for itself is sealed to the node that uses it, and a node whose key // is gone takes its secrets with it** — the store's superuser and the broker's administrator among // them. novox/hq ADR 0085 (amended) gives them a second recipient: a person, with a sealing key // whose private half is made where the operator is and never enters the mesh. Making the key and // telling the mesh about it are two commands, on purpose: the first needs no mesh at all and runs // wherever the operator keeps things; the second gives the mesh the public half and nothing else. // The controller's own container is a scratch image with no writable path, which is the right // shape for a program that must hold no key — so the private half could not be written there // even by mistake. // // operator key make [--out ] make a keypair: private half to the file, public half printed // operator key set tell the mesh which key to seal to // operator key show the public key, its fingerprint, and what it can recover const operatorUsage = "operator key make [--out ] | operator key set [--replace] | operator key show" func operatorCommand(ctx context.Context, args []string) error { if len(args) < 2 || args[0] != "key" { return errors.New(operatorUsage) } switch args[1] { case "make": return operatorKeyMake(args[2:]) case "set": return operatorKeySet(ctx, args[2:]) case "show": return operatorKeyShow(ctx) default: return errors.New(operatorUsage) } } // operatorKeyMake needs no mesh: it is the operator's, run wherever the key will live. func operatorKeyMake(args []string) error { set := flag.NewFlagSet("operator key make", flag.ContinueOnError) out := set.String("out", "operator.key", "where to write the private key (0600); keep it off the mesh, and keep it") if err := set.Parse(args); err != nil { return err } public, private, err := secrets.Keypair() if err != nil { return err } // Create-exclusive: a key somebody may still need is never overwritten, and there is no window // between checking and writing in which one could appear. if err := writeNew(*out, []byte(private+"\n")); err != nil { return err } fmt.Printf("operator key %s\n", secrets.Fingerprint(public)) fmt.Printf(" private half written to %s (0600) — keep it off the mesh, and keep it\n", *out) fmt.Printf(" public half, to give the mesh with `operator key set`:\n") fmt.Printf("public %s\n", public) return nil } func operatorKeySet(ctx context.Context, args []string) error { rest, flags := split(args) set := flag.NewFlagSet("operator key set", flag.ContinueOnError) replace := set.Bool("replace", false, "replace an existing operator key — secrets sealed to the old one stay sealed to it") if err := set.Parse(flags); err != nil { return err } if len(rest) != 1 { return errors.New(operatorUsage) } public := strings.TrimSpace(rest[0]) if _, err := secrets.Seal(public, []byte("probe")); err != nil { return fmt.Errorf("that is not a public sealing key: %w", err) } open, err := openStores(ctx) if err != nil { return err } defer open.Close() inv := open.inventory if current, err := inv.OperatorKey(ctx); err != nil { return err } else if current != "" && current != public && !*replace { return fmt.Errorf( "the mesh already has an operator key (%s). Pass --replace to change it — "+ "secrets sealed to the current key stay sealed to it until each is issued again", secrets.Fingerprint(current)) } orphaned, err := inv.SetOperatorKey(ctx, public) if err != nil { return err } fmt.Printf("operator key %s\n", secrets.Fingerprint(public)) fmt.Printf(" the mesh holds the public half only and cannot open what it seals to it;\n") fmt.Printf(" from now on every secret a module holds for itself is sealed to it as well.\n") fmt.Printf(" Secrets made before this cannot be — each is recoverable once issued again\n") if orphaned > 0 { fmt.Printf(" %d secret(s) are sealed to the previous key and stay so until issued again\n", orphaned) } return nil } func operatorKeyShow(ctx context.Context) error { open, err := openStores(ctx) if err != nil { return err } defer open.Close() inv := open.inventory key, err := inv.OperatorKey(ctx) if err != nil { return err } if key == "" { fmt.Println("the mesh has no operator key; `operator key make` then `operator key set` gives it one") return nil } kept, earlier, unrecoverable, err := inv.KeptForOperator(ctx) if err != nil { return err } fmt.Printf("operator key %s\n %s\n", secrets.Fingerprint(key), key) fmt.Printf(" %d secret(s) recoverable with it\n", len(kept)) if len(earlier) > 0 { fmt.Printf(" %d secret(s) sealed to an earlier operator key — recoverable with that key only, until issued again:\n", len(earlier)) for _, k := range earlier { fmt.Printf(" %s %s %s (%s)\n", k.Node, k.Module, k.Name, secrets.Fingerprint(k.Key)) } } if len(unrecoverable) > 0 { fmt.Printf(" %d secret(s) not recoverable — made before the mesh had an operator key:\n", len(unrecoverable)) for _, k := range unrecoverable { fmt.Printf(" %s %s %s\n", k.Node, k.Module, k.Name) } } return nil } // readPrivateKey is the operator's key from the file `operator key make` wrote. func readPrivateKey(path string) (string, error) { if path == "" { return "", errors.New("--key names the operator's private key, written by `operator key make`") } raw, err := os.ReadFile(path) if err != nil { return "", err } return strings.TrimSpace(string(raw)), nil }