@kitsy/cnos 1.2.0 → 1.4.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 +1003 -121
- 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-APCTXRUN.js → chunk-APIU4GTB.js} +1012 -195
- package/dist/chunk-EQSKV3DP.js +105 -0
- package/dist/{chunk-MLQGYCO7.js → chunk-FWJC4Y2D.js} +1 -1
- package/dist/{chunk-RD5WMHPM.js → chunk-HMM76UYZ.js} +1 -1
- package/dist/{chunk-EIN55XXA.js → chunk-J4K4JUJL.js} +1 -1
- package/dist/{chunk-SO5XREEU.js → chunk-JSBVYK2T.js} +32 -11
- package/dist/chunk-LJD4SM32.js +189 -0
- package/dist/{chunk-SXTMTACL.js → chunk-T6Y57KTT.js} +20 -31
- package/dist/chunk-WCHX2QFY.js +115 -0
- package/dist/{chunk-ZA74BO47.js → chunk-ZTPSFXWP.js} +1 -1
- package/dist/configure/index.cjs +3021 -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-CcsqAel3.d.ts → envNaming-Dvm_LP2D.d.ts} +1 -1
- package/dist/{envNaming-BTJpH93W.d.cts → envNaming-S4B-dHUx.d.cts} +1 -1
- package/dist/index.cjs +1243 -186
- package/dist/index.d.cts +2 -13
- package/dist/index.d.ts +2 -13
- package/dist/index.js +13 -25
- package/dist/internal.cjs +1525 -81
- package/dist/internal.d.cts +171 -14
- package/dist/internal.d.ts +171 -14
- package/dist/internal.js +652 -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 +65 -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 +105 -11
- package/dist/plugin/process-env.d.cts +4 -3
- package/dist/plugin/process-env.d.ts +4 -3
- package/dist/plugin/process-env.js +6 -4
- package/dist/{plugin-DkOIT5uI.d.cts → plugin-B4xwySxw.d.cts} +15 -2
- package/dist/{plugin-DkOIT5uI.d.ts → plugin-B4xwySxw.d.ts} +15 -2
- package/dist/runtime/index.cjs +1057 -136
- 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-C9clvXLo.d.ts → toPublicEnv-CvhGAfsB.d.ts} +1 -1
- package/dist/{toPublicEnv-DvFeV3qG.d.cts → toPublicEnv-ggmphZFs.d.cts} +1 -1
- package/package.json +11 -1
- package/dist/chunk-JUHPBAEH.js +0 -20
- package/dist/chunk-PQ4KSV76.js +0 -50
- package/dist/chunk-WHUGFPE4.js +0 -49
|
@@ -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";
|
|
@@ -123,7 +234,7 @@ function resolveWorkspaceScopedPath(workspaceRoot, template, tokens) {
|
|
|
123
234
|
return path.resolve(workspaceRoot, interpolated);
|
|
124
235
|
}
|
|
125
236
|
function resolveNamespaceDirectory(workspaceRoot, namespace, profile) {
|
|
126
|
-
const rootFolder = namespace === "value" ? "values" : "secrets";
|
|
237
|
+
const rootFolder = namespace === "value" ? "values" : namespace === "secret" ? "secrets" : namespace;
|
|
127
238
|
if (profile && profile !== "base") {
|
|
128
239
|
return path.resolve(workspaceRoot, "profiles", profile, rootFolder);
|
|
129
240
|
}
|
|
@@ -192,6 +303,11 @@ var DEFAULT_NAMESPACES = {
|
|
|
192
303
|
shareable: false,
|
|
193
304
|
readonly: true
|
|
194
305
|
},
|
|
306
|
+
process: {
|
|
307
|
+
kind: "system",
|
|
308
|
+
shareable: false,
|
|
309
|
+
readonly: true
|
|
310
|
+
},
|
|
195
311
|
public: {
|
|
196
312
|
kind: "projection",
|
|
197
313
|
source: "promote",
|
|
@@ -246,22 +362,82 @@ function normalizeNamespaces(namespaces) {
|
|
|
246
362
|
function normalizeVaults(vaults) {
|
|
247
363
|
return Object.fromEntries(
|
|
248
364
|
Object.entries(vaults ?? {}).map(([name, definition]) => {
|
|
365
|
+
const legacyPassphrase = definition.passphrase;
|
|
366
|
+
if (legacyPassphrase !== void 0) {
|
|
367
|
+
throw new CnosManifestError(
|
|
368
|
+
`Vault "${name}" uses legacy passphrase configuration. Use vaults.${name}.auth instead.`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
249
371
|
const provider = definition.provider?.trim();
|
|
250
372
|
if (!provider) {
|
|
251
373
|
throw new CnosManifestError(`Vault "${name}" requires a provider`);
|
|
252
374
|
}
|
|
375
|
+
const normalizedAuth = normalizeVaultAuth(name, provider, definition.auth);
|
|
376
|
+
const normalizedMapping = Object.fromEntries(
|
|
377
|
+
Object.entries(definition.mapping ?? {}).filter(
|
|
378
|
+
(entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
|
|
379
|
+
).map(([envVar, logicalRef]) => [envVar.trim(), logicalRef.trim()]).filter(([envVar, logicalRef]) => envVar.length > 0 && logicalRef.length > 0)
|
|
380
|
+
);
|
|
253
381
|
return [
|
|
254
382
|
name,
|
|
255
383
|
{
|
|
256
384
|
provider,
|
|
257
|
-
|
|
258
|
-
|
|
385
|
+
auth: normalizedAuth,
|
|
386
|
+
...Object.keys(normalizedMapping).length > 0 ? {
|
|
387
|
+
mapping: normalizedMapping
|
|
259
388
|
} : {}
|
|
260
389
|
}
|
|
261
390
|
];
|
|
262
391
|
})
|
|
263
392
|
);
|
|
264
393
|
}
|
|
394
|
+
function normalizeAuthSources(value) {
|
|
395
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
396
|
+
return void 0;
|
|
397
|
+
}
|
|
398
|
+
const sources = Array.isArray(value.from) ? value.from : void 0;
|
|
399
|
+
const normalized = (sources ?? []).map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
400
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
401
|
+
}
|
|
402
|
+
function normalizeVaultAuth(vaultName, provider, auth) {
|
|
403
|
+
if (provider === "local") {
|
|
404
|
+
const passphraseSources = normalizeAuthSources(auth?.passphrase);
|
|
405
|
+
const defaultToken = vaultName.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
406
|
+
const defaultSources = [
|
|
407
|
+
...defaultToken ? [`env:CNOS_SECRET_PASSPHRASE_${defaultToken}`] : [],
|
|
408
|
+
"env:CNOS_SECRET_PASSPHRASE",
|
|
409
|
+
`keychain:cnos/${vaultName}`,
|
|
410
|
+
"prompt"
|
|
411
|
+
];
|
|
412
|
+
return {
|
|
413
|
+
method: auth?.method ?? "passphrase",
|
|
414
|
+
passphrase: {
|
|
415
|
+
from: passphraseSources ?? defaultSources
|
|
416
|
+
},
|
|
417
|
+
...auth?.config ? { config: auth.config } : {}
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
if (provider === "github-secrets") {
|
|
421
|
+
return {
|
|
422
|
+
method: auth?.method ?? "environment",
|
|
423
|
+
...auth?.config ? { config: auth.config } : {}
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
...auth?.method ? { method: auth.method } : {},
|
|
428
|
+
...normalizeAuthSources(auth?.passphrase) ? {
|
|
429
|
+
passphrase: {
|
|
430
|
+
from: normalizeAuthSources(auth?.passphrase) ?? []
|
|
431
|
+
}
|
|
432
|
+
} : {},
|
|
433
|
+
...normalizeAuthSources(auth?.token) ? {
|
|
434
|
+
token: {
|
|
435
|
+
from: normalizeAuthSources(auth?.token) ?? []
|
|
436
|
+
}
|
|
437
|
+
} : {},
|
|
438
|
+
...auth?.config ? { config: auth.config } : {}
|
|
439
|
+
};
|
|
440
|
+
}
|
|
265
441
|
function normalizeManifest(manifest) {
|
|
266
442
|
const version = manifest.version ?? 1;
|
|
267
443
|
if (version !== 1) {
|
|
@@ -432,60 +608,65 @@ function validateProjectionIssue(manifest, key, target) {
|
|
|
432
608
|
}
|
|
433
609
|
}
|
|
434
610
|
|
|
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);
|
|
611
|
+
// ../core/src/secrets/sessionStore.ts
|
|
612
|
+
import { mkdir, readFile as readFile2, readdir, rm, writeFile } from "fs/promises";
|
|
613
|
+
import path3 from "path";
|
|
614
|
+
function buildSessionRoot(processEnv = process.env) {
|
|
615
|
+
return path3.join(path3.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets")), "sessions");
|
|
449
616
|
}
|
|
450
|
-
function
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
);
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
617
|
+
function buildSessionPath(vault, processEnv) {
|
|
618
|
+
return path3.join(buildSessionRoot(processEnv), `${vault}.json`);
|
|
619
|
+
}
|
|
620
|
+
async function writeVaultSessionKey(vault, derivedKey, processEnv) {
|
|
621
|
+
const filePath = buildSessionPath(vault, processEnv);
|
|
622
|
+
await mkdir(path3.dirname(filePath), { recursive: true });
|
|
623
|
+
const document = {
|
|
624
|
+
version: 1,
|
|
625
|
+
vault,
|
|
626
|
+
derivedKey: derivedKey.toString("hex"),
|
|
627
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
628
|
+
};
|
|
629
|
+
await writeFile(filePath, JSON.stringify(document, null, 2), "utf8");
|
|
630
|
+
return filePath;
|
|
631
|
+
}
|
|
632
|
+
async function readVaultSessionKey(vault, processEnv) {
|
|
633
|
+
try {
|
|
634
|
+
const source = await readFile2(buildSessionPath(vault, processEnv), "utf8");
|
|
635
|
+
const document = JSON.parse(source);
|
|
636
|
+
if (document.version !== 1 || typeof document.derivedKey !== "string") {
|
|
637
|
+
return void 0;
|
|
458
638
|
}
|
|
459
|
-
const
|
|
460
|
-
|
|
639
|
+
const key = Buffer.from(document.derivedKey, "hex");
|
|
640
|
+
return key.length > 0 ? key : void 0;
|
|
641
|
+
} catch {
|
|
642
|
+
return void 0;
|
|
461
643
|
}
|
|
462
|
-
return output;
|
|
463
644
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
function readValue(graph, key) {
|
|
467
|
-
return graph.entries.get(key)?.value;
|
|
645
|
+
async function clearVaultSessionKey(vault, processEnv) {
|
|
646
|
+
await rm(buildSessionPath(vault, processEnv), { force: true });
|
|
468
647
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// ../core/src/runtime/require.ts
|
|
477
|
-
function requireValue(graph, key) {
|
|
478
|
-
const value = readValue(graph, key);
|
|
479
|
-
if (value === void 0) {
|
|
480
|
-
throw new CnosKeyNotFoundError(key);
|
|
648
|
+
async function clearAllVaultSessionKeys(processEnv) {
|
|
649
|
+
const root = buildSessionRoot(processEnv);
|
|
650
|
+
try {
|
|
651
|
+
const entries = await readdir(root);
|
|
652
|
+
await Promise.all(entries.map((entry) => rm(path3.join(root, entry), { force: true })));
|
|
653
|
+
} catch {
|
|
481
654
|
}
|
|
482
|
-
return value;
|
|
483
655
|
}
|
|
484
656
|
|
|
485
657
|
// ../core/src/utils/secretStore.ts
|
|
486
|
-
import { createCipheriv, createDecipheriv,
|
|
487
|
-
import { mkdir, readdir, readFile as
|
|
488
|
-
import
|
|
658
|
+
import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
|
|
659
|
+
import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, rm as rm2, stat, writeFile as writeFile2 } from "fs/promises";
|
|
660
|
+
import path4 from "path";
|
|
661
|
+
var KEY_LENGTH = 32;
|
|
662
|
+
var SALT_LENGTH = 32;
|
|
663
|
+
var IV_LENGTH = 12;
|
|
664
|
+
var AUTH_TAG_LENGTH = 16;
|
|
665
|
+
var PBKDF2_ITERATIONS = 6e5;
|
|
666
|
+
var KEYSTORE_VERSION = 1;
|
|
667
|
+
var METADATA_VERSION = 1;
|
|
668
|
+
var META_FILENAME = "meta.yml";
|
|
669
|
+
var KEYSTORE_FILENAME = "keystore.enc";
|
|
489
670
|
function isObject(value) {
|
|
490
671
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
491
672
|
}
|
|
@@ -493,135 +674,657 @@ function isSecretReference(value) {
|
|
|
493
674
|
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
675
|
}
|
|
495
676
|
function resolveSecretStoreRoot(processEnv = process.env) {
|
|
496
|
-
return
|
|
677
|
+
return path4.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
|
|
497
678
|
}
|
|
498
|
-
function
|
|
499
|
-
return
|
|
500
|
-
}
|
|
501
|
-
function resolveSecretStoreFile(storeRoot, ref, vault = "default") {
|
|
502
|
-
return path3.join(storeRoot, "vaults", vault, "store", ...ref.split("/")).concat(".json");
|
|
503
|
-
}
|
|
504
|
-
function deriveKey(passphrase, salt) {
|
|
505
|
-
return scryptSync(passphrase, salt, 32);
|
|
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;
|
|
679
|
+
function normalizeVaultToken(vault = "default") {
|
|
680
|
+
return vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
510
681
|
}
|
|
511
682
|
function getVaultPassphraseEnvVar(vault = "default") {
|
|
512
|
-
const vaultToken = vault
|
|
683
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
513
684
|
return vaultToken && vaultToken !== "DEFAULT" ? `CNOS_SECRET_PASSPHRASE_${vaultToken}` : "CNOS_SECRET_PASSPHRASE";
|
|
514
685
|
}
|
|
515
686
|
function isPassphraseEnvRef(value) {
|
|
516
687
|
return typeof value === "string" && value.startsWith("env:") && value.length > 4;
|
|
517
688
|
}
|
|
518
|
-
function
|
|
519
|
-
|
|
689
|
+
function getVaultSessionKeyEnvVar(vault = "default") {
|
|
690
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
691
|
+
return `__CNOS_VAULT_KEY_${vaultToken || "DEFAULT"}__`;
|
|
692
|
+
}
|
|
693
|
+
function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
|
|
694
|
+
return processEnv[getVaultPassphraseEnvVar(vault)] ?? processEnv.CNOS_SECRET_PASSPHRASE;
|
|
695
|
+
}
|
|
696
|
+
function resolveVaultSessionKey(vault = "default", processEnv = process.env) {
|
|
697
|
+
const encoded = processEnv[getVaultSessionKeyEnvVar(vault)];
|
|
698
|
+
if (!encoded) {
|
|
699
|
+
return readVaultSessionKey(vault, processEnv);
|
|
700
|
+
}
|
|
701
|
+
try {
|
|
702
|
+
const key = Buffer.from(encoded, "hex");
|
|
703
|
+
return key.length === KEY_LENGTH ? key : void 0;
|
|
704
|
+
} catch {
|
|
520
705
|
return void 0;
|
|
521
706
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
707
|
+
}
|
|
708
|
+
function deriveVaultKey(passphrase, salt, iterations = PBKDF2_ITERATIONS) {
|
|
709
|
+
return pbkdf2Sync(passphrase, salt, iterations, KEY_LENGTH, "sha512");
|
|
710
|
+
}
|
|
711
|
+
function buildMetaPath(storeRoot, vault = "default") {
|
|
712
|
+
return path4.join(storeRoot, "vaults", vault, META_FILENAME);
|
|
713
|
+
}
|
|
714
|
+
function resolveSecretVaultFile(storeRoot, vault = "default") {
|
|
715
|
+
return buildMetaPath(storeRoot, vault);
|
|
716
|
+
}
|
|
717
|
+
function buildKeystorePath(storeRoot, vault = "default") {
|
|
718
|
+
return path4.join(storeRoot, "vaults", vault, KEYSTORE_FILENAME);
|
|
719
|
+
}
|
|
720
|
+
function buildLegacyVaultFile(storeRoot, vault = "default") {
|
|
721
|
+
return path4.join(storeRoot, "vaults", `${vault}.json`);
|
|
722
|
+
}
|
|
723
|
+
function buildLegacyVaultStoreRoot(storeRoot, vault = "default") {
|
|
724
|
+
return path4.join(storeRoot, "vaults", vault, "store");
|
|
725
|
+
}
|
|
726
|
+
function assertVaultMetadata(value, filePath) {
|
|
727
|
+
if (!isObject(value)) {
|
|
728
|
+
throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
|
|
729
|
+
}
|
|
730
|
+
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") {
|
|
731
|
+
throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
|
|
525
732
|
}
|
|
526
|
-
|
|
527
|
-
|
|
733
|
+
return value;
|
|
734
|
+
}
|
|
735
|
+
async function exists2(targetPath) {
|
|
736
|
+
try {
|
|
737
|
+
await stat(targetPath);
|
|
738
|
+
return true;
|
|
739
|
+
} catch {
|
|
740
|
+
return false;
|
|
528
741
|
}
|
|
529
|
-
return resolveSecretPassphrase(vault, processEnv);
|
|
530
742
|
}
|
|
531
|
-
function
|
|
532
|
-
const
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
};
|
|
743
|
+
async function detectLegacyVaultFormat(storeRoot, vault = "default") {
|
|
744
|
+
const legacyFile = buildLegacyVaultFile(storeRoot, vault);
|
|
745
|
+
const legacyStore = buildLegacyVaultStoreRoot(storeRoot, vault);
|
|
746
|
+
if (await exists2(legacyFile)) {
|
|
747
|
+
return legacyFile;
|
|
748
|
+
}
|
|
749
|
+
if (await exists2(legacyStore)) {
|
|
750
|
+
return legacyStore;
|
|
751
|
+
}
|
|
752
|
+
return void 0;
|
|
542
753
|
}
|
|
543
|
-
function
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
754
|
+
async function assertNoLegacyVaultFormat(storeRoot, vault = "default") {
|
|
755
|
+
const legacyPath = await detectLegacyVaultFormat(storeRoot, vault);
|
|
756
|
+
if (!legacyPath) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
throw new CnosSecurityError(
|
|
760
|
+
`Legacy CNOS local vault format detected for vault "${vault}" at ${legacyPath}. CNOS 1.4 requires the new keystore format. Remove and recreate the vault.`
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
function encryptPayload(payload, key) {
|
|
764
|
+
const iv = randomBytes(IV_LENGTH);
|
|
547
765
|
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
548
|
-
const
|
|
766
|
+
const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
|
|
767
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
549
768
|
const tag = cipher.getAuthTag();
|
|
550
|
-
return
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
ciphertext: ciphertext.toString("base64")
|
|
557
|
-
};
|
|
769
|
+
return Buffer.concat([
|
|
770
|
+
Buffer.from(Uint32Array.of(KEYSTORE_VERSION).buffer),
|
|
771
|
+
iv,
|
|
772
|
+
tag,
|
|
773
|
+
ciphertext
|
|
774
|
+
]);
|
|
558
775
|
}
|
|
559
|
-
function
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
|
|
776
|
+
function decryptPayload(buffer, key) {
|
|
777
|
+
if (buffer.length < 4 + IV_LENGTH + AUTH_TAG_LENGTH) {
|
|
778
|
+
throw new CnosSecurityError("Invalid CNOS local vault keystore");
|
|
779
|
+
}
|
|
780
|
+
const version = buffer.readUInt32LE(0);
|
|
781
|
+
if (version !== KEYSTORE_VERSION) {
|
|
782
|
+
throw new CnosSecurityError(`Unsupported CNOS local vault keystore version: ${version}`);
|
|
783
|
+
}
|
|
784
|
+
const ivOffset = 4;
|
|
785
|
+
const tagOffset = ivOffset + IV_LENGTH;
|
|
786
|
+
const cipherOffset = tagOffset + AUTH_TAG_LENGTH;
|
|
787
|
+
const iv = buffer.subarray(ivOffset, tagOffset);
|
|
788
|
+
const tag = buffer.subarray(tagOffset, cipherOffset);
|
|
789
|
+
const ciphertext = buffer.subarray(cipherOffset);
|
|
565
790
|
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
566
791
|
decipher.setAuthTag(tag);
|
|
567
|
-
|
|
568
|
-
|
|
792
|
+
try {
|
|
793
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
794
|
+
const payload = JSON.parse(plaintext);
|
|
795
|
+
if (!payload || !isObject(payload.secrets) || !isObject(payload.metadata)) {
|
|
796
|
+
throw new Error("invalid");
|
|
797
|
+
}
|
|
798
|
+
return {
|
|
799
|
+
secrets: Object.fromEntries(
|
|
800
|
+
Object.entries(payload.secrets).filter((entry) => typeof entry[1] === "string")
|
|
801
|
+
),
|
|
802
|
+
metadata: Object.fromEntries(
|
|
803
|
+
Object.entries(payload.metadata).filter(
|
|
804
|
+
(entry) => isObject(entry[1]) && typeof entry[1].createdAt === "string" && typeof entry[1].updatedAt === "string"
|
|
805
|
+
)
|
|
806
|
+
)
|
|
807
|
+
};
|
|
808
|
+
} catch {
|
|
809
|
+
throw new CnosAuthenticationError("Failed to decrypt CNOS local vault. Check vault authentication.");
|
|
810
|
+
}
|
|
569
811
|
}
|
|
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)
|
|
812
|
+
function buildInitialPayload() {
|
|
813
|
+
return {
|
|
814
|
+
secrets: {},
|
|
815
|
+
metadata: {}
|
|
579
816
|
};
|
|
580
|
-
await writeFile(filePath, JSON.stringify(document, null, 2), "utf8");
|
|
581
|
-
return filePath;
|
|
582
817
|
}
|
|
583
|
-
async function
|
|
584
|
-
const
|
|
585
|
-
const
|
|
818
|
+
async function writeVaultFiles(storeRoot, vault, meta, payload, key) {
|
|
819
|
+
const metaPath = buildMetaPath(storeRoot, vault);
|
|
820
|
+
const keystorePath = buildKeystorePath(storeRoot, vault);
|
|
821
|
+
await mkdir2(path4.dirname(metaPath), { recursive: true });
|
|
822
|
+
await writeFile2(metaPath, stringifyYaml(meta), "utf8");
|
|
823
|
+
await writeFile2(keystorePath, encryptPayload(payload, key));
|
|
824
|
+
}
|
|
825
|
+
async function readVaultMetadata(storeRoot, vault = "default") {
|
|
826
|
+
await assertNoLegacyVaultFormat(storeRoot, vault);
|
|
827
|
+
const metaPath = buildMetaPath(storeRoot, vault);
|
|
586
828
|
try {
|
|
587
|
-
await
|
|
588
|
-
return
|
|
829
|
+
const source = await readFile3(metaPath, "utf8");
|
|
830
|
+
return assertVaultMetadata(parseYaml(source), metaPath);
|
|
589
831
|
} catch (error) {
|
|
590
|
-
if (error.code
|
|
591
|
-
|
|
832
|
+
if (error.code === "ENOENT") {
|
|
833
|
+
return void 0;
|
|
592
834
|
}
|
|
835
|
+
throw error;
|
|
593
836
|
}
|
|
594
|
-
return createSecretVault(storeRoot, normalizedVault, passphrase);
|
|
595
837
|
}
|
|
596
838
|
async function listSecretVaults(storeRoot) {
|
|
597
|
-
const vaultRoot =
|
|
839
|
+
const vaultRoot = path4.join(storeRoot, "vaults");
|
|
598
840
|
try {
|
|
599
|
-
const entries = await
|
|
600
|
-
|
|
841
|
+
const entries = await readdir2(vaultRoot, { withFileTypes: true });
|
|
842
|
+
const vaults = await Promise.all(
|
|
843
|
+
entries.filter((entry) => entry.isDirectory()).map(async (entry) => await exists2(path4.join(vaultRoot, entry.name, META_FILENAME)) ? entry.name : void 0)
|
|
844
|
+
);
|
|
845
|
+
return vaults.filter((value) => Boolean(value)).sort((left, right) => left.localeCompare(right));
|
|
601
846
|
} catch {
|
|
602
847
|
return [];
|
|
603
848
|
}
|
|
604
849
|
}
|
|
605
|
-
async function
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
850
|
+
async function createSecretVault(storeRoot, vault, passphrase) {
|
|
851
|
+
const normalizedVault = vault.trim() || "default";
|
|
852
|
+
await assertNoLegacyVaultFormat(storeRoot, normalizedVault);
|
|
853
|
+
const salt = randomBytes(SALT_LENGTH);
|
|
854
|
+
const key = deriveVaultKey(passphrase, salt, PBKDF2_ITERATIONS);
|
|
855
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
856
|
+
const meta = {
|
|
857
|
+
version: METADATA_VERSION,
|
|
858
|
+
algorithm: "aes-256-gcm",
|
|
859
|
+
kdf: "pbkdf2-sha512",
|
|
860
|
+
iterations: PBKDF2_ITERATIONS,
|
|
861
|
+
salt: salt.toString("base64"),
|
|
862
|
+
createdAt,
|
|
863
|
+
secretCount: 0
|
|
864
|
+
};
|
|
865
|
+
await writeVaultFiles(storeRoot, normalizedVault, meta, buildInitialPayload(), key);
|
|
866
|
+
return buildMetaPath(storeRoot, normalizedVault);
|
|
611
867
|
}
|
|
612
|
-
async function
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
868
|
+
async function ensureSecretVault(storeRoot, vault, passphrase) {
|
|
869
|
+
const normalizedVault = vault.trim() || "default";
|
|
870
|
+
const meta = await readVaultMetadata(storeRoot, normalizedVault);
|
|
871
|
+
if (meta) {
|
|
872
|
+
return buildMetaPath(storeRoot, normalizedVault);
|
|
873
|
+
}
|
|
874
|
+
return createSecretVault(storeRoot, normalizedVault, passphrase);
|
|
875
|
+
}
|
|
876
|
+
function resolveConfiguredVaultPassphrase(definition, vault = "default", processEnv = process.env) {
|
|
877
|
+
if (definition?.provider !== "local") {
|
|
878
|
+
return void 0;
|
|
879
|
+
}
|
|
880
|
+
const configuredSources = definition.auth?.passphrase?.from ?? [];
|
|
881
|
+
for (const source of configuredSources) {
|
|
882
|
+
if (source.startsWith("env:")) {
|
|
883
|
+
const value = processEnv[source.slice(4)];
|
|
884
|
+
if (value) {
|
|
885
|
+
return value;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return resolveSecretPassphrase(vault, processEnv);
|
|
890
|
+
}
|
|
891
|
+
async function resolveVaultAccessKey(storeRoot, definition, vault = "default", processEnv = process.env) {
|
|
892
|
+
if (definition?.provider !== "local") {
|
|
893
|
+
return definition?.provider === "github-secrets" ? {
|
|
894
|
+
method: definition.auth?.method ?? "environment",
|
|
895
|
+
...definition?.auth?.config ? { config: definition.auth.config } : {}
|
|
896
|
+
} : void 0;
|
|
897
|
+
}
|
|
898
|
+
const sessionKey = await resolveVaultSessionKey(vault, processEnv);
|
|
899
|
+
if (sessionKey) {
|
|
900
|
+
return {
|
|
901
|
+
derivedKey: sessionKey,
|
|
902
|
+
method: "keychain",
|
|
903
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
const passphrase = resolveConfiguredVaultPassphrase(definition, vault, processEnv);
|
|
907
|
+
if (passphrase) {
|
|
908
|
+
return {
|
|
909
|
+
passphrase,
|
|
910
|
+
method: "passphrase",
|
|
911
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
const metadata = await readVaultMetadata(storeRoot, vault);
|
|
915
|
+
if (!metadata) {
|
|
916
|
+
return void 0;
|
|
917
|
+
}
|
|
918
|
+
throw new CnosAuthenticationError(
|
|
919
|
+
`Cannot authenticate to vault "${vault}". Set ${getVaultPassphraseEnvVar(vault)} or run cnos vault auth ${vault}.`
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
async function loadVaultPayload(storeRoot, vault, auth) {
|
|
923
|
+
const meta = await readVaultMetadata(storeRoot, vault);
|
|
924
|
+
if (!meta) {
|
|
925
|
+
throw new CnosManifestError(`Missing CNOS vault metadata for "${vault}"`);
|
|
926
|
+
}
|
|
927
|
+
const salt = Buffer.from(meta.salt, "base64");
|
|
928
|
+
const key = auth.derivedKey ?? (auth.passphrase ? deriveVaultKey(auth.passphrase, salt, meta.iterations) : void 0);
|
|
929
|
+
if (!key) {
|
|
930
|
+
throw new CnosAuthenticationError(`Vault "${vault}" requires authentication before access.`);
|
|
931
|
+
}
|
|
932
|
+
const buffer = await readFile3(buildKeystorePath(storeRoot, vault));
|
|
933
|
+
return {
|
|
934
|
+
meta,
|
|
935
|
+
payload: decryptPayload(buffer, key),
|
|
936
|
+
key
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
async function writeLocalSecret(storeRoot, ref, value, authOrPassphrase, vault = "default") {
|
|
940
|
+
const auth = typeof authOrPassphrase === "string" ? {
|
|
941
|
+
passphrase: authOrPassphrase,
|
|
942
|
+
method: "passphrase"
|
|
943
|
+
} : authOrPassphrase;
|
|
944
|
+
if (auth.passphrase) {
|
|
945
|
+
await ensureSecretVault(storeRoot, vault, auth.passphrase);
|
|
946
|
+
} else {
|
|
947
|
+
const meta2 = await readVaultMetadata(storeRoot, vault);
|
|
948
|
+
if (!meta2) {
|
|
949
|
+
throw new CnosAuthenticationError(`Vault "${vault}" requires passphrase-based authentication for initial creation.`);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
|
|
953
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
954
|
+
const existing = payload.metadata[ref];
|
|
955
|
+
payload.secrets[ref] = value;
|
|
956
|
+
payload.metadata[ref] = {
|
|
957
|
+
createdAt: existing?.createdAt ?? now,
|
|
958
|
+
updatedAt: now
|
|
959
|
+
};
|
|
960
|
+
const nextMeta = {
|
|
961
|
+
...meta,
|
|
962
|
+
secretCount: Object.keys(payload.secrets).length
|
|
963
|
+
};
|
|
964
|
+
await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
|
|
965
|
+
return buildKeystorePath(storeRoot, vault);
|
|
966
|
+
}
|
|
967
|
+
async function deleteLocalSecret(storeRoot, ref, auth, vault = "default") {
|
|
968
|
+
const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
|
|
969
|
+
if (!(ref in payload.secrets)) {
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
delete payload.secrets[ref];
|
|
973
|
+
delete payload.metadata[ref];
|
|
974
|
+
const nextMeta = {
|
|
975
|
+
...meta,
|
|
976
|
+
secretCount: Object.keys(payload.secrets).length
|
|
977
|
+
};
|
|
978
|
+
await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
981
|
+
async function readLocalSecret(storeRoot, ref, auth, vault = "default") {
|
|
982
|
+
const { payload } = await loadVaultPayload(storeRoot, vault, auth);
|
|
983
|
+
const value = payload.secrets[ref];
|
|
984
|
+
if (value === void 0) {
|
|
985
|
+
throw new CnosManifestError(`Missing local secret ref "${ref}" in vault "${vault}"`);
|
|
986
|
+
}
|
|
987
|
+
return value;
|
|
988
|
+
}
|
|
989
|
+
async function listLocalSecrets(storeRoot, auth, vault = "default") {
|
|
990
|
+
const { payload } = await loadVaultPayload(storeRoot, vault, auth);
|
|
991
|
+
return Object.keys(payload.secrets).sort((left, right) => left.localeCompare(right));
|
|
992
|
+
}
|
|
993
|
+
function resolveVaultDefinition(vaults, vault = "default") {
|
|
994
|
+
const definition = vaults?.[vault];
|
|
995
|
+
const provider = definition?.provider ?? "local";
|
|
996
|
+
return {
|
|
997
|
+
name: vault,
|
|
998
|
+
provider,
|
|
999
|
+
...definition?.auth ? { auth: definition.auth } : {},
|
|
1000
|
+
...definition?.mapping ? { mapping: definition.mapping } : {},
|
|
1001
|
+
requiresAuthentication: provider === "local"
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
async function removeLocalVaultFiles(storeRoot, vault = "default") {
|
|
1005
|
+
await rm2(path4.join(storeRoot, "vaults", vault), { recursive: true, force: true });
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// ../core/src/secrets/providers/github.ts
|
|
1009
|
+
var GithubSecretsVaultProvider = class {
|
|
1010
|
+
constructor(vaultId, definition, processEnv = process.env) {
|
|
1011
|
+
this.vaultId = vaultId;
|
|
1012
|
+
this.definition = definition;
|
|
1013
|
+
this.processEnv = processEnv;
|
|
1014
|
+
}
|
|
1015
|
+
vaultId;
|
|
1016
|
+
definition;
|
|
1017
|
+
processEnv;
|
|
1018
|
+
authenticated = false;
|
|
1019
|
+
async authenticate(_authConfig) {
|
|
1020
|
+
void _authConfig;
|
|
1021
|
+
this.authenticated = true;
|
|
1022
|
+
}
|
|
1023
|
+
isAuthenticated() {
|
|
1024
|
+
return this.authenticated;
|
|
1025
|
+
}
|
|
1026
|
+
resolveEnvVar(ref) {
|
|
1027
|
+
if (this.processEnv[ref] !== void 0) {
|
|
1028
|
+
return ref;
|
|
1029
|
+
}
|
|
1030
|
+
return Object.entries(this.definition.mapping ?? {}).find(([, logicalRef]) => logicalRef === ref)?.[0];
|
|
1031
|
+
}
|
|
1032
|
+
async batchGet(refs) {
|
|
1033
|
+
this.authenticated = true;
|
|
1034
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1035
|
+
for (const ref of Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right))) {
|
|
1036
|
+
const envVar = this.resolveEnvVar(ref);
|
|
1037
|
+
const value = envVar ? this.processEnv[envVar] : void 0;
|
|
1038
|
+
if (value !== void 0) {
|
|
1039
|
+
resolved.set(ref, value);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return resolved;
|
|
1043
|
+
}
|
|
1044
|
+
async get(ref) {
|
|
1045
|
+
const envVar = this.resolveEnvVar(ref);
|
|
1046
|
+
this.authenticated = true;
|
|
1047
|
+
return envVar ? this.processEnv[envVar] : void 0;
|
|
1048
|
+
}
|
|
1049
|
+
async set(ref, value) {
|
|
1050
|
+
void ref;
|
|
1051
|
+
void value;
|
|
1052
|
+
throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be written by CNOS.`);
|
|
1053
|
+
}
|
|
1054
|
+
async delete(ref) {
|
|
1055
|
+
void ref;
|
|
1056
|
+
throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be mutated by CNOS.`);
|
|
1057
|
+
}
|
|
1058
|
+
async list() {
|
|
1059
|
+
return Object.values(this.definition.mapping ?? {}).sort((left, right) => left.localeCompare(right));
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
// ../core/src/secrets/auditLog.ts
|
|
1064
|
+
import { appendFile, mkdir as mkdir3 } from "fs/promises";
|
|
1065
|
+
import path5 from "path";
|
|
1066
|
+
async function appendAuditEvent(event, processEnv = process.env) {
|
|
1067
|
+
const auditFile = processEnv.CNOS_AUDIT_FILE ?? path5.join(resolveSecretStoreRoot(processEnv), "audit", "access.log");
|
|
1068
|
+
await mkdir3(path5.dirname(auditFile), { recursive: true });
|
|
1069
|
+
await appendFile(
|
|
1070
|
+
auditFile,
|
|
1071
|
+
`${JSON.stringify({
|
|
1072
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1073
|
+
...event
|
|
1074
|
+
})}
|
|
1075
|
+
`,
|
|
1076
|
+
"utf8"
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// ../core/src/secrets/providers/local.ts
|
|
1081
|
+
var LocalSecretVaultProvider = class _LocalSecretVaultProvider {
|
|
1082
|
+
constructor(vaultId, definition, processEnv = process.env, storeRoot = resolveSecretStoreRoot(processEnv)) {
|
|
1083
|
+
this.vaultId = vaultId;
|
|
1084
|
+
this.processEnv = processEnv;
|
|
1085
|
+
this.storeRoot = storeRoot;
|
|
1086
|
+
this.definition = definition;
|
|
1087
|
+
}
|
|
1088
|
+
vaultId;
|
|
1089
|
+
processEnv;
|
|
1090
|
+
storeRoot;
|
|
1091
|
+
authConfig;
|
|
1092
|
+
definition;
|
|
1093
|
+
static fromVaults(vaults, vaultId, processEnv) {
|
|
1094
|
+
const definition = resolveVaultDefinition(vaults, vaultId);
|
|
1095
|
+
return new _LocalSecretVaultProvider(vaultId, definition, processEnv);
|
|
1096
|
+
}
|
|
1097
|
+
async authenticate(authConfig) {
|
|
1098
|
+
this.authConfig = authConfig;
|
|
1099
|
+
await this.list();
|
|
1100
|
+
}
|
|
1101
|
+
isAuthenticated() {
|
|
1102
|
+
return Boolean(this.authConfig);
|
|
1103
|
+
}
|
|
1104
|
+
async requireAuth() {
|
|
1105
|
+
if (this.authConfig) {
|
|
1106
|
+
return this.authConfig;
|
|
1107
|
+
}
|
|
1108
|
+
const resolved = await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
|
|
1109
|
+
if (!resolved) {
|
|
1110
|
+
throw new CnosAuthenticationError(
|
|
1111
|
+
`Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
this.authConfig = resolved;
|
|
1115
|
+
return resolved;
|
|
1116
|
+
}
|
|
1117
|
+
async batchGet(refs) {
|
|
1118
|
+
const auth = await this.requireAuth();
|
|
1119
|
+
const entries = await Promise.all(
|
|
1120
|
+
Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right)).map(async (ref) => [ref, await readLocalSecret(this.storeRoot, ref, auth, this.vaultId)])
|
|
1121
|
+
);
|
|
1122
|
+
return new Map(entries);
|
|
1123
|
+
}
|
|
1124
|
+
async get(ref) {
|
|
1125
|
+
const auth = await this.requireAuth();
|
|
1126
|
+
try {
|
|
1127
|
+
return await readLocalSecret(this.storeRoot, ref, auth, this.vaultId);
|
|
1128
|
+
} catch {
|
|
1129
|
+
return void 0;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
async set(ref, value) {
|
|
1133
|
+
const auth = await this.requireAuth();
|
|
1134
|
+
await writeLocalSecret(this.storeRoot, ref, value, auth, this.vaultId);
|
|
1135
|
+
await appendAuditEvent(
|
|
1136
|
+
{
|
|
1137
|
+
action: "write",
|
|
1138
|
+
vault: this.vaultId,
|
|
1139
|
+
ref,
|
|
1140
|
+
caller: "cli"
|
|
1141
|
+
},
|
|
1142
|
+
this.processEnv
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
async delete(ref) {
|
|
1146
|
+
const auth = await this.requireAuth();
|
|
1147
|
+
await deleteLocalSecret(this.storeRoot, ref, auth, this.vaultId);
|
|
1148
|
+
await appendAuditEvent(
|
|
1149
|
+
{
|
|
1150
|
+
action: "delete",
|
|
1151
|
+
vault: this.vaultId,
|
|
1152
|
+
ref,
|
|
1153
|
+
caller: "cli"
|
|
1154
|
+
},
|
|
1155
|
+
this.processEnv
|
|
616
1156
|
);
|
|
617
1157
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
1158
|
+
async list() {
|
|
1159
|
+
const auth = this.authConfig ?? await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
|
|
1160
|
+
if (!auth) {
|
|
1161
|
+
throw new CnosAuthenticationError(
|
|
1162
|
+
`Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
this.authConfig = auth;
|
|
1166
|
+
return listLocalSecrets(this.storeRoot, auth, this.vaultId);
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
// ../core/src/secrets/providers/registry.ts
|
|
1171
|
+
function createSecretVaultProvider(vaultId, definition, processEnv) {
|
|
1172
|
+
if (definition.provider === "local") {
|
|
1173
|
+
return new LocalSecretVaultProvider(vaultId, definition, processEnv);
|
|
1174
|
+
}
|
|
1175
|
+
if (definition.provider === "github-secrets") {
|
|
1176
|
+
return new GithubSecretsVaultProvider(vaultId, definition, processEnv);
|
|
1177
|
+
}
|
|
1178
|
+
throw new CnosManifestError(`Unsupported vault provider: ${definition.provider}`);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// ../core/src/secrets/prompt.ts
|
|
1182
|
+
import readline from "readline";
|
|
1183
|
+
import { Writable } from "stream";
|
|
1184
|
+
async function promptHidden(message) {
|
|
1185
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1186
|
+
return void 0;
|
|
1187
|
+
}
|
|
1188
|
+
const mutableStdout = new WritableMask();
|
|
1189
|
+
const rl = readline.createInterface({
|
|
1190
|
+
input: process.stdin,
|
|
1191
|
+
output: mutableStdout,
|
|
1192
|
+
terminal: true
|
|
1193
|
+
});
|
|
1194
|
+
try {
|
|
1195
|
+
mutableStdout.muted = true;
|
|
1196
|
+
const value = await new Promise((resolve) => {
|
|
1197
|
+
rl.question(message, resolve);
|
|
1198
|
+
});
|
|
1199
|
+
process.stdout.write("\n");
|
|
1200
|
+
return value;
|
|
1201
|
+
} finally {
|
|
1202
|
+
rl.close();
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
var WritableMask = class extends Writable {
|
|
1206
|
+
muted = false;
|
|
1207
|
+
_write(chunk, _encoding, callback) {
|
|
1208
|
+
if (!this.muted) {
|
|
1209
|
+
process.stdout.write(chunk);
|
|
1210
|
+
}
|
|
1211
|
+
callback();
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
// ../core/src/secrets/resolveAuth.ts
|
|
1216
|
+
function toAuthError(vaultId, sources) {
|
|
1217
|
+
return new CnosAuthenticationError(
|
|
1218
|
+
`Cannot authenticate to vault "${vaultId}". Tried: ${sources.join(", ")}. Set ${getVaultPassphraseEnvVar(vaultId)} or run cnos vault auth ${vaultId}.`
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
|
|
1222
|
+
const sessionKey = await resolveVaultSessionKey(vaultId, processEnv);
|
|
1223
|
+
if (sessionKey) {
|
|
1224
|
+
return {
|
|
1225
|
+
derivedKey: sessionKey,
|
|
1226
|
+
method: "keychain",
|
|
1227
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
if (definition.provider === "github-secrets") {
|
|
1231
|
+
return {
|
|
1232
|
+
method: definition.auth?.method ?? "environment",
|
|
1233
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
const sources = definition.auth?.passphrase?.from ?? [getVaultPassphraseEnvVar(vaultId)];
|
|
1237
|
+
for (const source of sources) {
|
|
1238
|
+
if (source.startsWith("env:")) {
|
|
1239
|
+
const value = processEnv[source.slice(4)];
|
|
1240
|
+
if (value) {
|
|
1241
|
+
return {
|
|
1242
|
+
passphrase: value,
|
|
1243
|
+
method: "passphrase",
|
|
1244
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
if (source.startsWith("keychain:")) {
|
|
1249
|
+
const value = await readKeychain(source.slice("keychain:".length));
|
|
1250
|
+
if (value) {
|
|
1251
|
+
return {
|
|
1252
|
+
derivedKey: Buffer.from(value, "hex"),
|
|
1253
|
+
method: "keychain",
|
|
1254
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (source === "prompt") {
|
|
1259
|
+
const value = await promptHidden(`Enter passphrase for vault "${vaultId}": `);
|
|
1260
|
+
if (value) {
|
|
1261
|
+
return {
|
|
1262
|
+
passphrase: value,
|
|
1263
|
+
method: "passphrase",
|
|
1264
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
const fallback = resolveSecretPassphrase(vaultId, processEnv);
|
|
1270
|
+
if (fallback) {
|
|
1271
|
+
return {
|
|
1272
|
+
passphrase: fallback,
|
|
1273
|
+
method: "passphrase",
|
|
1274
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
throw toAuthError(vaultId, [getVaultSessionKeyEnvVar(vaultId), ...sources]);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// ../core/src/runtime/projection.ts
|
|
1281
|
+
function setNestedValue(target, pathSegments, value) {
|
|
1282
|
+
const [head, ...tail] = pathSegments;
|
|
1283
|
+
if (!head) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
if (tail.length === 0) {
|
|
1287
|
+
target[head] = value;
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
const current = target[head];
|
|
1291
|
+
const nextTarget = current && typeof current === "object" && !Array.isArray(current) ? current : {};
|
|
1292
|
+
target[head] = nextTarget;
|
|
1293
|
+
setNestedValue(nextTarget, tail, value);
|
|
1294
|
+
}
|
|
1295
|
+
function toNamespaceObject(graph, namespace) {
|
|
1296
|
+
const output = {};
|
|
1297
|
+
const resolvedEntries = Array.from(graph.entries.values()).sort(
|
|
1298
|
+
(left, right) => left.key.localeCompare(right.key)
|
|
1299
|
+
);
|
|
1300
|
+
for (const entry of resolvedEntries) {
|
|
1301
|
+
if (namespace && entry.namespace !== namespace) {
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
const valuePath = namespace ? stripNamespace(entry.key) : entry.key;
|
|
1305
|
+
setNestedValue(output, valuePath.split("."), entry.value);
|
|
1306
|
+
}
|
|
1307
|
+
return output;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// ../core/src/runtime/read.ts
|
|
1311
|
+
function readValue(graph, key) {
|
|
1312
|
+
return graph.entries.get(key)?.value;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// ../core/src/runtime/readOr.ts
|
|
1316
|
+
function readOrValue(graph, key, fallback) {
|
|
1317
|
+
const value = readValue(graph, key);
|
|
1318
|
+
return value === void 0 ? fallback : value;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// ../core/src/runtime/require.ts
|
|
1322
|
+
function requireValue(graph, key) {
|
|
1323
|
+
const value = readValue(graph, key);
|
|
1324
|
+
if (value === void 0) {
|
|
1325
|
+
throw new CnosKeyNotFoundError(key);
|
|
623
1326
|
}
|
|
624
|
-
return
|
|
1327
|
+
return value;
|
|
625
1328
|
}
|
|
626
1329
|
|
|
627
1330
|
// ../core/src/runtime/toEnv.ts
|
|
@@ -705,23 +1408,23 @@ function toPublicEnv(graph, manifest, options = {}) {
|
|
|
705
1408
|
}
|
|
706
1409
|
|
|
707
1410
|
// ../core/src/runtime/dump.ts
|
|
708
|
-
import { mkdir as
|
|
709
|
-
import
|
|
1411
|
+
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
1412
|
+
import path6 from "path";
|
|
710
1413
|
function buildDumpFiles(graph, options = {}) {
|
|
711
|
-
const basePath = options.flatten ? "" :
|
|
1414
|
+
const basePath = options.flatten ? "" : path6.posix.join("workspaces", graph.workspace.workspaceId);
|
|
712
1415
|
const values = toNamespaceObject(graph, "value");
|
|
713
1416
|
const secrets = toNamespaceObject(graph, "secret");
|
|
714
1417
|
const files = [];
|
|
715
1418
|
if (Object.keys(values).length > 0) {
|
|
716
1419
|
files.push({
|
|
717
|
-
path:
|
|
1420
|
+
path: path6.posix.join(basePath, "values", graph.profile, "app.yml"),
|
|
718
1421
|
namespace: "value",
|
|
719
1422
|
content: stringifyYaml(values)
|
|
720
1423
|
});
|
|
721
1424
|
}
|
|
722
1425
|
if (Object.keys(secrets).length > 0) {
|
|
723
1426
|
files.push({
|
|
724
|
-
path:
|
|
1427
|
+
path: path6.posix.join(basePath, "secrets", graph.profile, "app.yml"),
|
|
725
1428
|
namespace: "secret",
|
|
726
1429
|
content: stringifyYaml(secrets)
|
|
727
1430
|
});
|
|
@@ -737,12 +1440,12 @@ function planDump(graph, options = {}) {
|
|
|
737
1440
|
};
|
|
738
1441
|
}
|
|
739
1442
|
async function writeDump(graph, options) {
|
|
740
|
-
const root =
|
|
1443
|
+
const root = path6.resolve(options.to);
|
|
741
1444
|
const plan = planDump(graph, options);
|
|
742
1445
|
for (const file of plan.files) {
|
|
743
|
-
const destination =
|
|
744
|
-
await
|
|
745
|
-
await
|
|
1446
|
+
const destination = path6.join(root, file.path);
|
|
1447
|
+
await mkdir4(path6.dirname(destination), { recursive: true });
|
|
1448
|
+
await writeFile3(destination, file.content, "utf8");
|
|
746
1449
|
}
|
|
747
1450
|
return {
|
|
748
1451
|
...plan,
|
|
@@ -773,11 +1476,11 @@ function normalizeMappingConfig(config = {}) {
|
|
|
773
1476
|
function toScreamingSnakeSegment(segment) {
|
|
774
1477
|
return segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
775
1478
|
}
|
|
776
|
-
function toScreamingSnake(
|
|
777
|
-
return
|
|
1479
|
+
function toScreamingSnake(path10) {
|
|
1480
|
+
return path10.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
|
|
778
1481
|
}
|
|
779
|
-
function fromScreamingSnake(
|
|
780
|
-
return
|
|
1482
|
+
function fromScreamingSnake(path10) {
|
|
1483
|
+
return path10.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
|
|
781
1484
|
}
|
|
782
1485
|
function logicalKeyToEnvVar(key, config = {}) {
|
|
783
1486
|
const normalized = normalizeMappingConfig(config);
|
|
@@ -929,12 +1632,12 @@ function createProvenanceInspector() {
|
|
|
929
1632
|
}
|
|
930
1633
|
|
|
931
1634
|
// ../core/src/manifest/loadWorkspaceFile.ts
|
|
932
|
-
import { readFile as
|
|
933
|
-
import
|
|
1635
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
1636
|
+
import path7 from "path";
|
|
934
1637
|
async function loadWorkspaceFile(repoRoot) {
|
|
935
|
-
const workspaceFilePath =
|
|
1638
|
+
const workspaceFilePath = path7.join(repoRoot, ".cnos-workspace.yml");
|
|
936
1639
|
try {
|
|
937
|
-
const source = await
|
|
1640
|
+
const source = await readFile4(workspaceFilePath, "utf8");
|
|
938
1641
|
const parsed = parseYaml(source);
|
|
939
1642
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
940
1643
|
throw new CnosManifestError(".cnos-workspace.yml must be a YAML object", workspaceFilePath);
|
|
@@ -957,8 +1660,8 @@ async function loadWorkspaceFile(repoRoot) {
|
|
|
957
1660
|
}
|
|
958
1661
|
|
|
959
1662
|
// ../core/src/profiles/expandProfileChain.ts
|
|
960
|
-
import { access as access2, readFile as
|
|
961
|
-
import
|
|
1663
|
+
import { access as access2, readFile as readFile5 } from "fs/promises";
|
|
1664
|
+
import path8 from "path";
|
|
962
1665
|
async function fileExists(targetPath) {
|
|
963
1666
|
try {
|
|
964
1667
|
await access2(targetPath);
|
|
@@ -995,11 +1698,11 @@ async function loadProfileDefinition(profileName, options) {
|
|
|
995
1698
|
return normalizeProfileDefinition(profileName, void 0);
|
|
996
1699
|
}
|
|
997
1700
|
for (const workspaceRoot of [...workspaceRoots].reverse()) {
|
|
998
|
-
const profilePath =
|
|
1701
|
+
const profilePath = path8.join(workspaceRoot.path, "profiles", `${profileName}.yml`);
|
|
999
1702
|
if (!await fileExists(profilePath)) {
|
|
1000
1703
|
continue;
|
|
1001
1704
|
}
|
|
1002
|
-
const document = await
|
|
1705
|
+
const document = await readFile5(profilePath, "utf8");
|
|
1003
1706
|
const parsed = parseYaml(document);
|
|
1004
1707
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1005
1708
|
throw new CnosManifestError("Profile definition must be a YAML object", profilePath);
|
|
@@ -1007,7 +1710,7 @@ async function loadProfileDefinition(profileName, options) {
|
|
|
1007
1710
|
const definition = normalizeProfileDefinition(
|
|
1008
1711
|
profileName,
|
|
1009
1712
|
parsed,
|
|
1010
|
-
options.manifestRoot ? toPortablePath(
|
|
1713
|
+
options.manifestRoot ? toPortablePath(path8.relative(path8.dirname(options.manifestRoot), profilePath)) : toPortablePath(profilePath)
|
|
1011
1714
|
);
|
|
1012
1715
|
if (definition.name !== profileName) {
|
|
1013
1716
|
throw new CnosManifestError(
|
|
@@ -1254,7 +1957,7 @@ function createProfileAwareResolver() {
|
|
|
1254
1957
|
|
|
1255
1958
|
// ../core/src/workspaces/resolveWorkspaceContext.ts
|
|
1256
1959
|
import { access as access3 } from "fs/promises";
|
|
1257
|
-
import
|
|
1960
|
+
import path9 from "path";
|
|
1258
1961
|
|
|
1259
1962
|
// ../core/src/workspaces/expandWorkspaceChain.ts
|
|
1260
1963
|
function expandWorkspaceChain(workspaceId, items) {
|
|
@@ -1291,7 +1994,7 @@ function expandWorkspaceChain(workspaceId, items) {
|
|
|
1291
1994
|
}
|
|
1292
1995
|
|
|
1293
1996
|
// ../core/src/workspaces/resolveWorkspaceContext.ts
|
|
1294
|
-
async function
|
|
1997
|
+
async function exists3(targetPath) {
|
|
1295
1998
|
try {
|
|
1296
1999
|
await access3(targetPath);
|
|
1297
2000
|
return true;
|
|
@@ -1299,15 +2002,18 @@ async function exists2(targetPath) {
|
|
|
1299
2002
|
return false;
|
|
1300
2003
|
}
|
|
1301
2004
|
}
|
|
1302
|
-
async function resolveLocalWorkspaceRoot(manifestRoot, workspaceId) {
|
|
1303
|
-
const workspaceRoot =
|
|
1304
|
-
if (await
|
|
2005
|
+
async function resolveLocalWorkspaceRoot(manifestRoot, workspaceId, manifest) {
|
|
2006
|
+
const workspaceRoot = path9.join(manifestRoot, "workspaces", workspaceId);
|
|
2007
|
+
if (await exists3(workspaceRoot)) {
|
|
1305
2008
|
return workspaceRoot;
|
|
1306
2009
|
}
|
|
1307
|
-
const
|
|
1308
|
-
(
|
|
2010
|
+
const customDataNamespaceRoots = Object.entries(manifest.namespaces).filter(
|
|
2011
|
+
([namespace, definition]) => namespace !== "value" && namespace !== "secret" && definition.kind === "data" && !definition.sensitive
|
|
2012
|
+
).map(([namespace]) => namespace);
|
|
2013
|
+
const legacyMarkers = ["values", "secrets", "env", "profiles", ...customDataNamespaceRoots].map(
|
|
2014
|
+
(segment) => path9.join(manifestRoot, segment)
|
|
1309
2015
|
);
|
|
1310
|
-
if ((await Promise.all(legacyMarkers.map((marker) =>
|
|
2016
|
+
if ((await Promise.all(legacyMarkers.map((marker) => exists3(marker)))).some(Boolean)) {
|
|
1311
2017
|
return manifestRoot;
|
|
1312
2018
|
}
|
|
1313
2019
|
return workspaceRoot;
|
|
@@ -1347,26 +2053,26 @@ function resolveGlobalRoot(manifest, workspaceFile, options) {
|
|
|
1347
2053
|
}
|
|
1348
2054
|
if (options.globalRoot) {
|
|
1349
2055
|
return {
|
|
1350
|
-
value:
|
|
2056
|
+
value: path9.resolve(expandHomePath(options.globalRoot)),
|
|
1351
2057
|
source: "cli"
|
|
1352
2058
|
};
|
|
1353
2059
|
}
|
|
1354
2060
|
if (workspaceFile?.globalRoot) {
|
|
1355
2061
|
return {
|
|
1356
|
-
value:
|
|
2062
|
+
value: path9.resolve(expandHomePath(workspaceFile.globalRoot)),
|
|
1357
2063
|
source: "workspace-file"
|
|
1358
2064
|
};
|
|
1359
2065
|
}
|
|
1360
2066
|
if (manifest.workspaces.global.root) {
|
|
1361
2067
|
return {
|
|
1362
|
-
value:
|
|
2068
|
+
value: path9.resolve(expandHomePath(manifest.workspaces.global.root)),
|
|
1363
2069
|
source: "manifest"
|
|
1364
2070
|
};
|
|
1365
2071
|
}
|
|
1366
2072
|
const cnosHome = options.processEnv?.CNOS_HOME;
|
|
1367
2073
|
if (cnosHome) {
|
|
1368
2074
|
return {
|
|
1369
|
-
value:
|
|
2075
|
+
value: path9.resolve(expandHomePath(cnosHome)),
|
|
1370
2076
|
source: "CNOS_HOME"
|
|
1371
2077
|
};
|
|
1372
2078
|
}
|
|
@@ -1383,7 +2089,7 @@ async function resolveWorkspaceContext(manifest, options) {
|
|
|
1383
2089
|
workspaceRoots.push({
|
|
1384
2090
|
scope: "global",
|
|
1385
2091
|
workspaceId: chainWorkspaceId,
|
|
1386
|
-
path:
|
|
2092
|
+
path: path9.join(globalRoot.value, "workspaces", globalWorkspaceId)
|
|
1387
2093
|
});
|
|
1388
2094
|
}
|
|
1389
2095
|
}
|
|
@@ -1391,7 +2097,7 @@ async function resolveWorkspaceContext(manifest, options) {
|
|
|
1391
2097
|
workspaceRoots.push({
|
|
1392
2098
|
scope: "local",
|
|
1393
2099
|
workspaceId: chainWorkspaceId,
|
|
1394
|
-
path: await resolveLocalWorkspaceRoot(options.manifestRoot, chainWorkspaceId)
|
|
2100
|
+
path: await resolveLocalWorkspaceRoot(options.manifestRoot, chainWorkspaceId, manifest)
|
|
1395
2101
|
});
|
|
1396
2102
|
}
|
|
1397
2103
|
return {
|
|
@@ -1556,6 +2262,7 @@ async function runPipeline(options) {
|
|
|
1556
2262
|
const collectedEntries = await Promise.all(
|
|
1557
2263
|
options.plugins.map(
|
|
1558
2264
|
(plugin) => plugin.load({
|
|
2265
|
+
manifest: options.manifest,
|
|
1559
2266
|
manifestConfig: {
|
|
1560
2267
|
...options.manifest.sources[plugin.id] ?? {},
|
|
1561
2268
|
envMapping: options.manifest.envMapping
|
|
@@ -1573,29 +2280,120 @@ async function runPipeline(options) {
|
|
|
1573
2280
|
return collectedEntries.flat();
|
|
1574
2281
|
}
|
|
1575
2282
|
|
|
2283
|
+
// ../core/src/secrets/secretCache.ts
|
|
2284
|
+
var SecretCache = class {
|
|
2285
|
+
cache = /* @__PURE__ */ new Map();
|
|
2286
|
+
authenticated = /* @__PURE__ */ new Set();
|
|
2287
|
+
load(vaultId, secrets) {
|
|
2288
|
+
this.authenticated.add(vaultId);
|
|
2289
|
+
for (const [ref, value] of secrets) {
|
|
2290
|
+
this.cache.set(`${vaultId}:${ref}`, value);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
isVaultAuthenticated(vaultId) {
|
|
2294
|
+
return this.authenticated.has(vaultId);
|
|
2295
|
+
}
|
|
2296
|
+
get(vaultId, ref) {
|
|
2297
|
+
return this.cache.get(`${vaultId}:${ref}`);
|
|
2298
|
+
}
|
|
2299
|
+
clear(vaultId) {
|
|
2300
|
+
if (!vaultId) {
|
|
2301
|
+
this.cache.clear();
|
|
2302
|
+
this.authenticated.clear();
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2305
|
+
this.authenticated.delete(vaultId);
|
|
2306
|
+
for (const key of Array.from(this.cache.keys())) {
|
|
2307
|
+
if (key.startsWith(`${vaultId}:`)) {
|
|
2308
|
+
this.cache.delete(key);
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
};
|
|
2313
|
+
|
|
2314
|
+
// ../core/src/secrets/batchResolve.ts
|
|
2315
|
+
function collectSecretDescriptors(graph) {
|
|
2316
|
+
return Array.from(graph.entries.values()).filter((entry) => entry.namespace === "secret" && isSecretReference(entry.value)).map((entry) => ({
|
|
2317
|
+
logicalKey: entry.key,
|
|
2318
|
+
ref: entry.value
|
|
2319
|
+
}));
|
|
2320
|
+
}
|
|
2321
|
+
async function batchResolveSecrets(graph, manifest, processEnv = process.env) {
|
|
2322
|
+
const cache = new SecretCache();
|
|
2323
|
+
const descriptors = collectSecretDescriptors(graph);
|
|
2324
|
+
const grouped = descriptors.reduce((accumulator, descriptor) => {
|
|
2325
|
+
const vaultId = descriptor.ref.vault ?? "default";
|
|
2326
|
+
const bucket = accumulator.get(vaultId) ?? [];
|
|
2327
|
+
bucket.push(descriptor);
|
|
2328
|
+
accumulator.set(vaultId, bucket);
|
|
2329
|
+
return accumulator;
|
|
2330
|
+
}, /* @__PURE__ */ new Map());
|
|
2331
|
+
for (const [vaultId, refs] of grouped) {
|
|
2332
|
+
const definition = manifest.vaults[vaultId] ?? { provider: "local", auth: { passphrase: { from: [] } } };
|
|
2333
|
+
const provider = createSecretVaultProvider(vaultId, definition, processEnv);
|
|
2334
|
+
const auth = await resolveVaultAuth(vaultId, definition, processEnv);
|
|
2335
|
+
await provider.authenticate(auth);
|
|
2336
|
+
const resolved = await provider.batchGet(refs.map((entry) => entry.ref.ref));
|
|
2337
|
+
cache.load(vaultId, resolved);
|
|
2338
|
+
await appendAuditEvent(
|
|
2339
|
+
{
|
|
2340
|
+
action: "batch_read",
|
|
2341
|
+
vault: vaultId,
|
|
2342
|
+
refs: Array.from(resolved.keys()).sort((left, right) => left.localeCompare(right)),
|
|
2343
|
+
caller: "runtime",
|
|
2344
|
+
workspace: graph.workspace.workspaceId,
|
|
2345
|
+
profile: graph.profile
|
|
2346
|
+
},
|
|
2347
|
+
processEnv
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
2350
|
+
return cache;
|
|
2351
|
+
}
|
|
2352
|
+
function resolveSecretEntryValue(key, value, cache) {
|
|
2353
|
+
if (!key.startsWith("secret.") || !isSecretReference(value)) {
|
|
2354
|
+
return value;
|
|
2355
|
+
}
|
|
2356
|
+
const vaultId = value.vault ?? "default";
|
|
2357
|
+
return cache.get(vaultId, value.ref) ?? value;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
1576
2360
|
// ../core/src/orchestrator/runtime.ts
|
|
1577
|
-
function createRuntime(manifest, graph, plugins = []) {
|
|
2361
|
+
function createRuntime(manifest, graph, plugins = [], secretCache) {
|
|
2362
|
+
function readLogicalKey(key) {
|
|
2363
|
+
const entry = graph.entries.get(key);
|
|
2364
|
+
if (!entry) {
|
|
2365
|
+
return void 0;
|
|
2366
|
+
}
|
|
2367
|
+
if (!secretCache) {
|
|
2368
|
+
return entry.value;
|
|
2369
|
+
}
|
|
2370
|
+
return resolveSecretEntryValue(key, entry.value, secretCache);
|
|
2371
|
+
}
|
|
1578
2372
|
return {
|
|
1579
2373
|
manifest,
|
|
1580
2374
|
plugins,
|
|
1581
2375
|
graph,
|
|
1582
2376
|
read(key) {
|
|
1583
|
-
return
|
|
2377
|
+
return readLogicalKey(key);
|
|
1584
2378
|
},
|
|
1585
2379
|
require(key) {
|
|
1586
|
-
|
|
2380
|
+
const value = readLogicalKey(key);
|
|
2381
|
+
if (value === void 0) {
|
|
2382
|
+
return requireValue(graph, key);
|
|
2383
|
+
}
|
|
2384
|
+
return value;
|
|
1587
2385
|
},
|
|
1588
2386
|
readOr(key, fallback) {
|
|
1589
2387
|
return readOrValue(graph, key, fallback);
|
|
1590
2388
|
},
|
|
1591
|
-
value(
|
|
1592
|
-
return
|
|
2389
|
+
value(path10) {
|
|
2390
|
+
return readLogicalKey(toLogicalKey("value", path10));
|
|
1593
2391
|
},
|
|
1594
|
-
secret(
|
|
1595
|
-
return
|
|
2392
|
+
secret(path10) {
|
|
2393
|
+
return readLogicalKey(toLogicalKey("secret", path10));
|
|
1596
2394
|
},
|
|
1597
|
-
meta(
|
|
1598
|
-
return
|
|
2395
|
+
meta(path10) {
|
|
2396
|
+
return readLogicalKey(toLogicalKey("meta", path10));
|
|
1599
2397
|
},
|
|
1600
2398
|
inspect(key) {
|
|
1601
2399
|
return inspectValue(graph, key);
|
|
@@ -1753,21 +2551,26 @@ async function createCnos(options = {}) {
|
|
|
1753
2551
|
});
|
|
1754
2552
|
const schemaApplied = applySchemaRules(graph, loadedManifest.manifest.schema);
|
|
1755
2553
|
const promotedGraph = promoteToPublic(schemaApplied.graph, loadedManifest.manifest);
|
|
2554
|
+
const secretCache = options.secretResolution === "lazy" ? void 0 : await batchResolveSecrets(promotedGraph, loadedManifest.manifest, options.processEnv);
|
|
1756
2555
|
return createRuntime(
|
|
1757
2556
|
loadedManifest.manifest,
|
|
1758
2557
|
appendMetaEntries({
|
|
1759
2558
|
...promotedGraph,
|
|
1760
2559
|
profileSource: activeProfile.source
|
|
1761
2560
|
}, options.cnosVersion),
|
|
1762
|
-
plugins
|
|
2561
|
+
plugins,
|
|
2562
|
+
secretCache
|
|
1763
2563
|
);
|
|
1764
2564
|
}
|
|
1765
2565
|
|
|
1766
2566
|
export {
|
|
1767
2567
|
CnosManifestError,
|
|
1768
2568
|
CnosSecurityError,
|
|
2569
|
+
CnosAuthenticationError,
|
|
1769
2570
|
inspectValue,
|
|
1770
2571
|
createProvenanceInspector,
|
|
2572
|
+
readKeychain,
|
|
2573
|
+
writeKeychain,
|
|
1771
2574
|
resolveManifestRoot,
|
|
1772
2575
|
resolveWorkspaceScopedPath,
|
|
1773
2576
|
resolveConfigDocumentPath,
|
|
@@ -1777,24 +2580,38 @@ export {
|
|
|
1777
2580
|
parseYaml,
|
|
1778
2581
|
stringifyYaml,
|
|
1779
2582
|
loadManifest,
|
|
2583
|
+
getNamespaceDefinition,
|
|
1780
2584
|
ensureProjectionAllowed,
|
|
1781
2585
|
applySchemaRules,
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
requireValue,
|
|
2586
|
+
writeVaultSessionKey,
|
|
2587
|
+
clearVaultSessionKey,
|
|
2588
|
+
clearAllVaultSessionKeys,
|
|
1786
2589
|
isSecretReference,
|
|
1787
2590
|
resolveSecretStoreRoot,
|
|
1788
|
-
resolveSecretVaultFile,
|
|
1789
|
-
resolveSecretPassphrase,
|
|
1790
2591
|
getVaultPassphraseEnvVar,
|
|
1791
2592
|
isPassphraseEnvRef,
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
2593
|
+
getVaultSessionKeyEnvVar,
|
|
2594
|
+
resolveSecretPassphrase,
|
|
2595
|
+
deriveVaultKey,
|
|
2596
|
+
resolveSecretVaultFile,
|
|
2597
|
+
detectLegacyVaultFormat,
|
|
2598
|
+
readVaultMetadata,
|
|
1795
2599
|
listSecretVaults,
|
|
2600
|
+
createSecretVault,
|
|
2601
|
+
resolveConfiguredVaultPassphrase,
|
|
2602
|
+
resolveVaultAccessKey,
|
|
1796
2603
|
writeLocalSecret,
|
|
2604
|
+
deleteLocalSecret,
|
|
1797
2605
|
readLocalSecret,
|
|
2606
|
+
listLocalSecrets,
|
|
2607
|
+
resolveVaultDefinition,
|
|
2608
|
+
removeLocalVaultFiles,
|
|
2609
|
+
createSecretVaultProvider,
|
|
2610
|
+
resolveVaultAuth,
|
|
2611
|
+
toNamespaceObject,
|
|
2612
|
+
readValue,
|
|
2613
|
+
readOrValue,
|
|
2614
|
+
requireValue,
|
|
1798
2615
|
toEnv,
|
|
1799
2616
|
toPublicEnv,
|
|
1800
2617
|
createCnos,
|