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 ` 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() }