Files
mesh-sdk/go/compose/credentials_test.go
T

472 lines
25 KiB
Go

package compose
import (
"encoding/json"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/overlay"
)
// secretValue is a value the Input carries that must never appear in anything the composition says.
const secretValue = "SECRET-VALUE-THAT-MUST-NOT-LEAK"
func rendered(t *testing.T, in Input, node string, mode Mode) Rendered {
t.Helper()
got, err := Render(in, node, mode)
if err != nil {
t.Fatalf("%v", err)
}
return got
}
// anAssistant is mesh-controller's outside_not_given_test.go anAssistant (cmd/mesh-controller at 6c2a8d62): a module with a secret
// the mesh makes and one only an outside party issues.
func anAssistant() catalogue.Manifest {
return catalogue.Manifest{Module: "assistant", Version: "1",
OwnSecrets: catalogue.OwnSecrets{
"password": {Path: "/var/lib/mesh/assistant/password", Taken: catalogue.TakenAtStart},
"vendor-key": {Path: "/var/lib/mesh/assistant/vendor-key", Taken: catalogue.TakenAtStart, IssuedBy: catalogue.IssuedOutside},
},
Resources: []map[string]any{{"id": "state", "type": "directory", "path": "/var/lib/mesh/assistant", "mode": "0700"}}}
}
func withAssistant(t *testing.T) Input {
t.Helper()
in := aMesh(t)
in.Catalogue["assistant"] = anAssistant()
laptop := nodeOf(t, &in, "laptop")
laptop.Assigned = append(laptop.Assigned, "assistant")
laptop.SealingKey = keyOf(t, "laptop")
return in
}
// Own secrets (plan.go renderingFor, novox/hq issue 275 and 401): one held and sealed to the machine's key is carried; one not held is
// foreseen in the Foreseeing mode and left out in the Reading mode, and nothing is made.
func TestAnOwnSecretHeldIsCarriedAndOneNotHeldIsForeseenOrLeftOut(t *testing.T) {
mk := func() Input {
in := withAssistant(t)
in.OwnSecrets = []OwnSecretRow{{Node: "laptop", Module: "assistant", Name: "password", Origin: "made",
SealedTo: nodeOf(t, &in, "laptop").SealingKey, Sealed: secretValue}}
return in
}
in := mk()
got := rendered(t, in, "laptop", Foreseeing)
if got.Rendering.Needed["assistant"]["password"] != secretValue || len(got.Rendering.Foreseen) != 0 {
t.Errorf("a held secret: needed %v foreseen %v", got.Rendering.Needed, got.Rendering.Foreseen)
}
// Not held: foreseen with a stand-in (Foreseeing), left out (Reading).
in.OwnSecrets = nil
got = rendered(t, in, "laptop", Foreseeing)
if !got.Rendering.Foreseen["assistant"]["password"] || got.Rendering.Needed["assistant"]["password"] != "" ||
len(got.Foreseen) != 1 || got.Foreseen[0] != "assistant/password" {
t.Errorf("a secret the send makes: foreseen %v / %v needed %v", got.Rendering.Foreseen, got.Foreseen, got.Rendering.Needed)
}
got = rendered(t, in, "laptop", Reading)
if len(got.Rendering.Needed["assistant"]) != 0 || len(got.Foreseen) != 0 || got.Rendering.Foreseen != nil {
t.Errorf("Reading composed a secret that was never issued: %v %v", got.Rendering.Needed, got.Foreseen)
}
// Held but sealed to a key the machine no longer has is not held.
in.OwnSecrets = []OwnSecretRow{{Node: "laptop", Module: "assistant", Name: "password", Origin: "made", SealedTo: "an-old-key", Sealed: secretValue}}
if got := rendered(t, in, "laptop", Foreseeing); !got.Rendering.Foreseen["assistant"]["password"] {
t.Errorf("a secret sealed to an old key is held: %v", got.Rendering.Needed)
}
// Held with no value in the Input: a stand-in, named.
in.OwnSecrets = []OwnSecretRow{{Node: "laptop", Module: "assistant", Name: "password", Origin: "made", SealedTo: nodeOf(t, &in, "laptop").SealingKey}}
if got := rendered(t, in, "laptop", Reading); !got.Rendering.Foreseen["assistant"]["password"] {
t.Errorf("a held secret with no value is not named: %v", got.Rendering.Foreseen)
}
unchanged(t, mk, func(in Input) { rendered(t, in, "laptop", Foreseeing) })
}
// An own secret issued outside the mesh that nobody gave is never made (novox/hq issue 401, ADR 0228): nothing is composed or
// foreseen for it; a value the mesh made for it is not one a person gave; a value given is placed once it is sealed to the key held now.
func TestAnOutsideSecretIsPlacedOnlyOnceAPersonGaveIt(t *testing.T) {
in := withAssistant(t)
key := nodeOf(t, &in, "laptop").SealingKey
for _, mode := range []Mode{Reading, Foreseeing} {
got := rendered(t, in, "laptop", mode)
if _, there := got.Rendering.Needed["assistant"]["vendor-key"]; there || got.Rendering.Foreseen["assistant"]["vendor-key"] {
t.Fatalf("%v: a secret nobody gave is composed: %v %v", mode, got.Rendering.Needed, got.Rendering.Foreseen)
}
}
in.OwnSecrets = []OwnSecretRow{{Node: "laptop", Module: "assistant", Name: "vendor-key", Origin: "made", SealedTo: key, Sealed: secretValue}}
if _, there := rendered(t, in, "laptop", Foreseeing).Rendering.Needed["assistant"]["vendor-key"]; there {
t.Error("a value the mesh made for a secret issued outside was placed")
}
in.OwnSecrets = []OwnSecretRow{{Node: "laptop", Module: "assistant", Name: "vendor-key", Origin: "accepted", SealedTo: key, Sealed: "the vendor's key, sealed"}}
if got := rendered(t, in, "laptop", Foreseeing).Rendering.Needed["assistant"]["vendor-key"]; got != "the vendor's key, sealed" {
t.Errorf("a value given is placed as %q", got)
}
in.OwnSecrets[0].SealedTo = "an-old-key"
if _, there := rendered(t, in, "laptop", Foreseeing).Rendering.Needed["assistant"]["vendor-key"]; there {
t.Error("a value given under an old key was placed")
}
}
// 1364/AC3 (the send's refusals, novox/hq issue 203 and 275): in the Foreseeing mode a bus credential nobody issued and a given secret
// under an old key are refused in the controller's words; with the credential issued, the secret is foreseen. No secret value is in
// an error.
func TestTheSendsRefusalsAreRefusedInTheSendsWords(t *testing.T) {
broker := catalogue.Manifest{Module: "gate", Version: "1",
OwnSecrets: catalogue.OwnSecrets{"broker": {Path: "/var/lib/mesh/gate/broker", Taken: catalogue.TakenAtStart}}}
in := withAssistant(t)
in.Catalogue["gate"] = broker
laptop := nodeOf(t, &in, "laptop")
laptop.Assigned = append(laptop.Assigned, "gate")
_, err := Render(in, "laptop", Foreseeing)
want := "gate on laptop has no bus credential: nothing was issued for laptop.gate, and a push would seal a placeholder " +
"its process cannot read (novox/hq issue 203). `module issue gate --node laptop`, then push again"
if err == nil || err.Error() != want {
t.Fatalf("got %v, want %q", err, want)
}
// Reading refuses nothing for it: the machine is not running it.
if _, err := Render(in, "laptop", Reading); err != nil {
t.Fatalf("Reading refused a secret the machine is not running: %v", err)
}
in.BusMinted = []string{"laptop.gate"}
if got := rendered(t, in, "laptop", Foreseeing); !got.Rendering.Foreseen["gate"]["broker"] {
t.Errorf("an issued bus credential is not foreseen: %v", got.Rendering.Foreseen)
}
// A given secret under an old key.
in.OwnSecrets = []OwnSecretRow{{Node: "laptop", Module: "assistant", Name: "password", Origin: "accepted", SealedTo: "an-old-key", Sealed: secretValue},
{Node: "laptop", Module: "gate", Name: "broker", Origin: "made", SealedTo: laptop.SealingKey, Sealed: secretValue}}
_, err = Render(in, "laptop", Foreseeing)
wantOld := "assistant on laptop holds \"password\", which was given to the mesh rather than made by it, and laptop has since generated a new " +
"sealing key. The mesh cannot make another; issue it again"
if err == nil || err.Error() != wantOld {
t.Fatalf("got %v, want %q", err, wantOld)
}
if strings.Contains(err.Error(), secretValue) {
t.Error("a secret value is in an error")
}
// A machine with no sealing key cannot be sealed to.
laptop.SealingKey = ""
in.OwnSecrets = nil
if _, err := Render(in, "laptop", Foreseeing); err == nil || !strings.Contains(err.Error(), "laptop has no sealing key, so nothing can be sealed to it") {
t.Fatalf("no sealing key: %v", err)
}
}
// aGrantMesh is the objects provider on anchor and its consumer files on laptop, with the pair credential made.
func aGrantMesh(t *testing.T) Input {
t.Helper()
in := theObjectsMesh(t, objects())
in.Issued = []Issued{{Provision: "s3-bucket", Consumer: "laptop", ConsumerModule: "files", Provider: "anchor", ForProvider: "sealed-to-anchor"}}
return in
}
// A credential made for a consumer is a grant on its provider's machine, carrying the consumer's values and the sealed copy; without
// the sealed copy a stand-in is composed and named (novox/hq plan.go grantsFor).
func TestAGrantIsCreatedForEachCredentialMadeForAConsumer(t *testing.T) {
in := aGrantMesh(t)
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.At != "laptop.internal" || g.Sealed != "sealed-to-anchor" {
t.Errorf("the grant is %+v", g)
}
if len(got.Foreseen) != 0 || len(got.Withheld) != 0 || len(got.Unbound) != 0 {
t.Errorf("foreseen %v withheld %v unbound %v", got.Foreseen, got.Withheld, got.Unbound)
}
in.Issued[0].ForProvider = ""
got = rendered(t, in, "anchor", Foreseeing)
if got.Rendering.Grants[0].Sealed != catalogue.ForeseenSealed || len(got.Foreseen) != 1 {
t.Errorf("a grant with no sealed copy: %+v foreseen %v", got.Rendering.Grants, got.Foreseen)
}
// A credential made for a consumer on another provider is not this node's grant.
if len(rendered(t, in, "laptop", Foreseeing).Rendering.Grants) != 0 {
t.Error("the consumer is granted its own credential")
}
// A consumer whose set does not resolve is skipped, not fatal.
nodeOf(t, &in, "laptop").Assigned = append(nodeOf(t, &in, "laptop").Assigned, "files")
in.Catalogue["files"] = catalogue.Manifest{Module: "files", Version: "1", Requires: []string{"s3-bucket"},
Claims: []catalogue.Claim{{Name: "x", Scope: catalogue.ScopeNode}}}
in.Catalogue["twin"] = catalogue.Manifest{Module: "twin", Version: "1", Claims: []catalogue.Claim{{Name: "x", Scope: catalogue.ScopeNode}}}
nodeOf(t, &in, "laptop").Assigned = append(nodeOf(t, &in, "laptop").Assigned, "twin")
if got, err := Render(in, "anchor", Foreseeing); err != nil || len(got.Rendering.Grants) != 0 {
t.Errorf("a consumer that does not resolve: %v %+v", err, got.Rendering.Grants)
}
}
// 1364/AC2: a consumer the grants leave out because its identity overflows the provision's bound is returned beside them, naming the
// consumer and the provision (novox/hq ADR 0225), and is not granted.
func TestAConsumerWhoseIdentityOverflowsIsLeftOutOfTheGrantsAndNamed(t *testing.T) {
in := aGrantMesh(t)
in.Nodes[1].Name = "a-machine-with-a-very-long-name"
in.Issued[0].Consumer = "a-machine-with-a-very-long-name"
got := rendered(t, in, "anchor", Foreseeing)
if len(got.Rendering.Grants) != 0 || len(got.Withheld) != 1 {
t.Fatalf("grants %+v withheld %+v", got.Rendering.Grants, got.Withheld)
}
w := got.Withheld[0]
if w.Consumer != "a-machine-with-a-very-long-name" || w.Provision != "s3-bucket" || w.Provider != "anchor" || w.Module != "files" ||
!strings.Contains(w.String(), "a-machine-with-a-very-long-name") || !strings.Contains(w.String(), "s3-bucket") {
t.Errorf("the consumer left out is %+v / %s", w, w.String())
}
if len(got.Rendering.Withheld) != 1 {
t.Errorf("the Rendering does not carry it: %+v", got.Rendering.Withheld)
}
}
// 1364/AC3: a credential on record for a consumer that its own resolution binds to another provider is withdrawn, named with where it is
// bound (novox/hq issue 274): the grant stays, with no consumer module asking.
func TestACredentialBoundToAnotherProviderIsWithdrawnAndSaidWhereItIsBound(t *testing.T) {
in := aGrantMesh(t)
second := objects()
second.Module = "objects2"
in.Catalogue["objects2"] = second
in.Nodes = append(in.Nodes, Node{Name: "ace", Place: Place{Site: "there", Address: "10.77.0.3"},
Assigned: []string{overlay.Name, "objects2"}, Capabilities: map[string]bool{"container-runtime": true, "wireguard": true, "systemd": true}})
nodeOf(t, &in, "laptop").Pins = map[string]catalogue.Chosen{"s3-bucket": {Node: "ace", Module: "objects2"}}
got := rendered(t, in, "anchor", Foreseeing)
if len(got.Unbound) != 1 {
t.Fatalf("unbound %+v grants %+v", got.Unbound, got.Rendering.Grants)
}
u := got.Unbound[0]
if u.Provision != "s3-bucket" || u.Provider != "anchor" || u.Consumer != "laptop" || u.Module != "files" ||
len(u.BoundTo) != 1 || u.BoundTo[0] != "ace" || !strings.Contains(u.String(), "ace") {
t.Errorf("unbound is %+v / %s", u, u.String())
}
if len(got.Rendering.Grants) != 1 || got.Rendering.Grants[0].From != "" {
t.Errorf("the withdrawn credential is still asked for: %+v", got.Rendering.Grants)
}
}
func aCertificateMesh(t *testing.T) Input {
t.Helper()
in := aMesh(t)
in.Catalogue["secure"] = catalogue.Manifest{Module: "secure", Version: "1", Certificate: &catalogue.Certificate{Into: "/etc/secure/tls.pem", Authority: "/etc/secure/ca.pem"}}
laptop := nodeOf(t, &in, "laptop")
laptop.Assigned = append(laptop.Assigned, "secure")
return in
}
// 1364/AC4: with no certificate and authority in the Input a stand-in is composed and named; given them, the composition carries them as
// given. A machine that never reported a serving key, and one not on the private network, are refused in the controller's words.
func TestACertificateIsCarriedAsGivenOrStoodInForAndNamed(t *testing.T) {
in := aCertificateMesh(t)
laptop := nodeOf(t, &in, "laptop")
if _, err := Render(in, "laptop", Foreseeing); err == nil || err.Error() != "laptop wants a certificate and has never told the mesh what key it serves with; it joins again to report one" {
t.Fatalf("no serving key: %v", err)
}
laptop.ServingKey = "serving-key"
got := rendered(t, in, "laptop", Foreseeing)
if got.Rendering.Certificate != ForeseenCertificate || got.Rendering.Authority != ForeseenCertificate || len(got.Foreseen) != 1 {
t.Errorf("no certificate in the Input: %q %q foreseen %v", got.Rendering.Certificate, got.Rendering.Authority, got.Foreseen)
}
laptop.Certificate, in.Authority = "the-certificate", "the-authority"
got = rendered(t, in, "laptop", Foreseeing)
if got.Rendering.Certificate != "the-certificate" || got.Rendering.Authority != "the-authority" || len(got.Foreseen) != 0 {
t.Errorf("a certificate given: %q %q foreseen %v", got.Rendering.Certificate, got.Rendering.Authority, got.Foreseen)
}
laptop.Place.Address = "" // placed nowhere: no name inside the mesh
if _, err := Render(in, "laptop", Foreseeing); err == nil || err.Error() != "laptop wants a certificate and is not on the private network, so it has no name inside the mesh to be certified for" {
t.Fatalf("off the network: %v", err)
}
}
func aValidKey(t *testing.T, name string) string { return keyOf(t, name) }
// 1364/AC5 and AC6: a core credential the node needs and the Input holds no box for is composed with a stand-in and returned as one
// to keep; a kept box sealed to the key held now is carried; a node-engine below the vault-box contract is sent none (and nothing is
// asked of it); a machine with no sealing key has its core module left out, said.
func TestACoreBoxTheInputHoldsNoneOfIsReturnedToKeepAndNothingIsWritten(t *testing.T) {
mk := func() Input {
in := aMesh(t)
in.Catalogue["nats"] = catalogue.Manifest{Module: "nats", Version: "1",
OwnSecrets: catalogue.OwnSecrets{"broker": {Path: "/var/lib/mesh/nats/broker", Taken: catalogue.TakenAtStart}}}
laptop := nodeOf(t, &in, "laptop")
laptop.Assigned = append(laptop.Assigned, "nats")
laptop.SealingKey = aValidKey(t, "laptop")
laptop.Stated, laptop.Contract = true, VaultBoxContract
in.BusMinted = []string{"laptop.nats"}
return in
}
in := mk()
got := rendered(t, in, "laptop", Foreseeing)
box := got.Rendering.VaultSealed["nats"]["broker"]
if string(box) != string(catalogue.ForeseenVaultBox) || len(got.CoreBoxesWanted) != 1 ||
got.CoreBoxesWanted[0] != (CoreBoxWanted{Node: "laptop", Module: "nats", Name: "broker"}) {
t.Errorf("no box held: %s wanted %v", box, got.CoreBoxesWanted)
}
kept := json.RawMessage(`{"v":1,"eph":"e","iv":"i","tag":"t","ct":"c"}`)
in.KeptCoreBoxes = []CoreBox{{Node: "laptop", Module: "nats", Name: "broker", Generation: 3, SealedTo: nodeOf(t, &in, "laptop").SealingKey, Box: kept}}
got = rendered(t, in, "laptop", Foreseeing)
if string(got.Rendering.VaultSealed["nats"]["broker"]) != string(kept) || len(got.CoreBoxesWanted) != 0 {
t.Errorf("a kept box: %s wanted %v", got.Rendering.VaultSealed["nats"]["broker"], got.CoreBoxesWanted)
}
in.KeptCoreBoxes[0].SealedTo = "an-old-key"
if got = rendered(t, in, "laptop", Foreseeing); len(got.CoreBoxesWanted) != 1 {
t.Errorf("a box sealed to an old key is kept: %v", got.CoreBoxesWanted)
}
// Below the vault-box contract: none, and the controller's own seal as before.
nodeOf(t, &in, "laptop").Contract = VaultBoxContract - 1
if got = rendered(t, in, "laptop", Foreseeing); got.Rendering.VaultSealed != nil || len(got.CoreBoxesWanted) != 0 {
t.Errorf("an engine below the contract is sent boxes: %v %v", got.Rendering.VaultSealed, got.CoreBoxesWanted)
}
// No sealing key: the module is left out, said.
nodeOf(t, &in, "laptop").Contract, nodeOf(t, &in, "laptop").SealingKey = VaultBoxContract, ""
got = rendered(t, in, "laptop", Reading)
if why := got.Rendering.NotPlaced["nats"]; !strings.Contains(why, "laptop has no sealing key, so the vault can seal nats's credentials to nothing there") {
t.Errorf("not placed: %v", got.Rendering.NotPlaced)
}
unchanged(t, mk, func(in Input) { rendered(t, in, "laptop", Foreseeing) })
}
// 1364/AC1: the bus's user list is carried for the machine that runs the bus and for no other; without it a stand-in is composed and
// named; the users left out for want of a credential are said; each machine's membership is carried. (The list is composed by the
// controller from the bus records, which stay the controller's: see the commit message.)
func TestTheBusUserListIsCarriedForTheMachineThatRunsTheBus(t *testing.T) {
in := aMesh(t)
in.Catalogue["bus"] = catalogue.Manifest{Module: "bus", Version: "1", BusUsers: "/etc/bus/accounts.conf",
Claims: []catalogue.Claim{{Name: "mesh-broker", Scope: catalogue.ScopeMesh}}}
anchor := nodeOf(t, &in, "anchor")
anchor.Assigned = append(anchor.Assigned, "bus")
in.BusUsers = "accounts { anchor.bus: hash }"
in.BusMissing = []string{"laptop.files", "laptop.web"}
in.BusMemberships = map[string]string{"laptop": "sealed-membership"}
got := rendered(t, in, "anchor", Foreseeing)
if got.Rendering.BusUsers != in.BusUsers || len(got.Foreseen) != 0 {
t.Errorf("the list is %q foreseen %v", got.Rendering.BusUsers, got.Foreseen)
}
if len(got.Said) != 1 || got.Said[0] != "the bus's user list leaves out 2 user(s) the mesh has minted no credential for: laptop.files, laptop.web. Each is a user that cannot connect until one is issued" {
t.Errorf("said %v", got.Said)
}
if other := rendered(t, in, "laptop", Foreseeing); other.Rendering.BusUsers != "" || other.Rendering.BusMembership != "sealed-membership" {
t.Errorf("laptop: list %q membership %q", other.Rendering.BusUsers, other.Rendering.BusMembership)
}
in.BusUsers = ""
got = rendered(t, in, "anchor", Foreseeing)
if got.Rendering.BusUsers != ForeseenBusUsers || len(got.Foreseen) != 1 {
t.Errorf("no list in the Input: %q foreseen %v", got.Rendering.BusUsers, got.Foreseen)
}
}
// 1364/AC7: nothing the composition says carries a secret value the Input holds.
func TestNoSecretValueOfTheInputIsInWhatTheCompositionSays(t *testing.T) {
in := aGrantMesh(t)
in.Issued[0].ForProvider = secretValue
laptop := nodeOf(t, &in, "laptop")
in.Catalogue["assistant"] = anAssistant()
laptop.Assigned = append(laptop.Assigned, "assistant")
laptop.SealingKey = keyOf(t, "laptop")
in.OwnSecrets = []OwnSecretRow{{Node: "laptop", Module: "assistant", Name: "password", Origin: "made", SealedTo: laptop.SealingKey, Sealed: secretValue}}
in.Credentials = []Credential{{Provision: "s3-bucket", Consumer: "laptop", ConsumerModule: "files", Provider: "anchor", State: CredentialHeld, Sealed: secretValue}}
for _, node := range []string{"anchor", "laptop"} {
got := rendered(t, in, node, Foreseeing)
for _, said := range append(append([]string{}, got.Foreseen...), got.Said...) {
if strings.Contains(said, secretValue) {
t.Errorf("%s: %q carries a secret value", node, said)
}
}
}
}
// 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)
}
}
// Round 1, finding 4: a member of a secret family a person gave, whose sealed value the Input does not carry, is placed with a
// stand-in and named foreseen, not dropped as if nobody gave it.
func TestAGivenFamilyMemberWithoutItsValueIsAStandInAndNamedForeseen(t *testing.T) {
in := aMesh(t)
in.Catalogue["mailer"] = catalogue.Manifest{Module: "mailer", Version: "1",
OwnSecrets: catalogue.OwnSecrets{"credential-*": {Path: "/var/lib/mailer/credential-*.secret", Taken: catalogue.TakenAtStart, IssuedBy: catalogue.IssuedOutside}}}
laptop := nodeOf(t, &in, "laptop")
laptop.Assigned = append(laptop.Assigned, "mailer")
laptop.SealingKey = keyOf(t, "laptop")
in.OwnSecrets = []OwnSecretRow{
{Node: "laptop", Module: "mailer", Name: "credential-a", Origin: "accepted", SealedTo: laptop.SealingKey},
{Node: "laptop", Module: "mailer", Name: "credential-b", Origin: "accepted", SealedTo: laptop.SealingKey, Sealed: "b-sealed"},
}
got := rendered(t, in, "laptop", Foreseeing)
needed := got.Rendering.Needed["mailer"]
if needed["credential-a"] != catalogue.ForeseenSealed || needed["credential-b"] != "b-sealed" {
t.Errorf("needed %v", needed)
}
if !has(got.Foreseen, "mailer/credential-a") || has(got.Foreseen, "mailer/credential-b") {
t.Errorf("foreseen %v", got.Foreseen)
}
}
// Round 1, finding 5: the suffix internal names end in is a field of the Input, and the environment is never read.
func TestTheInternalSuffixIsTheInputsAndNeverTheEnvironments(t *testing.T) {
t.Setenv(overlay.SuffixVar, "from-the-environment")
in := aGrantMesh(t)
got := rendered(t, in, "anchor", Foreseeing)
if got.Rendering.Suffix != "internal" || got.Rendering.Grants[0].At != "laptop.internal" {
t.Errorf("by default: suffix %q, grant at %q", got.Rendering.Suffix, got.Rendering.Grants[0].At)
}
in.InternalSuffix = ".lan"
got = rendered(t, in, "anchor", Foreseeing)
if got.Rendering.Suffix != "lan" || got.Rendering.Grants[0].At != "laptop.lan" {
t.Errorf("given: suffix %q, grant at %q", got.Rendering.Suffix, got.Rendering.Grants[0].At)
}
where, err := newComposer(in).whereEveryoneIs()
if err != nil || where["anchor"] != "anchor.lan" {
t.Errorf("where everyone is: %v %v", where, err)
}
if names := newComposer(in).namesInTheMesh(); names["anchor.lan"] == "" || names["anchor.internal"] != "" {
t.Errorf("names: %v", names)
}
}