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:
+54
-121
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user