Installer review: secrets are staged privately, and a bundle is 0600 whether or not it existed

From review: the store and broker passwords genesis makes were carried into
the controller through a world-readable file in /tmp, a bundle left at 0644 by
an earlier installer kept that mode while now holding them, a mesh raised by
the old installer would have been handed new passwords its servers do not have,
and the broker-admin action's marker did not depend on the value. Secrets now
stage in a 0700 directory owned by the controller's account; the bundle is
chmod'd; an existing store or broker volume with no credential file is refused
by name; the marker holds the password's fingerprint. Also: one install path
for the store, broker and vault, no error-string matching for the operator
key, and no unreachable fallback for the superuser.
This commit is contained in:
2026-09-21 01:26:35 +02:00
parent 036af3cfdc
commit 70d0f36896
8 changed files with 175 additions and 155 deletions
+54 -121
View File
@@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/novox/mesh-host/internal/declaration"
@@ -73,6 +72,27 @@ func InstallStore(ctx context.Context, o Options, control controlPlane,
}
say(" adopting " + store.Name + " — the store the foundation raised, unchanged")
// The superuser is the foundation's, made at genesis — carried in before the push, or the push
// would seal random bytes where a working password has to be and the provisioner would not open
// the store it is meant to manage.
if err := installProvider(ctx, o, control, module, manifest, "(the provisioner; the server is adopted, not built)",
func() error {
return deliverCredential(ctx, o, control, module, "superuser", StoreSuperuserFile, "the store's superuser", say)
},
say); err != nil {
return err
}
say(" adopted mesh-store — the foundation's store is now the " + module + " module")
return nil
}
// installProvider registers, builds, issues, assigns and pushes one catalogue module, with one
// thing done just before the push — the moment a credential the mesh could not have made has to
// be in it. InstallFromCatalogue is the same sequence without that moment; the store, the broker
// and the vault each need it or need the shape, and three copies of it drifted.
func installProvider(ctx context.Context, o Options, control controlPlane, module string,
manifest []byte, buildNote string, beforePush func() error, say func(string)) error {
remote := "/" + module + "-module.json"
if err := control.carrying(ctx, module+"-module.json", manifest, remote); err != nil {
return err
@@ -87,7 +107,7 @@ func InstallStore(ctx context.Context, o Options, control controlPlane,
"the catalogue CHECKOUT says what it is, the catalogue REPOSITORY is where a builder "+
"clones it", module)
}
say(" building " + module + " (the provisioner; the server is adopted, not built)")
say(strings.TrimRight(" building "+module+" "+buildNote, " "))
if _, err := control.within(buildWait).tell(ctx, "build", o.CatalogSource.Repository,
"--path", "modules/"+module, "--ref", refOr(o.CatalogSource.Ref), "--wait", "1200s"); err != nil {
return err
@@ -102,37 +122,40 @@ func InstallStore(ctx context.Context, o Options, control controlPlane,
if _, err := control.tell(ctx, "assign", o.Node, module); err != nil {
return err
}
// The superuser is the foundation's, made at genesis — carried in before the push, or the push
// would seal random bytes where a working password has to be and the provisioner would not open
// the store it is meant to manage.
if err := deliverSuperuser(ctx, o, control, module, store, say); err != nil {
return err
if beforePush != nil {
if err := beforePush(); err != nil {
return err
}
}
if _, err := pushNode(ctx, o, control, say); err != nil {
return err
}
say(" adopted mesh-store — the foundation's store is now the " + module + " module")
return nil
_, err := pushNode(ctx, o, control, say)
return err
}
func readCredentialFile(path string) (string, error) {
raw, err := os.ReadFile(path)
// deliverCredential carries a credential genesis made into a module as its own secret, through
// `secret accept`: the mesh cannot invent the value a running server already has.
func deliverCredential(ctx context.Context, o Options, control controlPlane, module, secret, file, what string,
say func(string)) error {
value, err := readCredentialFile(file)
if err != nil {
return "", err
return fmt.Errorf("%s is not at %s, so the %s module has nothing to open its server with — "+
"and the mesh cannot invent the one the server already has: %w", what, file, module, err)
}
value := strings.TrimRight(string(raw), "\r\n")
if value == "" {
return "", fmt.Errorf("%s is empty", path)
at := "/accepting-" + secret
if err := control.carryingSecret(ctx, "mesh-accepting-"+secret, []byte(value), at); err != nil {
return err
}
return value, nil
if _, err := control.tell(ctx, "secret", "accept", o.Node, module, secret, "--from", at); err != nil {
return err
}
say(" accepted " + secret + " — " + what + ", as genesis made it")
return nil
}
// InstallBroker makes the foundation's broker the `lavinmq` module, adopted in place — the same
// shape as InstallStore, for the same reasons. The administrator's password is the one genesis
// gave the image's default account (rootsecrets.go), carried in through `secret accept` so the
// module's provisioner can reach the management API as it.
// gave the image's default account (rootsecrets.go), carried in so the module's provisioner can
// reach the management API as it.
func InstallBroker(ctx context.Context, o Options, control controlPlane,
foundation *declaration.Declaration, say func(string)) error {
@@ -149,46 +172,11 @@ func InstallBroker(ctx context.Context, o Options, control controlPlane,
return err
}
say(" adopting " + broker.Name + " — the broker the foundation raised, unchanged")
remote := "/" + module + "-module.json"
if err := control.carrying(ctx, module+"-module.json", manifest, remote); err != nil {
return err
}
if _, err := control.tell(ctx, "module", "add", remote); err != nil {
return err
}
say(" registered " + module)
if o.CatalogSource.Repository == "" {
return fmt.Errorf("%s has to be built and there is no --catalog-source to build it from", module)
}
say(" building " + module + " (the provisioner; the server is adopted, not built)")
if _, err := control.within(buildWait).tell(ctx, "build", o.CatalogSource.Repository,
"--path", "modules/"+module, "--ref", refOr(o.CatalogSource.Ref), "--wait", "1200s"); err != nil {
return err
}
if _, err := control.tell(ctx, "module", "issue", module, "--node", o.Node); err != nil {
say(" no account " + module + " — it declares nothing to say on the broker")
} else {
say(" account issued " + module)
}
if _, err := control.tell(ctx, "assign", o.Node, module); err != nil {
return err
}
value, err := readCredentialFile(BrokerAdminFile)
if err != nil {
return fmt.Errorf("the broker's administrator password genesis made is not at %s: %w", BrokerAdminFile, err)
}
at := "/accepting-admin"
if err := control.carrying(ctx, "mesh-accepting-admin", []byte(value), at); err != nil {
return err
}
if _, err := control.tell(ctx, "secret", "accept", o.Node, module, "admin", "--from", at); err != nil {
return err
}
say(" accepted admin — the broker's administrator, as genesis made it")
if _, err := pushNode(ctx, o, control, say); err != nil {
if err := installProvider(ctx, o, control, module, manifest, "(the provisioner; the server is adopted, not built)",
func() error {
return deliverCredential(ctx, o, control, module, "admin", BrokerAdminFile, "the broker's administrator", say)
},
say); err != nil {
return err
}
say(" adopted " + broker.Name + " — the foundation's broker is now the " + module + " module")
@@ -196,38 +184,15 @@ func InstallBroker(ctx context.Context, o Options, control controlPlane,
}
// InstallVault installs the vault as a foundation module (novox/hq ADR 0085, amended). Nothing to
// adopt: it is its own runtime, built from the catalogue like any provider, and from its first push
// it keeps the export of every operator-sealed secret on its own disk.
// adopt and nothing to carry in: it is its own runtime, built from the catalogue like any provider,
// and from its first push it keeps the export of every operator-sealed secret on its own disk.
func InstallVault(ctx context.Context, o Options, control controlPlane, say func(string)) error {
const module = "mesh-vault"
manifest, err := readManifest(o.Catalogue, module)
if err != nil {
return err
}
remote := "/" + module + "-module.json"
if err := control.carrying(ctx, module+"-module.json", manifest, remote); err != nil {
return err
}
if _, err := control.tell(ctx, "module", "add", remote); err != nil {
return err
}
say(" registered " + module)
if o.CatalogSource.Repository == "" {
return fmt.Errorf("%s has to be built and there is no --catalog-source to build it from", module)
}
say(" building " + module)
if _, err := control.within(buildWait).tell(ctx, "build", o.CatalogSource.Repository,
"--path", "modules/"+module, "--ref", refOr(o.CatalogSource.Ref), "--wait", "1200s"); err != nil {
return err
}
if _, err := control.tell(ctx, "module", "issue", module, "--node", o.Node); err != nil {
return err
}
say(" account issued " + module)
if _, err := control.tell(ctx, "assign", o.Node, module); err != nil {
return err
}
if _, err := pushNode(ctx, o, control, say); err != nil {
if err := installProvider(ctx, o, control, module, manifest, "", nil, say); err != nil {
return err
}
say(" installed " + module + " — the operator-sealed export now lives on this machine, outside the store")
@@ -294,35 +259,3 @@ func serverMatchesFoundation(manifest []byte, store *declaration.Container, modu
"store with. Its server container has to carry the name the foundation raised",
module, store.Name)
}
// deliverSuperuser carries the store's superuser password into the module.
//
// It is the foundation's, set on the bundle's store container at genesis; the mesh cannot invent a
// credential that already made the databases, so it goes in through `secret accept`, exactly as the
// control plane's store connections do (control.go deliverStores).
func deliverSuperuser(ctx context.Context, o Options, control controlPlane, module string,
store *declaration.Container, say func(string)) error {
const secret = "superuser"
// Genesis made it and kept it in the file the store was raised from (rootsecrets.go); the
// template's environment variable is accepted too, for a bundle produced before that.
value, err := readCredentialFile(StoreSuperuserFile)
if err != nil {
value = strings.TrimSpace(store.Env["POSTGRES_PASSWORD"])
}
if value == "" {
return fmt.Errorf(
"neither %s nor the foundation's store names the superuser password, so the %s module "+
"has nothing to open the store with — and the mesh cannot invent the one that already "+
"made the databases", StoreSuperuserFile, module)
}
at := "/accepting-" + secret
if err := control.carrying(ctx, "mesh-accepting-"+secret, []byte(value), at); err != nil {
return err
}
if _, err := control.tell(ctx, "secret", "accept", o.Node, module, secret, "--from", at); err != nil {
return err
}
say(" accepted " + secret + " — the store's superuser, as the foundation made it")
return nil
}