Files
mesh-catalog/modules/mesh-vault/cmd/mesh-vault/askers.go
T

181 lines
8.3 KiB
Go

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.<node>`, 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, `<node>.<module>` 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/<node>/<module>/<name>` 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/<provider>/<provision>/<node>/<module>` (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/<provider>/<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/<provider>/<provision>/<consumer>/
// <module>[/<local>]` 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.<node>` (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, `<node>.<module>`: 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.<node>` and name is `own/<node>/<module>/<secret>`
// that the plan of module on node names, or `provision/<provider>/<provision>/<node>/<module>` 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.<node>); 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/<module>/<name>, "+
"the value a provider issued for one of its modules, provision/<provider>/<provision>/%s/<module>, or "+
"a provision's one credential, provision/<provider>/<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: "