Genesis makes the root secrets, the operator key, and installs the vault
The template raises the store with the password 'bootstrap' and the broker with its image's default administrator, and the installer carried both into the mesh as accepted secrets — permanent, and not secret (novox/hq issue 071). Now the installer makes both credentials, once, at the paths the postgres and lavinmq modules declare as their own secrets, rewrites the produced bundle to use them (the store reads its password from a file; the broker's default account is given the new password by an action before anything dials it), and writes the bundle at 0600 since it now carries them. Before the first secret is accepted it makes the operator's sealing key beside the bundle and gives the mesh the public half, so everything minted from there is sealed to it too (ADR 0085, amended). Phase three adopts the broker as the lavinmq module beside the store and installs mesh-vault as a foundation module; the run ends by writing the operator-sealed export beside the key.
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/novox/mesh-host/internal/declaration"
|
||||
@@ -116,6 +117,123 @@ func InstallStore(ctx context.Context, o Options, control controlPlane,
|
||||
return nil
|
||||
}
|
||||
|
||||
func readCredentialFile(path string) (string, error) {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
value := strings.TrimRight(string(raw), "\r\n")
|
||||
if value == "" {
|
||||
return "", fmt.Errorf("%s is empty", path)
|
||||
}
|
||||
return value, 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.
|
||||
func InstallBroker(ctx context.Context, o Options, control controlPlane,
|
||||
foundation *declaration.Declaration, say func(string)) error {
|
||||
|
||||
const module = "lavinmq"
|
||||
manifest, err := readManifest(o.Catalogue, module)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
broker, err := brokerIn(foundation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := serverMatchesFoundation(manifest, broker, module); err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
say(" adopted " + broker.Name + " — the foundation's broker is now the " + module + " module")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
say(" installed " + module + " — the operator-sealed export now lives on this machine, outside the store")
|
||||
return nil
|
||||
}
|
||||
|
||||
// storeIn finds the store container in the bundle this installer produced.
|
||||
func storeIn(d *declaration.Declaration) (*declaration.Container, error) {
|
||||
return foundationContainer(d, StoreID, "store")
|
||||
@@ -186,12 +304,17 @@ func deliverSuperuser(ctx context.Context, o Options, control controlPlane, modu
|
||||
store *declaration.Container, say func(string)) error {
|
||||
|
||||
const secret = "superuser"
|
||||
value := strings.TrimSpace(store.Env["POSTGRES_PASSWORD"])
|
||||
// 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(
|
||||
"the foundation's store names no POSTGRES_PASSWORD, so the %s module has no superuser "+
|
||||
"to open it with — and the mesh cannot invent the one that already made the databases",
|
||||
module)
|
||||
"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 {
|
||||
|
||||
Reference in New Issue
Block a user