181 lines
8.3 KiB
Go
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: "
|