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 (":"). ForeseenPorts []string // Foreseen are the stand-ins the composition used for what the Input does not hold: an own secret the next send makes // ("/"), 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 // And what the composition found on the way, which the controller writes to its log: a machine not counted as on the network. out.Said = c.saidBeside(out.Said) return out, nil }