compose: an offer of form none composes no credential and holds nothing
mesh/delivery merged: source.main moved to novox/mesh-sdk@d6c87ee1a7ae
mesh/delivery-group group fix/1126-provision-credentials merged: every member is merged
mesh/check pass c459f757308a on 036266daff85 | unit 6028 run, 142 kept, 16 skipped | integration 6091 run, 174 kept, 25 skipped | over bound: unit, in…
mesh/delivery merged: source.main moved to novox/mesh-sdk@d6c87ee1a7ae
mesh/delivery-group group fix/1126-provision-credentials merged: every member is merged
mesh/check pass c459f757308a on 036266daff85 | unit 6028 run, 142 kept, 16 skipped | integration 6091 run, 174 kept, 25 skipped | over bound: unit, in…
After the controller, nothing makes a pair row for a new consumer, so a need whose provider offers no credential (form none) was composed with a stand-in on both ends and the node-engine held the consumer (issue 1126: mesh-cli on ace, "stands in for the grant of mcp-endpoint"). The vault never mints for form none, so such a need now composes with no credential: the consumer's need carries none, the provider's grant carries the consumer and nothing sealed, neither is named foreseen, CredentialsNeeded asks the vault for neither, and no empty grant file is written. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -846,6 +846,11 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
|
||||
// and nothing would say so.
|
||||
continue
|
||||
}
|
||||
if g.Sealed == "" {
|
||||
// Its offer presents no credential (form none, issue 1126): nothing to write, and an empty credential
|
||||
// file would be read as one that failed. Who asked still reaches the provider in its contributions.
|
||||
continue
|
||||
}
|
||||
first = append(first, ownedBy(r.provisionsAs(m), map[string]any{
|
||||
// One file per holder — the consumer's module with its local name after it
|
||||
// where it keeps several (ADR 0094); the lab found two files with one id.
|
||||
|
||||
@@ -66,6 +66,23 @@ func TestAGrantSecretIsOwnedByTheAccountThatProvisions(t *testing.T) {
|
||||
"root-owned 0600 file is one it cannot read — which is silent, because the harness "+
|
||||
"calls it \"not readable yet\"", fmtPath(secret), got, "operator")
|
||||
}
|
||||
// A grant whose offer presents no credential (form none, issue 1126) carries nothing sealed: no secret file is
|
||||
// written for it, as none is for a need with no credential, and the provider still learns who asked.
|
||||
out, err = r.Declaration(Rendering{Grants: []Grant{{
|
||||
Provision: "mongodb-database", Consumer: "workstation", From: "photos", Slug: "photos", Values: map[string]any{}}}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
asked := false
|
||||
for _, res := range out {
|
||||
if res["type"] == "file" && strings.HasSuffix(fmtPath(res), ".secret") {
|
||||
t.Errorf("a grant with no credential writes %v", res)
|
||||
}
|
||||
asked = asked || fmtPath(res) == "/var/lib/mongodb/grants/mesh.json"
|
||||
}
|
||||
if !asked {
|
||||
t.Errorf("the provider no longer learns who asked: %v", out)
|
||||
}
|
||||
}
|
||||
|
||||
// And a provider whose code still runs in a container keeps the owner it declares, so this
|
||||
|
||||
@@ -372,7 +372,7 @@ func TestWhatAModuleOfItsOwnAccountReadsIsItsAccounts(t *testing.T) {
|
||||
with := Rendering{ArtifactStore: "anchor.internal:5101",
|
||||
Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}, "vault": {"broker": "own"}},
|
||||
Kept: &KeptExport{Export: 1, OperatorKey: "k"},
|
||||
Grants: []Grant{{Provision: "secret", Consumer: "anchor", From: "telegram",
|
||||
Grants: []Grant{{Provision: "secret", Consumer: "anchor", From: "telegram", Sealed: "sealed-to-anchor",
|
||||
Values: map[string]any{"name": "telegram"}}}}
|
||||
goRuntime := Manifest{Module: RuntimeModule, Version: "1",
|
||||
OwnSecrets: OwnSecrets{"broker": {Path: "/var/lib/mesh/" + RuntimeModule + "/broker"}},
|
||||
|
||||
@@ -105,7 +105,12 @@ func (c *composer) grantsFor(node string) ([]catalogue.Grant, []catalogue.Overfl
|
||||
}
|
||||
}
|
||||
sealed := s.ForProvider
|
||||
if sealed == "" {
|
||||
switch {
|
||||
case credentiallessRow(plan, s, node):
|
||||
// Its offer presents no credential (form none): the grant carries the consumer and nothing sealed, and
|
||||
// nothing stands in for it, so neither end waits for a value nobody makes (issue 1126).
|
||||
sealed = ""
|
||||
case sealed == "":
|
||||
sealed = catalogue.ForeseenSealed
|
||||
foreseen = append(foreseen, fmt.Sprintf("the grant of %s for %s on %s", s.Provision, s.ConsumerModule, s.Consumer))
|
||||
}
|
||||
@@ -116,6 +121,18 @@ func (c *composer) grantsFor(node string) ([]catalogue.Grant, []catalogue.Overfl
|
||||
return out, withheld, unbound, foreseen, nil
|
||||
}
|
||||
|
||||
// credentialless is whether a need's provider offers it with no credential (catalogue.CredentialFormNone): the vault
|
||||
// mints nothing for it, so nothing is asked of the vault, sealed into a grant or a file, or stood in for (issue 1126).
|
||||
func credentialless(n catalogue.Needed) bool { return n.CredentialForm == catalogue.CredentialFormNone }
|
||||
|
||||
// credentiallessRow is whether the consumer's resolution answers row's need from provider with an offer of form none.
|
||||
func credentiallessRow(consumer catalogue.Resolution, row Issued, provider string) bool {
|
||||
return slices.ContainsFunc(consumer.Needs, func(n catalogue.Needed) bool {
|
||||
return n.Name == row.Provision && n.For == row.ConsumerModule && n.Local == row.Local && n.From == provider &&
|
||||
credentialless(n)
|
||||
})
|
||||
}
|
||||
|
||||
// rowsFor is every pair credential this node's declaration must carry a grant for, in the order the controller reads them
|
||||
// (SecretsFrom: by provision, consumer, consumer module and local name): the ones the Input says were made, and the ones the same send
|
||||
// would make before it composes this node -- every need of a node that resolves (this one too), whose provider is this node, that is not
|
||||
|
||||
@@ -19,7 +19,8 @@ type CredentialKey struct {
|
||||
// as a consumer, each need of its resolution a provider's credential answers (a need answered by a record is a licence's,
|
||||
// not a pair's); as a provider, each credential it must carry a grant for (rowsFor). A node that composes its own asks the
|
||||
// vault for exactly these, sealed to its key, and composes with them; one it does not get is stood in for, which the
|
||||
// node's own apply refuses (CompositionsApplied).
|
||||
// node's own apply refuses (CompositionsApplied). A need whose provider offers it with no credential (form none) is
|
||||
// in neither list: the vault mints nothing for it (issue 1126).
|
||||
func CredentialsNeeded(in Input, node string) (asConsumer, asProvider []CredentialKey, err error) {
|
||||
c := newComposer(in)
|
||||
resolved, err := c.resolution(node)
|
||||
@@ -27,7 +28,7 @@ func CredentialsNeeded(in Input, node string) (asConsumer, asProvider []Credenti
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, n := range resolved.Needs {
|
||||
if n.ByRecord || n.From == "" {
|
||||
if n.ByRecord || n.From == "" || credentialless(n) {
|
||||
continue
|
||||
}
|
||||
asConsumer = append(asConsumer, CredentialKey{Provision: n.Name, Consumer: node, ConsumerModule: n.For,
|
||||
@@ -38,6 +39,10 @@ func CredentialsNeeded(in Input, node string) (asConsumer, asProvider []Credenti
|
||||
return nil, nil, fmt.Errorf("compose: the credentials %s provides: %w", node, err)
|
||||
}
|
||||
for _, r := range rows {
|
||||
// A grant whose offer presents no credential (form none) has none to ask for (issue 1126).
|
||||
if consumer, err := c.planFor(r.Consumer); err == nil && credentiallessRow(consumer.Plan, r, node) {
|
||||
continue
|
||||
}
|
||||
asProvider = append(asProvider, CredentialKey{Provision: r.Provision, Consumer: r.Consumer,
|
||||
ConsumerModule: r.ConsumerModule, Provider: node, Local: r.Local})
|
||||
}
|
||||
|
||||
@@ -384,30 +384,74 @@ func TestNoSecretValueOfTheInputIsInWhatTheCompositionSays(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Round 1, finding 2: a consumer the same send adds has no credential in the Input yet, and the provider's declaration of that send
|
||||
// carries its grant, composed with a stand-in and named foreseen.
|
||||
func TestAConsumerNoCredentialWasMadeForYetIsGrantedWithAStandInAndNamedForeseen(t *testing.T) {
|
||||
in := aGrantMesh(t)
|
||||
in.Issued = nil
|
||||
got := rendered(t, in, "anchor", Foreseeing)
|
||||
if len(got.Rendering.Grants) != 1 {
|
||||
t.Fatalf("grants are %+v", got.Rendering.Grants)
|
||||
}
|
||||
g := got.Rendering.Grants[0]
|
||||
if g.Provision != "s3-bucket" || g.Consumer != "laptop" || g.From != "files" || g.Sealed != catalogue.ForeseenSealed {
|
||||
t.Errorf("the grant is %+v", g)
|
||||
}
|
||||
if !has(got.Foreseen, "the grant of s3-bucket for files on laptop") {
|
||||
t.Errorf("foreseen %v", got.Foreseen)
|
||||
}
|
||||
// Only the provider's own: the consumer is granted nothing, and nothing in the Input changed.
|
||||
if len(rendered(t, in, "laptop", Foreseeing).Rendering.Grants) != 0 {
|
||||
t.Error("the consumer is granted its own credential")
|
||||
}
|
||||
if len(in.Issued) != 0 {
|
||||
t.Error("a row was added to the Input")
|
||||
// A consumer no credential was made for yet is granted with a stand-in and named foreseen, for every form whose credential
|
||||
// the vault mints or takes in; an offer of form none has no credential (issue 1126, 2026-11-02: mesh-cli on ace held for
|
||||
// "the grant of mcp-endpoint", whose provider node-tools offers form none): its grant carries the consumer and no
|
||||
// credential, nothing stands in on either side, and neither end asks the vault for one.
|
||||
func TestAConsumerNoCredentialWasMadeForYetIsGrantedWithAStandInUnlessItsOfferHasNone(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
form string
|
||||
cred *catalogue.OfferCredential
|
||||
}{
|
||||
{"undeclared", nil},
|
||||
{catalogue.CredentialFormPair, &catalogue.OfferCredential{Form: catalogue.CredentialFormPair}},
|
||||
{catalogue.CredentialFormShared, &catalogue.OfferCredential{Own: "access-key"}},
|
||||
{catalogue.CredentialFormNone, &catalogue.OfferCredential{Form: catalogue.CredentialFormNone}},
|
||||
} {
|
||||
in := aGrantMesh(t)
|
||||
in.Issued = nil
|
||||
provider := in.Catalogue["objects"]
|
||||
provider.Provides[0].Credential = tc.cred
|
||||
if tc.cred != nil && tc.cred.Own != "" {
|
||||
provider.OwnSecrets = catalogue.OwnSecrets{"access-key": {Path: "/var/lib/mesh/objects/access-key"}}
|
||||
}
|
||||
in.Catalogue["objects"] = provider
|
||||
none := tc.form == catalogue.CredentialFormNone
|
||||
got := rendered(t, in, "anchor", Foreseeing)
|
||||
if len(got.Rendering.Grants) != 1 {
|
||||
t.Fatalf("%s: grants are %+v", tc.form, got.Rendering.Grants)
|
||||
}
|
||||
g, wantSealed := got.Rendering.Grants[0], catalogue.ForeseenSealed
|
||||
if none {
|
||||
wantSealed = ""
|
||||
}
|
||||
if g.Provision != "s3-bucket" || g.Consumer != "laptop" || g.From != "files" || g.Sealed != wantSealed {
|
||||
t.Errorf("%s: the grant is %+v", tc.form, g)
|
||||
}
|
||||
if has(got.Foreseen, "the grant of s3-bucket for files on laptop") == none {
|
||||
t.Errorf("%s: foreseen %v", tc.form, got.Foreseen)
|
||||
}
|
||||
consumer, asProvider, err := CredentialsNeeded(in, "laptop")
|
||||
_, anchorProvides, err2 := CredentialsNeeded(in, "anchor")
|
||||
if err != nil || err2 != nil || len(asProvider) != 0 || (len(consumer) == 0) != none || (len(anchorProvides) == 0) != none {
|
||||
t.Errorf("%s: laptop asks %v, anchor provides %v (%v %v)", tc.form, consumer, anchorProvides, err, err2)
|
||||
}
|
||||
resolved, err := Resolve(in, "laptop")
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", tc.form, err)
|
||||
}
|
||||
if n := theFilesNeed(t, resolved); (n.Sealed == "") != none || (len(resolved.Foreseen) == 0) != none {
|
||||
t.Errorf("%s: the consumer's credential is %q, foreseen %v", tc.form, n.Sealed, resolved.Foreseen)
|
||||
}
|
||||
if lap := rendered(t, in, "laptop", Foreseeing); none && len(lap.Foreseen) != 0 {
|
||||
t.Errorf("%s: the consumer stands in for %v", tc.form, lap.Foreseen)
|
||||
}
|
||||
// Only the provider's own: the consumer is granted nothing, and nothing in the Input changed.
|
||||
if len(rendered(t, in, "laptop", Foreseeing).Rendering.Grants) != 0 {
|
||||
t.Errorf("%s: the consumer is granted its own credential", tc.form)
|
||||
}
|
||||
if len(in.Issued) != 0 {
|
||||
t.Errorf("%s: a row was added to the Input", tc.form)
|
||||
}
|
||||
// A row made long ago for an offer of form none (the controller's) carries no credential either.
|
||||
in.Issued = []Issued{{Provision: "s3-bucket", Consumer: "laptop", ConsumerModule: "files", Provider: "anchor", ForProvider: "sealed-to-anchor"}}
|
||||
if g := rendered(t, in, "anchor", Foreseeing).Rendering.Grants; none && (len(g) != 1 || g[0].Sealed != "") {
|
||||
t.Errorf("%s: an old row's grant is %+v", tc.form, g)
|
||||
}
|
||||
}
|
||||
// A credential only a person gives and nobody has is no grant.
|
||||
in := aGrantMesh(t)
|
||||
in.Issued = nil
|
||||
in.Credentials = []Credential{{Provision: "s3-bucket", Consumer: "laptop", ConsumerModule: "files", Provider: "anchor", State: CredentialNotGiven}}
|
||||
if got := rendered(t, in, "anchor", Foreseeing); len(got.Rendering.Grants) != 0 {
|
||||
t.Errorf("a credential nobody gave is granted: %+v", got.Rendering.Grants)
|
||||
|
||||
@@ -118,6 +118,10 @@ func (c *composer) planForNow(nodeName string) (Resolved, error) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
if credentialless(n) {
|
||||
// Its provider's offer presents no credential (form none): nothing is sealed, made or stood in for (issue 1126).
|
||||
continue
|
||||
}
|
||||
cred, held := c.credential(n, nodeName)
|
||||
_, outside := issuedOutsideBy(shelf, n)
|
||||
switch {
|
||||
|
||||
Reference in New Issue
Block a user