package bootstrap import ( "os" "strings" "testing" "github.com/novox/mesh-host/internal/declaration" ) // The produced bundle carries no well-known credential: the store reads its password from the // file genesis made, every connection string names the made values, and the broker's default // administrator is changed by an action before anything dials it (novox/hq issue 071). func TestTheProducedBundleCarriesNoWellKnownCredential(t *testing.T) { template, err := os.ReadFile("../../examples/foundation-first-node.lock") if err != nil { t.Skip("no example bundle beside this checkout") } r, err := Rewrite(template, "sha256:"+strings.Repeat("ab", 32)) if err != nil { t.Fatal(err) } creds := RootCredentials{Store: "STORE-PW-40-characters-of-random-base64u", Broker: "BROKER-PW-40-characters-of-random-base64"} got, err := RewriteRoot(&r, creds) if err != nil { t.Fatal(err) } text := string(r.Bundle) for _, gone := range []string{`"bootstrap"`, "postgres:bootstrap@", "guest:guest@"} { if strings.Contains(text, gone) { t.Errorf("the produced bundle still says %s", gone) } } if got.StoreURLs < 3 || got.BrokerURLs < 2 { t.Errorf("rewrote %d store and %d broker connections; the template has three and two", got.StoreURLs, got.BrokerURLs) } var store, action bool for _, res := range r.Declaration.Resources { switch x := res.(type) { case *declaration.Container: if x.Name != "mesh-store" { continue } store = true if _, has := x.Env["POSTGRES_PASSWORD"]; has { t.Error("the store still takes its password from its environment") } if x.Env["POSTGRES_PASSWORD_FILE"] != storeSuperuserMount { t.Errorf("the store reads its password from %q", x.Env["POSTGRES_PASSWORD_FILE"]) } if !strings.Contains(strings.Join(x.Volumes, " "), StoreSuperuserFile+":"+storeSuperuserMount) { t.Errorf("the store does not mount %s: %v", StoreSuperuserFile, x.Volumes) } case *declaration.Action: if x.ID != "broker-admin" { continue } action = true if x.In != "mesh-broker" || !strings.Contains(strings.Join(x.Command, " "), "change_password "+BrokerAdminUser+" '"+creds.Broker+"'") { t.Errorf("the broker-admin action is %v in %q", x.Command, x.In) } } } if !store || !action { t.Fatalf("store=%v action=%v", store, action) } // The order matters: the broker's password changes after it answers and before the control // plane, which dials it with the new one, is raised. var readyAt, adminAt, controlAt int for i, res := range r.Declaration.Resources { switch res.Identity() { case "broker-ready": readyAt = i case "broker-admin": adminAt = i case "control-plane": controlAt = i } } if !(readyAt < adminAt && adminAt < controlAt) { t.Errorf("order ready=%d admin=%d control=%d", readyAt, adminAt, controlAt) } } // A template that no longer says what this expects is refused, not half-rewritten. func TestATemplateWithoutTheKnownCredentialsIsRefused(t *testing.T) { template, err := os.ReadFile("../../examples/foundation-first-node.lock") if err != nil { t.Skip("no example bundle beside this checkout") } changed := strings.Replace(string(template), `"POSTGRES_PASSWORD": "bootstrap"`, `"POSTGRES_PASSWORD": "other"`, 1) r, err := Rewrite([]byte(changed), "sha256:"+strings.Repeat("ab", 32)) if err != nil { t.Fatal(err) } if _, err := RewriteRoot(&r, RootCredentials{Store: "x", Broker: "y"}); err == nil { t.Fatal("a template with an unknown store password was rewritten") } } // Made once and kept: a second run reads the same value; a dry run writes nothing. func TestRootSecretsAreKeptAcrossRuns(t *testing.T) { dir := t.TempDir() path := dir + "/superuser.secret" first, made, err := keptOrMade(path, false) if err != nil || !made || len(first) != 40 { t.Fatalf("first: %q made=%v err=%v", first, made, err) } if info, _ := os.Stat(path); info.Mode().Perm() != 0o600 { t.Errorf("mode %v", info.Mode().Perm()) } second, made, err := keptOrMade(path, false) if err != nil || made || second != first { t.Fatalf("second: %q made=%v err=%v", second, made, err) } dry := dir + "/dry.secret" if _, made, err := keptOrMade(dry, true); err != nil || !made { t.Fatal(err) } if _, err := os.Stat(dry); err == nil { t.Fatal("a dry run wrote a secret") } } // Nothing the installer says after making the credentials contains them. func TestTheTranscriptNeverSaysTheCredentials(t *testing.T) { var said []string say := Masking(func(l string) { said = append(said, l) }, RootCredentials{Store: "STORE-PW", Broker: "BROKER-PW"}) say("created context-schemas (docker run -e MESH_STORE_INVENTORY=postgres://postgres:STORE-PW@127.0.0.1:5432/inventory)") say("failed broker-admin (sh -c lavinmqctl change_password guest 'BROKER-PW' && echo x)") for _, l := range said { if strings.Contains(l, "STORE-PW") || strings.Contains(l, "BROKER-PW") { t.Errorf("said a credential: %s", l) } } if !strings.Contains(said[0], "postgres:…@") || !strings.Contains(said[1], "guest '…'") { t.Errorf("the lines were not the same lines with the values masked: %v", said) } } // The broker-admin marker is written with a line ending, because the verify reads it with `read`. func TestTheBrokerAdminMarkerHasALineEnding(t *testing.T) { template, err := os.ReadFile("../../examples/foundation-first-node.lock") if err != nil { t.Skip("no example bundle beside this checkout") } r, err := Rewrite(template, "sha256:"+strings.Repeat("ab", 32)) if err != nil { t.Fatal(err) } if _, err := RewriteRoot(&r, RootCredentials{Store: "s", Broker: "b"}); err != nil { t.Fatal(err) } for _, res := range r.Declaration.Resources { a, ok := res.(*declaration.Action) if !ok || a.ID != "broker-admin" { continue } cmd := strings.Join(a.Command, " ") if !strings.Contains(cmd, "&& echo ") || strings.Contains(cmd, "printf %s") { t.Errorf("the marker is written without a line ending: %s", cmd) } if !strings.Contains(strings.Join(a.Verify, " "), "read m <") { t.Errorf("the verify does not read the marker: %v", a.Verify) } } }