package main import ( "context" "sort" "strings" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/compose" "git.novox.be/novox/mesh-sdk/go/overlay" "github.com/novox/mesh-controller/internal/inventory" ) // The second half of the adapter (issue 1383): the credential and build fields of the Input, read and never made. // readCredentialsAndBuilds fills what the first half left: the credentials, the own secrets, the vault's boxes, the bus, // each node's serving key and certificate, and the builds. func readCredentialsAndBuilds(ctx context.Context, open *stores, in *compose.Input, plans map[string]compose.Resolved, shape inputShape, busRefused *error) ([]inventory.PairCredential, error) { if err := readIdentity(ctx, open, in); err != nil { return nil, err } wouldMake, err := readCredentials(ctx, open, in, plans, busRefused) if err != nil { return nil, err } return wouldMake, readBuilds(ctx, open, in, plans, shape) } // readIdentity is the mesh's authority and each node's serving key and the certificate kept for it, for a mesh in which a // module asks for a certificate. It establishes and issues nothing: a certificate not kept yet is composed with a stand-in, and // issued by the send (certificateFor). func readIdentity(ctx context.Context, open *stores, in *compose.Input) error { needs := false for _, m := range in.Catalogue { needs = needs || m.Certificate != nil } if !needs { return nil } ident, err := open.Identity(ctx) if err != nil { return err } if certificate, known, err := ident.KnownAuthority(ctx); err != nil { return err } else if known { in.Authority = certificate } nodes, err := open.inventory.Nodes(ctx) if err != nil { return err } ids := map[string]string{} for _, n := range nodes { ids[n.Name] = n.ID } for i := range in.Nodes { n := &in.Nodes[i] if n.ServingKey, err = ident.ServingKeyOf(ctx, ids[n.Name]); err != nil { return err } if n.ServingKey == "" { continue } if certificate, known, err := ident.KeptCertificate(ctx, overlay.InternalName(n.Name), n.ServingKey); err != nil { return err } else if known { n.Certificate = certificate } } return nil } // readCredentials is how each pair credential a node takes from elsewhere stands, the credentials made for each provider's // consumers, the own secrets, the vault's boxes and the bus. It reads and never makes: a credential a send would make is // left out (the composition's would-make), a credential only a person gives and nobody gave is not-given, and one the send // would be refused is refused with the controller's words. func readCredentials(ctx context.Context, open *stores, in *compose.Input, plans map[string]compose.Resolved, busRefused *error) ([]inventory.PairCredential, error) { var wouldMake []inventory.PairCredential inv := open.inventory shelf := in.Catalogue nodeOf := map[string]compose.Node{} for _, n := range in.Nodes { nodeOf[n.Name] = n } names := make([]string, 0, len(plans)) for name := range plans { names = append(names, name) } sort.Strings(names) for _, name := range names { resolved := plans[name] nodeShelf := shelfWith(shelf, nodeOf[name].Kept) for _, need := range resolved.Plan.Needs { if need.ByRecord { continue } p := inventory.PairCredential{Name: need.Name, Consumer: name, ConsumerModule: need.For, Provider: need.From, Local: need.Local} if module, outside := issuedOutsideBy(nodeShelf, need); outside { p.Kind, p.ProviderModule = inventory.PairOutside, module } else if need.SharedOwn != "" { p.Kind, p.Own = inventory.PairShared, need.SharedOwn p.ProviderModule = sharedProviderModule(resolved.Plan, shelf, need) } state, err := inv.PairCredentialState(ctx, p) if err != nil { return nil, err } c := compose.Credential{Provision: need.Name, Consumer: name, ConsumerModule: need.For, Provider: need.From, Local: need.Local} switch state.State { case inventory.PairHeld: c.State, c.Sealed = compose.CredentialHeld, state.Sealed case inventory.PairNotGiven: c.State, c.Why = compose.CredentialNotGiven, state.Why case inventory.PairRefused: c.State, c.Why = compose.CredentialRefused, state.Why default: // Left out: a need with no entry is the credential the next send makes, and perform makes it. wouldMake = append(wouldMake, p) continue } in.Credentials = append(in.Credentials, c) } } // The credentials made for each provider's consumers, so the grants it must create are read from them. for _, n := range in.Nodes { rows, err := inv.SecretsFrom(ctx, n.Name) if err != nil { return nil, err } for _, s := range rows { in.Issued = append(in.Issued, compose.Issued{Provision: s.Name, Consumer: s.Consumer, ConsumerModule: s.ConsumerModule, Local: s.Local, Provider: n.Name, ForProvider: s.ForProvider}) } } own, err := inv.OwnSecretRows(ctx) if err != nil { return nil, err } for _, r := range own { in.OwnSecrets = append(in.OwnSecrets, compose.OwnSecretRow{Node: r.Node, Module: r.Module, Name: r.Name, Origin: r.Origin, SealedTo: r.SealedTo, Sealed: r.Sealed}) } for _, n := range in.Nodes { boxes, err := inv.KeptCoreBoxes(ctx, n.Name) if err != nil { return nil, err } for module, byName := range boxes { for name, b := range byName { in.KeptCoreBoxes = append(in.KeptCoreBoxes, compose.CoreBox{Node: n.Name, Module: module, Name: name, Generation: b.Generation, SealedTo: b.SealedTo, Box: b.Box}) } } } sort.Slice(in.KeptCoreBoxes, func(i, j int) bool { a, b := in.KeptCoreBoxes[i], in.KeptCoreBoxes[j] if a.Node != b.Node { return a.Node < b.Node } if a.Module != b.Module { return a.Module < b.Module } return a.Name < b.Name }) // The bus: its user list as the controller composes it, for the machine that runs it, and who the mesh has minted a // credential for. var bus []catalogue.Manifest for _, m := range shelf { if m.BusUsers != "" && m.ClaimsSeat("mesh-broker") { bus = append(bus, m) } } if len(bus) > 0 { list, missing, err := busUserList(ctx, inv, bus) if err != nil && !strings.Contains(err.Error(), "not one user has a credential") { return nil, err } if err != nil { // The machine that runs the bus is refused, as the controller's own composition refused it: a list that // refuses every connection in the mesh is never composed (refusedBus); the other machines compose. *busRefused = err } in.BusUsers, in.BusMissing = list, missing } if in.BusMinted, err = inv.BusMinted(ctx); err != nil { return nil, err } if in.BusMemberships, err = inv.BusMemberships(ctx); err != nil { return nil, err } return wouldMake, nil } // readBuilds is the build half of the Input: every repository the mesh built, each module's current build, and the build made // from a commit of a module for the machines whose node-engine reads them. func readBuilds(ctx context.Context, open *stores, in *compose.Input, plans map[string]compose.Resolved, shape inputShape) error { inv := open.inventory held, err := inv.Held(ctx) if err != nil { return err } for repository := range held { in.Built = append(in.Built, repository) } sort.Strings(in.Built) in.CurrentBuilds = make(map[string]string, len(shape.current)) for module, b := range shape.current { in.CurrentBuilds[module] = b.Commit } // The build made from each commit a declaration may carry, for the machines that read a build per module. for _, n := range in.Nodes { if !n.ReadsModules { continue } resolved, ok := plans[n.Name] if !ok { continue } for _, m := range resolved.Plan.Modules { for _, commit := range []string{shape.current[m.Module].Commit, shape.kept[n.Name][m.Module], shape.sent[n.Name][m.Module]} { if commit == "" { continue } if _, done := in.BuildIDs[m.Module][commit]; done { continue } build, err := inv.BuildOf(ctx, m.Module, commit) if err != nil { return err } if in.BuildIDs == nil { in.BuildIDs = map[string]map[string]string{} } if in.BuildIDs[m.Module] == nil { in.BuildIDs[m.Module] = map[string]string{} } in.BuildIDs[m.Module][commit] = build } } } return nil } // shelfWith is the shelf with each module a node keeps at the build it runs put in place of the current one: a copy. func shelfWith(shelf map[string]catalogue.Manifest, kept map[string]catalogue.Manifest) map[string]catalogue.Manifest { if len(kept) == 0 { return shelf } out := make(map[string]catalogue.Manifest, len(shelf)+len(kept)) for name, m := range shelf { out[name] = m } for name, m := range kept { out[name] = m } return out } // sharedProviderModule is which module answers a shared credential on the providing node: the one in the plan, else the first // the catalogue names (providerModuleOf, in an order that does not move between calls). func sharedProviderModule(plan catalogue.Resolution, shelf map[string]catalogue.Manifest, n catalogue.Needed) string { for _, m := range plan.Modules { if _, shared := m.SharedCredentialOf(n.Name); shared { return m.Module } } names := make([]string, 0, len(shelf)) for name := range shelf { names = append(names, name) } sort.Strings(names) for _, name := range names { if _, shared := shelf[name].SharedCredentialOf(n.Name); shared { return name } } return "" }