Revert L3 (mesh-host, merge 0a4c35f): its node-engine removed every held module on novox, the bus server among them, at 08:54 CET on 2026-10-29 (issue 774)
L3 lands again with two fixes: a held module is never removed, and the grants cover L3's new subjects.
This commit is contained in:
@@ -12,9 +12,7 @@ package selfcompose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -30,7 +28,6 @@ import (
|
||||
"git.novox.be/novox/mesh-sdk/go/compose"
|
||||
"git.novox.be/novox/mesh-sdk/go/compose/frombus"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/overlay"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
|
||||
@@ -78,13 +75,6 @@ type Env struct {
|
||||
// Fetch reads an archive at a URL in the artifact store: a moved module's templates archive, whose texts its built
|
||||
// manifest carries (issue 1126). Read once per digest. Nil reads none, and a moved module does not compose.
|
||||
Fetch func(ctx context.Context, url string) ([]byte, error)
|
||||
// VaultAuthority is the vault's authority as this node read it from the vault's `signers` (ADR 0550 §2), composed
|
||||
// beside the controller's as the mesh's authority (frombus.SignersReader); "" or nil composes the controller's alone.
|
||||
VaultAuthority func() string
|
||||
// Certify asks the vault's `sign` for this node's own certificate, keeps it and writes the node's record again (ADR
|
||||
// 0550 §2): asked only when a module placed here asks for a certificate and the record holds none from an authority
|
||||
// the node trusts. Nil asks nothing, and such a module stands in, as before.
|
||||
Certify func(ctx context.Context) error
|
||||
}
|
||||
|
||||
// VaultSecretChanged is the vault's event that a secret changed: a name and its generation, never a value.
|
||||
@@ -109,8 +99,6 @@ type Composer struct {
|
||||
// sealedOwn is each own secret's sealed value as first handed, by module/name and generation: a value sealed again
|
||||
// is other bytes, so the one first had is kept while the generation stands, and the composition stays the same.
|
||||
sealedOwn map[string]ownSealed
|
||||
// certified is the serving key a certificate was last asked for successfully, so it is asked once per key.
|
||||
certified string
|
||||
said string
|
||||
refusal string
|
||||
// held is what the composer's last pass held: each module refused alone, and the node's own ("node") when it could
|
||||
@@ -513,8 +501,6 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
|
||||
if c.env.Running != nil {
|
||||
running = c.env.Running()
|
||||
}
|
||||
// Its own certificate, asked of the vault when a module here asks for one and the record holds none it trusts.
|
||||
c.certifyIfWanted(ctx, r, own)
|
||||
scope := frombus.Scope{Node: c.env.Node, KeptAt: kept, InOwn: inOwn, Running: running}
|
||||
missed := c.missed.Load()
|
||||
compositions, _, held, err := frombus.Compose(ctx, r, c.env.Mesh, scope, fps)
|
||||
@@ -760,69 +746,6 @@ func (c *Composer) Policy(ctx context.Context, module string) string {
|
||||
// reader is the composer's view of the bus as frombus reads it.
|
||||
type reader struct{ c *Composer }
|
||||
|
||||
// VaultAuthority is the vault's authority the node follows (frombus.SignersReader).
|
||||
func (r reader) VaultAuthority(context.Context) string {
|
||||
if r.c.env.VaultAuthority == nil {
|
||||
return ""
|
||||
}
|
||||
return r.c.env.VaultAuthority()
|
||||
}
|
||||
|
||||
var _ frombus.SignersReader = reader{}
|
||||
|
||||
// certificateWanted says whether a module the placement p puts on this node asks for a certificate for the machine's
|
||||
// internal name (its manifest's `certificate`), read from the indexer's manifests; a manifest that cannot be read is
|
||||
// taken as asking none, and the composition says what it misses.
|
||||
func (c *Composer) certificateWanted(ctx context.Context, r reader, p delivery.Placement) bool {
|
||||
for _, m := range p.Modules {
|
||||
c.mu.Lock()
|
||||
digest := c.modules[m.Module].Manifest
|
||||
c.mu.Unlock()
|
||||
if digest == "" {
|
||||
continue
|
||||
}
|
||||
raw, err := r.Manifest(ctx, digest)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if parsed, err := catalogue.ParseManifestForInputs(raw); err == nil && parsed.Certificate != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// certifyIfWanted asks Certify once per serving key when a module placed here asks for a certificate and this node's
|
||||
// record holds none for its internal name and serving key from an authority it trusts (the one it holds, or the vault's).
|
||||
func (c *Composer) certifyIfWanted(ctx context.Context, r reader, p delivery.Placement) {
|
||||
if c.env.Certify == nil || !c.certificateWanted(ctx, r, p) {
|
||||
return
|
||||
}
|
||||
c.mu.Lock()
|
||||
rec, ok := c.records[c.env.Node]
|
||||
asked := c.certified
|
||||
c.mu.Unlock()
|
||||
if !ok || rec.ServingKey == "" || asked == rec.ServingKey {
|
||||
return
|
||||
}
|
||||
key, err := base64.StdEncoding.DecodeString(rec.ServingKey)
|
||||
if err != nil || len(key) != ed25519.PublicKeySize {
|
||||
return
|
||||
}
|
||||
trusted := []string{rec.Authority, r.VaultAuthority(ctx)}
|
||||
if rec.Certificate != "" && delivery.CheckNodeCertificate(rec.Certificate, trusted, overlay.InternalName(c.env.Node),
|
||||
ed25519.PublicKey(key), time.Now()) == nil {
|
||||
return
|
||||
}
|
||||
if err := c.env.Certify(ctx); err != nil {
|
||||
c.sayOnce(fmt.Sprintf("a module here asks for a certificate, and the vault's sign gave none: %v", err))
|
||||
return
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.certified = rec.ServingKey
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (r reader) Placements(context.Context) (map[string]delivery.Placement, error) {
|
||||
r.c.mu.Lock()
|
||||
defer r.c.mu.Unlock()
|
||||
|
||||
@@ -3,7 +3,6 @@ package selfcompose
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -1220,32 +1219,6 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
b.mu.Unlock()
|
||||
|
||||
// L3 s7 (ADR 0550 §2): the node asks the vault for its own certificate only when a module placed here asks for one
|
||||
// and its record holds none from an authority it trusts; once per serving key.
|
||||
certBus := newFakeBus()
|
||||
certAsks := 0
|
||||
certEnv := certBus.env()
|
||||
certEnv.Certify = func(context.Context) error { certAsks++; return nil }
|
||||
certEnv.VaultAuthority = func() string { return "" }
|
||||
cc := New(certEnv)
|
||||
withCert := `{"module":"web","version":"1","certificate":{"into":"/etc/web/tls.crt"}}`
|
||||
cdigest := delivery.ManifestDigest([]byte(withCert))
|
||||
cc.manifests[digest] = []byte(lemurs)
|
||||
cc.manifests[cdigest] = []byte(withCert)
|
||||
cc.modules = map[string]delivery.Module{"lemurs": {Manifest: digest}, "web": {Manifest: cdigest}}
|
||||
cc.records["n1"] = delivery.NodeRecord{Node: "n1", ServingKey: base64.StdEncoding.EncodeToString(make([]byte, 32))}
|
||||
onlyLemurs := delivery.Placement{Node: "n1", Modules: []delivery.PlacedModule{{Module: "lemurs"}}}
|
||||
cc.certifyIfWanted(context.Background(), reader{c: cc}, onlyLemurs)
|
||||
if certAsks != 0 {
|
||||
t.Errorf("no module asks for a certificate, and the vault was asked %d times", certAsks)
|
||||
}
|
||||
withWeb := delivery.Placement{Node: "n1", Modules: []delivery.PlacedModule{{Module: "lemurs"}, {Module: "web"}}}
|
||||
cc.certifyIfWanted(context.Background(), reader{c: cc}, withWeb)
|
||||
cc.certifyIfWanted(context.Background(), reader{c: cc}, withWeb)
|
||||
if certAsks != 1 {
|
||||
t.Errorf("a module asks for a certificate: the vault was asked %d times, want once", certAsks)
|
||||
}
|
||||
}
|
||||
|
||||
// patternBus is a bus whose every watch gets the updates of its bucket matching its pattern, as the bus's own watches
|
||||
|
||||
Reference in New Issue
Block a user