mesh-vault provides `secret` (novox/hq ADR 0085, design 24). The value is the pair credential the controller mints — the vault holds no copy, only a ledger of who holds one, its fingerprint and every rotation, and two tools that answer by fingerprint and never by value. Rotation is `rotate secret`, unchanged machinery pointed at a secret with an owner (design 13). Named in the mesh's own namespace, beside mesh-controller and mesh-catalog, because it is the mesh's own code rather than wrapped software. redis is the first consumer: its own password stops being an own-secret nothing could rotate and becomes a `secret` it requires, read from the same file into the same hole. The server now restarts on its config, or it would keep the password it started with through every rotation (playbook 06).
49 lines
2.4 KiB
TypeScript
49 lines
2.4 KiB
TypeScript
// mesh-vault's provisioner — the adapter that makes vault a provider of the mesh `secret` interface. The
|
|
// reconcile loop, the contributions file, and reading the mesh's minted value are the sdk harness's;
|
|
// this writes only the per-service half (novox/hq ADR 0039/0040/0048) — and for a vault that half is
|
|
// taking custody, not creating anything.
|
|
//
|
|
// The `secret` interface (ADR 0085, design 24): a consumer requires a value for its own use — the
|
|
// password of a store it runs privately, an internal token — and reads it from the file the mesh
|
|
// writes on its machine. There is no server to create a login on. **The value is the pair
|
|
// credential itself**: the controller minted it, sealed it to both nodes, and delivered each its
|
|
// copy. What makes it *owned* is this: the vault records who holds it and its fingerprint, notices
|
|
// when `rotate secret` delivers a different one, and says so on the mesh. Rotation is not new
|
|
// machinery — it is the machinery that already moves a database password, pointed at a secret the
|
|
// vault provides (design 13).
|
|
|
|
import { runProvisioner, type Provision } from "@novox/mesh-sdk/provisioner";
|
|
import { emit } from "@novox/mesh-sdk/events";
|
|
import { Ledger } from "../client.js";
|
|
|
|
const ledger = Ledger.fromEnv();
|
|
|
|
/** Emit a lifecycle event without letting a broker hiccup fail the custody itself. */
|
|
async function announce(type: string, body: Record<string, string | number>): Promise<void> {
|
|
try {
|
|
await emit(type, body);
|
|
} catch (err) {
|
|
console.error(`[provisioner:secret] emit ${type} failed: ${err}`);
|
|
}
|
|
}
|
|
|
|
runProvisioner("secret", {
|
|
async create(p: Provision): Promise<void> {
|
|
const { held, outcome } = ledger.record(p.as, p.consumer ?? "", p.password);
|
|
if (outcome === "unchanged") return; // the harness re-runs create on restart; nothing happened
|
|
console.log(`[mesh-vault] ${outcome}: ${held.as} (${held.fingerprint.slice(0, 19)}…, rotations ${held.rotations})`);
|
|
await announce(`module.mesh-vault.secret.${outcome === "granted" ? "provisioned" : "rotated"}`, {
|
|
consumer: held.consumer,
|
|
as: held.as,
|
|
fingerprint: held.fingerprint,
|
|
rotations: held.rotations,
|
|
});
|
|
},
|
|
|
|
async remove(p: { as: string }): Promise<void> {
|
|
if (!ledger.withdraw(p.as)) return;
|
|
console.log(`[mesh-vault] withdrawn: ${p.as}`);
|
|
await announce("module.mesh-vault.secret.deprovisioned", { as: p.as });
|
|
},
|
|
});
|