//go:build integration package main import ( "context" "errors" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" "github.com/novox/mesh-controller/internal/broker" ) // Every other test mints without a vault to hand to: a stand-in that takes anything, replaced by the tests below. func init() { vaultAskerForIssue = func(context.Context) vaultAsker { return newStandInVault(nil).ask } } // A covered account is handed to the vault when it is minted (novox/hq ADR 0374, issue 787): the whole value, sealed // to the key the vault made for the call, with origin issued. A module that is neither core nor moved hands nothing. func TestModuleIssueHandsACoveredAccountToTheVaultAndOnlyThat(t *testing.T) { open := aMesh(t) ctx := t.Context() theVaultIsInstalled(t, open) inv := open.inventory known := broker.Broker{Fingerprint: "ab"} core := aProvisioner() core.Module = "nats" moved := aBot("moved-bot", true) moved.OwnSecrets["broker"] = catalogue.OwnSecret{Path: "/var/lib/moved-bot/broker"} plain := aBot("kept-bot", false) plain.OwnSecrets["broker"] = catalogue.OwnSecret{Path: "/var/lib/kept-bot/broker"} vault := newStandInVault(t) vaultAskerForIssue = func(context.Context) vaultAsker { return vault.ask } t.Cleanup(func() { vaultAskerForIssue = func(context.Context) vaultAsker { return newStandInVault(nil).ask } }) for _, m := range []catalogue.Manifest{core, moved, plain} { register(t, open, m) if _, err := assign(ctx, open, "laptop", m.Module); err != nil { t.Fatal(err) } } issue := func(m catalogue.Manifest, password string) { t.Helper() if err := issueWith(ctx, inv, m, "laptop", "", known, "10.0.0.1:4222", "module."+m.Module+".laptop", password); err != nil { t.Fatal(err) } } issue(core, "first-core-password") issue(moved, "first-moved-password") issue(plain, "kept-password") if err := issueProvisionerClient(ctx, inv, core, "laptop", known, "10.0.0.1:4222"); err != nil { t.Fatal(err) } said := vault.said() for _, want := range []string{`"module":"nats"`, `"module":"moved-bot"`, `"name":"broker"`, `"name":"` + broker.ProvisionerSecret + `"`, `"node":"laptop"`} { if !strings.Contains(said, want) { t.Errorf("the vault was told %s, without %s", said, want) } } if n := strings.Count(said, `"origin":"issued"`); n != 3 { t.Errorf("%d accounts were handed with origin issued, want 3 (the core's two and the moved module's): %s", n, said) } if strings.Contains(said, "kept-bot") { t.Errorf("an account that is neither core nor moved was handed to the vault: %s", said) } if strings.Contains(said, "first-core-password") || strings.Contains(said, "first-moved-password") { t.Errorf("a value crossed the bus in the clear: %s", said) } // A re-issue hands the new value, which the vault opens: the whole {url, fingerprint, node, user, password}. issue(moved, "second-moved-password") if got := vault.taken["accept"]; !strings.Contains(got, "second-moved-password") || !strings.Contains(got, `"user":"module.moved-bot.laptop"`) || !strings.Contains(got, `"node":"laptop"`) || !strings.Contains(got, `"url":"nats://10.0.0.1:4222"`) || !strings.Contains(got, `"fingerprint":"ab"`) { t.Errorf("the vault was handed %q after a re-issue, not the new account whole", got) } } // A vault that does not take the value fails the issue loudly: the bus's user list holds the new hash already. func TestModuleIssueFailsWhenTheVaultDoesNotTakeACoveredAccount(t *testing.T) { open := aMesh(t) ctx := t.Context() moved := aBot("moved-bot", true) moved.OwnSecrets["broker"] = catalogue.OwnSecret{Path: "/var/lib/moved-bot/broker"} theVaultIsInstalled(t, open) register(t, open, moved) if _, err := assign(ctx, open, "laptop", moved.Module); err != nil { t.Fatal(err) } vault := newStandInVault(t) vault.refuse = errors.New("the vault is not there") vaultAskerForIssue = func(context.Context) vaultAsker { return vault.ask } t.Cleanup(func() { vaultAskerForIssue = func(context.Context) vaultAsker { return newStandInVault(nil).ask } }) err := issueWith(ctx, open.inventory, moved, "laptop", "", broker.Broker{Fingerprint: "ab"}, "10.0.0.1:4222", "module.moved-bot.laptop", "pw") if err == nil || !strings.Contains(err.Error(), "vault did not take") { t.Fatalf("a vault that took nothing was reported as %v", err) } } func theVaultIsInstalled(t *testing.T, open *stores) { t.Helper() vault := catalogue.Manifest{Module: "mesh-vault", Version: "1"} register(t, open, vault) if _, err := assign(t.Context(), open, "laptop", vault.Module); err != nil { t.Fatal(err) } } // In genesis the bus's and the provisioner's accounts are issued before the vault is installed (ADR 0113 §15 to §18): // there is no vault to hand to, so nothing is asked and nothing fails, and the value stays in the controller's store // as before, for the move to carry into the vault. Once the vault is installed, one that does not answer fails. func TestModuleIssueBeforeTheVaultIsInstalledKeepsTheValueAndHandsNothing(t *testing.T) { open := aMesh(t) ctx := t.Context() inv := open.inventory known := broker.Broker{Fingerprint: "ab"} core := aProvisioner() core.Module = "nats" register(t, open, core) if _, err := assign(ctx, open, "laptop", core.Module); err != nil { t.Fatal(err) } vault := newStandInVault(t) vault.refuse = errors.New("there is no vault yet") vaultAskerForIssue = func(context.Context) vaultAsker { return vault.ask } t.Cleanup(func() { vaultAskerForIssue = func(context.Context) vaultAsker { return newStandInVault(nil).ask } }) if err := issueWith(ctx, inv, core, "laptop", "", known, "10.0.0.1:4222", "module.nats.laptop", "pw"); err != nil { t.Fatalf("genesis's issue of the bus's account failed with no vault installed: %v", err) } if err := issueProvisionerClient(ctx, inv, core, "laptop", known, "10.0.0.1:4222"); err != nil { t.Fatalf("genesis's issue of the client's account failed with no vault installed: %v", err) } if said := vault.said(); said != "" { t.Errorf("the vault was asked before it was installed: %s", said) } if _, held, err := inv.ModuleSecretIfIssued(ctx, "laptop", "nats", "broker"); err != nil || !held { t.Errorf("the value is not kept in the controller's store (held %v, %v)", held, err) } theVaultIsInstalled(t, open) if err := issueWith(ctx, inv, core, "laptop", "", known, "10.0.0.1:4222", "module.nats.laptop", "pw2"); err == nil { t.Error("a vault that is installed and does not answer did not fail the issue") } }