Its own store, its own test database, the same shape every other context has. Five properties: a key with nobody to seal it to is refused rather than kept readably; a key is sealed once per holder and the blobs differ because they are sealed to different machines; a holder recorded afterwards has none and the existing ones keep theirs; releasing a consumer takes its key; and a licence nobody recorded is refused by name. The last was the only one whose message mattered and whose message was not checked — the database's own foreign-key error is true and mentions a constraint, which sends somebody to read a schema instead of typing the name they meant. Partial sealing now says how far it got. The person holding the key is the only one who can finish, and running it again knowing what it will do is different from running it hoping.
245 lines
7.4 KiB
Go
245 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()
|
|
|
|
inv, err := openInventory(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer inv.Close()
|
|
|
|
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
|
|
}
|