Keep every password, token and API key the vault's, never a setting (hq issue 342)
mesh/merge-gate head fe2040c65ea2 merged into trunk 53e51864969f — pass: builds baserow, gitea, grafana, home-assistant, icecast, influxdb, letta, mailu, n…
mesh/repo-check head fe2040c65ea2 merged into trunk 53e51864969f — pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh): main's version judged it; the ch…
mesh/delivery-group group fix/342-a-secret-is-never-a-setting ready: every member ready, and composed together they pass
mesh/delivery superseded: a newer head of the same pull request

A setting outranked the vault: baserow, grafana, letta and unifi merged a layer's
password over the ${secret:...} in their runtime config, and the gitea,
home-assistant, icecast, influxdb, mailu and nextcloud clients read the settings
file before the vault's file. A secret in a setting cannot be rotated and is read
back by the settings verb.

The four files now keep their password protected, so the controller refuses a
setting of it. The six clients ignore a secret in the settings file and say so
once, by key and never with the value; icecast reads its admin password from the
vault's file. checks/secrets refuses a mergeable file whose secret a layer could
replace; searxng's nested server.secret_key is listed as known until the
controller protects a nested key.
This commit is contained in:
jochen
2026-10-13 07:32:19 +02:00
parent 53e5186496
commit fe2040c65e
23 changed files with 632 additions and 13 deletions
+13 -1
View File
@@ -52,6 +52,17 @@ function meshConfig(file?: string): Record<string, string> {
catch { return {}; }
}
/**
* A password, token or API key is the vault's, never a setting (novox/hq issue 342): a value for one of
* them in the settings file is ignored, and that is said once — by its key, never with its value.
*/
const saidIgnored = new Set<string>();
function ignoreSecretSetting(cfg: Record<string, unknown>, key: string): void {
if (cfg[key] === undefined || saidIgnored.has(key)) return;
saidIgnored.add(key);
console.error(`the setting ${key} is ignored: it comes from the vault`);
}
/** Read a secret from the file the mesh mounted it at (an own-secret), if the pointing env is set. */
function readSecret(path: string | undefined): string | undefined {
if (!path) return undefined;
@@ -81,7 +92,8 @@ export class MailuClient {
static fromEnv(env: NodeJS.ProcessEnv = process.env): MailuClient {
const cfg = meshConfig(env.MESH_MAILU_CONFIG_FILE);
const url = cfg.url ?? env.MESH_MAILU_URL;
const apiKey = cfg.apiKey ?? readSecret(env.MESH_MAILU_API_KEY_FILE) ?? env.MESH_MAILU_API_KEY;
ignoreSecretSetting(cfg, "apiKey");
const apiKey = readSecret(env.MESH_MAILU_API_KEY_FILE) ?? env.MESH_MAILU_API_KEY;
if (!url || !apiKey) {
throw new Error("Mailu is not configured — set MESH_MAILU_URL and the API token own-secret");
}