package catalogue import ( "encoding/json" "sort" "strconv" "strings" "time" ) // SettingsBy is the layers that apply to each module, keyed by module name. type SettingsBy map[string][]Layer // Generator works out a module's resources for one node, where they cannot be written in advance. type Generator interface { // Resources for this node. Absent means the node is not part of whatever this generates, // which is an ordinary answer rather than a failure — a machine assigned the module before it // has an address on the network is in exactly that state. Resources(node string) ([]map[string]any, bool, error) } // OpensPorts is a generator that also says what its resources accept connections on. // // **Separate from Generator, because most generators have nothing to say here** and requiring an // empty method of each would be a cost paid everywhere for one caller. // // It exists because a static field cannot express this. A hub accepts connections from every node // at other sites; a machine that is not a hub dials out and needs nothing open — and they are the // same module. `listens` in a manifest is one answer for every machine that runs it, so the // machine that most needs filtering, the one facing the public internet, was the one whose rule // set would have closed its own overlay. type OpensPorts interface { // Listens is what this node accepts on because of what was computed for it. Nothing is the // ordinary answer: most machines running a computed module open no port at all. Listens(node string) ([]Listening, error) } // Grant is one consumer's credential, on the machine that must create it. type Grant struct { // Provision is what was required. Provision string // Consumer is the node that will use it. Consumer string // From is the module on that machine which asked. // // **Part of who this credential is for, not a label** (novox/hq 04-ISSUES/022). Together with // Consumer it names one consumer; the node alone does not, because a machine routinely runs // several modules wanting the same thing. It is also what the provider names the role or the // bucket or the client after, so withdrawing one consumer does not take another's away. From string // Values are what that module contributed — the name it wants, and anything else the // provision's own vocabulary defines. Values map[string]any // Local is the name the credential goes by inside the consumer where it keeps several for one // provision (ADR 0094); empty for the ordinary one. The provider sees it as a holder of its own. Local string // Slug is the consumer module's identity slug, if it declared one — carried on the grant so the // provider side derives the same login the consumer does, even across nodes where the consumer's // manifest is not in view (novox/hq ADR 0049). Empty means "use the module name". Slug string // At is where the consuming machine is on the private network, empty if it is not on one. // // Passed in with the grant because it is a fact about another machine, and resolution answers // questions about one. A provider that must reach back to its consumer — a reverse proxy is // the whole reason this exists — otherwise has to know how the mesh names machines. At string // Sealed is the credential, closed to the providing node. Sealed string } // Rendering is everything needed to turn a resolution into the declaration a node is sent. type Rendering struct { // Certificate is what the mesh issued for this machine's internal name, and the mesh's own // certificate. Both public — the key they belong to never left the machine. Certificate string Authority string // Needed is each module's own secrets, sealed to this node, keyed by module and then by the // name the module gave it. Needed map[string]map[string]string // Foreseen is each own secret not made yet that the next send WILL make, keyed like Needed: a // composition asked ahead of the send (the self-check's D1) composes it with ForeseenSealed in // its place and lists it in Composed.Foreseen, rather than failing for a value only a send // makes. Nil on every composition that is sent, and on every other question, which then refuse // a secret not made with a *NotMadeError. Foreseen map[string]map[string]bool // VaultSealed is, for a module on the core's list (CoreCredentialModules), each own secret as the vault sealed it // to this machine (the mesh's sealed box, as the vault answered it), keyed like Needed (novox/hq ADR 0349 §16, // issue 755). Given only for a machine whose engine opens the box (VaultBoxContract): its file then carries the // box and never the controller's own sealed value. Nil everywhere else, and the composition is as it was. VaultSealed map[string]map[string]json.RawMessage // NotPlaced is each core module left out of this machine's declaration because the vault gave nothing for its // credential and no box was kept, with why (issue 755): the machine keeps what it holds of it, and a module not // yet there is not placed. Nil everywhere else. NotPlaced map[string]string // Mesh is every node's address on the private network, which is what a rule saying "from the // mesh" resolves to. Passed in for the same reason grants are: who else is on the network is // a fact about the mesh, and resolution answers questions about one machine. Mesh []string // Suffix is what a machine's internal name ends in, as the control plane composed Names — // `internal` unless the operator chose another — so a fact writing those names does not // compose it a second time. Suffix string // BusUsers is the mesh's composed user list, for the module holding `mesh-broker`. Empty on // every other node, and on this one until the controller has composed it. // // **Only the users, never the server's own settings**: those are the module's, in its image and // its mounts (Manifest.BusUsers). BusUsers string // BusMembership is this machine's membership for the bus the mesh is moving to, sealed to it // (design 28, task 5.2). Empty for a machine not being moved. Written as a file the host reads // after the declaration has applied, so the bus it names is standing before the machine leaves // the one it is on. BusMembership string // MeshRange is the private network's CIDR (the range node addresses are allocated from), for a // module that must name the whole mesh rather than one machine — an intrusion filter that must // never ban a tunnel peer, say. A per-mesh value the module cannot know, so it is carried here // and offered as ${machine:mesh-range}, the same way one machine's address is. MeshRange string // Accounts is each machine's operator account, by the same internal name Names uses (novox/hq // to-be 29). What an ssh Host block's `User` line is composed from; empty for a machine no // operator account is known on. Accounts map[string]string // Kept is every operator-sealed secret in the mesh, for a module that `keeps` them. Nil when // nothing on this node keeps them, or the mesh has no operator key. Kept *KeptExport // KeptRuns is, by module, the file of the export as the node runs it, for a composer that holds no export (a node // composing its own: the export is the controller's store's, issue 1126, C6): carried as it runs when Kept is nil. KeptRuns map[string]map[string]any // OutwardLinks is the links this machine reported as facing outside it, which the filter is // written around (novox/hq ADR 0140). Empty means the machine has not said, and the mesh // composes no filter for it rather than writing a rule around a link with no name. OutwardLinks []string // LocalNetworks is this machine's local networks (novox/hq ADR 0340): the networks it reported on a // link facing outside it that lie wholly in a private range (JudgeLocalNetworks), which a port // reached from `local-network` is admitted from beside the private network. Empty when it reported // none, and the rule set then says so rather than widening the port or dropping it. LocalNetworks []string // TunnelInterface is the interface the mesh's private network runs on, named here rather than // imported because the overlay package rests on this one. Traffic arriving on it is the mesh's, // not this machine's own guest, so the filter admits it only by a rule. TunnelInterface string // Foundation is the ports the mesh itself needs reachable on every machine, which no module // declares because the foundation is not a module (novox/hq 04-ISSUES/051 and 052). The broker // is the one that matters: a machine dials it to enrol, and a firewall derived only from // modules closes it. Foundation []int // Names is every machine's internal name and its address, for containers to be given. // // **A container does not inherit the machine's names**, so every internal name the mesh wrote // is invisible to what the machine runs. Given here rather than looked up on the machine, // because which machines exist is a fact about the mesh. Names map[string]string // Machines is only the machines, by the same internal name — the subset of Names that is a // node of this mesh rather than a name it was told to serve. Both matter and they are not the // same set: a container's hosts wants every name, so a routed name resolves to the proxy that // serves it, while a resolver told the mesh's suffix is authoritative for it answers from what // it is given and forwards nothing — so a routed name written there is a name nobody asks for, // standing beside the machines and looking as real as they do. Machines map[string]string // Zones is every zone a module in the mesh answers itself, where it is answered (novox/hq ADR // 0199): the mesh's resolver forwards each one there. Zones []ZoneAt // Holders is, for each replicated mesh seat, every machine holding it, by internal name and // private address (novox/hq ADR 0223) — the same shape as Machines. What a machine's resolver // file lists: every holder of the mesh's resolver, this machine first if it is one. Holders map[string]map[string]string Settings SettingsBy Generators map[string]Generator // Grants are the credentials this node must create, for the provisions it offers. Passed in // rather than resolved, because who consumes a node is a fact about the rest of the mesh and // resolution answers questions about one machine. Grants []Grant // Withheld is every consumer left out of Grants because its identity overflows the provision's // bound (novox/hq ADR 0225). Composed into nothing; carried so the machine's declaration can say // whom it does not serve, and why, beside what it does. Withheld []Overflow // Unbound is every consumer whose pair credential from this node is on record and whose own // resolution binds it elsewhere (novox/hq issue 274): never granted, carried to be said. Unbound []Unbound // Ports is where this machine puts what each module needs reachable, by module and by the // port the software itself uses (novox/hq ADR 0038). // // **The one place the number now lives.** A module used to write it three times — for the rule // set, for what a consumer is told, and for what the runtime publishes — and nothing checked // that the three agreed. They are all derived from this. Ports map[string]map[int]int // Adopted says the node is adopted (novox/hq ADR 0100): the firewall found on it stays in // force, so no module that loads a filter is declared there, and what the mesh needs // reachable is declared as openings, with its own ports guarded by a table that only refuses. Adopted bool // Given is the machine ports this node was given for its modules' ports, by module and by the // port the software uses (novox/hq ADR 0100) — the foundation's ports, as genesis chose them. // They win over anything the mesh would assign and over a manifest's own long-form mapping. Given map[string]map[int]int // Planned says this rendering composes the plans the seat mesh-provisioner keeps (compose.Planning). A contribution a // plan carries is then refused where its port is none the module listens on: no machine port stands for it, and its // provider dials what the plan says (issue 774, research 072 row 34). Planned bool // Taken is the modules taken on this adopted node (novox/hq ADR 0100). The guard is derived // from these only (ADR 0103): a port of a module assigned but not taken may still be the // predecessor's. Taken map[string]bool // Seats is where this machine put each mesh-scoped seat's holder, by seat and by the port the // holder's software uses (novox/hq 04-ISSUES/102) — read from the node's settings and // assignments for whichever module claims the seat, whether or not it is in this node's set. // What ${seat:…} answers with; see seat_into.go for why the answer may be absent. Seats map[string]map[int]int // ArtifactStore is the mesh's artifact store as this network reaches it (host:port) — the // node holding it and the port that node put it on — or empty when the mesh has none on its // network yet. Composed into every image and archive the mesh built, at this moment and never // stored (novox/hq 04-ISSUES/102). ArtifactStore string // SeatReach is where this machine reaches the holder of each mesh-scoped seat it may be asked // about (host:port), by seat: what ${seat::reach} answers with (novox/hq ADR 0222). Absent // when no holder is reachable yet; see seat_into.go for which seats are answered. SeatReach map[string]string // Built is every `/` the mesh has built. What tells a reference recorded // with an address — before references were kept without one — from an image a module runs // straight from a public registry. Built map[string]bool // DataRoot is where this node keeps the directories the mesh places for its modules // (novox/hq ADR 0112, to-be 27): a directory resource that states no path resolves to // //, and ${dir:} names that place from the module's own files, // mounts and environment. A node setting fixed at installation; empty means the default, // /var/lib — see dir_into.go. DataRoot string // ReadsHealth says this machine's node-engine reads a resource's `health` (novox/hq ADR 0240 Phase B: // its statement's contract is link.ReadinessContract or later). An older engine parses strictly and // refuses a field it does not know, whole — so to it the field is not sent, and what it runs is // judged by liveness alone. ReadsHealth bool // JudgesRoot says this machine's node-engine judges a user's declared `root` (novox/hq ADR 0266: its // statement's contract is link.RootContract or later). To an older, strict engine the field is not // sent, and the account it names is not judged — which the self-check says, as not judged. JudgesRoot bool // ReadsBusy says this machine's node-engine reads a container's `busy` (novox/hq ADR 0335: its statement's // contract is link.BusyContract or later). To an older, strict engine the field is not sent, and the // container is replaced at once, as before. ReadsBusy bool // Hands says this machine's node-engine writes a filled file on the provisioner client's word alone (its // statement's contract is link.HandedContract or later, design 57 P7), and Provisioner is the account the // provisioner's client runs as here, empty where none runs (novox/hq ADR 0349 §4). With both, a moved module's // templated files are handed to the client (handed.go); without either, it composes as before. Hands bool Provisioner string // Assigned is, by module, whether the vault judged it handed (AssignmentHands of the plan in its assignment), as the // vault answered the node composing itself; where it names a module, Handing follows it alone (issue 774). Nil, or a // module it does not name, is judged by the manifest and the machine, as the composer of the plans does. Assigned map[string]bool } // RootField is a user resource's field saying the account must never become root without a person // (novox/hq ADR 0266). const RootField = "root" // Composed is a declaration's resources and which module each came from. // // Owner is kept beside the resources because a resource id cannot be split back into its module: // a module's name may itself contain a dot. What the mesh adds of its own — an opening, the guard — // has no owner. // // Received is what each module on the machine is given for each requirement it receives — the same // contributions its received file is written from, kept beside it so the mesh can also issue them // on the bus in the module's membership (novox/hq ADR 0167). By module, then requirement. type Composed struct { Resources []map[string]any Owner map[string]string Received map[string]map[string][]Contribution // LeftOut is every module of this machine's set that was left out of its declaration, and // why (novox/hq ADR 0163, rule 6): a setting stored for it that its definition can no longer // compose. Its held things are kept and its containers untouched — the machine is told so — // and it is told everything else. LeftOut map[string]string // Foreseen is every own secret composed with ForeseenSealed in its place (Rendering.Foreseen), // as `module/name`, sorted: what the next send will make. Never set on a declaration that is // sent — a placeholder in a sealed file is a credential the process cannot read. Foreseen []string // Unplaced is every contribution the holder's template could not render into something, each // naming its module and why (novox/hq ADR 0255): that piece is left out and the machine is told // everything else. Push and plan say it; the merge gate refuses a change that adds one. Unplaced []string // HandedOnly is each file left out because only the provisioner's client fills it and the module is not handed on // this machine (a provider's consumers file, ADR 0373), said once in the plan. Not an unplaced contribution, which // the merge gate refuses: a module not handed composes as it did. HandedOnly []string // Filled is the path of every file handed to the provisioner, in the order composed: the declaration's // filled files (novox/hq ADR 0349 §4, §5). Plans is each handed module's plan on this machine, by module. // Both empty where nothing is handed, and the declaration is then what it was. Filled []string Plans map[string]Plan } // ForeseenSealed is what stands for an own secret a send will make, in a composition asked ahead of // the send. Not a sealed value: nothing composed with it may be sent. const ForeseenSealed = "foreseen: made by the next send" const BusMembershipPath = "/var/lib/mesh/membership-next.json" // mapping reads a container's port mapping — `[address:]outer:inner[/protocol]`, the address // possibly an IPv6 one in brackets — indexing from the end, so an address's own colons never // shift the ports. Not ok for a short form or anything that is not a mapping. func mapping(written string) (outer, inner int, address string, ok bool) { written = strings.TrimSpace(written) if cut := strings.LastIndex(written, "/"); cut >= 0 { written = written[:cut] } parts := strings.Split(written, ":") if len(parts) < 2 { return 0, 0, "", false } inner, err := strconv.Atoi(parts[len(parts)-1]) if err != nil { return 0, 0, "", false } outer, err = strconv.Atoi(parts[len(parts)-2]) if err != nil { return 0, 0, "", false } return outer, inner, strings.Join(parts[:len(parts)-2], ":"), true } // Contribution is one module telling the answer to a requirement what it needs from it. type Contribution struct { // From is the module that said it, so the provider and a person reading the file can tell // which route belongs to what. From string `json:"from"` // Node is the machine it said it from, empty when that is this one. // // A provision answered from anywhere in the mesh has consumers on other machines, and the // provider has to know who they are — a database told to create a password and not who for // cannot do anything with it. Contributions were node-local until this, which meant the one // case that most needed them was the one they did not reach. Node string `json:"node,omitempty"` // At is where that machine is on the private network, empty when it is not on one or when it // is this machine. // // The mesh knows it and a provider should not have to derive it. A reverse proxy is told // *send traffic to this consumer* and has to open a connection — so without this every // provider that reaches back to a consumer would have to know how the mesh names machines, // which is a convention leaking into every module that implements a provision. At string `json:"at,omitempty"` // As is who this consumer is: what the provider should call the login it creates. // // **Said by the mesh rather than invented by the provisioner** (novox/hq 04-ISSUES/023). It // used to be neither — the provisioner made a name, and the consumer, which has to present it // to authenticate, had no way to learn it. One derivation reaches both ends, so they agree by // construction. // Omitted when there is none. A contribution that is not a credential grant — a module // offering something to another on its own machine — has nobody to be identified to, and a // field that is always present and usually empty teaches a reader to ignore it. As string `json:"as,omitempty"` // Secret is the file on this machine holding that consumer's credential, sealed to it. // // Named rather than carried, for the same reason the private network's key is: the mesh // discarded the value and could not put it here if it wanted to. What is here is where to // find it. Secret string `json:"secret,omitempty"` // Values are the module's own, with settings applied. What the keys mean is agreed by the // requirement's name — everything providing `reverse-proxy` understands the same shape, which // is what makes swapping one for another cost nothing. Values map[string]any `json:"values"` // Derived is what this provider's own definition said it derives for this consumer, already // derived (novox/hq ADR 0202). // // **The provider is told, rather than recomputing it.** A served value may name the consumer's // identity — a bucket named for who is asking, a database prefixed with it — and before this // the rule lived twice: once in the provisioner's code, once transcribed into every consumer's // definition. The mesh fills the provider's own statement here and delivers the same filled // value to the consumer, so the two cannot disagree: there is no second computation to // disagree with. // // Only the keys that are per-consumer. The rest of what the provider serves is the same for // everyone and is in its own definition, where it already is. Derived map[string]any `json:"derived,omitempty"` } // Kept is one secret as the operator can recover it: where it belongs, and the value sealed to the // operator's key. Never a node's blob, and never a value. // // Two kinds, addressed the same way — by the node and module that hold it and the name they know // it by. An `own` secret is one a module has for itself; a `pair` secret is a credential the // module was granted for a provision it requires (`name` is the provision), and Provider says which // node grants it. type Kept struct { Node string `json:"node"` Module string `json:"module"` Name string `json:"name"` // Kind is `own` or `pair`. Kind string `json:"kind"` // Provider is the node granting a pair credential; empty for an own secret. Provider string `json:"provider,omitempty"` // Origin is `made` or `accepted` — whether the mesh minted it or a person supplied it. Origin string `json:"origin"` // Sealed is the value, sealed to the operator key named in Key. Sealed string `json:"sealed"` Key string `json:"key"` MadeAt time.Time `json:"made-at"` // Local is the credential's name inside the consumer where it holds several (ADR 0094). Local string `json:"local,omitempty"` } // KeptExport is what a person keeps beside the operator key, and what a vault keeps on its disk: // every operator-sealed copy, and the honest list of what has none. type KeptExport struct { Export int `json:"export"` OperatorKey string `json:"operator-key"` Fingerprint string `json:"fingerprint"` // Kept is sealed to OperatorKey. EarlierKey is sealed to a key the mesh has since replaced — // recoverable with that key, if the person still has it, and with nothing else. Unrecoverable // has no operator copy at all. Kept []Kept `json:"kept"` EarlierKey []Kept `json:"sealed-to-earlier-key,omitempty"` Unrecoverable []Kept `json:"unrecoverable,omitempty"` } // sortedKeys is map iteration made repeatable, which everything written to a machine needs. func sortedKeys[V any](m map[string]V) []string { out := make([]string, 0, len(m)) for k := range m { out = append(out, k) } sort.Strings(out) return out } func asPort(v any) (int, bool) { switch n := v.(type) { case int: return n, true case float64: return int(n), true } return 0, false } // PreparationArgument is how the mesh asks a module to prepare its state: one word, to the module's // own program, whatever that program is (novox/hq ADR 0135). // // **One word for every kind of module.** A module built as a Go binary receives it as its argument; // one built as a bundle receives it through the runtime, whose entry takes the same word. So the // mesh has one way of asking and a module has one way of answering, and neither learns the other's // shape. const PreparationArgument = "prepare" // endpointPorts is a module's endpoint names against the ports they listen on. func endpointPorts(m Manifest) map[string]int { out := map[string]int{} for _, l := range m.Listens { if name := strings.TrimSpace(l.Name); name != "" { out[name] = l.Port } } return out }