mesh/merge-gate head 1bc5719b8b85 merged into trunk a2d2af00dc5d beside mesh-host#116@bff1ac14 — pass, with a note: builds mesh-sdk (+40 dependent(s)) → ac…
mesh/repo-check head 1bc5719b8b85 merged into trunk a2d2af00dc5d beside mesh-host#116@bff1ac14 — pass: its merge-check.sh passed, each part in its toolchai…
mesh/delivery delivered
mesh/delivery-group group release/57-config-and-provisioning delivered: every member is delivered
73 lines
2.9 KiB
Go
73 lines
2.9 KiB
Go
package provider
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
)
|
|
|
|
// The provider's consumers' credentials, from the file its machine's provisioner client fills (novox/hq ADR 0373,
|
|
// issue 679).
|
|
//
|
|
// **The provider never asks the vault.** A provider in the tool runner has no account of its own, and the vault
|
|
// hands a secret only to a node's provisioner client or to a module under an account of its own (ADR 0349 clause
|
|
// 11, ADR 0370 clause 2). So the provider's template names `${consumers:<provision>}`, and its machine's client
|
|
// fills that file with one entry per consumer bound to it for that provision, each credential asked of the vault in
|
|
// the client's own name; the file is the provider's alone (mode 0600, owned). ConsumersFile is that file as the
|
|
// helper's Vault: it reads the file on each ask, so a consumer that binds or a credential the vault remakes reaches
|
|
// the provider at its next pass.
|
|
|
|
// ConsumerCredential is one entry of the file: the consumer, the vault's name of its credential, and its value.
|
|
type ConsumerCredential struct {
|
|
// Consumer and Module are the consumer's machine and module, Local its local name for the provision, if any.
|
|
Consumer string `json:"consumer"`
|
|
Module string `json:"module"`
|
|
Local string `json:"local,omitempty"`
|
|
// Credential is the vault's name of the consumer's credential, PairName's.
|
|
Credential string `json:"credential"`
|
|
// Value is the credential itself.
|
|
Value string `json:"value"`
|
|
}
|
|
|
|
// ConsumersFile answers a credential from the files the provider machine's client fills: one per provision whose
|
|
// template names `${consumers:<provision>}`, each a JSON list of ConsumerCredential.
|
|
type ConsumersFile struct {
|
|
Paths []string
|
|
}
|
|
|
|
// Value is the credential named name, from the first file carrying it. A file not there yet is passed over; one not
|
|
// in the client's form is an error, never read as no consumers.
|
|
func (f ConsumersFile) Value(_ context.Context, name string) ([]byte, error) {
|
|
var missing []string
|
|
for _, at := range f.Paths {
|
|
raw, err := os.ReadFile(at)
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
missing = append(missing, at)
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the consumers' file %s: %w", at, err)
|
|
}
|
|
var entries []ConsumerCredential
|
|
if err := json.Unmarshal(raw, &entries); err != nil {
|
|
return nil, fmt.Errorf("the consumers' file %s is not the provisioner client's list of consumers", at)
|
|
}
|
|
for _, e := range entries {
|
|
if e.Credential == name {
|
|
if e.Value == "" {
|
|
return nil, fmt.Errorf("the consumers' file %s carries %s with no value", at, name)
|
|
}
|
|
return []byte(e.Value), nil
|
|
}
|
|
}
|
|
}
|
|
if len(missing) == len(f.Paths) {
|
|
return nil, fmt.Errorf("%s: no consumers' file is filled yet (%v)", name, f.Paths)
|
|
}
|
|
return nil, fmt.Errorf("%s is in no consumers' file: the vault holds none for it yet, or its machine's client "+
|
|
"has not filled it", name)
|
|
}
|