The mesh writes its own user list, and at genesis there is no mesh yet to write it. So the installer carries the first one — the controller's own account at a well-known bootstrap password, exactly as the store is reached at `postgres:bootstrap` and the old bus at `guest:guest`, and rotated with them. From the controller's first composition onward the file is the controller's. That left a gap I would not have found by reading: the controller's own account is created before there is a controller to mint one, so nothing recorded a hash for it, and its first composition would have left the writer out of the file it was writing — a bus nothing can connect to, produced by the thing connected to it. It now records a hash of the credential it is actually using, and only if none is recorded, so a restart cannot put the bootstrap password back over a rotated one. The carried list and the derived one are two statements of one fact, so a test compares them: every subject the controller derives must be in the template, and nothing wider. It earned itself immediately — the composer was granting both a role's whole event branch and the one event it actually follows, which is a wider way of saying the same thing, and the wider one wins. Only the submitting half of a role is granted now; what comes back is named exactly. Getting this wrong is the worst kind of silent. A controller whose carried permissions are narrower than the ones it derives comes up, connects, and is refused on the first thing it tries, with an authorisation error naming a subject and not the template that forgot it — and a mesh cannot be raised twice to find out.
61 lines
2.6 KiB
Go
61 lines
2.6 KiB
Go
package broker
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Which bus the mesh is on is one fact, and being told about both is refused.
|
|
//
|
|
// **Not a warning.** A mesh half on each bus is one where a declaration goes out on one and the
|
|
// report comes back on the other, and every component reports success while it happens — which is
|
|
// the exact failure ADR 0074 exists to catch, arriving through configuration instead of through code.
|
|
func TestBeingToldAboutBothBusesIsRefused(t *testing.T) {
|
|
err := MustBeOneBus("amqps://broker:5671/", "nats://bus:4222")
|
|
if err == nil {
|
|
t.Fatal("a control plane told about both buses was allowed to start")
|
|
}
|
|
// The remedy is in the words, because whoever reads this has to choose one and the wrong choice
|
|
// is a rollout half done.
|
|
for _, want := range []string{AMQPVarName, NATSVar, "unset"} {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Errorf("the refusal does not mention %s: %v", want, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// One bus, or none, is ordinary. None is a control plane that publishes nothing and holds records,
|
|
// which several of its own commands are.
|
|
func TestOneBusOrNeitherIsAllowed(t *testing.T) {
|
|
for _, c := range []struct{ what, amqp, nats string }{
|
|
{"the bus the mesh runs on today", "amqps://broker:5671/", ""},
|
|
{"the bus being built", "", "nats://bus:4222"},
|
|
{"neither", "", ""},
|
|
{"neither, with whitespace for an address", " ", "\t"},
|
|
} {
|
|
if err := MustBeOneBus(c.amqp, c.nats); err != nil {
|
|
t.Errorf("%s was refused: %v", c.what, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The controller's own credential arrives in its address, and has to be readable out of it — its user
|
|
// is created by the installer at a bootstrap password, before the controller exists to mint one.
|
|
func TestACredentialIsReadOutOfABusAddress(t *testing.T) {
|
|
for _, c := range []struct{ in, user, password, bare string }{
|
|
{"nats://controller:secret@127.0.0.1:4222", "controller", "secret", "nats://127.0.0.1:4222"},
|
|
{"controller:secret@127.0.0.1:4222", "controller", "secret", "127.0.0.1:4222"},
|
|
{"nats://127.0.0.1:4222", "", "", "nats://127.0.0.1:4222"},
|
|
{"127.0.0.1:4222", "", "", "127.0.0.1:4222"},
|
|
// A password containing an at-sign: split on the last one, or the address becomes part of the
|
|
// credential and the connection goes somewhere nobody named.
|
|
{"nats://controller:a@b@127.0.0.1:4222", "controller", "a@b", "nats://127.0.0.1:4222"},
|
|
} {
|
|
user, password, bare := CredentialIn(c.in)
|
|
if user != c.user || password != c.password || bare != c.bare {
|
|
t.Errorf("%q read as %q/%q at %q; wanted %q/%q at %q",
|
|
c.in, user, password, bare, c.user, c.password, c.bare)
|
|
}
|
|
}
|
|
}
|