Prepare every send as the controller's composition did: make the own secrets a file names, and ask the vault every time (issues 1385, 1386)
mesh-sdk's composition refuses a module's file that names an own secret not made yet in every mode, where the controller's Allocating composition made it on the way; and it asked the vault on every send, where a send whose kept box was valid composed with no stand-in and so never performed. prepareSend, before the composition of every send, makes the own secrets no row holds (not the handed, not the outside-issued, not one the composition would refuse) and asks the vault for the core's credentials, keeping the boxes it answers and returning the modules the controller seals itself. perform no longer asks the vault.
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -33,14 +34,7 @@ import (
|
||||
//
|
||||
// perform returns whether it made or recorded anything: the reason the caller reads the stores again.
|
||||
func perform(ctx context.Context, open *stores, in *meshInput, node string, d compose.Declared) (bool, error) {
|
||||
did, _, err := performSend(ctx, open, in, node, d)
|
||||
return did, err
|
||||
}
|
||||
|
||||
// performSend is perform, and the core modules whose credentials the vault did not answer for with no box kept: the
|
||||
// controller seals those itself until the vault holds them (core_box.go, ADR 0396), and the send carries that seal.
|
||||
func performSend(ctx context.Context, open *stores, in *meshInput, node string, d compose.Declared) (bool, []string, error) {
|
||||
did, sealedByController := false, []string(nil)
|
||||
did := false
|
||||
inv := open.inventory
|
||||
|
||||
// The seats passed on: recorded in the store with the controller's own hand act (ADR 0449 §4, issue 1317).
|
||||
@@ -79,7 +73,7 @@ func performSend(ctx context.Context, open *stores, in *meshInput, node string,
|
||||
// down. The error is the one planFor met: the machine that needs it, and why none could be made.
|
||||
for _, p := range in.wouldMake {
|
||||
if err := makePair(ctx, inv, p); err != nil {
|
||||
return did, nil, fmt.Errorf("%s on %s needs %s from %s and no credential could be made for it: %w",
|
||||
return did, fmt.Errorf("%s on %s needs %s from %s and no credential could be made for it: %w",
|
||||
p.ConsumerModule, p.Consumer, p.Name, p.Provider, err)
|
||||
}
|
||||
did = true
|
||||
@@ -89,13 +83,13 @@ func performSend(ctx context.Context, open *stores, in *meshInput, node string,
|
||||
if len(d.ForeseenPorts) > 0 {
|
||||
shelf, err := inv.Catalogue(ctx)
|
||||
if err != nil {
|
||||
return did, nil, err
|
||||
return did, err
|
||||
}
|
||||
for _, one := range d.ForeseenPorts {
|
||||
module, port, ok := strings.Cut(one, ":")
|
||||
wanted, perr := strconv.Atoi(port)
|
||||
if !ok || perr != nil {
|
||||
return did, nil, fmt.Errorf("the composition named a port the controller cannot read: %q", one)
|
||||
return did, fmt.Errorf("the composition named a port the controller cannot read: %q", one)
|
||||
}
|
||||
fixed := false
|
||||
for _, l := range nodeShelf(in, shelf, node)[module].Listens {
|
||||
@@ -104,7 +98,7 @@ func performSend(ctx context.Context, open *stores, in *meshInput, node string,
|
||||
}
|
||||
}
|
||||
if _, err := inv.PortFor(ctx, node, module, wanted, fixed); err != nil {
|
||||
return did, nil, fmt.Errorf("%s needs %d reachable on %s and it could not be assigned: %w", module, wanted, node, err)
|
||||
return did, fmt.Errorf("%s needs %d reachable on %s and it could not be assigned: %w", module, wanted, node, err)
|
||||
}
|
||||
did = true
|
||||
}
|
||||
@@ -118,45 +112,22 @@ func performSend(ctx context.Context, open *stores, in *meshInput, node string,
|
||||
continue // a grant's sealed copy, the certificate or the bus's list: made below or by nobody
|
||||
}
|
||||
if _, err := inv.SecretForModule(ctx, node, module, name); err != nil {
|
||||
return did, nil, err
|
||||
return did, err
|
||||
}
|
||||
did = true
|
||||
}
|
||||
|
||||
// The core's own credentials, asked of the vault on every send so a rotated one reaches the machine (core_box.go).
|
||||
n, ok := nodeOf(in.Input, node)
|
||||
if ok && n.Stated && n.Contract >= compose.VaultBoxContract {
|
||||
resolved, err := compose.Resolve(in.Input, node)
|
||||
if err != nil {
|
||||
return did, nil, err
|
||||
}
|
||||
hands := n.Stated && n.Contract >= compose.HandedContract
|
||||
handing := catalogue.Rendering{Hands: hands, Provisioner: provisionerAccount(resolved.Plan)}
|
||||
boxes, err := coreBoxesFor(ctx, inv, node, resolved.Plan.Modules, func(m catalogue.Manifest) bool {
|
||||
return handing.Handing(m)
|
||||
}, Allocating)
|
||||
if err != nil {
|
||||
return did, nil, err
|
||||
}
|
||||
if len(boxes.keep) > 0 {
|
||||
did = true
|
||||
}
|
||||
// A core credential the vault did not answer for and no box was kept for is sealed by the controller itself until the
|
||||
// vault holds it (core_box.go, ADR 0396): the composition stands in a box for it, and the send carries the seal.
|
||||
sealedByController = boxes.byController
|
||||
}
|
||||
|
||||
// The certificate of a machine that was never certified: issued and kept, with the authority when there is none yet.
|
||||
for _, one := range d.StoodIn {
|
||||
if strings.HasPrefix(one, "the certificate and the authority of ") {
|
||||
if _, _, err := certificateFor(ctx, open, node); err != nil {
|
||||
return did, nil, err
|
||||
return did, err
|
||||
}
|
||||
did = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return did, sealedByController, nil
|
||||
return did, nil
|
||||
}
|
||||
|
||||
// makePair makes one pair credential by the controller's own read of it: the credential for the pair, or the provider's one
|
||||
@@ -220,3 +191,85 @@ func nodeShelf(in *meshInput, shelf map[string]catalogue.Manifest, node string)
|
||||
n, _ := nodeOf(in.Input, node)
|
||||
return shelfWith(shelf, n.Kept)
|
||||
}
|
||||
|
||||
// prepareSend is what a send does before it composes, every send and whatever the composition would have named: the
|
||||
// controller's own composition did it on the way, so a module's file that names an own secret composes (mesh-sdk's
|
||||
// composition can stand in for none: a file naming a secret not made yet is refused in every mode), and the core's credentials
|
||||
// are asked of the vault on every send, so a rotated one reaches the machine and a silent vault is said (core_box.go).
|
||||
//
|
||||
// - Each own secret of a module the node runs (not one handed to the provisioner, not one issued outside the mesh, not one a
|
||||
// person gives) that no row holds is made, unless the composition would refuse it (a bus credential nobody issued): that
|
||||
// refusal is the composition's to say, in its words.
|
||||
// - The core's credentials are asked of the vault, the boxes newly answered are kept, and the modules the vault did not answer
|
||||
// for with no box kept are returned: the controller seals those itself until the vault holds them (ADR 0396).
|
||||
func prepareSend(ctx context.Context, open *stores, node string) ([]string, error) {
|
||||
in, err := inputFor(ctx, open)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.refused[node] != nil {
|
||||
return nil, nil // empty-on-error: refused alone for what it keeps; the composition that follows says so, in its words
|
||||
}
|
||||
resolved, err := compose.Resolve(in.Input, node)
|
||||
if err != nil {
|
||||
return nil, nil // empty-on-error: it does not compose for its own reasons; the composition that follows says them
|
||||
}
|
||||
n, _ := nodeOf(in.Input, node)
|
||||
hands := n.Stated && n.Contract >= compose.HandedContract
|
||||
handing := catalogue.Rendering{Hands: hands, Provisioner: provisionerAccount(resolved.Plan)}
|
||||
inv := open.inventory
|
||||
did := false
|
||||
if n.SealingKey != "" {
|
||||
held := map[string]bool{}
|
||||
for _, r := range in.OwnSecrets {
|
||||
if r.Node == node {
|
||||
held[r.Module+"/"+r.Name] = true
|
||||
}
|
||||
}
|
||||
for _, m := range resolved.Plan.Modules {
|
||||
if handing.Handing(m) {
|
||||
continue
|
||||
}
|
||||
plain := m.OwnSecrets.Plain()
|
||||
names := make([]string, 0, len(plain))
|
||||
for name := range plain {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
if plain[name].IssuedBy == catalogue.IssuedOutside || held[m.Module+"/"+name] {
|
||||
continue
|
||||
}
|
||||
switch name {
|
||||
case "broker":
|
||||
if !slices.Contains(in.BusMinted, node+"."+m.Module) {
|
||||
continue
|
||||
}
|
||||
case compose.ProvisionerSecret:
|
||||
if !slices.Contains(in.BusMinted, "provisioner."+node) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if _, err := inv.SecretForModule(ctx, node, m.Module, name); err == nil {
|
||||
did = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var byController []string
|
||||
if n.Stated && n.Contract >= compose.VaultBoxContract {
|
||||
boxes, err := coreBoxesFor(ctx, inv, node, resolved.Plan.Modules, func(m catalogue.Manifest) bool {
|
||||
return handing.Handing(m)
|
||||
}, Allocating)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
did = did || len(boxes.keep) > 0
|
||||
byController = boxes.byController
|
||||
}
|
||||
if did {
|
||||
// What was made is in the stores now: the composition reads them afresh.
|
||||
forgetInput(ctx)
|
||||
}
|
||||
return byController, nil
|
||||
}
|
||||
|
||||
@@ -228,7 +228,10 @@ func composeSend(ctx context.Context, open *stores, node string) (sendable, erro
|
||||
func composeSendValidated(ctx context.Context, open *stores, node string) (sendable, []string, error) {
|
||||
// The core modules whose credentials the controller sealed itself because the vault was silent and no box was kept
|
||||
// (ADR 0396): the composition stands in a vault box for each, and a send carries the controller's own seal instead.
|
||||
var byController []string
|
||||
byController, err := prepareSend(ctx, open, node)
|
||||
if err != nil {
|
||||
return sendable{}, nil, err
|
||||
}
|
||||
for attempt := 0; ; attempt++ {
|
||||
in, err := inputFor(ctx, open)
|
||||
if err != nil {
|
||||
@@ -276,11 +279,9 @@ func composeSendValidated(ctx context.Context, open *stores, node string) (senda
|
||||
return sendable{}, nil, fmt.Errorf("%s was composed twice and still names what only a send makes (%s): nothing is sent "+
|
||||
"that stands in for a value", node, strings.Join(left, "; "))
|
||||
}
|
||||
_, sealed, err := performSend(ctx, open, in, node, d)
|
||||
if err != nil {
|
||||
if _, err := perform(ctx, open, in, node, d); err != nil {
|
||||
return sendable{}, nil, err
|
||||
}
|
||||
byController = sealed
|
||||
// What was made is in the stores now: the next read is a fresh one.
|
||||
forgetInput(ctx)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user