Key a provision's credential as the vault's own provision secret-<provision> and say what a provision depends on, so a provider waits for its credential and its own requirements (issue 1954, ADR 0570 clauses 3, 6)

This commit is contained in:
2026-10-29 22:43:56 +01:00
parent 161fba56ae
commit f701db0516
3 changed files with 82 additions and 0 deletions
+10
View File
@@ -1775,6 +1775,10 @@ func (m Manifest) marshalFields() ([]byte, error) {
return json.Marshal(keys)
}
// CredentialProvisionPrefix starts the name of a provision's credential, the vault's provision beside it (novox/hq ADR
// 0570 clause 6): `secret-<provision>`.
const CredentialProvisionPrefix = "secret-"
// SecretFile is one file a module is given a credential in: the local name it goes by inside
// the module (empty for the ordinary one-file case, where the requirement's name serves) and where.
type SecretFile struct {
@@ -1937,6 +1941,12 @@ func parseManifest(raw []byte, predefined PredefinedList) (Manifest, error) {
if !name.MatchString(p) {
problems = append(problems, fmt.Sprintf("%q is not a usable name to provide", p))
}
// A provision's credential is a provision of its own, `secret-<provision>` (novox/hq ADR 0570 clause 6, the
// lead 2026-10-29): no module provides a name of that form, so the two keys never meet.
if strings.HasPrefix(p, CredentialProvisionPrefix) {
problems = append(problems, fmt.Sprintf("%s provides %q; a name starting %q is the mesh's for a provision's "+
"credential, kept by the vault (novox/hq ADR 0570)", m.Module, p, CredentialProvisionPrefix))
}
// A bound says what keeps the name, so the refusal it causes can say it (ADR 0225); and it
// leaves room for the shortest identity the mesh makes, or it would refuse every consumer.
if id := offer.Identity; id != nil && !id.None {
+48
View File
@@ -9,6 +9,7 @@ import (
"encoding/json"
"fmt"
"regexp"
"sort"
"strings"
"time"
@@ -121,6 +122,53 @@ func VaultName(form string, p Provision) string {
return ""
}
// VaultModule is the provider of every provision's credential (novox/hq ADR 0570 clauses 1, 6).
const VaultModule = "mesh-vault"
// CredentialKey is the key of a provision's credential, itself a provision whose provider is the vault (clause 6; the
// key form the lead settled on 2026-10-29): `<consumer node>.<consumer>.secret-<provision>[.<local>]` for a value of the
// consumer's own (pair, issued, given), and `<provider node>.<provider>.secret-<provision>` for the provider's one
// shared value. False for a form with no vault value.
func CredentialKey(form string, consumer, provider End, provision, local string) (string, bool) {
name := catalogue.CredentialProvisionPrefix + provision
switch form {
case FormPair, FormIssued, FormGiven:
k, err := Key(consumer.Node, consumer.Module, name, local)
return k, err == nil
case FormShared:
k, err := Key(provider.Node, provider.Module, name, "")
return k, err == nil
}
return "", false
}
// DependsOn is what a provision waits for before its provider makes it (clause 3): its credential's key, where its form
// has a vault value, and the key of every provision its provider module itself requires on its own node, read from
// the provider's plan there (nil when the provider is not handed and has none). Sorted, none twice.
func DependsOn(consumer End, entry catalogue.PlanProvision, providerPlan *catalogue.Plan) []string {
seen := map[string]bool{}
var out []string
add := func(k string) {
if k != "" && !seen[k] {
seen[k] = true
out = append(out, k)
}
}
provider := End{Module: entry.ProviderModule, Node: entry.Provider}
if k, ok := CredentialKey(entry.CredentialForm, consumer, provider, entry.Provision, entry.Local); ok {
add(k)
}
if providerPlan != nil {
for _, own := range providerPlan.Provisions {
if k, err := Key(provider.Node, provider.Module, own.Provision, own.Local); err == nil {
add(k)
}
}
}
sort.Strings(out)
return out
}
// End is one end of a provision: a module on a node.
type End struct {
Module string `json:"module"`
+24
View File
@@ -82,6 +82,30 @@ func TestAProvisionIsAReferenceUnderItsConsumersKey(t *testing.T) {
if got := VaultName(FormPair, Provision{Provision: "p", Local: "r", Provider: End{Node: "a"}, Consumer: End{Node: "b", Module: "m"}}); got != "pair/a/p/b/m/r" {
t.Errorf("a local name's pair is %q", got)
}
// A provision waits for its credential, the vault's provision `secret-<provision>`, and for what its provider itself
// requires on its node (clause 3; the key form the lead settled on 2026-10-29).
keycloak, postgres := End{Module: "keycloak", Node: "novox"}, End{Module: "postgres", Node: "ace"}
providerPlan := &catalogue.Plan{Provisions: []catalogue.PlanProvision{{Provision: "backup-target", Provider: "ace", ProviderModule: "restic"}}}
for _, c := range []struct {
form string
plan *catalogue.Plan
want string
}{
{FormPair, nil, "novox.keycloak.secret-postgres-database"},
{FormShared, nil, "ace.postgres.secret-postgres-database"},
{FormPair, providerPlan, "ace.postgres.backup-target,novox.keycloak.secret-postgres-database"},
{"", nil, ""},
} {
entry := catalogue.PlanProvision{Provision: "postgres-database", Provider: postgres.Node, ProviderModule: postgres.Module,
CredentialForm: c.form}
if got := strings.Join(DependsOn(keycloak, entry, c.plan), ","); got != c.want {
t.Errorf("%q depends on %q, want %q", c.form, got, c.want)
}
}
if _, err := catalogue.ParseManifest([]byte(`{"module":"x","version":"1","provides":[{"name":"secret-db","scope":"mesh"}]}`)); err == nil ||
!strings.Contains(err.Error(), "secret-") {
t.Errorf("a provision named secret-*: %v", err)
}
if ProvisionsBucket != "mesh-provisioner_provisions" || PlansRead != "mesh-provisioner.plans" {
t.Errorf("the states are %s and %s", ProvisionsBucket, PlansRead)
}