package main import ( "encoding/json" "fmt" "strings" ) // Who may ask `get` for which secret (novox/hq ADR 0349 §11, design 57 V4, issue 647). // // The rule is: a node's provisioner client, running as a service under a bus account of its own // (`provisioner.`, design 57 P3), for a secret that node's declaration names; or a module, for a secret its // manifest declares, only once its code runs under a bus account of its own (issue 569). The second does not exist // yet, so the vault answers the first alone (Declared), and refuses every other caller. // // **What a node's declaration names, as the vault can read it.** The vault cannot read a node's declaration, which // the node-engine keeps for root. The controller carries the provisioner's plan of each module it handed to the // provisioner in that module's assignment, `.` of the assignments mirror (mesh-controller internal/ // inventory Provisioning), the same value the node's client fills from; its `secrets` are the module's own secrets // the declaration names for its files there. So a client is handed `own///` only when the // asker is that node's client, the module is on that node and handed to the provisioner, and its plan names the // secret. A module only pulled in by another has a value there of its own, which names what requires it (issue 765), // and is read the same way. Everything else is refused, and the refusal recorded with why (vault.go). // // **A value a provider's software issued for one holder** (issue 757): the vault keeps it as // `provision////` (perholder.go, issue 749). A node's client is handed it when // the asker is that node's client (perholder.go's rule, before this one) and the module's plan there names that // provision from that provider among its `provisions`: the credentials of what it requires that its machine's // client fills from the vault. // // **The provision's one credential** (issue 677): `provision//` is handed to a node's client // when the vault made, adopted or recorded it for a holder on that node (pair.go) and that holder is still there, // handed to the provisioner: the provider's module on its machine, or a consumer whose plan there names the // provision from that provider (without a name, which is a credential per holder). // // **A consumer's own login** (issue 677's note 12, ADR 0048, ADR 0158 §11): `pair//// // [/]` is handed to that consumer's machine while its plan there names the provision from that // provider under that local name, and to the provider's machine while the provider is handed there; never to // another consumer. // Askers decides whether caller (the bus user the runtime proved) may have the secret name, and if not, // why not. type Askers interface { May(caller, name string) (bool, string) } // NoAsker allows nobody: what a vault with no rule given answers. type NoAsker struct{} // May refuses every caller. func (NoAsker) May(string, string) (bool, string) { return false, "the vault hands a secret to no asker without a rule of who may ask (novox/hq ADR 0349 §11)" } // AllowList is a stand-in rule: each caller, and the names it may have. type AllowList map[string][]string // May allows a caller only the names listed for it. func (l AllowList) May(caller, name string) (bool, string) { for _, n := range l[caller] { if n == name { return true, "" } } return false, "not on the list of who may ask for it" } // ProvisionerPrefix begins a node's provisioner client's bus user, `provisioner.` (mesh-controller // internal/broker, KindProvisioner). const ProvisionerPrefix = "provisioner." // AssignmentsRead is the controller's assignments mirror as this module reads it (its manifest's `reads`). const AssignmentsRead = "mesh-controller.assignments" // AssignmentReader reads one value of the assignments mirror, `.`: nil when there is none. type AssignmentReader func(key string) ([]byte, error) // Declared is the rule the running vault keeps: a node's provisioner client, for a secret of a module on its node // that the module's plan there names. type Declared struct { Read AssignmentReader // Store is the vault's store, whose record of who holds a provision's one credential (pair.go) the rule reads. Store *Store } // plannedSecrets is as much of an assignment's value as the rule reads: the plan's secrets, by name. type plannedSecrets struct { Node string `json:"node"` Module string `json:"module"` Provisioning *struct { Secrets []struct { Name string `json:"name"` } `json:"secrets"` // Provisions are what the module requires, each with its provider (issue 760); one with a name is a // credential the machine's client fills from the vault (issue 757). Provisions []struct { Name string `json:"name"` Provision string `json:"provision"` Provider string `json:"provider"` } `json:"provisions"` } `json:"provisioning"` } // May allows caller the secret name when caller is `provisioner.` and name is `own///` // that the plan of module on node names, or `provision////` whose provision from // that provider the plan names (issue 757). func (d Declared) May(caller, name string) (bool, string) { node, ok := strings.CutPrefix(caller, ProvisionerPrefix) if !ok || !namePart.MatchString(node) { return false, "the vault hands a secret only to a node's provisioner client under its own account " + "(provisioner.); a module's code is answered once it runs under an account of its own (issue 569)" } parts := strings.Split(name, "/") if len(parts) == 3 && parts[0] == "provision" { if why := sharedRefusal(d.Store, d.Read, node, parts[1], parts[2]); why != "" { return false, why } return true, "" } if (len(parts) == 5 || len(parts) == 6) && parts[0] == "pair" { if why := pairRowRefusal(d.Read, node, parts); why != "" { return false, why } return true, "" } var holderNode, module, secret, provider string switch { case len(parts) == 4 && parts[0] == "own": holderNode, module, secret = parts[1], parts[2], parts[3] case len(parts) == 5 && parts[0] == "provision": provider, secret, holderNode, module = parts[1], parts[2], parts[3], parts[4] default: return false, fmt.Sprintf("a node's provisioner client is handed a module's own secret, own/%s//, "+ "the value a provider issued for one of its modules, provision///%s/, or "+ "a provision's one credential, provision//; %s is none of them", node, node, name) } if holderNode != node { return false, fmt.Sprintf("%s is a secret of machine %s, and %s asks for machine %s alone", name, holderNode, caller, node) } if d.Read == nil { return false, "the vault cannot read the assignments, so it cannot tell what " + node + "'s declaration names" } raw, err := d.Read(node + "." + module) if err != nil { return false, fmt.Sprintf(notYetPrefix+"the assignment %s.%s could not be read, so what it names is not known: %v", node, module, err) } if raw == nil { return false, fmt.Sprintf("%s is not on %s: neither assigned there nor required by a module there", module, node) } var a plannedSecrets if err := json.Unmarshal(raw, &a); err != nil || a.Node != node || a.Module != module { return false, fmt.Sprintf("the assignment %s.%s is not the controller's", node, module) } if a.Provisioning == nil { return false, fmt.Sprintf("%s on %s is not handed to the provisioner, so its secrets reach it in its "+ "declaration (novox/hq ADR 0349 §17)", module, node) } if provider != "" { for _, p := range a.Provisioning.Provisions { if p.Name != "" && p.Provision == secret && p.Provider == provider { return true, "" } } return false, fmt.Sprintf("%s's plan on %s names no credential of %s from %s", module, node, secret, provider) } for _, s := range a.Provisioning.Secrets { if s.Name == secret { return true, "" } } return false, fmt.Sprintf("%s's plan on %s names no secret %s", module, node, secret) } // notYetPrefix begins a reason that is not a refusal (issue 928): what the rule needs could not be read just now, a // bus error rather than an answer. The vault says "holds no secret named ... yet", which the client waits on and asks // again, and records no refusal. const notYetPrefix = "not yet: "