diff --git a/internal/catalogue/resolve.go b/internal/catalogue/resolve.go index 0fc14f5..6d0763c 100644 --- a/internal/catalogue/resolve.go +++ b/internal/catalogue/resolve.go @@ -231,7 +231,21 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world } // Already answered by something in the set. This is the case that makes assigning zsh do // what a person meant by it. + // + // **Except when the thing answering it grants a credential** (novox/hq 04-ISSUES/021). A + // shell answered here needs nothing more; a database answered here still needs a + // password, because the consumer reaches it over TCP from its own container and the + // database asks exactly as it would from another machine. **The machine stops being a + // trust boundary the moment both ends are containers**, and treating it as one gave the + // commonest arrangement of all — a service and its database on one node — the weakest + // handling, silently. if satisfied[want] && !isModule(catalogue, want) { + if brokered[want] { + // Answered here, and still a need: the provider is this node. + needs = append(needs, Needed{ + Name: want, From: node.Name, At: node.At, + Serves: servedHere(catalogue, chosen, want), For: because[want]}) + } continue } @@ -439,6 +453,22 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world return resolution, nil } +// servedHere is what a provider in this node's own set says a consumer needs to know. +// +// The same facts a provider elsewhere would have contributed through the world, taken from the +// module directly because a provider on this machine never passes through it. +func servedHere(catalogue map[string]Manifest, chosen map[string]bool, want string) map[string]any { + for name, m := range catalogue { + if !chosen[name] { + continue + } + if serves, ok := m.Serves[want]; ok { + return serves + } + } + return nil +} + // isModule reports whether a name is a module in its own right rather than only something // modules provide. // diff --git a/internal/catalogue/resolve_test.go b/internal/catalogue/resolve_test.go index c95d5c3..5288036 100644 --- a/internal/catalogue/resolve_test.go +++ b/internal/catalogue/resolve_test.go @@ -335,3 +335,59 @@ func mustDeclare(t *testing.T, r Resolution) []map[string]any { } return out } + +// A requirement answered on the same machine is still a requirement (novox/hq 04-ISSUES/021). +// +// **The machine stops being a trust boundary once both ends are containers.** A consumer reaches +// its provider over TCP from its own container, and the database asks for a password exactly as it +// would from another machine. Before this, two such modules resolved cleanly with zero needs: no +// credential was made, the consumer's secret file was never written, and whatever read it failed +// somewhere else entirely. +// +// It went unnoticed because everything proven until then was cross-machine, which is the +// interesting case for a mesh and the rare one in practice. +func TestSomethingAnsweredOnThisMachineIsStillANeed(t *testing.T) { + provider := Manifest{ + Module: "postgres", Version: "1", + Provides: FromAnywhere("postgres-database"), + Serves: map[string]map[string]any{"postgres-database": {"port": 5432}}, + Grants: map[string]string{"postgres-database": "/var/lib/postgres/grants"}, + } + consumer := Manifest{ + Module: "keycloak", Version: "1", + Requires: []string{"postgres-database"}, + Secrets: map[string]string{"postgres-database": "/var/lib/keycloak/database.env"}, + } + got, err := Resolve(shelf(provider, consumer), []string{"postgres", "keycloak"}, + Node{Name: "anchor", At: "10.0.0.1", Capabilities: map[string]bool{}}, World{}) + if err != nil { + t.Fatal(err) + } + if len(got.Needs) != 1 { + t.Fatalf("%d need(s); a consumer of a brokered provision needs a credential wherever "+ + "the provider is", len(got.Needs)) + } + if got.Needs[0].From != "anchor" { + t.Errorf("the need names %q as the provider, and it is this machine", got.Needs[0].From) + } + // And it carries what the provider says a consumer must know, which a local provider never + // contributes through the world. + if got.Needs[0].Serves["port"] != 5432 { + t.Errorf("the need carries %v, and the provider serves port 5432", got.Needs[0].Serves) + } +} + +// A name nothing grants is unchanged: answered here means answered, and nothing more is owed. +func TestSomethingOrdinaryAnsweredHereNeedsNothing(t *testing.T) { + got, err := Resolve(shelf( + mod("zsh", []string{"shell"}, nil, nil), + mod("editor-user", nil, []string{"shell"}, nil), + ), []string{"zsh", "editor-user"}, + Node{Name: "anchor", At: "10.0.0.1", Capabilities: map[string]bool{}}, World{}) + if err != nil { + t.Fatal(err) + } + if len(got.Needs) != 0 { + t.Fatalf("a shell answered on this machine produced %d need(s)", len(got.Needs)) + } +}