The callers that only ask (status, the doctor's probes, facts, bindings, data, acts, nodes show, images, release, recorded push, consumers) resolve through resolveNode and compose through composeQuestion, over one Input read per operation (withInputCache). A question composes with Reading or Foreseeing and never calls PairCredential's maker: the credential a send would make is named, and perform makes it on a send.
148 lines
5.4 KiB
Go
148 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/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) {
|
|
// One reading of the mesh for every machine this looks at.
|
|
ctx = withInputCache(ctx)
|
|
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 := resolveNode(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()
|
|
}
|