From 5497d362dd5715ef93ff0c1011fe3489f67e6cf8 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 29 Oct 2026 22:37:44 +0100 Subject: [PATCH] Refuse a provision's value unless the vault's credential name and generation are the ones its reference names (issue 1954 L6-E1; ADR 0570 clauses 1, 6) The value written is the one the provision names: a1-47 makes Reference.ID the vault's credential name. A mismatch in either refuses the write, naming both. Test: a row in TestAWriteAndAnApplyOfTheSameFileSerialise. --- cmd/mesh-host/filling_test.go | 10 +++++++++- cmd/mesh-host/provision_secrets.go | 8 +++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/cmd/mesh-host/filling_test.go b/cmd/mesh-host/filling_test.go index a25cc55..cc5df0e 100644 --- a/cmd/mesh-host/filling_test.go +++ b/cmd/mesh-host/filling_test.go @@ -50,6 +50,7 @@ func TestAWriteAndAnApplyOfTheSameFileSerialise(t *testing.T) { // The provision m reads, as mesh-provisioner keeps it, and the vault's value of it, sealed to this machine (faked: // the opener reads the box's "v"). provision := map[string]string{} + vaultName := "provision/postgres/db" secrets := provisionSecrets{node: "n1", read: func(_ context.Context, key string) ([]byte, bool, error) { v, ok := provision[key] @@ -60,7 +61,7 @@ func TestAWriteAndAnApplyOfTheSameFileSerialise(t *testing.T) { var a struct{ Credentials []map[string]any } _ = json.Unmarshal(raw, &a) c := a.Credentials[0] - c["generation"], c["sealed"] = 2, map[string]string{"v": "s3cret"} + c["name"], c["generation"], c["sealed"] = vaultName, 2, map[string]string{"v": "s3cret"} return json.Marshal(map[string]any{"node": "n1", "credentials": []any{c}}) }, open: func(sealed string) ([]byte, error) { @@ -149,9 +150,12 @@ func TestAWriteAndAnApplyOfTheSameFileSerialise(t *testing.T) { content string want string // the file after; "" keeps what was there mode os.FileMode + vault string // the vault's credential name, when not the one the provision names }{ {name: "a provision not provided is refused", state: provisions.Asked, content: "password=${secret:db}\n"}, {name: "provided with no reference is refused", state: provisions.Provided, content: "password=${secret:db}\n"}, + {name: "the vault's value is another than the provision names", state: provisions.Provided, ref: true, + vault: "pair/n2/db/n1/m", content: "password=${secret:db}\n"}, {name: "provided: filled at the write, 0600", state: provisions.Provided, ref: true, content: "password=${secret:db}\n", want: "password=s3cret\n", mode: 0o600}, {name: "no placeholder is written as it came, as the declaration says", content: "plain\n", want: "plain\n", mode: 0o644}, @@ -160,6 +164,10 @@ func TestAWriteAndAnApplyOfTheSameFileSerialise(t *testing.T) { if tc.state != "" { provision["n1.m.db"] = keyed(tc.state, tc.ref) } + vaultName = "provision/postgres/db" + if tc.vault != "" { + vaultName = tc.vault + } a := e.Write(context.Background(), uid, filling.Request{Path: path, Content: []byte(tc.content)}) got, _ := os.ReadFile(path) if tc.want == "" { diff --git a/cmd/mesh-host/provision_secrets.go b/cmd/mesh-host/provision_secrets.go index e9c32f7..887cc4c 100644 --- a/cmd/mesh-host/provision_secrets.go +++ b/cmd/mesh-host/provision_secrets.go @@ -108,6 +108,7 @@ func (p provisionSecrets) value(ctx context.Context, module, provision, local st Node string `json:"node"` Credentials []struct { asked + Name string `json:"name"` Generation int `json:"generation"` Sealed json.RawMessage `json:"sealed"` } `json:"credentials"` @@ -122,9 +123,10 @@ func (p provisionSecrets) value(ctx context.Context, module, provision, local st if c.asked != ask { continue } - if c.Generation < pr.Reference.Generation { - return nil, fmt.Errorf("the vault holds %s at generation %d and the provision names %d: not written until it has it", - key, c.Generation, pr.Reference.Generation) + // The value the provision names, by the vault's credential name and its generation (clause 6): another is refused. + if c.Name != pr.Reference.ID || c.Generation != pr.Reference.Generation { + return nil, fmt.Errorf("the vault answered %s generation %d for %s, and the provision names %s generation %d: "+ + "nothing is written until they agree", c.Name, c.Generation, key, pr.Reference.ID, pr.Reference.Generation) } value, err := p.open(string(c.Sealed)) if err != nil {