Grant a provider every consumer the same send would make a credential for, with a stand-in named foreseen, so the identity bound is judged for a consumer a change adds; rows in the controller's order (review round 1, finding 2). Three tests failed first; the suite is 1254 tests in 1.05 s
This commit is contained in:
@@ -37,11 +37,9 @@ const (
|
||||
// beside them (novox/hq ADR 0225); one whose resolution binds it to another provider is withdrawn and returned beside them
|
||||
// (issue 274). Every other is granted.
|
||||
func (c *composer) grantsFor(node string) ([]catalogue.Grant, []catalogue.Overflow, []catalogue.Unbound, []string, error) {
|
||||
var issued []Issued
|
||||
for _, row := range c.in.Issued {
|
||||
if row.Provider == node {
|
||||
issued = append(issued, row)
|
||||
}
|
||||
issued, err := c.rowsFor(node)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
onNetwork, err := c.whereEveryoneIs()
|
||||
if err != nil {
|
||||
@@ -114,6 +112,61 @@ func (c *composer) grantsFor(node string) ([]catalogue.Grant, []catalogue.Overfl
|
||||
return out, withheld, unbound, foreseen, nil
|
||||
}
|
||||
|
||||
// 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 another node that resolves, whose provider is this node, that is not
|
||||
// answered by a record and not a credential only a person gives and nobody gave. Those have no sealed copy (ForProvider empty), so the
|
||||
// grant is composed with a stand-in and named foreseen (plan.go: planFor makes the secret in every mode before grantsFor reads it).
|
||||
func (c *composer) rowsFor(node string) ([]Issued, error) {
|
||||
var rows []Issued
|
||||
for _, row := range c.in.Issued {
|
||||
if row.Provider == node {
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
for _, other := range c.in.sorted() {
|
||||
if other.Name == node {
|
||||
continue
|
||||
}
|
||||
resolved, err := c.planFor(other.Name)
|
||||
switch {
|
||||
case unresolvable(err):
|
||||
continue
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("what %s asked of %s cannot be read: %w", other.Name, node, err)
|
||||
}
|
||||
shelf := c.shelfFor(other)
|
||||
for _, n := range resolved.Plan.Needs {
|
||||
if n.From != node || n.ByRecord {
|
||||
continue
|
||||
}
|
||||
cred, held := c.credential(n, other.Name)
|
||||
if _, outside := issuedOutsideBy(shelf, n); cred.State == CredentialNotGiven || (outside && !(held && cred.State == CredentialHeld)) {
|
||||
continue
|
||||
}
|
||||
if slices.ContainsFunc(rows, func(r Issued) bool {
|
||||
return r.Provision == n.Name && r.Consumer == other.Name && r.ConsumerModule == n.For && r.Local == n.Local
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, Issued{Provision: n.Name, Consumer: other.Name, ConsumerModule: n.For, Local: n.Local, Provider: node})
|
||||
}
|
||||
}
|
||||
sort.SliceStable(rows, func(i, j int) bool {
|
||||
a, b := rows[i], rows[j]
|
||||
switch {
|
||||
case a.Provision != b.Provision:
|
||||
return a.Provision < b.Provision
|
||||
case a.Consumer != b.Consumer:
|
||||
return a.Consumer < b.Consumer
|
||||
case a.ConsumerModule != b.ConsumerModule:
|
||||
return a.ConsumerModule < b.ConsumerModule
|
||||
}
|
||||
return a.Local < b.Local
|
||||
})
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
// boundOfGrant is the identity bound the consumer's own resolution states for the requirement this grant answers. A requirement
|
||||
// not found there is held to the tightest bound the mesh knows rather than to none.
|
||||
func boundOfGrant(consumer catalogue.Resolution, s Issued, provider string) catalogue.IdentityBound {
|
||||
|
||||
@@ -365,3 +365,62 @@ 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 credential only a person gives and nobody has is no grant.
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// And a consumer whose identity overflows the provision's bound is judged before any credential is made for it (issue 263).
|
||||
func TestAnOverflowingConsumerNoCredentialWasMadeForYetIsWithheld(t *testing.T) {
|
||||
in := aGrantMesh(t)
|
||||
in.Issued = nil
|
||||
in.Catalogue["resolver"] = storeManifests()[1]
|
||||
in.Catalogue["networkmanager"] = catalogue.Manifest{Module: "networkmanager", Version: "1", Requires: []string{"wildcard-resolution", "s3-bucket"}}
|
||||
nodeOf(t, &in, "laptop").Assigned = append(nodeOf(t, &in, "laptop").Assigned, "networkmanager")
|
||||
nodeOf(t, &in, "anchor").Assigned = append(nodeOf(t, &in, "anchor").Assigned, "resolver")
|
||||
in.Holdings = []catalogue.Held{{Claim: "mesh-dns-resolver", Scope: catalogue.ScopeMesh, Node: "anchor", Module: "resolver", Site: "here"}}
|
||||
got := rendered(t, in, "anchor", Foreseeing)
|
||||
if len(got.Withheld) != 1 || got.Withheld[0].Module != "networkmanager" {
|
||||
t.Fatalf("withheld %+v", got.Withheld)
|
||||
}
|
||||
}
|
||||
|
||||
// The grants come in the order the controller reads them, whatever order the Input gives its rows.
|
||||
func TestGrantsComeInTheOrderOfProvisionConsumerModuleAndLocal(t *testing.T) {
|
||||
in := aGrantMesh(t)
|
||||
in.Issued = []Issued{
|
||||
{Provision: "s3-bucket", Consumer: "laptop", ConsumerModule: "files", Local: "b", Provider: "anchor", ForProvider: "x"},
|
||||
{Provision: "s3-bucket", Consumer: "laptop", ConsumerModule: "files", Local: "a", Provider: "anchor", ForProvider: "x"},
|
||||
}
|
||||
rows, err := newComposer(in).rowsFor("anchor")
|
||||
// The credential the same send would make (no local name) comes first, then the two the Input gives, a before b.
|
||||
if err != nil || len(rows) != 3 || rows[0].Local != "" || rows[1].Local != "a" || rows[2].Local != "b" {
|
||||
t.Errorf("rows are %+v %v", rows, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user