Rotate a credential and move both ends together
The invariant novox/hq ADR 0001 records as unowned, and it was measurably false in HAL: a provision documented as never rotating minted a new password on every adoption and updated only the provider's row. Consumers on three nodes held dead credentials for two days while the mesh reported success. Nothing enumerated who held the old one. Three things make that impossible here. The holders are a set the mesh can name — each pair has its own credential, so rotating one consumer touches one role and the affected list is a query rather than an assumption. Both ends are pushed by this command rather than a later one, because leaving the sending to whoever remembered is the fault exactly. And it is all-or-nothing: if any affected machine cannot be resolved, nothing is sent and the old credential keeps working, which is a mesh that has not rotated rather than one that has half-rotated. The window is stated rather than hidden: a role's password changes on the provider and the file changes on the consumer, and they cannot be simultaneous. The provisioner now takes its superuser password from the file the mesh wrote, which is how the mesh delivers one. Passing it through the environment needed a person in the middle of the one path that exists so there is not one — and put a superuser password where `docker inspect` prints it.
This commit is contained in:
@@ -24,6 +24,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
@@ -171,7 +172,11 @@ func run(ctx context.Context) error {
|
||||
return fmt.Errorf("the manifest at %s is not readable: %w", grants, err)
|
||||
}
|
||||
|
||||
db, err := pgx.Connect(ctx, os.Getenv("MESH_PROVISION_POSTGRES"))
|
||||
where, err := connectionString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db, err := pgx.Connect(ctx, where)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -306,3 +311,51 @@ func sorted(given []contribution) []contribution {
|
||||
// to be safe today" is not a property anything should rest on.
|
||||
func quoteName(s string) string { return `"` + strings.ReplaceAll(s, `"`, `""`) + `"` }
|
||||
func quoteString(s string) string { return `'` + strings.ReplaceAll(s, `'`, `''`) + `'` }
|
||||
|
||||
// connectionString is where this provisioner reaches the database it owns.
|
||||
//
|
||||
// **The password comes from a file**, because that is how the mesh delivers one. A module's own
|
||||
// secret — a superuser password here — is sealed to the machine and written by the host; a
|
||||
// provisioner told to take it from an environment variable would need somebody to read the file
|
||||
// and pass it in, which is a person in the middle of the one path that exists so there is not
|
||||
// one.
|
||||
//
|
||||
// It is also the difference between a credential that lives in a file and one that lives in a
|
||||
// process listing: `docker inspect` prints environment, and a superuser password printed by an
|
||||
// ordinary diagnostic is a superuser password in whatever collected that diagnostic.
|
||||
//
|
||||
// MESH_PROVISION_POSTGRES alone still works, for a provisioner somebody runs by hand.
|
||||
func connectionString() (string, error) {
|
||||
where := strings.TrimSpace(os.Getenv("MESH_PROVISION_POSTGRES"))
|
||||
if where == "" {
|
||||
return "", fmt.Errorf(
|
||||
"MESH_PROVISION_POSTGRES is not set, so this provisioner does not know which " +
|
||||
"database it owns")
|
||||
}
|
||||
path := strings.TrimSpace(os.Getenv("MESH_PROVISION_PASSWORD_FILE"))
|
||||
if path == "" {
|
||||
return where, nil
|
||||
}
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"cannot read the password this provisioner was given at %s: %w", path, err)
|
||||
}
|
||||
password := strings.TrimSpace(string(raw))
|
||||
if password == "" {
|
||||
// An empty file connects as nobody and is refused by the database, three layers from
|
||||
// here, as an authentication problem with no cause anybody changed.
|
||||
return "", fmt.Errorf("%s is empty, so this provisioner has no password", path)
|
||||
}
|
||||
|
||||
parsed, err := url.Parse(where)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("MESH_PROVISION_POSTGRES is not a URL: %w", err)
|
||||
}
|
||||
user := parsed.User.Username()
|
||||
if user == "" {
|
||||
user = "postgres"
|
||||
}
|
||||
parsed.User = url.UserPassword(user, password)
|
||||
return parsed.String(), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user