120 lines
5.4 KiB
Go
120 lines
5.4 KiB
Go
package main
|
|
|
|
// The controller's `rotate <provision> [--consumer <machine>] [--module <module>]` reaches the vault (issue 767): a
|
|
// provider's one shared value is rotated for every holder together, and it is never rotated for one consumer alone;
|
|
// a per-login provider (ADR 0158 §11) rotates the named consumer's own login and no other.
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// postgresLogins is a vault holding a login each for gitea on g14 and umami on ace, from postgres on novox.
|
|
func postgresLogins(t *testing.T) (testVault, string, string) {
|
|
t.Helper()
|
|
v := pairVault(t, assignments{
|
|
"novox.postgres": handedWith("novox", "postgres"),
|
|
"g14.gitea": handedWith("g14", "gitea", "postgres-database@novox/postgres"),
|
|
"ace.umami": handedWith("ace", "umami", "postgres-database@novox/postgres"),
|
|
})
|
|
gitea, umami := "pair/novox/postgres-database/g14/gitea", "pair/novox/postgres-database/ace/umami"
|
|
for name, value := range map[string]string{gitea: "login-of-gitea", umami: "login-of-umami"} {
|
|
if _, err := v.keepWith(name, value, "node.novox", "", OriginMade, keepOptions{neverReplace: true,
|
|
movedFrom: &MovedFrom{By: "node.novox"}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return v, gitea, umami
|
|
}
|
|
|
|
func TestARotationNamingOneConsumerRemakesThatConsumersLoginAlone(t *testing.T) {
|
|
for _, one := range []Narrow{{Consumer: "g14"}, {ConsumerModule: "gitea"}, {Consumer: "g14", ConsumerModule: "gitea"}} {
|
|
v, gitea, umami := postgresLogins(t)
|
|
before := changedSaid(t, v, umami, "")
|
|
changed, err := v.RotateFor(ControllerCaller, "provision/novox/postgres-database", one, "person.jochen",
|
|
"suspected on g14")
|
|
if err != nil || len(changed.Each) != 1 || changed.Each[0].Name != gitea {
|
|
t.Fatalf("%+v: the rotation %+v (%v)", one, changed, err)
|
|
}
|
|
if got, _, err := asks(v, "provisioner.g14", gitea); err != nil || got == "login-of-gitea" {
|
|
t.Fatalf("%+v: gitea's login was not remade: %q (%v)", one, got, err)
|
|
}
|
|
if got, gen, err := asks(v, "provisioner.ace", umami); err != nil || got != "login-of-umami" || gen != 1 {
|
|
t.Fatalf("%+v: umami's login was touched: %q generation %d (%v)", one, got, gen, err)
|
|
}
|
|
if changedSaid(t, v, umami, "") != before {
|
|
t.Fatalf("%+v: secret-changed was said for umami's login", one)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestARotationNamingAConsumerThatHoldsNoLoginChangesNothing(t *testing.T) {
|
|
v, gitea, umami := postgresLogins(t)
|
|
_, err := v.RotateFor(ControllerCaller, "provision/novox/postgres-database", Narrow{Consumer: "g14",
|
|
ConsumerModule: "umami"}, "person.jochen", "suspected")
|
|
if err == nil || !strings.Contains(err.Error(), "umami on g14") {
|
|
t.Fatalf("a consumer holding no login was not refused by name: %v", err)
|
|
}
|
|
for name, want := range map[string]string{gitea: "login-of-gitea", umami: "login-of-umami"} {
|
|
if got, _, _ := asks(v, "provisioner.novox", name); got != want {
|
|
t.Fatalf("%s changed on a refused rotation", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A shared value is one for every holder (ADR 0158): rotated, every holder gets the new one after secret-changed; it
|
|
// is never rotated for one consumer alone, which would change the value for all of them.
|
|
func TestASharedValueIsRotatedForEveryHolderAndNeverForOneConsumer(t *testing.T) {
|
|
v := pairVault(t, assignments{
|
|
"anchor.stand-in": handedWith("anchor", "stand-in"),
|
|
"g14.app": handedWith("g14", "app", "stand-in-api@anchor/stand-in"),
|
|
"ace.other": handedWith("ace", "other", "stand-in-api@anchor/stand-in"),
|
|
})
|
|
movedRow(t, v, "g14", "app", "the-value-they-share", "20260901T000000-1", OriginMade)
|
|
movedRow(t, v, "ace", "other", "the-value-they-share", "20260901T000000-1", OriginMade)
|
|
if o := pass(t, v, standIn); o.Outcome != PairAdopted {
|
|
t.Fatalf("stamped rows: %+v", o)
|
|
}
|
|
|
|
if _, err := v.RotateFor(ControllerCaller, standIn, Narrow{Consumer: "g14"}, "person.jochen", "suspected"); err == nil ||
|
|
!strings.Contains(err.Error(), "every holder") {
|
|
t.Fatalf("a shared value was rotated for one consumer: %v", err)
|
|
}
|
|
if got, gen, _ := asks(v, "provisioner.g14", standIn); got != "the-value-they-share" || gen != 1 {
|
|
t.Fatal("a refused rotation changed the shared value")
|
|
}
|
|
|
|
changed, err := v.RotateFor(ControllerCaller, standIn, Narrow{}, "person.jochen", "yearly")
|
|
if err != nil || changed.Generation != 2 {
|
|
t.Fatalf("the rotation: %+v (%v)", changed, err)
|
|
}
|
|
var seen string
|
|
for _, c := range []string{"provisioner.anchor", "provisioner.g14", "provisioner.ace"} {
|
|
got, gen, err := asks(v, c, standIn)
|
|
if err != nil || got == "the-value-they-share" || gen != 2 || (seen != "" && got != seen) {
|
|
t.Fatalf("%s got %q generation %d (%v): not the one new value", c, got, gen, err)
|
|
}
|
|
seen = got
|
|
}
|
|
if changedSaid(t, v, standIn, seen) == 0 {
|
|
t.Fatal("secret-changed was not said for the shared value")
|
|
}
|
|
}
|
|
|
|
func TestARotationOfAModulesOwnSecretNamesNoConsumer(t *testing.T) {
|
|
v := newVault(t, nil)
|
|
if _, err := v.RotateFor(ControllerCaller, "own/g14/app/key", Narrow{Consumer: "g14"}, "person.jochen", "x"); err == nil {
|
|
t.Fatal("a module's own secret was rotated with a consumer named")
|
|
}
|
|
}
|
|
|
|
func TestTheRotateToolTakesTheConsumerAndItsModule(t *testing.T) {
|
|
n, err := narrowNamed(map[string]any{"consumer": "g14", "consumer_module": "gitea"})
|
|
if err != nil || n != (Narrow{Consumer: "g14", ConsumerModule: "gitea"}) {
|
|
t.Fatalf("%+v (%v)", n, err)
|
|
}
|
|
if _, err := narrowNamed(map[string]any{"consumer": "g14/x"}); err == nil {
|
|
t.Fatal("a consumer that is not a name was taken")
|
|
}
|
|
}
|