Files
mesh-controller/cmd/mesh-control/licence.go
T
jschoubben 6eeb10f066 A command opens each store once, not once per machine
Working out what a machine should be reaches the identity context for its
certificate and the licence context for its model access. Both were opened —
and waited on — inside functions called for every node in a push. Two machines
hid it. Fifty would be fifty connect-and-wait cycles for data that does not
change while the push runs.

So a command holds what it has open, and passes it. Each context is opened on
first use rather than up front, because most commands need one and paying to
reach three would be the same waste from the other side.

The contexts stay separate, which is the point: this is one struct holding
three connections to three databases, not one connection to a shared one. No
context reaches another's store, and each still holds only its own credential
(novox/hq ADR 0008).

A pure move again — the gate is green before and after, and no test changed.
2026-08-31 13:35:39 +02:00

246 lines
7.4 KiB
Go

package main
import (
"bufio"
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"strings"
)
// licenceCommand is everything about model access the mesh holds.
//
// **A licence is a named thing and the name is the operator's** (novox/hq ADR 0024). *The personal
// account*, *the organisation's account* — those are names a person uses, and the mesh has to use
// them too, because the whole point is saying which one a given consumer uses.
func licenceCommand(ctx context.Context, args []string) error {
if len(args) == 0 {
return errors.New("licence add|list|use|release|key|forget")
}
switch args[0] {
case "add":
return licenceAdd(ctx, args[1:])
case "list":
return licenceList(ctx)
case "use":
return licenceUse(ctx, args[1:], true)
case "release":
return licenceUse(ctx, args[1:], false)
case "key":
return licenceKey(ctx, args[1:])
case "forget":
return licenceForget(ctx, args[1:])
}
return fmt.Errorf("licence %q; it is add, list, use, release, key or forget", args[0])
}
func licenceAdd(ctx context.Context, args []string) error {
set := flag.NewFlagSet("licence add", flag.ContinueOnError)
// What a consumer must know that is not secret — a base URL, a model name. Never the key.
serves := set.String("serves", "",
"JSON a consumer must know that is not secret, such as a base URL or a model")
positionals, err := parseAround(set, args)
if err != nil {
return err
}
if len(positionals) != 2 {
return errors.New(`licence add <provider> <name> [--serves '{"model":"..."}']`)
}
provider, name := positionals[0], positionals[1]
values := map[string]any{}
if strings.TrimSpace(*serves) != "" {
if err := json.Unmarshal([]byte(*serves), &values); err != nil {
return fmt.Errorf("--serves is not JSON: %w", err)
}
}
held, err := openLicences(ctx)
if err != nil {
return err
}
defer held.Close()
if err := held.Add(ctx, name, provider, values); err != nil {
return err
}
fmt.Printf("%s (%s) recorded. Nothing uses it yet, and it has no key:\n"+
" licence use %s <node> <module>\n licence key %s\n", name, provider, name, name)
return nil
}
func licenceList(ctx context.Context) error {
held, err := openLicences(ctx)
if err != nil {
return err
}
defer held.Close()
all, err := held.All(ctx)
if err != nil {
return err
}
if len(all) == 0 {
// Said, not printed as nothing: an empty list and a failed read must never look the same.
fmt.Println("this mesh holds no licences")
return nil
}
for _, one := range all {
holders, err := held.HoldersOf(ctx, one.Name)
if err != nil {
return err
}
fmt.Printf("%s (%s)\n", one.Name, one.Provider)
if len(holders) == 0 {
fmt.Printf(" nobody uses it\n")
}
for _, h := range holders {
// Whether it has a key is the question somebody is actually asking, so it is said
// per holder rather than per licence: the key was sealed to the holders that existed
// when it was supplied, and one recorded afterwards has none.
state := "has no key — supply it again with `licence key " + one.Name + "`"
if h.Sealed != "" {
state = "has a key"
}
fmt.Printf(" %s on %s: %s\n", h.Module, h.Node, state)
}
}
return nil
}
func licenceUse(ctx context.Context, args []string, using bool) error {
verb := "use"
if !using {
verb = "release"
}
if len(args) != 3 {
return fmt.Errorf("licence %s <name> <node> <module>", verb)
}
name, node, module := args[0], args[1], args[2]
held, err := openLicences(ctx)
if err != nil {
return err
}
defer held.Close()
if !using {
if err := held.StopUsing(ctx, name, node, module); err != nil {
return err
}
fmt.Printf("%s on %s no longer uses %s. Its copy of the key goes on the next push\n",
module, node, name)
return nil
}
if err := held.Use(ctx, name, node, module); err != nil {
return err
}
fmt.Printf("%s on %s uses %s.\n", module, node, name)
// The consequence, said now rather than discovered as a machine that resolves and receives
// nothing: the mesh discarded the plaintext, so a holder added after the key was supplied has
// no key and the mesh cannot make one.
sealed, err := held.KeyFor(ctx, name, node, module)
if err != nil {
return err
}
if sealed == "" {
fmt.Printf(" It has no key yet — the mesh discarded the plaintext when it was supplied "+
"and cannot seal another. Supply it again:\n licence key %s\n", name)
}
return nil
}
// licenceKey is the *accept* verb novox/hq ADR 0024 names as missing.
//
// Take a value, seal it to each holder, and discard the plaintext. Every other credential the
// mesh handles it generated itself; an API key arrives from a person, and a mesh that kept
// operator-supplied keys readably is the arrangement this project measured and rejected.
func licenceKey(ctx context.Context, args []string) error {
set := flag.NewFlagSet("licence key", flag.ContinueOnError)
// A file rather than an argument, by default. A key on a command line is a key in shell
// history and in every process listing taken while it ran.
from := set.String("file", "", "read the key from a file instead of standard input")
positionals, err := parseAround(set, args)
if err != nil {
return err
}
if len(positionals) != 1 {
return errors.New("licence key <name> [--file <path>]")
}
name := positionals[0]
var value string
if *from != "" {
raw, err := os.ReadFile(*from)
if err != nil {
return err
}
value = strings.TrimSpace(string(raw))
} else {
fmt.Fprintln(os.Stderr, "reading the key from standard input; it is not echoed anywhere")
reader := bufio.NewReader(os.Stdin)
line, err := reader.ReadString('\n')
if err != nil && line == "" {
return fmt.Errorf("nothing was given on standard input: %w", err)
}
value = strings.TrimSpace(line)
}
held, err := openLicences(ctx)
if err != nil {
return err
}
defer held.Close()
open, err := openStores(ctx)
if err != nil {
return err
}
defer open.Close()
inv := open.inventory
sealed, err := held.Accept(ctx, name, value, func(node string) (string, error) {
return inv.SealingKeyOf(ctx, node)
})
if err != nil {
if sealed > 0 {
// Some holders got it and some did not, and the person holding the key is the only
// one who can finish the job. Saying how far it got is the difference between running
// this again knowing what it will do and running it hoping.
return fmt.Errorf(
"%w\n\n%d holder(s) were sealed before this. Running `licence key %s` again "+
"with the same key seals the rest and changes nothing for those already done",
err, sealed, name)
}
return err
}
// Not echoed back, ever. What is stored is unreadable by whoever holds it, the mesh included,
// and printing the value here would put the one copy that matters on a terminal.
fmt.Printf("sealed to %d holder(s). The mesh has discarded the key and cannot read it back\n",
sealed)
fmt.Printf(" run `push` to deliver it\n")
return nil
}
func licenceForget(ctx context.Context, args []string) error {
if len(args) != 1 {
return errors.New("licence forget <name>")
}
held, err := openLicences(ctx)
if err != nil {
return err
}
defer held.Close()
if err := held.Forget(ctx, args[0]); err != nil {
return err
}
// Said plainly, because the mesh cannot do it and pretending otherwise is worse than useless:
// a licence outliving its holder is a live credential nobody is watching.
fmt.Printf("%s is forgotten, and every record of who held it with it.\n"+
" The key itself is not the mesh's to revoke — do that where the licence was bought\n",
args[0])
return nil
}