package inventory import ( "context" "errors" "fmt" "github.com/jackc/pgx/v5" "github.com/novox/mesh-controller/internal/catalogue" ) // The operator's sealing key: the one holder of secrets that is not a node. // // Every secret a module holds for itself is sealed to the node that uses it, and a node whose key // is gone takes its secrets with it — the mesh's root secrets included. novox/hq ADR 0085 (amended) // gives them a second recipient: a person, holding a key whose private half never enters the mesh. // What is recorded here is the public half, which is all the mesh needs to seal to it; what it // yields is one more blob per secret that the mesh cannot open. // OperatorKey is the public key secrets are also sealed to, or empty when the mesh has none. func (i *Inventory) OperatorKey(ctx context.Context) (string, error) { var key string err := i.store.Pool().QueryRow(ctx, `select public from operator_key order by made_at desc limit 1`).Scan(&key) if errors.Is(err, pgx.ErrNoRows) { return "", nil } return key, err } // SetOperatorKey records the operator's public key, replacing any earlier one. // // **Replacing is said, not silent.** Secrets sealed to the earlier key stay sealed to it: the // plaintext is gone, so they cannot be sealed again to the new one until each is issued again. The // number of them is returned so the caller can say so — a key swapped with nothing said would look // like a mesh with a recovery path and be a mesh without one. func (i *Inventory) SetOperatorKey(ctx context.Context, public string) (orphaned int, err error) { if public == "" { return 0, fmt.Errorf("an operator key is a public key, and this is nothing") } tx, err := i.store.Pool().Begin(ctx) if err != nil { return 0, err } defer tx.Rollback(ctx) if err := tx.QueryRow(ctx, `select count(*) from module_secret where operator_key is not null and operator_key <> $1`, public).Scan(&orphaned); err != nil { return 0, err } if _, err := tx.Exec(ctx, `delete from operator_key where public <> $1`, public); err != nil { return 0, err } if _, err := tx.Exec(ctx, `insert into operator_key (public) values ($1) on conflict (public) do nothing`, public); err != nil { return 0, err } return orphaned, tx.Commit(ctx) } // Kept is the catalogue's: one secret as the operator can recover it. type Kept = catalogue.Kept // KeptForOperator is every secret the operator can recover, and which cannot. // // The second list is the honest half: a secret minted before the mesh had an operator key has no // operator-sealed copy and cannot get one — the plaintext was discarded. Naming those is what lets // an export say what it does not cover, rather than being taken for complete. func (i *Inventory) KeptForOperator(ctx context.Context) (kept []Kept, unrecoverable []Kept, err error) { rows, err := i.store.Pool().Query(ctx, `select n.name, s.module, s.name, s.origin, coalesce(s.operator_sealed, ''), coalesce(s.operator_key, ''), s.made_at from module_secret s join node n on n.id = s.node order by n.name, s.module, s.name`) if err != nil { return nil, nil, err } defer rows.Close() for rows.Next() { var k Kept if err := rows.Scan(&k.Node, &k.Module, &k.Name, &k.Origin, &k.Sealed, &k.Key, &k.MadeAt); err != nil { return nil, nil, err } if k.Sealed == "" { unrecoverable = append(unrecoverable, k) continue } kept = append(kept, k) } return kept, unrecoverable, rows.Err() } // KeptSecret is one secret's operator-sealed copy, for recovery. func (i *Inventory) KeptSecret(ctx context.Context, node, module, name string) (Kept, error) { var k Kept err := i.store.Pool().QueryRow(ctx, `select n.name, s.module, s.name, s.origin, coalesce(s.operator_sealed, ''), coalesce(s.operator_key, ''), s.made_at from module_secret s join node n on n.id = s.node where n.name = $1 and s.module = $2 and s.name = $3`, node, module, name). Scan(&k.Node, &k.Module, &k.Name, &k.Origin, &k.Sealed, &k.Key, &k.MadeAt) if errors.Is(err, pgx.ErrNoRows) { return Kept{}, fmt.Errorf("%s on %s holds nothing called %q", module, node, name) } if err != nil { return Kept{}, err } if k.Sealed == "" { return Kept{}, fmt.Errorf( "%s on %s holds %q, but it was made before the mesh had an operator key and so has no "+ "copy a person can open. Issue it again (secret accept, or let the mesh remake it) "+ "and it will", module, node, name) } return k, nil }