mesh/merge-gate head c9dd07f919a2 merged into trunk 404b4409e70e — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head c9dd07f919a2 merged into trunk 404b4409e70e — pass: its merge-check.sh passed (run 2)
mesh/delivery delivered
A Servarr app's API key, an indexer's key and a downloader's password are values the provider's
software holds; the pair path made a random value for every consumer of them, sealed it to both ends
and reported success, and the provider refused it. An offer may now say `"credential": {"issued-by":
"outside"}`: SecretFor never makes a value for it, the plan reads such a pair as given or not given
and sends the consumer no credential file until a person gives it, without refusing its machine;
remakeShared and SharedSecretFor never make a provider's own secret issued outside either.
`secret accept <provider's node> <provider module> <provision>` seals one value to every consumer that
binds the provision from that provider, the provider and the operator, under one generation; the
per-pair form is refused for such a provision, and rotate names the accept. A consumer may wait for a
provision's credential: the controller excuses the wait only when the store bears it out, and its
needs-operator condition names the one accept line.
Adds the replay of issue 164 (TestReplay164), written with only what the controller had before.
146 lines
5.3 KiB
Go
146 lines
5.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// A provision whose credential only a person outside the mesh can give (novox/hq ADR 0337, issue 164, issue 165):
|
|
// given once for its provider, at the controller's terminal, and sealed in one act to every consumer that binds the
|
|
// provision from that provider now. The value is never read back, printed or kept.
|
|
|
|
// outsideOffer is the provider module's own name when `secret accept <node> <module> <name>` names a provision that
|
|
// module offers with a credential issued outside the mesh, and "" when it names anything else (an own secret, or a
|
|
// pair with --provider).
|
|
func outsideOffer(ctx context.Context, module, name, provider string) (string, error) {
|
|
if provider != "" {
|
|
return "", nil
|
|
}
|
|
open, err := openStores(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer open.Close()
|
|
shelf, err := open.inventory.Catalogue(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if m, known := shelf[module]; known && m.IssuedOutsideOf(name) {
|
|
return module, nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
// offeredOutsideBy is a module that offers the provision with a credential issued outside the mesh, or "".
|
|
func offeredOutsideBy(ctx context.Context, provision string) (string, error) {
|
|
open, err := openStores(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer open.Close()
|
|
shelf, err := open.inventory.Catalogue(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
module, _ := catalogue.OffersIssuedOutside(shelf, provision)
|
|
return module, nil
|
|
}
|
|
|
|
// consumersFrom is every consumer that binds a provision from one provider now, found by resolving every machine as
|
|
// its next send would, and the machines that could not be resolved, with why.
|
|
func consumersFrom(ctx context.Context, open *stores, provider, providerModule, provision string) (
|
|
[]inventory.PairEnd, []string, error) {
|
|
nodes, err := open.inventory.Nodes(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
var out []inventory.PairEnd
|
|
var unresolved []string
|
|
for _, n := range nodes {
|
|
plan, _, err := planFor(ctx, open, n.Name)
|
|
if err != nil {
|
|
unresolved = append(unresolved, fmt.Sprintf("%s: %s", n.Name, oneLine(err.Error())))
|
|
continue
|
|
}
|
|
out = append(out, bindsFrom(plan, n.Name, provider, providerModule, provision)...)
|
|
}
|
|
return out, unresolved, nil
|
|
}
|
|
|
|
// bindsFrom is every consumer on one machine whose resolution answers the provision from that provider. Pure.
|
|
func bindsFrom(plan catalogue.Resolution, machine, provider, providerModule, provision string) []inventory.PairEnd {
|
|
var out []inventory.PairEnd
|
|
for _, need := range plan.Needs {
|
|
if need.ByRecord || need.Name != provision || need.From != provider {
|
|
continue
|
|
}
|
|
if need.Module != "" && need.Module != providerModule {
|
|
continue
|
|
}
|
|
out = append(out, inventory.PairEnd{Consumer: machine, ConsumerModule: need.For, Local: need.Local})
|
|
}
|
|
sort.Slice(out, func(i, j int) bool {
|
|
return out[i].ConsumerModule+"\x00"+out[i].Local < out[j].ConsumerModule+"\x00"+out[j].Local
|
|
})
|
|
return out
|
|
}
|
|
|
|
// acceptForProvision gives a provision's credential issued outside the mesh, once for its provider (ADR 0337 §3).
|
|
func acceptForProvision(ctx context.Context, provider, providerModule, provision, value string) error {
|
|
open, err := openStores(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer open.Close()
|
|
consumers, unresolved, err := consumersFrom(ctx, open, provider, providerModule, provision)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
accepted, err := open.inventory.AcceptSecretForProvision(ctx, provision, provider, providerModule, value, consumers)
|
|
value = ""
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Print(acceptedSaid(provider, providerModule, provision, accepted, unresolved))
|
|
if len(accepted.Sealed) == 0 {
|
|
return nil
|
|
}
|
|
if err := sendTo(ctx, open, accepted.Machines); err != nil {
|
|
return fmt.Errorf("%s is sealed and could not be sent: %w; `push %s` sends it", provision, err,
|
|
strings.Join(accepted.Machines, "`, `push "))
|
|
}
|
|
fmt.Printf(" sent %s; each consumer starts again on it\n", strings.Join(accepted.Machines, ", "))
|
|
return nil
|
|
}
|
|
|
|
// acceptedSaid is what one accept for a provider did, in words that never carry the value. Pure.
|
|
func acceptedSaid(provider, providerModule, provision string, a inventory.ProvisionAccepted, unresolved []string) string {
|
|
var b strings.Builder
|
|
if len(a.Sealed) == 0 {
|
|
fmt.Fprintf(&b, "%s from %s on %s: no consumer binds it now, so nothing was sealed; a consumer that binds "+
|
|
"later waits for it, and this line given again seals it to every consumer then\n", provision,
|
|
providerModule, provider)
|
|
} else {
|
|
var names []string
|
|
for _, c := range a.Sealed {
|
|
names = append(names, fmt.Sprintf("%s on %s%s", c.ConsumerModule, c.Consumer, asLocal(c.Local)))
|
|
}
|
|
fmt.Fprintf(&b, "%s from %s on %s is given, sealed to %s and to %s\n", provision, providerModule, provider,
|
|
strings.Join(names, ", "), provider)
|
|
fmt.Fprintf(&b, " the mesh cannot read it back, never makes one of its own for it, and will not rotate it\n")
|
|
}
|
|
for _, c := range a.Unsealable {
|
|
fmt.Fprintf(&b, " not sealed to %s on %s: no sealing key on that machine; give it again once it has one\n",
|
|
c.ConsumerModule, c.Consumer)
|
|
}
|
|
for _, u := range unresolved {
|
|
fmt.Fprintf(&b, " not known whether a consumer on %s binds it, which did not resolve; give it again once it does\n", u)
|
|
}
|
|
return b.String()
|
|
}
|