Compose the credential half of the rendering over an Input: grants, own secrets, core boxes, the certificate, the bus's user list (issue 1364). Ported from mesh-control 6c2a8d62 plan.go renderingFor, grantsFor, certificateFor and core_box.go in their Reading and Foreseeing modes: nothing is made, kept or written; a stand-in is named foreseen, a core box to keep is returned, a consumer left out of a grant and a credential bound elsewhere are returned naming them. The bus's user list is an Input value the controller composes: the broker's account composer (internal/broker, about 900 lines that import the lease and the bus client) is not ported, see the report of this branch.
This commit is contained in:
@@ -0,0 +1,440 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// Mode says what a composition does about what a send would make. Both modes read what the Input holds and make nothing; they
|
||||
// differ as the controller's Reading and Foreseeing modes do.
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
// Reading composes what is held and leaves out an own secret the Input holds none for: that machine cannot be running it.
|
||||
Reading Mode = iota
|
||||
// Foreseeing composes an own secret the next send would make with a stand-in and names it foreseen, while one the send would
|
||||
// refuse (a bus credential nobody issued, a given secret under an old key) is refused here, in the same words.
|
||||
Foreseeing
|
||||
)
|
||||
|
||||
// ForeseenCertificate and ForeseenBusUsers stand for what the Input does not carry, in a composition asked ahead of the send.
|
||||
// Never sent.
|
||||
const (
|
||||
ForeseenCertificate = "foreseen: issued by the next send"
|
||||
ForeseenBusUsers = "foreseen: composed from the bus records by the next send"
|
||||
)
|
||||
|
||||
// grantsFor is every credential this node must create, because something elsewhere uses it (plan.go grantsFor): the mirror of
|
||||
// what a consumer is given. A consumer whose identity overflows the provision's bound is left out of the grants and returned
|
||||
// beside them (novox/hq ADR 0225); one whose resolution binds it to another provider is withdrawn and returned beside them
|
||||
// (issue 274). Every other is granted.
|
||||
func (c *composer) grantsFor(node string) ([]catalogue.Grant, []catalogue.Overflow, []catalogue.Unbound, []string, error) {
|
||||
var issued []Issued
|
||||
for _, row := range c.in.Issued {
|
||||
if row.Provider == node {
|
||||
issued = append(issued, row)
|
||||
}
|
||||
}
|
||||
onNetwork, err := c.whereEveryoneIs()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
out := make([]catalogue.Grant, 0, len(issued))
|
||||
var withheld []catalogue.Overflow
|
||||
var unbound []catalogue.Unbound
|
||||
var foreseen []string
|
||||
for _, s := range issued {
|
||||
resolved, err := c.planFor(s.Consumer)
|
||||
switch {
|
||||
case unresolvable(err):
|
||||
// Their set does not resolve. Skipped rather than fatal: this node is not the place to report another machine's problem.
|
||||
continue
|
||||
case err != nil:
|
||||
// The mesh could not be asked what they wanted, which is not the same as their wanting nothing.
|
||||
return nil, nil, nil, nil, fmt.Errorf("what %s asked of %s cannot be read: %w", s.Consumer, s.Provision, err)
|
||||
}
|
||||
plan := resolved.Plan
|
||||
values, asks, err := plan.ContributionsFrom(s.Provision, s.ConsumerModule, resolved.Settings)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
// A port in there is the consumer's software port until this: the assignment that moved it is that machine's.
|
||||
consumer, err := c.in.node(s.Consumer)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
values = catalogue.AtPublishedPort(values, s.ConsumerModule, consumer.Ports[s.ConsumerModule])
|
||||
from := s.ConsumerModule
|
||||
if !asks {
|
||||
// That module no longer wants this: left empty, which is how a login is withdrawn rather than kept working for ever.
|
||||
from = ""
|
||||
}
|
||||
if bound := plan.BindsFrom(s.Provision, s.ConsumerModule, s.Local); from != "" && !slices.Contains(bound, node) {
|
||||
// It still asks, and not of this node: withdrawn exactly as a credential nobody asks for, and said.
|
||||
from = ""
|
||||
unbound = append(unbound, catalogue.Unbound{Provision: s.Provision, Provider: node,
|
||||
Consumer: s.Consumer, Module: s.ConsumerModule, Local: s.Local, BoundTo: bound})
|
||||
}
|
||||
// The consumer's identity slug, from its own manifest, carried on the grant so the provider derives the same login the
|
||||
// consumer does (novox/hq ADR 0049).
|
||||
slug := ""
|
||||
for _, mm := range plan.Modules {
|
||||
if mm.Module == s.ConsumerModule {
|
||||
slug = mm.Slug
|
||||
break
|
||||
}
|
||||
}
|
||||
if from != "" {
|
||||
bound := boundOfGrant(plan, s, node)
|
||||
source := catalogue.IdentitySource(slug, s.ConsumerModule)
|
||||
if catalogue.CheckIdentityWithin(s.Consumer, source, bound) != nil {
|
||||
withheld = append(withheld, catalogue.Overflow{Provision: s.Provision, Provider: node,
|
||||
Consumer: s.Consumer, Module: s.ConsumerModule,
|
||||
Identity: catalogue.ConsumerIdentity(s.Consumer, source), Bound: bound})
|
||||
continue
|
||||
}
|
||||
}
|
||||
sealed := s.ForProvider
|
||||
if sealed == "" {
|
||||
sealed = catalogue.ForeseenSealed
|
||||
foreseen = append(foreseen, fmt.Sprintf("the grant of %s for %s on %s", s.Provision, s.ConsumerModule, s.Consumer))
|
||||
}
|
||||
out = append(out, catalogue.Grant{
|
||||
Provision: s.Provision, Consumer: s.Consumer, At: onNetwork[s.Consumer],
|
||||
From: from, Values: values, Slug: slug, Sealed: sealed, Local: s.Local})
|
||||
}
|
||||
return out, withheld, unbound, foreseen, nil
|
||||
}
|
||||
|
||||
// boundOfGrant is the identity bound the consumer's own resolution states for the requirement this grant answers. A requirement
|
||||
// not found there is held to the tightest bound the mesh knows rather than to none.
|
||||
func boundOfGrant(consumer catalogue.Resolution, s Issued, provider string) catalogue.IdentityBound {
|
||||
for _, n := range consumer.Needs {
|
||||
if n.Name == s.Provision && n.For == s.ConsumerModule && n.From == provider {
|
||||
return n.Identity
|
||||
}
|
||||
}
|
||||
return catalogue.DefaultIdentityBound
|
||||
}
|
||||
|
||||
// ownSecretRow is the row for one own secret of a module on a node.
|
||||
func (c *composer) ownSecretRow(node, module, name string) (OwnSecretRow, bool) {
|
||||
for _, r := range c.in.OwnSecrets {
|
||||
if r.Node == node && r.Module == module && r.Name == name {
|
||||
return r, true
|
||||
}
|
||||
}
|
||||
return OwnSecretRow{}, false
|
||||
}
|
||||
|
||||
// givenMember is one own secret a person gave on a machine and whether it is sealed to the key the machine holds now.
|
||||
type givenMember struct {
|
||||
Name string
|
||||
Sealed string
|
||||
Current bool
|
||||
}
|
||||
|
||||
// givenMembers is every member of a module's secret family given on a machine, by name (inv.GivenMembers): the mesh never
|
||||
// makes a member, so only a value a person gave is one; a machine with no sealing key holds none.
|
||||
func (c *composer) givenMembers(node Node, module, family string) []givenMember {
|
||||
if node.SealingKey == "" {
|
||||
return nil
|
||||
}
|
||||
prefix := catalogue.FamilyPrefix(family)
|
||||
var out []givenMember
|
||||
for _, r := range c.in.OwnSecrets {
|
||||
if r.Node == node.Name && r.Module == module && r.Origin == "accepted" && strings.HasPrefix(r.Name, prefix) {
|
||||
out = append(out, givenMember{Name: r.Name, Sealed: r.Sealed, Current: r.SealedTo == node.SealingKey})
|
||||
}
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
|
||||
return out
|
||||
}
|
||||
|
||||
// givenOwnSecret is an own secret issued outside the mesh as a person gave it on a machine (inv.GivenOwnSecret): not given when
|
||||
// the value held was made by the mesh or the machine has no sealing key.
|
||||
func (c *composer) givenOwnSecret(node Node, module, name string) (givenMember, bool) {
|
||||
if node.SealingKey == "" {
|
||||
return givenMember{}, false
|
||||
}
|
||||
r, ok := c.ownSecretRow(node.Name, module, name)
|
||||
if !ok || r.Origin != "accepted" {
|
||||
return givenMember{}, false
|
||||
}
|
||||
return givenMember{Name: name, Sealed: r.Sealed, Current: r.SealedTo == node.SealingKey}, true
|
||||
}
|
||||
|
||||
// moduleSecretIfIssued is the read half of the controller's SecretForModule (inv.ModuleSecretIfIssued): the secret when one is
|
||||
// held and sealed to the key the machine holds now, and otherwise not held. A key it was sealed to that the machine no longer
|
||||
// has is reported as absent rather than as an error.
|
||||
func (c *composer) moduleSecretIfIssued(node Node, module, name string) (sealed string, held bool) {
|
||||
if node.SealingKey == "" {
|
||||
return "", false
|
||||
}
|
||||
r, ok := c.ownSecretRow(node.Name, module, name)
|
||||
if !ok || r.SealedTo != node.SealingKey {
|
||||
return "", false
|
||||
}
|
||||
return r.Sealed, true
|
||||
}
|
||||
|
||||
// wouldMakeSecretForModule says what the send would do about an own secret without doing it: nil when it would make one (or one
|
||||
// is held), and otherwise the very refusal it would meet (inv.WouldMakeSecretForModule).
|
||||
func (c *composer) wouldMakeSecretForModule(node Node, module, name string) error {
|
||||
if node.SealingKey == "" {
|
||||
return fmt.Errorf("%s needs a secret and %s has no sealing key, so nothing can be sealed to it", module, node.Name)
|
||||
}
|
||||
r, ok := c.ownSecretRow(node.Name, module, name)
|
||||
switch {
|
||||
case !ok:
|
||||
return nil
|
||||
case r.SealedTo != node.SealingKey && r.Origin == "accepted":
|
||||
return fmt.Errorf("%s on %s holds %q, which was given to the mesh rather than made by it, and %s has "+
|
||||
"since generated a new sealing key. The mesh cannot make another; issue it again", module, node.Name, name, node.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// busCredentialIssued refuses an own secret called `broker` whose bus account nobody issued (novox/hq issue 203): the broker
|
||||
// credential is never invented, an account on the bus minted by `module issue`.
|
||||
func (c *composer) busCredentialIssued(node, module, name string) error {
|
||||
var user string
|
||||
switch name {
|
||||
case "broker":
|
||||
user = node + "." + module
|
||||
case ProvisionerSecret:
|
||||
user = "provisioner." + node
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if !slices.Contains(c.in.BusMinted, user) {
|
||||
return fmt.Errorf(
|
||||
"%s on %s has no bus credential: nothing was issued for %s, and a push "+
|
||||
"would seal a placeholder its process cannot read (novox/hq issue 203). "+
|
||||
"`module issue %s --node %s`, then push again",
|
||||
module, node, user, module, node)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// givenUnderAnOldKey is the refusal for a value a person gave sealed to a key the machine no longer holds.
|
||||
func givenUnderAnOldKey(module, node, name string) error {
|
||||
return fmt.Errorf("%s on %s holds %q, which was given to the mesh rather than made by it, and %s has "+
|
||||
"since generated a new sealing key. The mesh cannot make another; give it again", module, node, name, node)
|
||||
}
|
||||
|
||||
// ownSecretsFor is each module's own secrets as this node composes them (plan.go renderingFor): the needed ones sealed to the
|
||||
// node, and, in the Foreseeing mode, the ones the next send would make named foreseen. A module handed to the provisioner has its
|
||||
// secrets from the vault through the client, so none of them is composed here.
|
||||
func (c *composer) ownSecretsFor(node Node, plan catalogue.Resolution, handing catalogue.Rendering, mode Mode) (
|
||||
needed map[string]map[string]string, foreseen map[string]map[string]bool, err error) {
|
||||
needed = map[string]map[string]string{}
|
||||
foreseen = map[string]map[string]bool{}
|
||||
mark := func(module, name string) {
|
||||
if foreseen[module] == nil {
|
||||
foreseen[module] = map[string]bool{}
|
||||
}
|
||||
foreseen[module][name] = true
|
||||
}
|
||||
for _, m := range plan.Modules {
|
||||
if handing.Handing(m) {
|
||||
continue
|
||||
}
|
||||
// A secret family is never made (novox/hq ADR 0283): each member a person gave on this machine is placed, and one not
|
||||
// given is nothing -- the module says it waits for it.
|
||||
for _, family := range m.OwnSecrets.Families() {
|
||||
for _, g := range c.givenMembers(node, m.Module, family) {
|
||||
if _, fam, ok := m.OwnSecrets.Lookup(g.Name); !ok || fam != family {
|
||||
continue // a longer family's member, or a name no longer of this family
|
||||
}
|
||||
if !g.Current {
|
||||
continue
|
||||
}
|
||||
if needed[m.Module] == nil {
|
||||
needed[m.Module] = map[string]string{}
|
||||
}
|
||||
needed[m.Module][g.Name] = g.Sealed
|
||||
}
|
||||
}
|
||||
names := make([]string, 0)
|
||||
for name := range m.OwnSecrets.Plain() {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
own := m.OwnSecrets.Plain()[name]
|
||||
// An own secret issued outside the mesh is never made (novox/hq issue 401, ADR 0228; ADR 0283 for a family): only a
|
||||
// value a person gave is placed. One not given is nothing, and a value the mesh made for it before is not one either.
|
||||
if own.IssuedBy == catalogue.IssuedOutside {
|
||||
g, given := c.givenOwnSecret(node, m.Module, name)
|
||||
if !given || !g.Current {
|
||||
continue
|
||||
}
|
||||
if g.Sealed == "" {
|
||||
mark(m.Module, name)
|
||||
continue
|
||||
}
|
||||
if needed[m.Module] == nil {
|
||||
needed[m.Module] = map[string]string{}
|
||||
}
|
||||
needed[m.Module][name] = g.Sealed
|
||||
continue
|
||||
}
|
||||
sealed, held := c.moduleSecretIfIssued(node, m.Module, name)
|
||||
if !held && mode == Foreseeing {
|
||||
// Asked ahead of the send: what the send would do about it, by the send's own rules. Made by it -- a stand-in,
|
||||
// named; refused by it -- refused here, the same words (novox/hq issue 275).
|
||||
if err := c.busCredentialIssued(node.Name, m.Module, name); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := c.wouldMakeSecretForModule(node, m.Module, name); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
mark(m.Module, name)
|
||||
continue
|
||||
}
|
||||
if !held {
|
||||
// Never issued, so this machine cannot be running it: left out rather than invented.
|
||||
continue
|
||||
}
|
||||
if sealed == "" {
|
||||
// Held, and its value is not in the Input: composed with a stand-in, named.
|
||||
mark(m.Module, name)
|
||||
continue
|
||||
}
|
||||
if needed[m.Module] == nil {
|
||||
needed[m.Module] = map[string]string{}
|
||||
}
|
||||
needed[m.Module][name] = sealed
|
||||
}
|
||||
}
|
||||
return needed, foreseen, nil
|
||||
}
|
||||
|
||||
// CoreBoxWanted is a core credential whose box the Input does not hold for a machine: the next send asks the vault for it and
|
||||
// keeps the answer. It is returned for the caller to ask and keep; this package asks and keeps nothing.
|
||||
type CoreBoxWanted struct {
|
||||
Node string
|
||||
Module string
|
||||
Name string
|
||||
}
|
||||
|
||||
// coreModulesOf is every module of the plan on the core's list whose own secrets this machine's declaration carries, with their
|
||||
// plain own secret names, sorted: one handed to the provisioner has its secrets from the vault through the client, and one issued
|
||||
// outside the mesh is never the vault's to seal here.
|
||||
func coreModulesOf(modules []catalogue.Manifest, handed func(catalogue.Manifest) bool) map[string][]string {
|
||||
out := map[string][]string{}
|
||||
for _, m := range modules {
|
||||
if !catalogue.CarriesVaultSealed(m.Module) || (handed != nil && handed(m)) {
|
||||
continue
|
||||
}
|
||||
var names []string
|
||||
for name, own := range m.OwnSecrets.Plain() {
|
||||
if own.IssuedBy != catalogue.IssuedOutside {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
if len(names) > 0 {
|
||||
sort.Strings(names)
|
||||
out[m.Module] = names
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// sealingKeyValid says a machine's sealing key, recorded raw in base64, is a raw X25519 key.
|
||||
func sealingKeyValid(raw string) error {
|
||||
b, err := base64.StdEncoding.DecodeString(strings.TrimSpace(raw))
|
||||
if err != nil || len(b) != 32 {
|
||||
return errors.New("the machine's sealing key is not a raw X25519 key")
|
||||
}
|
||||
_, err = ecdh.X25519().NewPublicKey(b)
|
||||
return err
|
||||
}
|
||||
|
||||
// coreBoxesFor decides each core credential of a machine in a composition that sends nothing and asks no vault (core_box.go
|
||||
// coreBoxesOf, in the modes that are not a send): the kept box while it is sealed to the key the machine holds now, and otherwise a
|
||||
// stand-in that the next send replaces by asking the vault -- returned in wanted for the caller to ask and keep. A machine with no
|
||||
// sealing key, or one whose key is not a key, leaves its module out, said.
|
||||
func (c *composer) coreBoxesFor(node Node, modules []catalogue.Manifest, handing catalogue.Rendering) (
|
||||
sealed map[string]map[string]json.RawMessage, notPlaced map[string]string, wanted []CoreBoxWanted) {
|
||||
core := coreModulesOf(modules, func(m catalogue.Manifest) bool { return handing.Handing(m) })
|
||||
sealed, notPlaced = map[string]map[string]json.RawMessage{}, map[string]string{}
|
||||
mods := make([]string, 0, len(core))
|
||||
for m := range core {
|
||||
mods = append(mods, m)
|
||||
}
|
||||
sort.Strings(mods)
|
||||
for _, module := range mods {
|
||||
if node.SealingKey == "" {
|
||||
notPlaced[module] = fmt.Sprintf("%s has no sealing key, so the vault can seal %s's credentials to "+
|
||||
"nothing there (novox/hq ADR 0349 §16)", node.Name, module)
|
||||
continue
|
||||
}
|
||||
if err := sealingKeyValid(node.SealingKey); err != nil {
|
||||
notPlaced[module] = fmt.Sprintf("%s's credentials cannot be asked of the vault: %v", module, err)
|
||||
continue
|
||||
}
|
||||
boxes := map[string]json.RawMessage{}
|
||||
for _, name := range core[module] {
|
||||
var kept *CoreBox
|
||||
for i := range c.in.KeptCoreBoxes {
|
||||
k := &c.in.KeptCoreBoxes[i]
|
||||
if k.Node == node.Name && k.Module == module && k.Name == name {
|
||||
kept = k
|
||||
}
|
||||
}
|
||||
if kept != nil && kept.SealedTo == node.SealingKey && len(kept.Box) > 0 {
|
||||
boxes[name] = kept.Box
|
||||
continue
|
||||
}
|
||||
boxes[name] = catalogue.ForeseenVaultBox
|
||||
wanted = append(wanted, CoreBoxWanted{Node: node.Name, Module: module, Name: name})
|
||||
}
|
||||
sealed[module] = boxes
|
||||
}
|
||||
return sealed, notPlaced, wanted
|
||||
}
|
||||
|
||||
// certificateFor is what the mesh certifies about one machine's internal name (plan.go certificateFor): refused for a machine that
|
||||
// never told the mesh what key it serves with, and for one not on the private network, which has no name to be certified for.
|
||||
// The certificate and the authority are public values the Input may carry; without them stand-ins are composed, and named.
|
||||
func (c *composer) certificateFor(node Node) (certificate, authority string, foreseen bool, err error) {
|
||||
if node.ServingKey == "" {
|
||||
return "", "", false, fmt.Errorf(
|
||||
"%s wants a certificate and has never told the mesh what key it serves with; it "+
|
||||
"joins again to report one", node.Name)
|
||||
}
|
||||
where, err := c.whereEveryoneIs()
|
||||
if err != nil {
|
||||
return "", "", false, err
|
||||
}
|
||||
if where[node.Name] == "" {
|
||||
return "", "", false, fmt.Errorf(
|
||||
"%s wants a certificate and is not on the private network, so it has no name inside "+
|
||||
"the mesh to be certified for", node.Name)
|
||||
}
|
||||
certificate, authority = node.Certificate, c.in.Authority
|
||||
if certificate == "" || authority == "" {
|
||||
return ForeseenCertificate, ForeseenCertificate, true, nil
|
||||
}
|
||||
return certificate, authority, false, nil
|
||||
}
|
||||
|
||||
// holdsTheBus says whether a node's set includes the module that asks for the bus's user list.
|
||||
func holdsTheBus(modules []catalogue.Manifest) bool {
|
||||
for _, m := range modules {
|
||||
if m.BusUsers != "" && m.ClaimsSeat("mesh-broker") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// Rendered is everything a node's declaration is composed with (plan.go renderingFor), over an Input: the place half (Places)
|
||||
// and the credential half. Nothing was allocated, made, kept or written; what a send would do is returned for the caller to do.
|
||||
type Rendered struct {
|
||||
Rendering catalogue.Rendering
|
||||
// ForeseenPorts are the ports the mesh may assign that the Input holds none for ("<module>:<port>").
|
||||
ForeseenPorts []string
|
||||
// Foreseen are the stand-ins the composition used for what the Input does not hold: an own secret the next send makes
|
||||
// ("<module>/<name>"), a grant's sealed copy, the certificate and the bus's user list.
|
||||
Foreseen []string
|
||||
// CoreBoxesWanted are the core credentials whose box the send must ask the vault for and keep.
|
||||
CoreBoxesWanted []CoreBoxWanted
|
||||
// Withheld are the consumers the grants leave out because their identity overflows the provision's bound (novox/hq ADR 0225),
|
||||
// and Unbound those whose credential from this node is on record and whose own resolution binds them elsewhere (issue
|
||||
// 274). The controller says them; a check refuses a change that adds one (ADR 0446 clause 1).
|
||||
Withheld []catalogue.Overflow
|
||||
Unbound []catalogue.Unbound
|
||||
// Said are the lines the controller says while composing (the bus users left out for want of a credential).
|
||||
Said []string
|
||||
}
|
||||
|
||||
// Render composes the rendering of a node: the grants it must create, where it puts its ports, its own secrets, the vault's boxes,
|
||||
// its certificate, the names of the mesh, the bus's user list and its membership. The refusals are the controller's, in its words;
|
||||
// where more than one fault stands, the first reported may differ from the controller's, which interleaves the halves.
|
||||
func Render(in Input, node string, mode Mode) (Rendered, error) {
|
||||
return newComposer(in).render(node, mode)
|
||||
}
|
||||
|
||||
func (c *composer) render(nodeName string, mode Mode) (Rendered, error) {
|
||||
resolved, err := c.planFor(nodeName)
|
||||
if err != nil {
|
||||
return Rendered{}, err
|
||||
}
|
||||
node, err := c.in.node(nodeName)
|
||||
if err != nil {
|
||||
return Rendered{}, err
|
||||
}
|
||||
plan := resolved.Plan
|
||||
var out Rendered
|
||||
foreseen := func(s string) { out.Foreseen = append(out.Foreseen, s) }
|
||||
|
||||
grants, withheld, unbound, grantsForeseen, err := c.grantsFor(nodeName)
|
||||
if err != nil {
|
||||
return Rendered{}, err
|
||||
}
|
||||
placed, err := c.places(nodeName, resolved)
|
||||
if err != nil {
|
||||
return Rendered{}, err
|
||||
}
|
||||
r := placed.Rendering
|
||||
out.ForeseenPorts = placed.ForeseenPorts
|
||||
r.Grants, r.Withheld, r.Unbound = grants, withheld, unbound
|
||||
out.Withheld, out.Unbound = withheld, unbound
|
||||
for _, g := range grantsForeseen {
|
||||
foreseen(g)
|
||||
}
|
||||
|
||||
// And each module's own secrets: a module handed to the provisioner has its secrets from the vault through the client.
|
||||
handing := catalogue.Rendering{Hands: r.Hands, Provisioner: r.Provisioner}
|
||||
needed, foreseenOwn, err := c.ownSecretsFor(node, plan, handing, mode)
|
||||
if err != nil {
|
||||
return Rendered{}, err
|
||||
}
|
||||
r.Needed = needed
|
||||
if mode == Foreseeing || len(foreseenOwn) > 0 {
|
||||
r.Foreseen = foreseenOwn
|
||||
}
|
||||
var names []string
|
||||
for module, secrets := range foreseenOwn {
|
||||
for name := range secrets {
|
||||
names = append(names, module+"/"+name)
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, n := range names {
|
||||
foreseen(n)
|
||||
}
|
||||
|
||||
// The core's own credentials as the vault sealed them, for an engine that opens the vault's box.
|
||||
if node.Stated && node.Contract >= VaultBoxContract {
|
||||
sealed, notPlaced, wanted := c.coreBoxesFor(node, plan.Modules, handing)
|
||||
r.VaultSealed, r.NotPlaced = sealed, notPlaced
|
||||
out.CoreBoxesWanted = wanted
|
||||
}
|
||||
|
||||
// And a certificate for this machine's name inside the mesh, when anything on it asks.
|
||||
for _, m := range plan.Modules {
|
||||
if m.Certificate == nil {
|
||||
continue
|
||||
}
|
||||
certificate, authority, stood, err := c.certificateFor(node)
|
||||
if err != nil {
|
||||
return Rendered{}, err
|
||||
}
|
||||
r.Certificate, r.Authority = certificate, authority
|
||||
if stood {
|
||||
foreseen("the certificate and the authority of " + nodeName)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// The bus's user list, for the machine that runs the bus: names and hashes the Input carries, never passwords.
|
||||
if holdsTheBus(plan.Modules) {
|
||||
r.BusUsers = c.in.BusUsers
|
||||
if r.BusUsers == "" {
|
||||
r.BusUsers = ForeseenBusUsers
|
||||
foreseen("the bus's user list")
|
||||
}
|
||||
if len(c.in.BusMissing) > 0 {
|
||||
out.Said = append(out.Said, fmt.Sprintf("the bus's user list leaves out %d user(s) the mesh has minted no credential "+
|
||||
"for: %s. Each is a user that cannot connect until one is issued", len(c.in.BusMissing), strings.Join(c.in.BusMissing, ", ")))
|
||||
}
|
||||
}
|
||||
r.BusMembership = c.in.BusMemberships[nodeName]
|
||||
out.Rendering = r
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user