package main import ( "bufio" "context" "errors" "flag" "fmt" "io" "os" "strings" ) // secretCommand gives the mesh a value it must carry and could not have invented. // // **Every other secret in this mesh is one the mesh made** — generated, sealed to the machine that // will use it, and never readable again. That is right for something coming into existence, and // wrong for something that already exists: a database created last year has the password it was // created with, and generating a new one puts 32 random bytes where a working credential was. // The machine applies it, reports success, and whatever reads it fails to authenticate somewhere // else entirely — with the mesh insisting the secret was delivered, which it was. // // So this is the entry point for **adopting** something already running. The store has carried // the distinction since the beginning: a module secret records whether it was `made` or // `accepted`, and refuses to invent a replacement for the second. Nothing until now could write // one, so the only accepted secret in the mesh was the broker account issued to a build machine. // // The value is sealed on the way in and the plaintext discarded, exactly as a generated one is. // **The only difference between the two is where the value came from.** func secretCommand(ctx context.Context, args []string) error { if len(args) == 0 || args[0] != "accept" { return errors.New("secret accept [--from ]") } rest, flags := split(args[1:]) set := flag.NewFlagSet("secret accept", flag.ContinueOnError) from := set.String("from", "", "read the value from this file instead of asking (use - for standard input)") if err := set.Parse(flags); err != nil { return err } if len(rest) != 3 { return errors.New("secret accept [--from ]") } node, module, name := rest[0], rest[1], rest[2] value, err := valueFor(node, module, name, *from) if err != nil { return err } value = asSupplied(value) if value == "" { return errors.New("there is nothing to seal") } open, err := openStores(ctx) if err != nil { return err } defer open.Close() if err := open.inventory.AcceptSecretForModule(ctx, node, module, name, value); err != nil { return err } // Not printed back, and there is nowhere it could be printed from: it is sealed to that // machine and the mesh cannot read it again. fmt.Printf("%s on %s now holds %q, sealed to that machine.\n", module, node, name) fmt.Printf(" the mesh cannot read it back, and will not replace it with one of its own\n") fmt.Printf(" run `push %s` to send it\n", node) return nil } // split separates what this command is about from how it was asked. // // **Because the standard library stops parsing at the first non-flag argument.** With the // positionals first — which is the order that reads correctly — everything after them is left // sitting in the arguments, so `secret accept a b c --from -` arrives as five positionals and the // flag is never seen. The host's own parser carries the same note, and the fault it names is // worse than this one: there, a flag somebody passed was silently ignored and the command // succeeded anyway. func split(args []string) (positional, flags []string) { for i, arg := range args { if strings.HasPrefix(arg, "-") { return args[:i], args[i:] } } return args, nil } // asSupplied is the value with its line ending removed and nothing else. // // **A file has a trailing newline and a password does not**, so the ending goes — a credential // wrong by one byte fails in a way nobody connects to how it was supplied. // // **And only the ending.** Trimming both ends is the obvious thing and it is wrong: a password // chosen with a leading space is one the mesh would then deliver as a different password, silently, // with the operator certain they had supplied it correctly. func asSupplied(raw string) string { return strings.TrimRight(raw, "\r\n") } // valueFor gets the secret without putting it somewhere it can be read afterwards. // // **Not an argument, and there is no flag that takes one.** A value on the command line is in the // shell's history, in the process list for as long as it runs, and in whatever collects either. // The paths here are a file the operator already has, or a prompt that does not echo — the same // two ways a model-access key is supplied (novox/hq ADR 0024). func valueFor(node, module, name, from string) (string, error) { switch { case from == "-": body, err := io.ReadAll(os.Stdin) if err != nil { return "", err } return string(body), nil case from != "": body, err := os.ReadFile(from) if err != nil { return "", err } return string(body), nil default: // The same path a model-access key takes, and for the same reason: a value given as an // argument is in the shell's history and in the process list. Read from standard input, // echoed nowhere by this program. fmt.Fprintf(os.Stderr, "reading %s's %q for %s from standard input; it is not echoed anywhere\n", module, name, node) line, err := bufio.NewReader(os.Stdin).ReadString('\n') if err != nil && line == "" { return "", fmt.Errorf("nothing was given on standard input: %w", err) } return line, nil } }