@kitsy/cnos 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/build/index.cjs +902 -113
- package/dist/build/index.d.cts +1 -1
- package/dist/build/index.d.ts +1 -1
- package/dist/build/index.js +22 -10
- package/dist/{chunk-WHUGFPE4.js → chunk-CDXJISGB.js} +1 -1
- package/dist/{chunk-APCTXRUN.js → chunk-DRKDNY4I.js} +998 -191
- package/dist/chunk-E7SE6N26.js +189 -0
- package/dist/{chunk-SO5XREEU.js → chunk-EDCLLCNL.js} +32 -11
- package/dist/{chunk-SXTMTACL.js → chunk-FC3IV6A7.js} +1 -31
- package/dist/{chunk-MLQGYCO7.js → chunk-JDII6O72.js} +1 -1
- package/dist/chunk-K6QYI2T4.js +105 -0
- package/dist/{chunk-EIN55XXA.js → chunk-OOKFRWTN.js} +1 -1
- package/dist/{chunk-ZA74BO47.js → chunk-OWUZQ4OH.js} +1 -1
- package/dist/{chunk-RD5WMHPM.js → chunk-QTKXPY3N.js} +1 -1
- package/dist/configure/index.cjs +2928 -0
- package/dist/configure/index.d.cts +12 -0
- package/dist/configure/index.d.ts +12 -0
- package/dist/configure/index.js +24 -0
- package/dist/{envNaming-BTJpH93W.d.cts → envNaming-D6k66myh.d.cts} +1 -1
- package/dist/{envNaming-CcsqAel3.d.ts → envNaming-Dy3WYiGK.d.ts} +1 -1
- package/dist/index.cjs +1142 -178
- package/dist/index.d.cts +2 -13
- package/dist/index.d.ts +2 -13
- package/dist/index.js +13 -25
- package/dist/internal.cjs +1512 -80
- package/dist/internal.d.cts +170 -14
- package/dist/internal.d.ts +170 -14
- package/dist/internal.js +645 -5
- package/dist/plugin/basic-schema.cjs +29 -2
- package/dist/plugin/basic-schema.d.cts +1 -1
- package/dist/plugin/basic-schema.d.ts +1 -1
- package/dist/plugin/basic-schema.js +2 -2
- package/dist/plugin/cli-args.cjs +29 -2
- package/dist/plugin/cli-args.d.cts +1 -1
- package/dist/plugin/cli-args.d.ts +1 -1
- package/dist/plugin/cli-args.js +2 -2
- package/dist/plugin/dotenv.cjs +36 -9
- package/dist/plugin/dotenv.d.cts +2 -2
- package/dist/plugin/dotenv.d.ts +2 -2
- package/dist/plugin/dotenv.js +2 -2
- package/dist/plugin/env-export.cjs +31 -2
- package/dist/plugin/env-export.d.cts +2 -2
- package/dist/plugin/env-export.d.ts +2 -2
- package/dist/plugin/env-export.js +2 -2
- package/dist/plugin/filesystem.cjs +46 -91
- package/dist/plugin/filesystem.d.cts +1 -1
- package/dist/plugin/filesystem.d.ts +1 -1
- package/dist/plugin/filesystem.js +2 -2
- package/dist/plugin/process-env.cjs +31 -4
- package/dist/plugin/process-env.d.cts +2 -2
- package/dist/plugin/process-env.d.ts +2 -2
- package/dist/plugin/process-env.js +2 -2
- package/dist/{plugin-DkOIT5uI.d.cts → plugin-CyNkf7Dm.d.cts} +14 -2
- package/dist/{plugin-DkOIT5uI.d.ts → plugin-CyNkf7Dm.d.ts} +14 -2
- package/dist/runtime/index.cjs +956 -128
- package/dist/runtime/index.d.cts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +11 -186
- package/dist/{toPublicEnv-DvFeV3qG.d.cts → toPublicEnv-Cz72m6y0.d.cts} +1 -1
- package/dist/{toPublicEnv-C9clvXLo.d.ts → toPublicEnv-D2PZkaN-.d.ts} +1 -1
- package/package.json +11 -1
- package/dist/chunk-JUHPBAEH.js +0 -20
- package/dist/chunk-PQ4KSV76.js +0 -50
|
@@ -17,6 +17,11 @@ var CnosSecurityError = class extends CnosError {
|
|
|
17
17
|
super(message);
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
+
var CnosAuthenticationError = class extends CnosError {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(message);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
20
25
|
var CnosKeyNotFoundError = class extends CnosError {
|
|
21
26
|
constructor(key) {
|
|
22
27
|
super(`Missing required CNOS config key: ${key}`);
|
|
@@ -58,6 +63,112 @@ function inspectValue(graph, key) {
|
|
|
58
63
|
};
|
|
59
64
|
}
|
|
60
65
|
|
|
66
|
+
// ../core/src/keychain/linux.ts
|
|
67
|
+
import { execFile, spawn } from "child_process";
|
|
68
|
+
import { promisify } from "util";
|
|
69
|
+
var execFileAsync = promisify(execFile);
|
|
70
|
+
async function readLinuxKeychain(entry) {
|
|
71
|
+
try {
|
|
72
|
+
const { stdout } = await execFileAsync("secret-tool", ["lookup", "service", "cnos", "account", entry]);
|
|
73
|
+
const value = stdout.trim();
|
|
74
|
+
return value.length > 0 ? value : void 0;
|
|
75
|
+
} catch {
|
|
76
|
+
return void 0;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function writeLinuxKeychain(entry, value) {
|
|
80
|
+
await new Promise((resolve, reject) => {
|
|
81
|
+
const child = spawn("secret-tool", ["store", "--label", `CNOS ${entry}`, "service", "cnos", "account", entry], {
|
|
82
|
+
stdio: ["pipe", "ignore", "pipe"]
|
|
83
|
+
});
|
|
84
|
+
let stderr = "";
|
|
85
|
+
child.stdin?.end(value);
|
|
86
|
+
child.stderr?.on("data", (chunk) => {
|
|
87
|
+
stderr += chunk.toString();
|
|
88
|
+
});
|
|
89
|
+
child.on("error", reject);
|
|
90
|
+
child.on("close", (code) => {
|
|
91
|
+
if (code === 0) {
|
|
92
|
+
resolve();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
reject(new Error(stderr || `secret-tool exited with code ${code ?? 1}`));
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ../core/src/keychain/macos.ts
|
|
101
|
+
import { execFile as execFile2 } from "child_process";
|
|
102
|
+
import { promisify as promisify2 } from "util";
|
|
103
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
104
|
+
async function readMacosKeychain(entry) {
|
|
105
|
+
try {
|
|
106
|
+
const { stdout } = await execFileAsync2("security", ["find-generic-password", "-a", "cnos", "-s", entry, "-w"]);
|
|
107
|
+
const value = stdout.trim();
|
|
108
|
+
return value.length > 0 ? value : void 0;
|
|
109
|
+
} catch {
|
|
110
|
+
return void 0;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function writeMacosKeychain(entry, value) {
|
|
114
|
+
await execFileAsync2("security", ["add-generic-password", "-a", "cnos", "-s", entry, "-w", value, "-U"]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ../core/src/keychain/windows.ts
|
|
118
|
+
import { execFile as execFile3 } from "child_process";
|
|
119
|
+
import { promisify as promisify3 } from "util";
|
|
120
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
121
|
+
function wrap(script) {
|
|
122
|
+
return ["-NoProfile", "-Command", script];
|
|
123
|
+
}
|
|
124
|
+
async function readWindowsKeychain(entry) {
|
|
125
|
+
try {
|
|
126
|
+
const { stdout } = await execFileAsync3(
|
|
127
|
+
"powershell",
|
|
128
|
+
wrap(`Add-Type -AssemblyName System.Runtime.WindowsRuntime; [Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime] > $null; $vault = New-Object Windows.Security.Credentials.PasswordVault; $credential = $vault.Retrieve('cnos','${entry}'); $credential.RetrievePassword(); Write-Output $credential.Password`)
|
|
129
|
+
);
|
|
130
|
+
const value = stdout.trim();
|
|
131
|
+
return value.length > 0 ? value : void 0;
|
|
132
|
+
} catch {
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function writeWindowsKeychain(entry, value) {
|
|
137
|
+
await execFileAsync3(
|
|
138
|
+
"powershell",
|
|
139
|
+
wrap(`Add-Type -AssemblyName System.Runtime.WindowsRuntime; [Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime] > $null; $vault = New-Object Windows.Security.Credentials.PasswordVault; try { $existing = $vault.Retrieve('cnos','${entry}'); $vault.Remove($existing) } catch {}; $credential = New-Object Windows.Security.Credentials.PasswordCredential('cnos','${entry}','${value}'); $vault.Add($credential)`)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ../core/src/keychain/index.ts
|
|
144
|
+
async function readKeychain(entry) {
|
|
145
|
+
if (process.platform === "win32") {
|
|
146
|
+
return readWindowsKeychain(entry);
|
|
147
|
+
}
|
|
148
|
+
if (process.platform === "darwin") {
|
|
149
|
+
return readMacosKeychain(entry);
|
|
150
|
+
}
|
|
151
|
+
if (process.platform === "linux") {
|
|
152
|
+
return readLinuxKeychain(entry);
|
|
153
|
+
}
|
|
154
|
+
return void 0;
|
|
155
|
+
}
|
|
156
|
+
async function writeKeychain(entry, value) {
|
|
157
|
+
if (process.platform === "win32") {
|
|
158
|
+
await writeWindowsKeychain(entry, value);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (process.platform === "darwin") {
|
|
162
|
+
await writeMacosKeychain(entry, value);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (process.platform === "linux") {
|
|
166
|
+
await writeLinuxKeychain(entry, value);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
throw new CnosAuthenticationError(`OS keychain is not supported on platform "${process.platform}".`);
|
|
170
|
+
}
|
|
171
|
+
|
|
61
172
|
// ../core/src/utils/path.ts
|
|
62
173
|
import { access } from "fs/promises";
|
|
63
174
|
import os from "os";
|
|
@@ -246,22 +357,82 @@ function normalizeNamespaces(namespaces) {
|
|
|
246
357
|
function normalizeVaults(vaults) {
|
|
247
358
|
return Object.fromEntries(
|
|
248
359
|
Object.entries(vaults ?? {}).map(([name, definition]) => {
|
|
360
|
+
const legacyPassphrase = definition.passphrase;
|
|
361
|
+
if (legacyPassphrase !== void 0) {
|
|
362
|
+
throw new CnosManifestError(
|
|
363
|
+
`Vault "${name}" uses legacy passphrase configuration. Use vaults.${name}.auth instead.`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
249
366
|
const provider = definition.provider?.trim();
|
|
250
367
|
if (!provider) {
|
|
251
368
|
throw new CnosManifestError(`Vault "${name}" requires a provider`);
|
|
252
369
|
}
|
|
370
|
+
const normalizedAuth = normalizeVaultAuth(name, provider, definition.auth);
|
|
371
|
+
const normalizedMapping = Object.fromEntries(
|
|
372
|
+
Object.entries(definition.mapping ?? {}).filter(
|
|
373
|
+
(entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
|
|
374
|
+
).map(([envVar, logicalRef]) => [envVar.trim(), logicalRef.trim()]).filter(([envVar, logicalRef]) => envVar.length > 0 && logicalRef.length > 0)
|
|
375
|
+
);
|
|
253
376
|
return [
|
|
254
377
|
name,
|
|
255
378
|
{
|
|
256
379
|
provider,
|
|
257
|
-
|
|
258
|
-
|
|
380
|
+
auth: normalizedAuth,
|
|
381
|
+
...Object.keys(normalizedMapping).length > 0 ? {
|
|
382
|
+
mapping: normalizedMapping
|
|
259
383
|
} : {}
|
|
260
384
|
}
|
|
261
385
|
];
|
|
262
386
|
})
|
|
263
387
|
);
|
|
264
388
|
}
|
|
389
|
+
function normalizeAuthSources(value) {
|
|
390
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
391
|
+
return void 0;
|
|
392
|
+
}
|
|
393
|
+
const sources = Array.isArray(value.from) ? value.from : void 0;
|
|
394
|
+
const normalized = (sources ?? []).map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
395
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
396
|
+
}
|
|
397
|
+
function normalizeVaultAuth(vaultName, provider, auth) {
|
|
398
|
+
if (provider === "local") {
|
|
399
|
+
const passphraseSources = normalizeAuthSources(auth?.passphrase);
|
|
400
|
+
const defaultToken = vaultName.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
401
|
+
const defaultSources = [
|
|
402
|
+
...defaultToken ? [`env:CNOS_SECRET_PASSPHRASE_${defaultToken}`] : [],
|
|
403
|
+
"env:CNOS_SECRET_PASSPHRASE",
|
|
404
|
+
`keychain:cnos/${vaultName}`,
|
|
405
|
+
"prompt"
|
|
406
|
+
];
|
|
407
|
+
return {
|
|
408
|
+
method: auth?.method ?? "passphrase",
|
|
409
|
+
passphrase: {
|
|
410
|
+
from: passphraseSources ?? defaultSources
|
|
411
|
+
},
|
|
412
|
+
...auth?.config ? { config: auth.config } : {}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (provider === "github-secrets") {
|
|
416
|
+
return {
|
|
417
|
+
method: auth?.method ?? "environment",
|
|
418
|
+
...auth?.config ? { config: auth.config } : {}
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
...auth?.method ? { method: auth.method } : {},
|
|
423
|
+
...normalizeAuthSources(auth?.passphrase) ? {
|
|
424
|
+
passphrase: {
|
|
425
|
+
from: normalizeAuthSources(auth?.passphrase) ?? []
|
|
426
|
+
}
|
|
427
|
+
} : {},
|
|
428
|
+
...normalizeAuthSources(auth?.token) ? {
|
|
429
|
+
token: {
|
|
430
|
+
from: normalizeAuthSources(auth?.token) ?? []
|
|
431
|
+
}
|
|
432
|
+
} : {},
|
|
433
|
+
...auth?.config ? { config: auth.config } : {}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
265
436
|
function normalizeManifest(manifest) {
|
|
266
437
|
const version = manifest.version ?? 1;
|
|
267
438
|
if (version !== 1) {
|
|
@@ -432,60 +603,65 @@ function validateProjectionIssue(manifest, key, target) {
|
|
|
432
603
|
}
|
|
433
604
|
}
|
|
434
605
|
|
|
435
|
-
// ../core/src/
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
if (tail.length === 0) {
|
|
442
|
-
target[head] = value;
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
const current = target[head];
|
|
446
|
-
const nextTarget = current && typeof current === "object" && !Array.isArray(current) ? current : {};
|
|
447
|
-
target[head] = nextTarget;
|
|
448
|
-
setNestedValue(nextTarget, tail, value);
|
|
606
|
+
// ../core/src/secrets/sessionStore.ts
|
|
607
|
+
import { mkdir, readFile as readFile2, readdir, rm, writeFile } from "fs/promises";
|
|
608
|
+
import path3 from "path";
|
|
609
|
+
function buildSessionRoot(processEnv = process.env) {
|
|
610
|
+
return path3.join(path3.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets")), "sessions");
|
|
449
611
|
}
|
|
450
|
-
function
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
);
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
612
|
+
function buildSessionPath(vault, processEnv) {
|
|
613
|
+
return path3.join(buildSessionRoot(processEnv), `${vault}.json`);
|
|
614
|
+
}
|
|
615
|
+
async function writeVaultSessionKey(vault, derivedKey, processEnv) {
|
|
616
|
+
const filePath = buildSessionPath(vault, processEnv);
|
|
617
|
+
await mkdir(path3.dirname(filePath), { recursive: true });
|
|
618
|
+
const document = {
|
|
619
|
+
version: 1,
|
|
620
|
+
vault,
|
|
621
|
+
derivedKey: derivedKey.toString("hex"),
|
|
622
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
623
|
+
};
|
|
624
|
+
await writeFile(filePath, JSON.stringify(document, null, 2), "utf8");
|
|
625
|
+
return filePath;
|
|
626
|
+
}
|
|
627
|
+
async function readVaultSessionKey(vault, processEnv) {
|
|
628
|
+
try {
|
|
629
|
+
const source = await readFile2(buildSessionPath(vault, processEnv), "utf8");
|
|
630
|
+
const document = JSON.parse(source);
|
|
631
|
+
if (document.version !== 1 || typeof document.derivedKey !== "string") {
|
|
632
|
+
return void 0;
|
|
458
633
|
}
|
|
459
|
-
const
|
|
460
|
-
|
|
634
|
+
const key = Buffer.from(document.derivedKey, "hex");
|
|
635
|
+
return key.length > 0 ? key : void 0;
|
|
636
|
+
} catch {
|
|
637
|
+
return void 0;
|
|
461
638
|
}
|
|
462
|
-
return output;
|
|
463
639
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
function readValue(graph, key) {
|
|
467
|
-
return graph.entries.get(key)?.value;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// ../core/src/runtime/readOr.ts
|
|
471
|
-
function readOrValue(graph, key, fallback) {
|
|
472
|
-
const value = readValue(graph, key);
|
|
473
|
-
return value === void 0 ? fallback : value;
|
|
640
|
+
async function clearVaultSessionKey(vault, processEnv) {
|
|
641
|
+
await rm(buildSessionPath(vault, processEnv), { force: true });
|
|
474
642
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
643
|
+
async function clearAllVaultSessionKeys(processEnv) {
|
|
644
|
+
const root = buildSessionRoot(processEnv);
|
|
645
|
+
try {
|
|
646
|
+
const entries = await readdir(root);
|
|
647
|
+
await Promise.all(entries.map((entry) => rm(path3.join(root, entry), { force: true })));
|
|
648
|
+
} catch {
|
|
481
649
|
}
|
|
482
|
-
return value;
|
|
483
650
|
}
|
|
484
651
|
|
|
485
652
|
// ../core/src/utils/secretStore.ts
|
|
486
|
-
import { createCipheriv, createDecipheriv,
|
|
487
|
-
import { mkdir, readdir, readFile as
|
|
488
|
-
import
|
|
653
|
+
import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
|
|
654
|
+
import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, rm as rm2, stat, writeFile as writeFile2 } from "fs/promises";
|
|
655
|
+
import path4 from "path";
|
|
656
|
+
var KEY_LENGTH = 32;
|
|
657
|
+
var SALT_LENGTH = 32;
|
|
658
|
+
var IV_LENGTH = 12;
|
|
659
|
+
var AUTH_TAG_LENGTH = 16;
|
|
660
|
+
var PBKDF2_ITERATIONS = 6e5;
|
|
661
|
+
var KEYSTORE_VERSION = 1;
|
|
662
|
+
var METADATA_VERSION = 1;
|
|
663
|
+
var META_FILENAME = "meta.yml";
|
|
664
|
+
var KEYSTORE_FILENAME = "keystore.enc";
|
|
489
665
|
function isObject(value) {
|
|
490
666
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
491
667
|
}
|
|
@@ -493,135 +669,657 @@ function isSecretReference(value) {
|
|
|
493
669
|
return isObject(value) && typeof value.provider === "string" && value.provider.trim().length > 0 && typeof value.ref === "string" && value.ref.trim().length > 0 && (value.vault === void 0 && true || typeof value.vault === "string" && value.vault.trim().length > 0) && Object.keys(value).every((key) => ["provider", "ref", "vault"].includes(key));
|
|
494
670
|
}
|
|
495
671
|
function resolveSecretStoreRoot(processEnv = process.env) {
|
|
496
|
-
return
|
|
497
|
-
}
|
|
498
|
-
function resolveSecretVaultFile(storeRoot, vault = "default") {
|
|
499
|
-
return path3.join(storeRoot, "vaults", `${vault}.json`);
|
|
500
|
-
}
|
|
501
|
-
function resolveSecretStoreFile(storeRoot, ref, vault = "default") {
|
|
502
|
-
return path3.join(storeRoot, "vaults", vault, "store", ...ref.split("/")).concat(".json");
|
|
672
|
+
return path4.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
|
|
503
673
|
}
|
|
504
|
-
function
|
|
505
|
-
return
|
|
506
|
-
}
|
|
507
|
-
function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
|
|
508
|
-
const vaultToken = vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
509
|
-
return processEnv[`CNOS_SECRET_PASSPHRASE_${vaultToken}`] ?? processEnv.CNOS_SECRET_PASSPHRASE;
|
|
674
|
+
function normalizeVaultToken(vault = "default") {
|
|
675
|
+
return vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
510
676
|
}
|
|
511
677
|
function getVaultPassphraseEnvVar(vault = "default") {
|
|
512
|
-
const vaultToken = vault
|
|
678
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
513
679
|
return vaultToken && vaultToken !== "DEFAULT" ? `CNOS_SECRET_PASSPHRASE_${vaultToken}` : "CNOS_SECRET_PASSPHRASE";
|
|
514
680
|
}
|
|
515
681
|
function isPassphraseEnvRef(value) {
|
|
516
682
|
return typeof value === "string" && value.startsWith("env:") && value.length > 4;
|
|
517
683
|
}
|
|
518
|
-
function
|
|
519
|
-
|
|
684
|
+
function getVaultSessionKeyEnvVar(vault = "default") {
|
|
685
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
686
|
+
return `__CNOS_VAULT_KEY_${vaultToken || "DEFAULT"}__`;
|
|
687
|
+
}
|
|
688
|
+
function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
|
|
689
|
+
return processEnv[getVaultPassphraseEnvVar(vault)] ?? processEnv.CNOS_SECRET_PASSPHRASE;
|
|
690
|
+
}
|
|
691
|
+
function resolveVaultSessionKey(vault = "default", processEnv = process.env) {
|
|
692
|
+
const encoded = processEnv[getVaultSessionKeyEnvVar(vault)];
|
|
693
|
+
if (!encoded) {
|
|
694
|
+
return readVaultSessionKey(vault, processEnv);
|
|
695
|
+
}
|
|
696
|
+
try {
|
|
697
|
+
const key = Buffer.from(encoded, "hex");
|
|
698
|
+
return key.length === KEY_LENGTH ? key : void 0;
|
|
699
|
+
} catch {
|
|
520
700
|
return void 0;
|
|
521
701
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
702
|
+
}
|
|
703
|
+
function deriveVaultKey(passphrase, salt, iterations = PBKDF2_ITERATIONS) {
|
|
704
|
+
return pbkdf2Sync(passphrase, salt, iterations, KEY_LENGTH, "sha512");
|
|
705
|
+
}
|
|
706
|
+
function buildMetaPath(storeRoot, vault = "default") {
|
|
707
|
+
return path4.join(storeRoot, "vaults", vault, META_FILENAME);
|
|
708
|
+
}
|
|
709
|
+
function resolveSecretVaultFile(storeRoot, vault = "default") {
|
|
710
|
+
return buildMetaPath(storeRoot, vault);
|
|
711
|
+
}
|
|
712
|
+
function buildKeystorePath(storeRoot, vault = "default") {
|
|
713
|
+
return path4.join(storeRoot, "vaults", vault, KEYSTORE_FILENAME);
|
|
714
|
+
}
|
|
715
|
+
function buildLegacyVaultFile(storeRoot, vault = "default") {
|
|
716
|
+
return path4.join(storeRoot, "vaults", `${vault}.json`);
|
|
717
|
+
}
|
|
718
|
+
function buildLegacyVaultStoreRoot(storeRoot, vault = "default") {
|
|
719
|
+
return path4.join(storeRoot, "vaults", vault, "store");
|
|
720
|
+
}
|
|
721
|
+
function assertVaultMetadata(value, filePath) {
|
|
722
|
+
if (!isObject(value)) {
|
|
723
|
+
throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
|
|
525
724
|
}
|
|
526
|
-
if (
|
|
527
|
-
|
|
725
|
+
if (value.version !== METADATA_VERSION || value.algorithm !== "aes-256-gcm" || value.kdf !== "pbkdf2-sha512" || typeof value.iterations !== "number" || typeof value.salt !== "string" || typeof value.createdAt !== "string" || typeof value.secretCount !== "number") {
|
|
726
|
+
throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
|
|
528
727
|
}
|
|
529
|
-
return
|
|
728
|
+
return value;
|
|
530
729
|
}
|
|
531
|
-
function
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
730
|
+
async function exists2(targetPath) {
|
|
731
|
+
try {
|
|
732
|
+
await stat(targetPath);
|
|
733
|
+
return true;
|
|
734
|
+
} catch {
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
async function detectLegacyVaultFormat(storeRoot, vault = "default") {
|
|
739
|
+
const legacyFile = buildLegacyVaultFile(storeRoot, vault);
|
|
740
|
+
const legacyStore = buildLegacyVaultStoreRoot(storeRoot, vault);
|
|
741
|
+
if (await exists2(legacyFile)) {
|
|
742
|
+
return legacyFile;
|
|
743
|
+
}
|
|
744
|
+
if (await exists2(legacyStore)) {
|
|
745
|
+
return legacyStore;
|
|
746
|
+
}
|
|
747
|
+
return void 0;
|
|
748
|
+
}
|
|
749
|
+
async function assertNoLegacyVaultFormat(storeRoot, vault = "default") {
|
|
750
|
+
const legacyPath = await detectLegacyVaultFormat(storeRoot, vault);
|
|
751
|
+
if (!legacyPath) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
throw new CnosSecurityError(
|
|
755
|
+
`Legacy CNOS local vault format detected for vault "${vault}" at ${legacyPath}. CNOS 1.4 requires the new keystore format. Remove and recreate the vault.`
|
|
756
|
+
);
|
|
542
757
|
}
|
|
543
|
-
function
|
|
544
|
-
const
|
|
545
|
-
const iv = randomBytes(12);
|
|
546
|
-
const key = deriveKey(passphrase, salt);
|
|
758
|
+
function encryptPayload(payload, key) {
|
|
759
|
+
const iv = randomBytes(IV_LENGTH);
|
|
547
760
|
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
548
|
-
const
|
|
761
|
+
const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
|
|
762
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
549
763
|
const tag = cipher.getAuthTag();
|
|
550
|
-
return
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
ciphertext: ciphertext.toString("base64")
|
|
557
|
-
};
|
|
764
|
+
return Buffer.concat([
|
|
765
|
+
Buffer.from(Uint32Array.of(KEYSTORE_VERSION).buffer),
|
|
766
|
+
iv,
|
|
767
|
+
tag,
|
|
768
|
+
ciphertext
|
|
769
|
+
]);
|
|
558
770
|
}
|
|
559
|
-
function
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
|
|
771
|
+
function decryptPayload(buffer, key) {
|
|
772
|
+
if (buffer.length < 4 + IV_LENGTH + AUTH_TAG_LENGTH) {
|
|
773
|
+
throw new CnosSecurityError("Invalid CNOS local vault keystore");
|
|
774
|
+
}
|
|
775
|
+
const version = buffer.readUInt32LE(0);
|
|
776
|
+
if (version !== KEYSTORE_VERSION) {
|
|
777
|
+
throw new CnosSecurityError(`Unsupported CNOS local vault keystore version: ${version}`);
|
|
778
|
+
}
|
|
779
|
+
const ivOffset = 4;
|
|
780
|
+
const tagOffset = ivOffset + IV_LENGTH;
|
|
781
|
+
const cipherOffset = tagOffset + AUTH_TAG_LENGTH;
|
|
782
|
+
const iv = buffer.subarray(ivOffset, tagOffset);
|
|
783
|
+
const tag = buffer.subarray(tagOffset, cipherOffset);
|
|
784
|
+
const ciphertext = buffer.subarray(cipherOffset);
|
|
565
785
|
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
566
786
|
decipher.setAuthTag(tag);
|
|
567
|
-
|
|
568
|
-
|
|
787
|
+
try {
|
|
788
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
789
|
+
const payload = JSON.parse(plaintext);
|
|
790
|
+
if (!payload || !isObject(payload.secrets) || !isObject(payload.metadata)) {
|
|
791
|
+
throw new Error("invalid");
|
|
792
|
+
}
|
|
793
|
+
return {
|
|
794
|
+
secrets: Object.fromEntries(
|
|
795
|
+
Object.entries(payload.secrets).filter((entry) => typeof entry[1] === "string")
|
|
796
|
+
),
|
|
797
|
+
metadata: Object.fromEntries(
|
|
798
|
+
Object.entries(payload.metadata).filter(
|
|
799
|
+
(entry) => isObject(entry[1]) && typeof entry[1].createdAt === "string" && typeof entry[1].updatedAt === "string"
|
|
800
|
+
)
|
|
801
|
+
)
|
|
802
|
+
};
|
|
803
|
+
} catch {
|
|
804
|
+
throw new CnosAuthenticationError("Failed to decrypt CNOS local vault. Check vault authentication.");
|
|
805
|
+
}
|
|
569
806
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const document = {
|
|
575
|
-
version: 1,
|
|
576
|
-
name: normalizedVault,
|
|
577
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
578
|
-
verifier: encryptDocument(`cnos-vault:${normalizedVault}`, passphrase)
|
|
807
|
+
function buildInitialPayload() {
|
|
808
|
+
return {
|
|
809
|
+
secrets: {},
|
|
810
|
+
metadata: {}
|
|
579
811
|
};
|
|
580
|
-
await writeFile(filePath, JSON.stringify(document, null, 2), "utf8");
|
|
581
|
-
return filePath;
|
|
582
812
|
}
|
|
583
|
-
async function
|
|
584
|
-
const
|
|
585
|
-
const
|
|
813
|
+
async function writeVaultFiles(storeRoot, vault, meta, payload, key) {
|
|
814
|
+
const metaPath = buildMetaPath(storeRoot, vault);
|
|
815
|
+
const keystorePath = buildKeystorePath(storeRoot, vault);
|
|
816
|
+
await mkdir2(path4.dirname(metaPath), { recursive: true });
|
|
817
|
+
await writeFile2(metaPath, stringifyYaml(meta), "utf8");
|
|
818
|
+
await writeFile2(keystorePath, encryptPayload(payload, key));
|
|
819
|
+
}
|
|
820
|
+
async function readVaultMetadata(storeRoot, vault = "default") {
|
|
821
|
+
await assertNoLegacyVaultFormat(storeRoot, vault);
|
|
822
|
+
const metaPath = buildMetaPath(storeRoot, vault);
|
|
586
823
|
try {
|
|
587
|
-
await
|
|
588
|
-
return
|
|
824
|
+
const source = await readFile3(metaPath, "utf8");
|
|
825
|
+
return assertVaultMetadata(parseYaml(source), metaPath);
|
|
589
826
|
} catch (error) {
|
|
590
|
-
if (error.code
|
|
591
|
-
|
|
827
|
+
if (error.code === "ENOENT") {
|
|
828
|
+
return void 0;
|
|
592
829
|
}
|
|
830
|
+
throw error;
|
|
593
831
|
}
|
|
594
|
-
return createSecretVault(storeRoot, normalizedVault, passphrase);
|
|
595
832
|
}
|
|
596
833
|
async function listSecretVaults(storeRoot) {
|
|
597
|
-
const vaultRoot =
|
|
834
|
+
const vaultRoot = path4.join(storeRoot, "vaults");
|
|
598
835
|
try {
|
|
599
|
-
const entries = await
|
|
600
|
-
|
|
836
|
+
const entries = await readdir2(vaultRoot, { withFileTypes: true });
|
|
837
|
+
const vaults = await Promise.all(
|
|
838
|
+
entries.filter((entry) => entry.isDirectory()).map(async (entry) => await exists2(path4.join(vaultRoot, entry.name, META_FILENAME)) ? entry.name : void 0)
|
|
839
|
+
);
|
|
840
|
+
return vaults.filter((value) => Boolean(value)).sort((left, right) => left.localeCompare(right));
|
|
601
841
|
} catch {
|
|
602
842
|
return [];
|
|
603
843
|
}
|
|
604
844
|
}
|
|
605
|
-
async function
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
845
|
+
async function createSecretVault(storeRoot, vault, passphrase) {
|
|
846
|
+
const normalizedVault = vault.trim() || "default";
|
|
847
|
+
await assertNoLegacyVaultFormat(storeRoot, normalizedVault);
|
|
848
|
+
const salt = randomBytes(SALT_LENGTH);
|
|
849
|
+
const key = deriveVaultKey(passphrase, salt, PBKDF2_ITERATIONS);
|
|
850
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
851
|
+
const meta = {
|
|
852
|
+
version: METADATA_VERSION,
|
|
853
|
+
algorithm: "aes-256-gcm",
|
|
854
|
+
kdf: "pbkdf2-sha512",
|
|
855
|
+
iterations: PBKDF2_ITERATIONS,
|
|
856
|
+
salt: salt.toString("base64"),
|
|
857
|
+
createdAt,
|
|
858
|
+
secretCount: 0
|
|
859
|
+
};
|
|
860
|
+
await writeVaultFiles(storeRoot, normalizedVault, meta, buildInitialPayload(), key);
|
|
861
|
+
return buildMetaPath(storeRoot, normalizedVault);
|
|
611
862
|
}
|
|
612
|
-
async function
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
863
|
+
async function ensureSecretVault(storeRoot, vault, passphrase) {
|
|
864
|
+
const normalizedVault = vault.trim() || "default";
|
|
865
|
+
const meta = await readVaultMetadata(storeRoot, normalizedVault);
|
|
866
|
+
if (meta) {
|
|
867
|
+
return buildMetaPath(storeRoot, normalizedVault);
|
|
868
|
+
}
|
|
869
|
+
return createSecretVault(storeRoot, normalizedVault, passphrase);
|
|
870
|
+
}
|
|
871
|
+
function resolveConfiguredVaultPassphrase(definition, vault = "default", processEnv = process.env) {
|
|
872
|
+
if (definition?.provider !== "local") {
|
|
873
|
+
return void 0;
|
|
874
|
+
}
|
|
875
|
+
const configuredSources = definition.auth?.passphrase?.from ?? [];
|
|
876
|
+
for (const source of configuredSources) {
|
|
877
|
+
if (source.startsWith("env:")) {
|
|
878
|
+
const value = processEnv[source.slice(4)];
|
|
879
|
+
if (value) {
|
|
880
|
+
return value;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return resolveSecretPassphrase(vault, processEnv);
|
|
885
|
+
}
|
|
886
|
+
async function resolveVaultAccessKey(storeRoot, definition, vault = "default", processEnv = process.env) {
|
|
887
|
+
if (definition?.provider !== "local") {
|
|
888
|
+
return definition?.provider === "github-secrets" ? {
|
|
889
|
+
method: definition.auth?.method ?? "environment",
|
|
890
|
+
...definition?.auth?.config ? { config: definition.auth.config } : {}
|
|
891
|
+
} : void 0;
|
|
892
|
+
}
|
|
893
|
+
const sessionKey = await resolveVaultSessionKey(vault, processEnv);
|
|
894
|
+
if (sessionKey) {
|
|
895
|
+
return {
|
|
896
|
+
derivedKey: sessionKey,
|
|
897
|
+
method: "keychain",
|
|
898
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
const passphrase = resolveConfiguredVaultPassphrase(definition, vault, processEnv);
|
|
902
|
+
if (passphrase) {
|
|
903
|
+
return {
|
|
904
|
+
passphrase,
|
|
905
|
+
method: "passphrase",
|
|
906
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
const metadata = await readVaultMetadata(storeRoot, vault);
|
|
910
|
+
if (!metadata) {
|
|
911
|
+
return void 0;
|
|
912
|
+
}
|
|
913
|
+
throw new CnosAuthenticationError(
|
|
914
|
+
`Cannot authenticate to vault "${vault}". Set ${getVaultPassphraseEnvVar(vault)} or run cnos vault auth ${vault}.`
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
async function loadVaultPayload(storeRoot, vault, auth) {
|
|
918
|
+
const meta = await readVaultMetadata(storeRoot, vault);
|
|
919
|
+
if (!meta) {
|
|
920
|
+
throw new CnosManifestError(`Missing CNOS vault metadata for "${vault}"`);
|
|
921
|
+
}
|
|
922
|
+
const salt = Buffer.from(meta.salt, "base64");
|
|
923
|
+
const key = auth.derivedKey ?? (auth.passphrase ? deriveVaultKey(auth.passphrase, salt, meta.iterations) : void 0);
|
|
924
|
+
if (!key) {
|
|
925
|
+
throw new CnosAuthenticationError(`Vault "${vault}" requires authentication before access.`);
|
|
926
|
+
}
|
|
927
|
+
const buffer = await readFile3(buildKeystorePath(storeRoot, vault));
|
|
928
|
+
return {
|
|
929
|
+
meta,
|
|
930
|
+
payload: decryptPayload(buffer, key),
|
|
931
|
+
key
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
async function writeLocalSecret(storeRoot, ref, value, authOrPassphrase, vault = "default") {
|
|
935
|
+
const auth = typeof authOrPassphrase === "string" ? {
|
|
936
|
+
passphrase: authOrPassphrase,
|
|
937
|
+
method: "passphrase"
|
|
938
|
+
} : authOrPassphrase;
|
|
939
|
+
if (auth.passphrase) {
|
|
940
|
+
await ensureSecretVault(storeRoot, vault, auth.passphrase);
|
|
941
|
+
} else {
|
|
942
|
+
const meta2 = await readVaultMetadata(storeRoot, vault);
|
|
943
|
+
if (!meta2) {
|
|
944
|
+
throw new CnosAuthenticationError(`Vault "${vault}" requires passphrase-based authentication for initial creation.`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
|
|
948
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
949
|
+
const existing = payload.metadata[ref];
|
|
950
|
+
payload.secrets[ref] = value;
|
|
951
|
+
payload.metadata[ref] = {
|
|
952
|
+
createdAt: existing?.createdAt ?? now,
|
|
953
|
+
updatedAt: now
|
|
954
|
+
};
|
|
955
|
+
const nextMeta = {
|
|
956
|
+
...meta,
|
|
957
|
+
secretCount: Object.keys(payload.secrets).length
|
|
958
|
+
};
|
|
959
|
+
await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
|
|
960
|
+
return buildKeystorePath(storeRoot, vault);
|
|
961
|
+
}
|
|
962
|
+
async function deleteLocalSecret(storeRoot, ref, auth, vault = "default") {
|
|
963
|
+
const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
|
|
964
|
+
if (!(ref in payload.secrets)) {
|
|
965
|
+
return false;
|
|
966
|
+
}
|
|
967
|
+
delete payload.secrets[ref];
|
|
968
|
+
delete payload.metadata[ref];
|
|
969
|
+
const nextMeta = {
|
|
970
|
+
...meta,
|
|
971
|
+
secretCount: Object.keys(payload.secrets).length
|
|
972
|
+
};
|
|
973
|
+
await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
|
|
974
|
+
return true;
|
|
975
|
+
}
|
|
976
|
+
async function readLocalSecret(storeRoot, ref, auth, vault = "default") {
|
|
977
|
+
const { payload } = await loadVaultPayload(storeRoot, vault, auth);
|
|
978
|
+
const value = payload.secrets[ref];
|
|
979
|
+
if (value === void 0) {
|
|
980
|
+
throw new CnosManifestError(`Missing local secret ref "${ref}" in vault "${vault}"`);
|
|
981
|
+
}
|
|
982
|
+
return value;
|
|
983
|
+
}
|
|
984
|
+
async function listLocalSecrets(storeRoot, auth, vault = "default") {
|
|
985
|
+
const { payload } = await loadVaultPayload(storeRoot, vault, auth);
|
|
986
|
+
return Object.keys(payload.secrets).sort((left, right) => left.localeCompare(right));
|
|
987
|
+
}
|
|
988
|
+
function resolveVaultDefinition(vaults, vault = "default") {
|
|
989
|
+
const definition = vaults?.[vault];
|
|
990
|
+
const provider = definition?.provider ?? "local";
|
|
991
|
+
return {
|
|
992
|
+
name: vault,
|
|
993
|
+
provider,
|
|
994
|
+
...definition?.auth ? { auth: definition.auth } : {},
|
|
995
|
+
...definition?.mapping ? { mapping: definition.mapping } : {},
|
|
996
|
+
requiresAuthentication: provider === "local"
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
async function removeLocalVaultFiles(storeRoot, vault = "default") {
|
|
1000
|
+
await rm2(path4.join(storeRoot, "vaults", vault), { recursive: true, force: true });
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// ../core/src/secrets/providers/github.ts
|
|
1004
|
+
var GithubSecretsVaultProvider = class {
|
|
1005
|
+
constructor(vaultId, definition, processEnv = process.env) {
|
|
1006
|
+
this.vaultId = vaultId;
|
|
1007
|
+
this.definition = definition;
|
|
1008
|
+
this.processEnv = processEnv;
|
|
1009
|
+
}
|
|
1010
|
+
vaultId;
|
|
1011
|
+
definition;
|
|
1012
|
+
processEnv;
|
|
1013
|
+
authenticated = false;
|
|
1014
|
+
async authenticate(_authConfig) {
|
|
1015
|
+
void _authConfig;
|
|
1016
|
+
this.authenticated = true;
|
|
1017
|
+
}
|
|
1018
|
+
isAuthenticated() {
|
|
1019
|
+
return this.authenticated;
|
|
1020
|
+
}
|
|
1021
|
+
resolveEnvVar(ref) {
|
|
1022
|
+
if (this.processEnv[ref] !== void 0) {
|
|
1023
|
+
return ref;
|
|
1024
|
+
}
|
|
1025
|
+
return Object.entries(this.definition.mapping ?? {}).find(([, logicalRef]) => logicalRef === ref)?.[0];
|
|
1026
|
+
}
|
|
1027
|
+
async batchGet(refs) {
|
|
1028
|
+
this.authenticated = true;
|
|
1029
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1030
|
+
for (const ref of Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right))) {
|
|
1031
|
+
const envVar = this.resolveEnvVar(ref);
|
|
1032
|
+
const value = envVar ? this.processEnv[envVar] : void 0;
|
|
1033
|
+
if (value !== void 0) {
|
|
1034
|
+
resolved.set(ref, value);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return resolved;
|
|
1038
|
+
}
|
|
1039
|
+
async get(ref) {
|
|
1040
|
+
const envVar = this.resolveEnvVar(ref);
|
|
1041
|
+
this.authenticated = true;
|
|
1042
|
+
return envVar ? this.processEnv[envVar] : void 0;
|
|
1043
|
+
}
|
|
1044
|
+
async set(ref, value) {
|
|
1045
|
+
void ref;
|
|
1046
|
+
void value;
|
|
1047
|
+
throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be written by CNOS.`);
|
|
1048
|
+
}
|
|
1049
|
+
async delete(ref) {
|
|
1050
|
+
void ref;
|
|
1051
|
+
throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be mutated by CNOS.`);
|
|
1052
|
+
}
|
|
1053
|
+
async list() {
|
|
1054
|
+
return Object.values(this.definition.mapping ?? {}).sort((left, right) => left.localeCompare(right));
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
// ../core/src/secrets/auditLog.ts
|
|
1059
|
+
import { appendFile, mkdir as mkdir3 } from "fs/promises";
|
|
1060
|
+
import path5 from "path";
|
|
1061
|
+
async function appendAuditEvent(event, processEnv = process.env) {
|
|
1062
|
+
const auditFile = processEnv.CNOS_AUDIT_FILE ?? path5.join(resolveSecretStoreRoot(processEnv), "audit", "access.log");
|
|
1063
|
+
await mkdir3(path5.dirname(auditFile), { recursive: true });
|
|
1064
|
+
await appendFile(
|
|
1065
|
+
auditFile,
|
|
1066
|
+
`${JSON.stringify({
|
|
1067
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1068
|
+
...event
|
|
1069
|
+
})}
|
|
1070
|
+
`,
|
|
1071
|
+
"utf8"
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// ../core/src/secrets/providers/local.ts
|
|
1076
|
+
var LocalSecretVaultProvider = class _LocalSecretVaultProvider {
|
|
1077
|
+
constructor(vaultId, definition, processEnv = process.env, storeRoot = resolveSecretStoreRoot(processEnv)) {
|
|
1078
|
+
this.vaultId = vaultId;
|
|
1079
|
+
this.processEnv = processEnv;
|
|
1080
|
+
this.storeRoot = storeRoot;
|
|
1081
|
+
this.definition = definition;
|
|
1082
|
+
}
|
|
1083
|
+
vaultId;
|
|
1084
|
+
processEnv;
|
|
1085
|
+
storeRoot;
|
|
1086
|
+
authConfig;
|
|
1087
|
+
definition;
|
|
1088
|
+
static fromVaults(vaults, vaultId, processEnv) {
|
|
1089
|
+
const definition = resolveVaultDefinition(vaults, vaultId);
|
|
1090
|
+
return new _LocalSecretVaultProvider(vaultId, definition, processEnv);
|
|
1091
|
+
}
|
|
1092
|
+
async authenticate(authConfig) {
|
|
1093
|
+
this.authConfig = authConfig;
|
|
1094
|
+
await this.list();
|
|
1095
|
+
}
|
|
1096
|
+
isAuthenticated() {
|
|
1097
|
+
return Boolean(this.authConfig);
|
|
1098
|
+
}
|
|
1099
|
+
async requireAuth() {
|
|
1100
|
+
if (this.authConfig) {
|
|
1101
|
+
return this.authConfig;
|
|
1102
|
+
}
|
|
1103
|
+
const resolved = await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
|
|
1104
|
+
if (!resolved) {
|
|
1105
|
+
throw new CnosAuthenticationError(
|
|
1106
|
+
`Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
this.authConfig = resolved;
|
|
1110
|
+
return resolved;
|
|
1111
|
+
}
|
|
1112
|
+
async batchGet(refs) {
|
|
1113
|
+
const auth = await this.requireAuth();
|
|
1114
|
+
const entries = await Promise.all(
|
|
1115
|
+
Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right)).map(async (ref) => [ref, await readLocalSecret(this.storeRoot, ref, auth, this.vaultId)])
|
|
1116
|
+
);
|
|
1117
|
+
return new Map(entries);
|
|
1118
|
+
}
|
|
1119
|
+
async get(ref) {
|
|
1120
|
+
const auth = await this.requireAuth();
|
|
1121
|
+
try {
|
|
1122
|
+
return await readLocalSecret(this.storeRoot, ref, auth, this.vaultId);
|
|
1123
|
+
} catch {
|
|
1124
|
+
return void 0;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
async set(ref, value) {
|
|
1128
|
+
const auth = await this.requireAuth();
|
|
1129
|
+
await writeLocalSecret(this.storeRoot, ref, value, auth, this.vaultId);
|
|
1130
|
+
await appendAuditEvent(
|
|
1131
|
+
{
|
|
1132
|
+
action: "write",
|
|
1133
|
+
vault: this.vaultId,
|
|
1134
|
+
ref,
|
|
1135
|
+
caller: "cli"
|
|
1136
|
+
},
|
|
1137
|
+
this.processEnv
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
async delete(ref) {
|
|
1141
|
+
const auth = await this.requireAuth();
|
|
1142
|
+
await deleteLocalSecret(this.storeRoot, ref, auth, this.vaultId);
|
|
1143
|
+
await appendAuditEvent(
|
|
1144
|
+
{
|
|
1145
|
+
action: "delete",
|
|
1146
|
+
vault: this.vaultId,
|
|
1147
|
+
ref,
|
|
1148
|
+
caller: "cli"
|
|
1149
|
+
},
|
|
1150
|
+
this.processEnv
|
|
616
1151
|
);
|
|
617
1152
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
1153
|
+
async list() {
|
|
1154
|
+
const auth = this.authConfig ?? await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
|
|
1155
|
+
if (!auth) {
|
|
1156
|
+
throw new CnosAuthenticationError(
|
|
1157
|
+
`Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
this.authConfig = auth;
|
|
1161
|
+
return listLocalSecrets(this.storeRoot, auth, this.vaultId);
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
|
|
1165
|
+
// ../core/src/secrets/providers/registry.ts
|
|
1166
|
+
function createSecretVaultProvider(vaultId, definition, processEnv) {
|
|
1167
|
+
if (definition.provider === "local") {
|
|
1168
|
+
return new LocalSecretVaultProvider(vaultId, definition, processEnv);
|
|
1169
|
+
}
|
|
1170
|
+
if (definition.provider === "github-secrets") {
|
|
1171
|
+
return new GithubSecretsVaultProvider(vaultId, definition, processEnv);
|
|
1172
|
+
}
|
|
1173
|
+
throw new CnosManifestError(`Unsupported vault provider: ${definition.provider}`);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// ../core/src/secrets/prompt.ts
|
|
1177
|
+
import readline from "readline";
|
|
1178
|
+
import { Writable } from "stream";
|
|
1179
|
+
async function promptHidden(message) {
|
|
1180
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1181
|
+
return void 0;
|
|
1182
|
+
}
|
|
1183
|
+
const mutableStdout = new WritableMask();
|
|
1184
|
+
const rl = readline.createInterface({
|
|
1185
|
+
input: process.stdin,
|
|
1186
|
+
output: mutableStdout,
|
|
1187
|
+
terminal: true
|
|
1188
|
+
});
|
|
1189
|
+
try {
|
|
1190
|
+
mutableStdout.muted = true;
|
|
1191
|
+
const value = await new Promise((resolve) => {
|
|
1192
|
+
rl.question(message, resolve);
|
|
1193
|
+
});
|
|
1194
|
+
process.stdout.write("\n");
|
|
1195
|
+
return value;
|
|
1196
|
+
} finally {
|
|
1197
|
+
rl.close();
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
var WritableMask = class extends Writable {
|
|
1201
|
+
muted = false;
|
|
1202
|
+
_write(chunk, _encoding, callback) {
|
|
1203
|
+
if (!this.muted) {
|
|
1204
|
+
process.stdout.write(chunk);
|
|
1205
|
+
}
|
|
1206
|
+
callback();
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
// ../core/src/secrets/resolveAuth.ts
|
|
1211
|
+
function toAuthError(vaultId, sources) {
|
|
1212
|
+
return new CnosAuthenticationError(
|
|
1213
|
+
`Cannot authenticate to vault "${vaultId}". Tried: ${sources.join(", ")}. Set ${getVaultPassphraseEnvVar(vaultId)} or run cnos vault auth ${vaultId}.`
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
|
|
1217
|
+
const sessionKey = await resolveVaultSessionKey(vaultId, processEnv);
|
|
1218
|
+
if (sessionKey) {
|
|
1219
|
+
return {
|
|
1220
|
+
derivedKey: sessionKey,
|
|
1221
|
+
method: "keychain",
|
|
1222
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
if (definition.provider === "github-secrets") {
|
|
1226
|
+
return {
|
|
1227
|
+
method: definition.auth?.method ?? "environment",
|
|
1228
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
const sources = definition.auth?.passphrase?.from ?? [getVaultPassphraseEnvVar(vaultId)];
|
|
1232
|
+
for (const source of sources) {
|
|
1233
|
+
if (source.startsWith("env:")) {
|
|
1234
|
+
const value = processEnv[source.slice(4)];
|
|
1235
|
+
if (value) {
|
|
1236
|
+
return {
|
|
1237
|
+
passphrase: value,
|
|
1238
|
+
method: "passphrase",
|
|
1239
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
if (source.startsWith("keychain:")) {
|
|
1244
|
+
const value = await readKeychain(source.slice("keychain:".length));
|
|
1245
|
+
if (value) {
|
|
1246
|
+
return {
|
|
1247
|
+
derivedKey: Buffer.from(value, "hex"),
|
|
1248
|
+
method: "keychain",
|
|
1249
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
if (source === "prompt") {
|
|
1254
|
+
const value = await promptHidden(`Enter passphrase for vault "${vaultId}": `);
|
|
1255
|
+
if (value) {
|
|
1256
|
+
return {
|
|
1257
|
+
passphrase: value,
|
|
1258
|
+
method: "passphrase",
|
|
1259
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
const fallback = resolveSecretPassphrase(vaultId, processEnv);
|
|
1265
|
+
if (fallback) {
|
|
1266
|
+
return {
|
|
1267
|
+
passphrase: fallback,
|
|
1268
|
+
method: "passphrase",
|
|
1269
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1270
|
+
};
|
|
623
1271
|
}
|
|
624
|
-
|
|
1272
|
+
throw toAuthError(vaultId, [getVaultSessionKeyEnvVar(vaultId), ...sources]);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// ../core/src/runtime/projection.ts
|
|
1276
|
+
function setNestedValue(target, pathSegments, value) {
|
|
1277
|
+
const [head, ...tail] = pathSegments;
|
|
1278
|
+
if (!head) {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
if (tail.length === 0) {
|
|
1282
|
+
target[head] = value;
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
const current = target[head];
|
|
1286
|
+
const nextTarget = current && typeof current === "object" && !Array.isArray(current) ? current : {};
|
|
1287
|
+
target[head] = nextTarget;
|
|
1288
|
+
setNestedValue(nextTarget, tail, value);
|
|
1289
|
+
}
|
|
1290
|
+
function toNamespaceObject(graph, namespace) {
|
|
1291
|
+
const output = {};
|
|
1292
|
+
const resolvedEntries = Array.from(graph.entries.values()).sort(
|
|
1293
|
+
(left, right) => left.key.localeCompare(right.key)
|
|
1294
|
+
);
|
|
1295
|
+
for (const entry of resolvedEntries) {
|
|
1296
|
+
if (namespace && entry.namespace !== namespace) {
|
|
1297
|
+
continue;
|
|
1298
|
+
}
|
|
1299
|
+
const valuePath = namespace ? stripNamespace(entry.key) : entry.key;
|
|
1300
|
+
setNestedValue(output, valuePath.split("."), entry.value);
|
|
1301
|
+
}
|
|
1302
|
+
return output;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// ../core/src/runtime/read.ts
|
|
1306
|
+
function readValue(graph, key) {
|
|
1307
|
+
return graph.entries.get(key)?.value;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// ../core/src/runtime/readOr.ts
|
|
1311
|
+
function readOrValue(graph, key, fallback) {
|
|
1312
|
+
const value = readValue(graph, key);
|
|
1313
|
+
return value === void 0 ? fallback : value;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// ../core/src/runtime/require.ts
|
|
1317
|
+
function requireValue(graph, key) {
|
|
1318
|
+
const value = readValue(graph, key);
|
|
1319
|
+
if (value === void 0) {
|
|
1320
|
+
throw new CnosKeyNotFoundError(key);
|
|
1321
|
+
}
|
|
1322
|
+
return value;
|
|
625
1323
|
}
|
|
626
1324
|
|
|
627
1325
|
// ../core/src/runtime/toEnv.ts
|
|
@@ -705,23 +1403,23 @@ function toPublicEnv(graph, manifest, options = {}) {
|
|
|
705
1403
|
}
|
|
706
1404
|
|
|
707
1405
|
// ../core/src/runtime/dump.ts
|
|
708
|
-
import { mkdir as
|
|
709
|
-
import
|
|
1406
|
+
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
1407
|
+
import path6 from "path";
|
|
710
1408
|
function buildDumpFiles(graph, options = {}) {
|
|
711
|
-
const basePath = options.flatten ? "" :
|
|
1409
|
+
const basePath = options.flatten ? "" : path6.posix.join("workspaces", graph.workspace.workspaceId);
|
|
712
1410
|
const values = toNamespaceObject(graph, "value");
|
|
713
1411
|
const secrets = toNamespaceObject(graph, "secret");
|
|
714
1412
|
const files = [];
|
|
715
1413
|
if (Object.keys(values).length > 0) {
|
|
716
1414
|
files.push({
|
|
717
|
-
path:
|
|
1415
|
+
path: path6.posix.join(basePath, "values", graph.profile, "app.yml"),
|
|
718
1416
|
namespace: "value",
|
|
719
1417
|
content: stringifyYaml(values)
|
|
720
1418
|
});
|
|
721
1419
|
}
|
|
722
1420
|
if (Object.keys(secrets).length > 0) {
|
|
723
1421
|
files.push({
|
|
724
|
-
path:
|
|
1422
|
+
path: path6.posix.join(basePath, "secrets", graph.profile, "app.yml"),
|
|
725
1423
|
namespace: "secret",
|
|
726
1424
|
content: stringifyYaml(secrets)
|
|
727
1425
|
});
|
|
@@ -737,12 +1435,12 @@ function planDump(graph, options = {}) {
|
|
|
737
1435
|
};
|
|
738
1436
|
}
|
|
739
1437
|
async function writeDump(graph, options) {
|
|
740
|
-
const root =
|
|
1438
|
+
const root = path6.resolve(options.to);
|
|
741
1439
|
const plan = planDump(graph, options);
|
|
742
1440
|
for (const file of plan.files) {
|
|
743
|
-
const destination =
|
|
744
|
-
await
|
|
745
|
-
await
|
|
1441
|
+
const destination = path6.join(root, file.path);
|
|
1442
|
+
await mkdir4(path6.dirname(destination), { recursive: true });
|
|
1443
|
+
await writeFile3(destination, file.content, "utf8");
|
|
746
1444
|
}
|
|
747
1445
|
return {
|
|
748
1446
|
...plan,
|
|
@@ -773,11 +1471,11 @@ function normalizeMappingConfig(config = {}) {
|
|
|
773
1471
|
function toScreamingSnakeSegment(segment) {
|
|
774
1472
|
return segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
775
1473
|
}
|
|
776
|
-
function toScreamingSnake(
|
|
777
|
-
return
|
|
1474
|
+
function toScreamingSnake(path10) {
|
|
1475
|
+
return path10.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
|
|
778
1476
|
}
|
|
779
|
-
function fromScreamingSnake(
|
|
780
|
-
return
|
|
1477
|
+
function fromScreamingSnake(path10) {
|
|
1478
|
+
return path10.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
|
|
781
1479
|
}
|
|
782
1480
|
function logicalKeyToEnvVar(key, config = {}) {
|
|
783
1481
|
const normalized = normalizeMappingConfig(config);
|
|
@@ -929,12 +1627,12 @@ function createProvenanceInspector() {
|
|
|
929
1627
|
}
|
|
930
1628
|
|
|
931
1629
|
// ../core/src/manifest/loadWorkspaceFile.ts
|
|
932
|
-
import { readFile as
|
|
933
|
-
import
|
|
1630
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
1631
|
+
import path7 from "path";
|
|
934
1632
|
async function loadWorkspaceFile(repoRoot) {
|
|
935
|
-
const workspaceFilePath =
|
|
1633
|
+
const workspaceFilePath = path7.join(repoRoot, ".cnos-workspace.yml");
|
|
936
1634
|
try {
|
|
937
|
-
const source = await
|
|
1635
|
+
const source = await readFile4(workspaceFilePath, "utf8");
|
|
938
1636
|
const parsed = parseYaml(source);
|
|
939
1637
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
940
1638
|
throw new CnosManifestError(".cnos-workspace.yml must be a YAML object", workspaceFilePath);
|
|
@@ -957,8 +1655,8 @@ async function loadWorkspaceFile(repoRoot) {
|
|
|
957
1655
|
}
|
|
958
1656
|
|
|
959
1657
|
// ../core/src/profiles/expandProfileChain.ts
|
|
960
|
-
import { access as access2, readFile as
|
|
961
|
-
import
|
|
1658
|
+
import { access as access2, readFile as readFile5 } from "fs/promises";
|
|
1659
|
+
import path8 from "path";
|
|
962
1660
|
async function fileExists(targetPath) {
|
|
963
1661
|
try {
|
|
964
1662
|
await access2(targetPath);
|
|
@@ -995,11 +1693,11 @@ async function loadProfileDefinition(profileName, options) {
|
|
|
995
1693
|
return normalizeProfileDefinition(profileName, void 0);
|
|
996
1694
|
}
|
|
997
1695
|
for (const workspaceRoot of [...workspaceRoots].reverse()) {
|
|
998
|
-
const profilePath =
|
|
1696
|
+
const profilePath = path8.join(workspaceRoot.path, "profiles", `${profileName}.yml`);
|
|
999
1697
|
if (!await fileExists(profilePath)) {
|
|
1000
1698
|
continue;
|
|
1001
1699
|
}
|
|
1002
|
-
const document = await
|
|
1700
|
+
const document = await readFile5(profilePath, "utf8");
|
|
1003
1701
|
const parsed = parseYaml(document);
|
|
1004
1702
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1005
1703
|
throw new CnosManifestError("Profile definition must be a YAML object", profilePath);
|
|
@@ -1007,7 +1705,7 @@ async function loadProfileDefinition(profileName, options) {
|
|
|
1007
1705
|
const definition = normalizeProfileDefinition(
|
|
1008
1706
|
profileName,
|
|
1009
1707
|
parsed,
|
|
1010
|
-
options.manifestRoot ? toPortablePath(
|
|
1708
|
+
options.manifestRoot ? toPortablePath(path8.relative(path8.dirname(options.manifestRoot), profilePath)) : toPortablePath(profilePath)
|
|
1011
1709
|
);
|
|
1012
1710
|
if (definition.name !== profileName) {
|
|
1013
1711
|
throw new CnosManifestError(
|
|
@@ -1254,7 +1952,7 @@ function createProfileAwareResolver() {
|
|
|
1254
1952
|
|
|
1255
1953
|
// ../core/src/workspaces/resolveWorkspaceContext.ts
|
|
1256
1954
|
import { access as access3 } from "fs/promises";
|
|
1257
|
-
import
|
|
1955
|
+
import path9 from "path";
|
|
1258
1956
|
|
|
1259
1957
|
// ../core/src/workspaces/expandWorkspaceChain.ts
|
|
1260
1958
|
function expandWorkspaceChain(workspaceId, items) {
|
|
@@ -1291,7 +1989,7 @@ function expandWorkspaceChain(workspaceId, items) {
|
|
|
1291
1989
|
}
|
|
1292
1990
|
|
|
1293
1991
|
// ../core/src/workspaces/resolveWorkspaceContext.ts
|
|
1294
|
-
async function
|
|
1992
|
+
async function exists3(targetPath) {
|
|
1295
1993
|
try {
|
|
1296
1994
|
await access3(targetPath);
|
|
1297
1995
|
return true;
|
|
@@ -1300,14 +1998,14 @@ async function exists2(targetPath) {
|
|
|
1300
1998
|
}
|
|
1301
1999
|
}
|
|
1302
2000
|
async function resolveLocalWorkspaceRoot(manifestRoot, workspaceId) {
|
|
1303
|
-
const workspaceRoot =
|
|
1304
|
-
if (await
|
|
2001
|
+
const workspaceRoot = path9.join(manifestRoot, "workspaces", workspaceId);
|
|
2002
|
+
if (await exists3(workspaceRoot)) {
|
|
1305
2003
|
return workspaceRoot;
|
|
1306
2004
|
}
|
|
1307
2005
|
const legacyMarkers = ["values", "secrets", "env", "profiles"].map(
|
|
1308
|
-
(segment) =>
|
|
2006
|
+
(segment) => path9.join(manifestRoot, segment)
|
|
1309
2007
|
);
|
|
1310
|
-
if ((await Promise.all(legacyMarkers.map((marker) =>
|
|
2008
|
+
if ((await Promise.all(legacyMarkers.map((marker) => exists3(marker)))).some(Boolean)) {
|
|
1311
2009
|
return manifestRoot;
|
|
1312
2010
|
}
|
|
1313
2011
|
return workspaceRoot;
|
|
@@ -1347,26 +2045,26 @@ function resolveGlobalRoot(manifest, workspaceFile, options) {
|
|
|
1347
2045
|
}
|
|
1348
2046
|
if (options.globalRoot) {
|
|
1349
2047
|
return {
|
|
1350
|
-
value:
|
|
2048
|
+
value: path9.resolve(expandHomePath(options.globalRoot)),
|
|
1351
2049
|
source: "cli"
|
|
1352
2050
|
};
|
|
1353
2051
|
}
|
|
1354
2052
|
if (workspaceFile?.globalRoot) {
|
|
1355
2053
|
return {
|
|
1356
|
-
value:
|
|
2054
|
+
value: path9.resolve(expandHomePath(workspaceFile.globalRoot)),
|
|
1357
2055
|
source: "workspace-file"
|
|
1358
2056
|
};
|
|
1359
2057
|
}
|
|
1360
2058
|
if (manifest.workspaces.global.root) {
|
|
1361
2059
|
return {
|
|
1362
|
-
value:
|
|
2060
|
+
value: path9.resolve(expandHomePath(manifest.workspaces.global.root)),
|
|
1363
2061
|
source: "manifest"
|
|
1364
2062
|
};
|
|
1365
2063
|
}
|
|
1366
2064
|
const cnosHome = options.processEnv?.CNOS_HOME;
|
|
1367
2065
|
if (cnosHome) {
|
|
1368
2066
|
return {
|
|
1369
|
-
value:
|
|
2067
|
+
value: path9.resolve(expandHomePath(cnosHome)),
|
|
1370
2068
|
source: "CNOS_HOME"
|
|
1371
2069
|
};
|
|
1372
2070
|
}
|
|
@@ -1383,7 +2081,7 @@ async function resolveWorkspaceContext(manifest, options) {
|
|
|
1383
2081
|
workspaceRoots.push({
|
|
1384
2082
|
scope: "global",
|
|
1385
2083
|
workspaceId: chainWorkspaceId,
|
|
1386
|
-
path:
|
|
2084
|
+
path: path9.join(globalRoot.value, "workspaces", globalWorkspaceId)
|
|
1387
2085
|
});
|
|
1388
2086
|
}
|
|
1389
2087
|
}
|
|
@@ -1573,29 +2271,120 @@ async function runPipeline(options) {
|
|
|
1573
2271
|
return collectedEntries.flat();
|
|
1574
2272
|
}
|
|
1575
2273
|
|
|
2274
|
+
// ../core/src/secrets/secretCache.ts
|
|
2275
|
+
var SecretCache = class {
|
|
2276
|
+
cache = /* @__PURE__ */ new Map();
|
|
2277
|
+
authenticated = /* @__PURE__ */ new Set();
|
|
2278
|
+
load(vaultId, secrets) {
|
|
2279
|
+
this.authenticated.add(vaultId);
|
|
2280
|
+
for (const [ref, value] of secrets) {
|
|
2281
|
+
this.cache.set(`${vaultId}:${ref}`, value);
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
isVaultAuthenticated(vaultId) {
|
|
2285
|
+
return this.authenticated.has(vaultId);
|
|
2286
|
+
}
|
|
2287
|
+
get(vaultId, ref) {
|
|
2288
|
+
return this.cache.get(`${vaultId}:${ref}`);
|
|
2289
|
+
}
|
|
2290
|
+
clear(vaultId) {
|
|
2291
|
+
if (!vaultId) {
|
|
2292
|
+
this.cache.clear();
|
|
2293
|
+
this.authenticated.clear();
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
this.authenticated.delete(vaultId);
|
|
2297
|
+
for (const key of Array.from(this.cache.keys())) {
|
|
2298
|
+
if (key.startsWith(`${vaultId}:`)) {
|
|
2299
|
+
this.cache.delete(key);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
};
|
|
2304
|
+
|
|
2305
|
+
// ../core/src/secrets/batchResolve.ts
|
|
2306
|
+
function collectSecretDescriptors(graph) {
|
|
2307
|
+
return Array.from(graph.entries.values()).filter((entry) => entry.namespace === "secret" && isSecretReference(entry.value)).map((entry) => ({
|
|
2308
|
+
logicalKey: entry.key,
|
|
2309
|
+
ref: entry.value
|
|
2310
|
+
}));
|
|
2311
|
+
}
|
|
2312
|
+
async function batchResolveSecrets(graph, manifest, processEnv = process.env) {
|
|
2313
|
+
const cache = new SecretCache();
|
|
2314
|
+
const descriptors = collectSecretDescriptors(graph);
|
|
2315
|
+
const grouped = descriptors.reduce((accumulator, descriptor) => {
|
|
2316
|
+
const vaultId = descriptor.ref.vault ?? "default";
|
|
2317
|
+
const bucket = accumulator.get(vaultId) ?? [];
|
|
2318
|
+
bucket.push(descriptor);
|
|
2319
|
+
accumulator.set(vaultId, bucket);
|
|
2320
|
+
return accumulator;
|
|
2321
|
+
}, /* @__PURE__ */ new Map());
|
|
2322
|
+
for (const [vaultId, refs] of grouped) {
|
|
2323
|
+
const definition = manifest.vaults[vaultId] ?? { provider: "local", auth: { passphrase: { from: [] } } };
|
|
2324
|
+
const provider = createSecretVaultProvider(vaultId, definition, processEnv);
|
|
2325
|
+
const auth = await resolveVaultAuth(vaultId, definition, processEnv);
|
|
2326
|
+
await provider.authenticate(auth);
|
|
2327
|
+
const resolved = await provider.batchGet(refs.map((entry) => entry.ref.ref));
|
|
2328
|
+
cache.load(vaultId, resolved);
|
|
2329
|
+
await appendAuditEvent(
|
|
2330
|
+
{
|
|
2331
|
+
action: "batch_read",
|
|
2332
|
+
vault: vaultId,
|
|
2333
|
+
refs: Array.from(resolved.keys()).sort((left, right) => left.localeCompare(right)),
|
|
2334
|
+
caller: "runtime",
|
|
2335
|
+
workspace: graph.workspace.workspaceId,
|
|
2336
|
+
profile: graph.profile
|
|
2337
|
+
},
|
|
2338
|
+
processEnv
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2341
|
+
return cache;
|
|
2342
|
+
}
|
|
2343
|
+
function resolveSecretEntryValue(key, value, cache) {
|
|
2344
|
+
if (!key.startsWith("secret.") || !isSecretReference(value)) {
|
|
2345
|
+
return value;
|
|
2346
|
+
}
|
|
2347
|
+
const vaultId = value.vault ?? "default";
|
|
2348
|
+
return cache.get(vaultId, value.ref) ?? value;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
1576
2351
|
// ../core/src/orchestrator/runtime.ts
|
|
1577
|
-
function createRuntime(manifest, graph, plugins = []) {
|
|
2352
|
+
function createRuntime(manifest, graph, plugins = [], secretCache) {
|
|
2353
|
+
function readLogicalKey(key) {
|
|
2354
|
+
const entry = graph.entries.get(key);
|
|
2355
|
+
if (!entry) {
|
|
2356
|
+
return void 0;
|
|
2357
|
+
}
|
|
2358
|
+
if (!secretCache) {
|
|
2359
|
+
return entry.value;
|
|
2360
|
+
}
|
|
2361
|
+
return resolveSecretEntryValue(key, entry.value, secretCache);
|
|
2362
|
+
}
|
|
1578
2363
|
return {
|
|
1579
2364
|
manifest,
|
|
1580
2365
|
plugins,
|
|
1581
2366
|
graph,
|
|
1582
2367
|
read(key) {
|
|
1583
|
-
return
|
|
2368
|
+
return readLogicalKey(key);
|
|
1584
2369
|
},
|
|
1585
2370
|
require(key) {
|
|
1586
|
-
|
|
2371
|
+
const value = readLogicalKey(key);
|
|
2372
|
+
if (value === void 0) {
|
|
2373
|
+
return requireValue(graph, key);
|
|
2374
|
+
}
|
|
2375
|
+
return value;
|
|
1587
2376
|
},
|
|
1588
2377
|
readOr(key, fallback) {
|
|
1589
2378
|
return readOrValue(graph, key, fallback);
|
|
1590
2379
|
},
|
|
1591
|
-
value(
|
|
1592
|
-
return
|
|
2380
|
+
value(path10) {
|
|
2381
|
+
return readLogicalKey(toLogicalKey("value", path10));
|
|
1593
2382
|
},
|
|
1594
|
-
secret(
|
|
1595
|
-
return
|
|
2383
|
+
secret(path10) {
|
|
2384
|
+
return readLogicalKey(toLogicalKey("secret", path10));
|
|
1596
2385
|
},
|
|
1597
|
-
meta(
|
|
1598
|
-
return
|
|
2386
|
+
meta(path10) {
|
|
2387
|
+
return readLogicalKey(toLogicalKey("meta", path10));
|
|
1599
2388
|
},
|
|
1600
2389
|
inspect(key) {
|
|
1601
2390
|
return inspectValue(graph, key);
|
|
@@ -1753,21 +2542,26 @@ async function createCnos(options = {}) {
|
|
|
1753
2542
|
});
|
|
1754
2543
|
const schemaApplied = applySchemaRules(graph, loadedManifest.manifest.schema);
|
|
1755
2544
|
const promotedGraph = promoteToPublic(schemaApplied.graph, loadedManifest.manifest);
|
|
2545
|
+
const secretCache = options.secretResolution === "lazy" ? void 0 : await batchResolveSecrets(promotedGraph, loadedManifest.manifest, options.processEnv);
|
|
1756
2546
|
return createRuntime(
|
|
1757
2547
|
loadedManifest.manifest,
|
|
1758
2548
|
appendMetaEntries({
|
|
1759
2549
|
...promotedGraph,
|
|
1760
2550
|
profileSource: activeProfile.source
|
|
1761
2551
|
}, options.cnosVersion),
|
|
1762
|
-
plugins
|
|
2552
|
+
plugins,
|
|
2553
|
+
secretCache
|
|
1763
2554
|
);
|
|
1764
2555
|
}
|
|
1765
2556
|
|
|
1766
2557
|
export {
|
|
1767
2558
|
CnosManifestError,
|
|
1768
2559
|
CnosSecurityError,
|
|
2560
|
+
CnosAuthenticationError,
|
|
1769
2561
|
inspectValue,
|
|
1770
2562
|
createProvenanceInspector,
|
|
2563
|
+
readKeychain,
|
|
2564
|
+
writeKeychain,
|
|
1771
2565
|
resolveManifestRoot,
|
|
1772
2566
|
resolveWorkspaceScopedPath,
|
|
1773
2567
|
resolveConfigDocumentPath,
|
|
@@ -1779,22 +2573,35 @@ export {
|
|
|
1779
2573
|
loadManifest,
|
|
1780
2574
|
ensureProjectionAllowed,
|
|
1781
2575
|
applySchemaRules,
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
requireValue,
|
|
2576
|
+
writeVaultSessionKey,
|
|
2577
|
+
clearVaultSessionKey,
|
|
2578
|
+
clearAllVaultSessionKeys,
|
|
1786
2579
|
isSecretReference,
|
|
1787
2580
|
resolveSecretStoreRoot,
|
|
1788
|
-
resolveSecretVaultFile,
|
|
1789
|
-
resolveSecretPassphrase,
|
|
1790
2581
|
getVaultPassphraseEnvVar,
|
|
1791
2582
|
isPassphraseEnvRef,
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
2583
|
+
getVaultSessionKeyEnvVar,
|
|
2584
|
+
resolveSecretPassphrase,
|
|
2585
|
+
deriveVaultKey,
|
|
2586
|
+
resolveSecretVaultFile,
|
|
2587
|
+
detectLegacyVaultFormat,
|
|
2588
|
+
readVaultMetadata,
|
|
1795
2589
|
listSecretVaults,
|
|
2590
|
+
createSecretVault,
|
|
2591
|
+
resolveConfiguredVaultPassphrase,
|
|
2592
|
+
resolveVaultAccessKey,
|
|
1796
2593
|
writeLocalSecret,
|
|
2594
|
+
deleteLocalSecret,
|
|
1797
2595
|
readLocalSecret,
|
|
2596
|
+
listLocalSecrets,
|
|
2597
|
+
resolveVaultDefinition,
|
|
2598
|
+
removeLocalVaultFiles,
|
|
2599
|
+
createSecretVaultProvider,
|
|
2600
|
+
resolveVaultAuth,
|
|
2601
|
+
toNamespaceObject,
|
|
2602
|
+
readValue,
|
|
2603
|
+
readOrValue,
|
|
2604
|
+
requireValue,
|
|
1798
2605
|
toEnv,
|
|
1799
2606
|
toPublicEnv,
|
|
1800
2607
|
createCnos,
|