The node's composer asks the vault for the pair credentials its composition reads, as consumer and provider, sealed to its key (issue 1126, ADR 0550 §5)
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: shanks: novox/mesh-catalog#505: checks/placement/go.mod names mesh-sdk 91413de33516, which is neither on mesh-sdk's trunk nor the g…

This commit is contained in:
2026-10-26 19:21:22 +01:00
parent 1241ee7041
commit 1e38c42646
5 changed files with 97 additions and 8 deletions
+65 -4
View File
@@ -39,9 +39,9 @@ type Env struct {
Get func(ctx context.Context, bucket, key string) ([]byte, error)
Mesh frombus.Mesh
Say func(string)
// Own asks the vault's `own` in this node's engine's name: its modules' own secrets sealed to its key (ADR 0550 §5).
// Nil asks nothing, and a module with an own secret does not compose.
Own func(ctx context.Context) (json.RawMessage, error)
// Own asks the vault's `own` in this node's engine's name, with the arguments given: its modules' own secrets and the
// pair credentials asked, sealed to its key (ADR 0550 §5). Nil asks nothing, and a module with either does not compose.
Own func(ctx context.Context, args map[string]any) (json.RawMessage, error)
}
// Composer composes the node and is the agent's watcher of its compositions (agent.Env.Composed).
@@ -511,7 +511,7 @@ func (r reader) OwnSecrets(ctx context.Context, node string) ([]compose.OwnSecre
if node != r.c.env.Node || r.c.env.Own == nil {
return nil, nil
}
raw, err := r.c.env.Own(ctx)
raw, err := r.c.env.Own(ctx, map[string]any{})
if err != nil {
return nil, fmt.Errorf("the vault's own: %w", err)
}
@@ -547,3 +547,64 @@ func (r reader) OwnSecrets(ctx context.Context, node string) ([]compose.OwnSecre
}
return out, nil
}
func (r reader) Credentials(ctx context.Context, node string, asConsumer, asProvider []compose.CredentialKey) ([]compose.Credential, []compose.Issued, error) {
if node != r.c.env.Node || r.c.env.Own == nil {
return nil, nil, nil
}
type asked struct {
Role string `json:"role"`
compose.CredentialKey
}
var ask []asked
for _, k := range asConsumer {
ask = append(ask, asked{Role: "consumer", CredentialKey: k})
}
for _, k := range asProvider {
ask = append(ask, asked{Role: "provider", CredentialKey: k})
}
raw, err := r.c.env.Own(ctx, map[string]any{"credentials": ask})
if err != nil {
return nil, nil, fmt.Errorf("the vault's own: %w", err)
}
var a struct {
Node string `json:"node"`
Key string `json:"key"`
Credentials []struct {
Role string `json:"role"`
compose.CredentialKey
Name string `json:"name"`
Generation int `json:"generation"`
Sealed json.RawMessage `json:"sealed"`
} `json:"credentials"`
}
if err := json.Unmarshal(raw, &a); err != nil {
return nil, nil, fmt.Errorf("the vault's own answer is not one: %w", err)
}
if a.Node != node {
return nil, nil, fmt.Errorf("the vault answered %q's credentials to %s", a.Node, node)
}
r.c.mu.Lock()
defer r.c.mu.Unlock()
var creds []compose.Credential
var issued []compose.Issued
for _, c := range a.Credentials {
// The value first had is kept while its generation stands, so sealing it again moves no composition.
id := c.Role + "|" + c.Name + "|" + c.Provision + "|" + c.Consumer + "|" + c.ConsumerModule + "|" + c.Local
kept, ok := r.c.sealedOwn[id]
if !ok || kept.generation != c.Generation || kept.key != a.Key {
kept = ownSealed{generation: c.Generation, key: a.Key, sealed: string(c.Sealed)}
r.c.sealedOwn[id] = kept
}
k := c.CredentialKey
switch c.Role {
case "consumer":
creds = append(creds, compose.Credential{Provision: k.Provision, Consumer: k.Consumer, ConsumerModule: k.ConsumerModule,
Provider: k.Provider, Local: k.Local, State: compose.CredentialHeld, Sealed: kept.sealed})
case "provider":
issued = append(issued, compose.Issued{Provision: k.Provision, Consumer: k.Consumer, ConsumerModule: k.ConsumerModule,
Local: k.Local, Provider: k.Provider, ForProvider: kept.sealed})
}
}
return creds, issued, nil
}