@kitsy/cnos 1.1.2 → 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 +6 -3
- package/dist/browser/index.cjs +94 -0
- package/dist/browser/index.d.cts +16 -0
- package/dist/browser/index.d.ts +16 -0
- package/dist/browser/index.js +67 -0
- package/dist/build/index.cjs +2889 -0
- package/dist/build/index.d.cts +5 -0
- package/dist/build/index.d.ts +5 -0
- package/dist/build/index.js +26 -0
- package/dist/{chunk-53HXUSM6.js → chunk-CDXJISGB.js} +1 -1
- package/dist/{chunk-33ZDYDQJ.js → chunk-DRKDNY4I.js} +1470 -462
- package/dist/chunk-E7SE6N26.js +189 -0
- package/dist/chunk-EDCLLCNL.js +200 -0
- package/dist/{chunk-7FBRVJD6.js → chunk-FC3IV6A7.js} +1 -31
- package/dist/{chunk-JQGGSNCL.js → chunk-JDII6O72.js} +1 -1
- package/dist/chunk-K6QYI2T4.js +105 -0
- package/dist/{chunk-IHSV5AFX.js → chunk-OOKFRWTN.js} +1 -1
- package/dist/{chunk-HOS4E7XO.js → chunk-OWUZQ4OH.js} +1 -1
- package/dist/{chunk-IQOUWY6T.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-BrOk5ndZ.d.cts → envNaming-D6k66myh.d.cts} +1 -1
- package/dist/{envNaming-DCaNdnrF.d.ts → envNaming-Dy3WYiGK.d.ts} +1 -1
- package/dist/index.cjs +1396 -264
- package/dist/index.d.cts +2 -12
- package/dist/index.d.ts +2 -12
- package/dist/index.js +13 -143
- package/dist/internal.cjs +1913 -63
- package/dist/internal.d.cts +190 -8
- package/dist/internal.d.ts +190 -8
- package/dist/internal.js +669 -3
- 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 +38 -11
- 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 +60 -48
- 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 +33 -6
- 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-BVNEHj19.d.cts → plugin-CyNkf7Dm.d.cts} +42 -2
- package/dist/{plugin-BVNEHj19.d.ts → plugin-CyNkf7Dm.d.ts} +42 -2
- package/dist/runtime/index.cjs +3116 -0
- package/dist/runtime/index.d.cts +23 -0
- package/dist/runtime/index.d.ts +23 -0
- package/dist/runtime/index.js +15 -0
- package/dist/{toPublicEnv-Dd152fFy.d.cts → toPublicEnv-Cz72m6y0.d.cts} +1 -1
- package/dist/{toPublicEnv-Gwz3xTK0.d.ts → toPublicEnv-D2PZkaN-.d.ts} +1 -1
- package/package.json +26 -1
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// ../core/src/utils/path.ts
|
|
2
|
-
import { access } from "fs/promises";
|
|
3
|
-
import os from "os";
|
|
4
|
-
import path from "path";
|
|
5
|
-
|
|
6
1
|
// ../core/src/errors.ts
|
|
7
2
|
var CnosError = class extends Error {
|
|
8
3
|
constructor(message) {
|
|
@@ -17,6 +12,16 @@ var CnosManifestError = class extends CnosError {
|
|
|
17
12
|
}
|
|
18
13
|
manifestPath;
|
|
19
14
|
};
|
|
15
|
+
var CnosSecurityError = class extends CnosError {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
}
|
|
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}`);
|
|
@@ -25,7 +30,149 @@ var CnosKeyNotFoundError = class extends CnosError {
|
|
|
25
30
|
key;
|
|
26
31
|
};
|
|
27
32
|
|
|
33
|
+
// ../core/src/runtime/inspect.ts
|
|
34
|
+
function inspectValue(graph, key) {
|
|
35
|
+
const entry = graph.entries.get(key);
|
|
36
|
+
if (!entry) {
|
|
37
|
+
throw new CnosKeyNotFoundError(key);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
key: entry.key,
|
|
41
|
+
value: entry.value,
|
|
42
|
+
namespace: entry.namespace,
|
|
43
|
+
profile: graph.profile,
|
|
44
|
+
profileSource: graph.profileSource,
|
|
45
|
+
workspace: {
|
|
46
|
+
id: graph.workspace.workspaceId,
|
|
47
|
+
source: graph.workspace.workspaceSource,
|
|
48
|
+
chain: graph.workspace.workspaceChain
|
|
49
|
+
},
|
|
50
|
+
winner: {
|
|
51
|
+
sourceId: entry.winner.sourceId,
|
|
52
|
+
pluginId: entry.winner.pluginId,
|
|
53
|
+
workspaceId: entry.winner.workspaceId,
|
|
54
|
+
...entry.winner.origin ? { origin: entry.winner.origin } : {}
|
|
55
|
+
},
|
|
56
|
+
overridden: entry.overridden.map((override) => ({
|
|
57
|
+
sourceId: override.sourceId,
|
|
58
|
+
pluginId: override.pluginId,
|
|
59
|
+
workspaceId: override.workspaceId,
|
|
60
|
+
value: override.value,
|
|
61
|
+
...override.origin ? { origin: override.origin } : {}
|
|
62
|
+
}))
|
|
63
|
+
};
|
|
64
|
+
}
|
|
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
|
+
|
|
28
172
|
// ../core/src/utils/path.ts
|
|
173
|
+
import { access } from "fs/promises";
|
|
174
|
+
import os from "os";
|
|
175
|
+
import path from "path";
|
|
29
176
|
var PRIMARY_CNOS_DIR = ".cnos";
|
|
30
177
|
var LEGACY_CNOS_DIR = "cnos";
|
|
31
178
|
async function exists(filePath) {
|
|
@@ -120,10 +267,401 @@ function stringifyYaml(value) {
|
|
|
120
267
|
return stringify(value);
|
|
121
268
|
}
|
|
122
269
|
|
|
123
|
-
// ../core/src/
|
|
124
|
-
import {
|
|
125
|
-
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
270
|
+
// ../core/src/manifest/loadManifest.ts
|
|
271
|
+
import { readFile } from "fs/promises";
|
|
126
272
|
import path2 from "path";
|
|
273
|
+
|
|
274
|
+
// ../core/src/manifest/normalizeManifest.ts
|
|
275
|
+
var DEFAULT_RESOLVE_FROM = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
276
|
+
var DEFAULT_LOADERS = [
|
|
277
|
+
"filesystem-values",
|
|
278
|
+
"filesystem-secrets",
|
|
279
|
+
"dotenv",
|
|
280
|
+
"process-env",
|
|
281
|
+
"cli-args"
|
|
282
|
+
];
|
|
283
|
+
var DEFAULT_VALIDATORS = ["basic-schema"];
|
|
284
|
+
var DEFAULT_EXPORTERS = ["env", "public-env"];
|
|
285
|
+
var DEFAULT_INSPECTORS = ["provenance"];
|
|
286
|
+
var DEFAULT_FRAMEWORK_PREFIXES = {
|
|
287
|
+
next: "NEXT_PUBLIC_",
|
|
288
|
+
vite: "VITE_",
|
|
289
|
+
nuxt: "NUXT_PUBLIC_"
|
|
290
|
+
};
|
|
291
|
+
var DEFAULT_NAMESPACES = {
|
|
292
|
+
value: {
|
|
293
|
+
kind: "data",
|
|
294
|
+
shareable: true
|
|
295
|
+
},
|
|
296
|
+
secret: {
|
|
297
|
+
kind: "data",
|
|
298
|
+
shareable: false,
|
|
299
|
+
sensitive: true
|
|
300
|
+
},
|
|
301
|
+
meta: {
|
|
302
|
+
kind: "system",
|
|
303
|
+
shareable: false,
|
|
304
|
+
readonly: true
|
|
305
|
+
},
|
|
306
|
+
public: {
|
|
307
|
+
kind: "projection",
|
|
308
|
+
source: "promote",
|
|
309
|
+
shareable: true,
|
|
310
|
+
readonly: true
|
|
311
|
+
},
|
|
312
|
+
env: {
|
|
313
|
+
kind: "projection",
|
|
314
|
+
source: "envMapping",
|
|
315
|
+
shareable: true,
|
|
316
|
+
readonly: true
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
function validateResolveFrom(resolveFrom) {
|
|
320
|
+
const validValues = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
321
|
+
for (const entry of resolveFrom) {
|
|
322
|
+
if (!validValues.includes(entry)) {
|
|
323
|
+
throw new CnosManifestError(`Unsupported profiles.resolveFrom entry: ${entry}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return resolveFrom;
|
|
327
|
+
}
|
|
328
|
+
function normalizeWorkspaceItems(items) {
|
|
329
|
+
return Object.fromEntries(
|
|
330
|
+
Object.entries(items ?? {}).map(([workspaceId, item]) => [
|
|
331
|
+
workspaceId,
|
|
332
|
+
{
|
|
333
|
+
extends: Array.isArray(item?.extends) ? item.extends.map((entry) => entry.trim()).filter(Boolean) : item?.extends ? [item.extends.trim()].filter(Boolean) : [],
|
|
334
|
+
...item?.globalId?.trim() ? { globalId: item.globalId.trim() } : {}
|
|
335
|
+
}
|
|
336
|
+
])
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
function normalizeNamespaces(namespaces) {
|
|
340
|
+
const normalized = Object.fromEntries(
|
|
341
|
+
Object.entries(namespaces ?? {}).map(([namespace, definition]) => [
|
|
342
|
+
namespace,
|
|
343
|
+
{
|
|
344
|
+
kind: definition.kind ?? "data",
|
|
345
|
+
shareable: definition.shareable ?? false,
|
|
346
|
+
...definition.sensitive !== void 0 ? { sensitive: definition.sensitive } : {},
|
|
347
|
+
...definition.readonly !== void 0 ? { readonly: definition.readonly } : {},
|
|
348
|
+
...definition.source ? { source: definition.source } : {}
|
|
349
|
+
}
|
|
350
|
+
])
|
|
351
|
+
);
|
|
352
|
+
return {
|
|
353
|
+
...DEFAULT_NAMESPACES,
|
|
354
|
+
...normalized
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function normalizeVaults(vaults) {
|
|
358
|
+
return Object.fromEntries(
|
|
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
|
+
}
|
|
366
|
+
const provider = definition.provider?.trim();
|
|
367
|
+
if (!provider) {
|
|
368
|
+
throw new CnosManifestError(`Vault "${name}" requires a provider`);
|
|
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
|
+
);
|
|
376
|
+
return [
|
|
377
|
+
name,
|
|
378
|
+
{
|
|
379
|
+
provider,
|
|
380
|
+
auth: normalizedAuth,
|
|
381
|
+
...Object.keys(normalizedMapping).length > 0 ? {
|
|
382
|
+
mapping: normalizedMapping
|
|
383
|
+
} : {}
|
|
384
|
+
}
|
|
385
|
+
];
|
|
386
|
+
})
|
|
387
|
+
);
|
|
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
|
+
}
|
|
436
|
+
function normalizeManifest(manifest) {
|
|
437
|
+
const version = manifest.version ?? 1;
|
|
438
|
+
if (version !== 1) {
|
|
439
|
+
throw new CnosManifestError(`Unsupported CNOS manifest version: ${version}`);
|
|
440
|
+
}
|
|
441
|
+
const projectName = manifest.project?.name?.trim();
|
|
442
|
+
if (!projectName) {
|
|
443
|
+
throw new CnosManifestError("Manifest requires project.name");
|
|
444
|
+
}
|
|
445
|
+
const defaultProfile = manifest.profiles?.default?.trim() || "base";
|
|
446
|
+
const workspaceItems = normalizeWorkspaceItems(manifest.workspaces?.items);
|
|
447
|
+
const resolveFrom = validateResolveFrom(manifest.profiles?.resolveFrom ?? DEFAULT_RESOLVE_FROM);
|
|
448
|
+
const filesystemValues = {
|
|
449
|
+
root: "./",
|
|
450
|
+
format: "yaml",
|
|
451
|
+
...manifest.sources?.["filesystem-values"] ?? {}
|
|
452
|
+
};
|
|
453
|
+
const filesystemSecrets = {
|
|
454
|
+
root: "./",
|
|
455
|
+
format: "yaml",
|
|
456
|
+
...manifest.sources?.["filesystem-secrets"] ?? {}
|
|
457
|
+
};
|
|
458
|
+
const dotenv = {
|
|
459
|
+
root: "./env",
|
|
460
|
+
...manifest.sources?.dotenv ?? {}
|
|
461
|
+
};
|
|
462
|
+
return {
|
|
463
|
+
version: 1,
|
|
464
|
+
project: {
|
|
465
|
+
name: projectName
|
|
466
|
+
},
|
|
467
|
+
workspaces: {
|
|
468
|
+
...manifest.workspaces?.default?.trim() ? {
|
|
469
|
+
default: manifest.workspaces.default.trim()
|
|
470
|
+
} : {},
|
|
471
|
+
global: {
|
|
472
|
+
enabled: manifest.workspaces?.global?.enabled ?? false,
|
|
473
|
+
...manifest.workspaces?.global?.root?.trim() ? {
|
|
474
|
+
root: manifest.workspaces.global.root.trim()
|
|
475
|
+
} : {},
|
|
476
|
+
allowWrite: manifest.workspaces?.global?.allowWrite ?? false
|
|
477
|
+
},
|
|
478
|
+
items: workspaceItems
|
|
479
|
+
},
|
|
480
|
+
profiles: {
|
|
481
|
+
default: defaultProfile,
|
|
482
|
+
resolveFrom
|
|
483
|
+
},
|
|
484
|
+
plugins: {
|
|
485
|
+
loaders: manifest.plugins?.loaders ?? DEFAULT_LOADERS,
|
|
486
|
+
resolver: manifest.plugins?.resolver ?? "profile-aware",
|
|
487
|
+
validators: manifest.plugins?.validators ?? DEFAULT_VALIDATORS,
|
|
488
|
+
exporters: manifest.plugins?.exporters ?? DEFAULT_EXPORTERS,
|
|
489
|
+
inspectors: manifest.plugins?.inspectors ?? DEFAULT_INSPECTORS
|
|
490
|
+
},
|
|
491
|
+
sources: {
|
|
492
|
+
...manifest.sources ?? {},
|
|
493
|
+
"filesystem-values": filesystemValues,
|
|
494
|
+
"filesystem-secrets": filesystemSecrets,
|
|
495
|
+
dotenv
|
|
496
|
+
},
|
|
497
|
+
resolution: {
|
|
498
|
+
precedence: manifest.resolution?.precedence ?? [
|
|
499
|
+
"filesystem-values",
|
|
500
|
+
"filesystem-secrets",
|
|
501
|
+
"dotenv",
|
|
502
|
+
"process-env",
|
|
503
|
+
"cli-args"
|
|
504
|
+
],
|
|
505
|
+
arrayPolicy: manifest.resolution?.arrayPolicy ?? "replace"
|
|
506
|
+
},
|
|
507
|
+
envMapping: {
|
|
508
|
+
...manifest.envMapping?.convention ? {
|
|
509
|
+
convention: manifest.envMapping.convention
|
|
510
|
+
} : {},
|
|
511
|
+
explicit: manifest.envMapping?.explicit ?? {}
|
|
512
|
+
},
|
|
513
|
+
public: {
|
|
514
|
+
promote: manifest.public?.promote ?? [],
|
|
515
|
+
frameworks: {
|
|
516
|
+
...DEFAULT_FRAMEWORK_PREFIXES,
|
|
517
|
+
...manifest.public?.frameworks ?? {}
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
namespaces: normalizeNamespaces(manifest.namespaces),
|
|
521
|
+
vaults: normalizeVaults(manifest.vaults),
|
|
522
|
+
writePolicy: {
|
|
523
|
+
define: {
|
|
524
|
+
defaultProfile: manifest.writePolicy?.define?.defaultProfile ?? defaultProfile,
|
|
525
|
+
targets: {
|
|
526
|
+
value: manifest.writePolicy?.define?.targets?.value ?? "./values/app.yml",
|
|
527
|
+
secret: manifest.writePolicy?.define?.targets?.secret ?? "./secrets/app.yml"
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
schema: manifest.schema ?? {}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// ../core/src/manifest/loadManifest.ts
|
|
536
|
+
async function loadManifest(options = {}) {
|
|
537
|
+
const manifestRoot = await resolveManifestRoot(options.root);
|
|
538
|
+
const manifestPath = path2.join(manifestRoot, "cnos.yml");
|
|
539
|
+
let source;
|
|
540
|
+
try {
|
|
541
|
+
source = await readFile(manifestPath, "utf8");
|
|
542
|
+
} catch {
|
|
543
|
+
throw new CnosManifestError("Unable to read CNOS manifest", manifestPath);
|
|
544
|
+
}
|
|
545
|
+
const rawManifest = parseYaml(source);
|
|
546
|
+
if (!rawManifest || typeof rawManifest !== "object") {
|
|
547
|
+
throw new CnosManifestError("CNOS manifest must be a YAML object", manifestPath);
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
manifestRoot,
|
|
551
|
+
repoRoot: path2.dirname(manifestRoot),
|
|
552
|
+
manifestPath,
|
|
553
|
+
manifest: normalizeManifest(rawManifest),
|
|
554
|
+
rawManifest
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ../core/src/promotions/validatePromotion.ts
|
|
559
|
+
var DEFAULT_DATA_NAMESPACE = {
|
|
560
|
+
kind: "data",
|
|
561
|
+
shareable: false
|
|
562
|
+
};
|
|
563
|
+
function getNamespaceNameForKey(key) {
|
|
564
|
+
const [namespace] = key.split(".");
|
|
565
|
+
if (!namespace || !key.includes(".")) {
|
|
566
|
+
throw new CnosManifestError(`Logical key must be namespace-qualified: ${key}`);
|
|
567
|
+
}
|
|
568
|
+
return namespace;
|
|
569
|
+
}
|
|
570
|
+
function getNamespaceDefinition(manifest, namespaceOrKey) {
|
|
571
|
+
const namespace = namespaceOrKey.includes(".") ? getNamespaceNameForKey(namespaceOrKey) : namespaceOrKey;
|
|
572
|
+
return manifest.namespaces[namespace] ?? DEFAULT_DATA_NAMESPACE;
|
|
573
|
+
}
|
|
574
|
+
function ensureProjectionAllowed(manifest, key, target) {
|
|
575
|
+
const namespace = getNamespaceNameForKey(key);
|
|
576
|
+
const definition = getNamespaceDefinition(manifest, namespace);
|
|
577
|
+
if (definition.kind !== "data") {
|
|
578
|
+
throw new CnosManifestError(
|
|
579
|
+
`Cannot promote ${key} to ${target} because namespace "${namespace}" is not a data namespace.`
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
if (definition.sensitive) {
|
|
583
|
+
throw new CnosSecurityError(
|
|
584
|
+
`Cannot promote ${key} to ${target} because namespace "${namespace}" is sensitive.`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
if (!definition.shareable) {
|
|
588
|
+
throw new CnosSecurityError(
|
|
589
|
+
`Cannot promote ${key} to ${target} because namespace "${namespace}" is not shareable.`
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function validateProjectionIssue(manifest, key, target) {
|
|
594
|
+
try {
|
|
595
|
+
ensureProjectionAllowed(manifest, key, target);
|
|
596
|
+
return void 0;
|
|
597
|
+
} catch (error) {
|
|
598
|
+
return {
|
|
599
|
+
code: target === "public" ? "public.invalid-promotion" : "env.invalid-mapping",
|
|
600
|
+
key,
|
|
601
|
+
message: error instanceof Error ? error.message : String(error)
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
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");
|
|
611
|
+
}
|
|
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;
|
|
633
|
+
}
|
|
634
|
+
const key = Buffer.from(document.derivedKey, "hex");
|
|
635
|
+
return key.length > 0 ? key : void 0;
|
|
636
|
+
} catch {
|
|
637
|
+
return void 0;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
async function clearVaultSessionKey(vault, processEnv) {
|
|
641
|
+
await rm(buildSessionPath(vault, processEnv), { force: true });
|
|
642
|
+
}
|
|
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 {
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// ../core/src/utils/secretStore.ts
|
|
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";
|
|
127
665
|
function isObject(value) {
|
|
128
666
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
129
667
|
}
|
|
@@ -131,103 +669,657 @@ function isSecretReference(value) {
|
|
|
131
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));
|
|
132
670
|
}
|
|
133
671
|
function resolveSecretStoreRoot(processEnv = process.env) {
|
|
134
|
-
return
|
|
672
|
+
return path4.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
|
|
135
673
|
}
|
|
136
|
-
function
|
|
137
|
-
return
|
|
674
|
+
function normalizeVaultToken(vault = "default") {
|
|
675
|
+
return vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
138
676
|
}
|
|
139
|
-
function
|
|
140
|
-
|
|
677
|
+
function getVaultPassphraseEnvVar(vault = "default") {
|
|
678
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
679
|
+
return vaultToken && vaultToken !== "DEFAULT" ? `CNOS_SECRET_PASSPHRASE_${vaultToken}` : "CNOS_SECRET_PASSPHRASE";
|
|
141
680
|
}
|
|
142
|
-
function
|
|
143
|
-
return
|
|
681
|
+
function isPassphraseEnvRef(value) {
|
|
682
|
+
return typeof value === "string" && value.startsWith("env:") && value.length > 4;
|
|
683
|
+
}
|
|
684
|
+
function getVaultSessionKeyEnvVar(vault = "default") {
|
|
685
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
686
|
+
return `__CNOS_VAULT_KEY_${vaultToken || "DEFAULT"}__`;
|
|
144
687
|
}
|
|
145
688
|
function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
|
|
146
|
-
|
|
147
|
-
|
|
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 {
|
|
700
|
+
return void 0;
|
|
701
|
+
}
|
|
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);
|
|
724
|
+
}
|
|
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);
|
|
727
|
+
}
|
|
728
|
+
return value;
|
|
729
|
+
}
|
|
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
|
+
);
|
|
148
757
|
}
|
|
149
|
-
function
|
|
150
|
-
const
|
|
151
|
-
const iv = randomBytes(12);
|
|
152
|
-
const key = deriveKey(passphrase, salt);
|
|
758
|
+
function encryptPayload(payload, key) {
|
|
759
|
+
const iv = randomBytes(IV_LENGTH);
|
|
153
760
|
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
154
|
-
const
|
|
761
|
+
const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
|
|
762
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
155
763
|
const tag = cipher.getAuthTag();
|
|
764
|
+
return Buffer.concat([
|
|
765
|
+
Buffer.from(Uint32Array.of(KEYSTORE_VERSION).buffer),
|
|
766
|
+
iv,
|
|
767
|
+
tag,
|
|
768
|
+
ciphertext
|
|
769
|
+
]);
|
|
770
|
+
}
|
|
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);
|
|
785
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
786
|
+
decipher.setAuthTag(tag);
|
|
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
|
+
}
|
|
806
|
+
}
|
|
807
|
+
function buildInitialPayload() {
|
|
156
808
|
return {
|
|
157
|
-
|
|
809
|
+
secrets: {},
|
|
810
|
+
metadata: {}
|
|
811
|
+
};
|
|
812
|
+
}
|
|
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);
|
|
823
|
+
try {
|
|
824
|
+
const source = await readFile3(metaPath, "utf8");
|
|
825
|
+
return assertVaultMetadata(parseYaml(source), metaPath);
|
|
826
|
+
} catch (error) {
|
|
827
|
+
if (error.code === "ENOENT") {
|
|
828
|
+
return void 0;
|
|
829
|
+
}
|
|
830
|
+
throw error;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
async function listSecretVaults(storeRoot) {
|
|
834
|
+
const vaultRoot = path4.join(storeRoot, "vaults");
|
|
835
|
+
try {
|
|
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));
|
|
841
|
+
} catch {
|
|
842
|
+
return [];
|
|
843
|
+
}
|
|
844
|
+
}
|
|
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,
|
|
158
853
|
algorithm: "aes-256-gcm",
|
|
854
|
+
kdf: "pbkdf2-sha512",
|
|
855
|
+
iterations: PBKDF2_ITERATIONS,
|
|
159
856
|
salt: salt.toString("base64"),
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
ciphertext: ciphertext.toString("base64")
|
|
857
|
+
createdAt,
|
|
858
|
+
secretCount: 0
|
|
163
859
|
};
|
|
860
|
+
await writeVaultFiles(storeRoot, normalizedVault, meta, buildInitialPayload(), key);
|
|
861
|
+
return buildMetaPath(storeRoot, normalizedVault);
|
|
164
862
|
}
|
|
165
|
-
function
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
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
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
throw toAuthError(vaultId, [getVaultSessionKeyEnvVar(vaultId), ...sources]);
|
|
175
1273
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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);
|
|
188
1289
|
}
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
throw error;
|
|
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;
|
|
198
1298
|
}
|
|
1299
|
+
const valuePath = namespace ? stripNamespace(entry.key) : entry.key;
|
|
1300
|
+
setNestedValue(output, valuePath.split("."), entry.value);
|
|
199
1301
|
}
|
|
200
|
-
return
|
|
1302
|
+
return output;
|
|
201
1303
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.replace(/\.json$/, "")).sort((left, right) => left.localeCompare(right));
|
|
207
|
-
} catch {
|
|
208
|
-
return [];
|
|
209
|
-
}
|
|
1304
|
+
|
|
1305
|
+
// ../core/src/runtime/read.ts
|
|
1306
|
+
function readValue(graph, key) {
|
|
1307
|
+
return graph.entries.get(key)?.value;
|
|
210
1308
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return filePath;
|
|
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;
|
|
217
1314
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const filePath = resolveSecretStoreFile(storeRoot, ref, vault);
|
|
225
|
-
const source = await readFile(filePath, "utf8");
|
|
226
|
-
const document = JSON.parse(source);
|
|
227
|
-
if (document.version !== 1 || document.algorithm !== "aes-256-gcm" || typeof document.salt !== "string" || typeof document.iv !== "string" || typeof document.tag !== "string" || typeof document.ciphertext !== "string") {
|
|
228
|
-
throw new CnosManifestError("Invalid local secret document", filePath);
|
|
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);
|
|
229
1321
|
}
|
|
230
|
-
return
|
|
1322
|
+
return value;
|
|
231
1323
|
}
|
|
232
1324
|
|
|
233
1325
|
// ../core/src/runtime/toEnv.ts
|
|
@@ -254,6 +1346,10 @@ function toEnv(graph, manifest, options = {}) {
|
|
|
254
1346
|
if (!entry) {
|
|
255
1347
|
continue;
|
|
256
1348
|
}
|
|
1349
|
+
const namespaceDefinition = getNamespaceDefinition(manifest, entry.namespace);
|
|
1350
|
+
if (namespaceDefinition.kind !== "data" || !namespaceDefinition.shareable || namespaceDefinition.sensitive) {
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
257
1353
|
if (entry.namespace === "secret" && !includeSecrets) {
|
|
258
1354
|
continue;
|
|
259
1355
|
}
|
|
@@ -265,64 +1361,9 @@ function toEnv(graph, manifest, options = {}) {
|
|
|
265
1361
|
return output;
|
|
266
1362
|
}
|
|
267
1363
|
|
|
268
|
-
// ../core/src/utils/envNaming.ts
|
|
269
|
-
function normalizeMappingConfig(config = {}) {
|
|
270
|
-
return {
|
|
271
|
-
convention: config.convention,
|
|
272
|
-
explicit: config.explicit ?? {}
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
function toScreamingSnakeSegment(segment) {
|
|
276
|
-
return segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
277
|
-
}
|
|
278
|
-
function toScreamingSnake(path8) {
|
|
279
|
-
return path8.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
|
|
280
|
-
}
|
|
281
|
-
function fromScreamingSnake(path8) {
|
|
282
|
-
return path8.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
|
|
283
|
-
}
|
|
284
|
-
function logicalKeyToEnvVar(key, config = {}) {
|
|
285
|
-
const normalized = normalizeMappingConfig(config);
|
|
286
|
-
const explicitEntry = Object.entries(normalized.explicit).find(([, logicalKey]) => logicalKey === key);
|
|
287
|
-
if (explicitEntry) {
|
|
288
|
-
return explicitEntry[0];
|
|
289
|
-
}
|
|
290
|
-
if (normalized.convention !== "SCREAMING_SNAKE") {
|
|
291
|
-
return void 0;
|
|
292
|
-
}
|
|
293
|
-
if (key.startsWith("value.")) {
|
|
294
|
-
return toScreamingSnake(key.slice("value.".length));
|
|
295
|
-
}
|
|
296
|
-
if (key.startsWith("secret.")) {
|
|
297
|
-
return `SECRET_${toScreamingSnake(key.slice("secret.".length))}`;
|
|
298
|
-
}
|
|
299
|
-
return void 0;
|
|
300
|
-
}
|
|
301
|
-
function envVarToLogicalKey(envVar, config = {}) {
|
|
302
|
-
const normalized = normalizeMappingConfig(config);
|
|
303
|
-
const explicitMatch = normalized.explicit[envVar];
|
|
304
|
-
if (explicitMatch) {
|
|
305
|
-
return explicitMatch;
|
|
306
|
-
}
|
|
307
|
-
if (normalized.convention !== "SCREAMING_SNAKE") {
|
|
308
|
-
return void 0;
|
|
309
|
-
}
|
|
310
|
-
if (envVar.startsWith("SECRET_")) {
|
|
311
|
-
const stripped = envVar.slice("SECRET_".length);
|
|
312
|
-
if (!stripped) {
|
|
313
|
-
return void 0;
|
|
314
|
-
}
|
|
315
|
-
return `secret.${fromScreamingSnake(stripped)}`;
|
|
316
|
-
}
|
|
317
|
-
if (!/^[A-Z][A-Z0-9_]*$/.test(envVar)) {
|
|
318
|
-
return void 0;
|
|
319
|
-
}
|
|
320
|
-
return `value.${fromScreamingSnake(envVar)}`;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
1364
|
// ../core/src/runtime/toPublicEnv.ts
|
|
324
|
-
function
|
|
325
|
-
return
|
|
1365
|
+
function fallbackPublicEnvVar(valuePath) {
|
|
1366
|
+
return valuePath.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
326
1367
|
}
|
|
327
1368
|
function normalizeEnvValue2(value) {
|
|
328
1369
|
if (value === void 0 || value === null) {
|
|
@@ -349,22 +1390,12 @@ function resolvePublicPrefix(manifest, options) {
|
|
|
349
1390
|
}
|
|
350
1391
|
return configuredPrefix;
|
|
351
1392
|
}
|
|
352
|
-
function ensurePublicPromotionKey(key) {
|
|
353
|
-
if (!key.startsWith("value.")) {
|
|
354
|
-
throw new CnosManifestError(`public.promote may only contain value.* keys: ${key}`);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
1393
|
function toPublicEnv(graph, manifest, options = {}) {
|
|
358
1394
|
const prefix = resolvePublicPrefix(manifest, options);
|
|
359
1395
|
const output = {};
|
|
360
|
-
const promotions =
|
|
361
|
-
for (const
|
|
362
|
-
|
|
363
|
-
const resolved = graph.entries.get(key);
|
|
364
|
-
if (!resolved) {
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
const baseEnvVar = logicalKeyToEnvVar(key, manifest.envMapping) ?? fallbackValueEnvVar(key);
|
|
1396
|
+
const promotions = Array.from(graph.entries.values()).filter((entry) => entry.namespace === "public").sort((left, right) => left.key.localeCompare(right.key));
|
|
1397
|
+
for (const resolved of promotions) {
|
|
1398
|
+
const baseEnvVar = fallbackPublicEnvVar(stripNamespace(resolved.key));
|
|
368
1399
|
const envVar = prefix && !baseEnvVar.startsWith(prefix) ? `${prefix}${baseEnvVar}` : baseEnvVar;
|
|
369
1400
|
output[envVar] = normalizeEnvValue2(resolved.value);
|
|
370
1401
|
}
|
|
@@ -372,55 +1403,23 @@ function toPublicEnv(graph, manifest, options = {}) {
|
|
|
372
1403
|
}
|
|
373
1404
|
|
|
374
1405
|
// ../core/src/runtime/dump.ts
|
|
375
|
-
import { mkdir as
|
|
376
|
-
import
|
|
377
|
-
|
|
378
|
-
// ../core/src/runtime/projection.ts
|
|
379
|
-
function setNestedValue(target, pathSegments, value) {
|
|
380
|
-
const [head, ...tail] = pathSegments;
|
|
381
|
-
if (!head) {
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
if (tail.length === 0) {
|
|
385
|
-
target[head] = value;
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
const current = target[head];
|
|
389
|
-
const nextTarget = current && typeof current === "object" && !Array.isArray(current) ? current : {};
|
|
390
|
-
target[head] = nextTarget;
|
|
391
|
-
setNestedValue(nextTarget, tail, value);
|
|
392
|
-
}
|
|
393
|
-
function toNamespaceObject(graph, namespace) {
|
|
394
|
-
const output = {};
|
|
395
|
-
const resolvedEntries = Array.from(graph.entries.values()).sort(
|
|
396
|
-
(left, right) => left.key.localeCompare(right.key)
|
|
397
|
-
);
|
|
398
|
-
for (const entry of resolvedEntries) {
|
|
399
|
-
if (namespace && entry.namespace !== namespace) {
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
const valuePath = namespace ? stripNamespace(entry.key) : entry.key;
|
|
403
|
-
setNestedValue(output, valuePath.split("."), entry.value);
|
|
404
|
-
}
|
|
405
|
-
return output;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// ../core/src/runtime/dump.ts
|
|
1406
|
+
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
1407
|
+
import path6 from "path";
|
|
409
1408
|
function buildDumpFiles(graph, options = {}) {
|
|
410
|
-
const basePath = options.flatten ? "" :
|
|
1409
|
+
const basePath = options.flatten ? "" : path6.posix.join("workspaces", graph.workspace.workspaceId);
|
|
411
1410
|
const values = toNamespaceObject(graph, "value");
|
|
412
1411
|
const secrets = toNamespaceObject(graph, "secret");
|
|
413
1412
|
const files = [];
|
|
414
1413
|
if (Object.keys(values).length > 0) {
|
|
415
1414
|
files.push({
|
|
416
|
-
path:
|
|
1415
|
+
path: path6.posix.join(basePath, "values", graph.profile, "app.yml"),
|
|
417
1416
|
namespace: "value",
|
|
418
1417
|
content: stringifyYaml(values)
|
|
419
1418
|
});
|
|
420
1419
|
}
|
|
421
1420
|
if (Object.keys(secrets).length > 0) {
|
|
422
1421
|
files.push({
|
|
423
|
-
path:
|
|
1422
|
+
path: path6.posix.join(basePath, "secrets", graph.profile, "app.yml"),
|
|
424
1423
|
namespace: "secret",
|
|
425
1424
|
content: stringifyYaml(secrets)
|
|
426
1425
|
});
|
|
@@ -436,12 +1435,12 @@ function planDump(graph, options = {}) {
|
|
|
436
1435
|
};
|
|
437
1436
|
}
|
|
438
1437
|
async function writeDump(graph, options) {
|
|
439
|
-
const root =
|
|
1438
|
+
const root = path6.resolve(options.to);
|
|
440
1439
|
const plan = planDump(graph, options);
|
|
441
1440
|
for (const file of plan.files) {
|
|
442
|
-
const destination =
|
|
443
|
-
await
|
|
444
|
-
await
|
|
1441
|
+
const destination = path6.join(root, file.path);
|
|
1442
|
+
await mkdir4(path6.dirname(destination), { recursive: true });
|
|
1443
|
+
await writeFile3(destination, file.content, "utf8");
|
|
445
1444
|
}
|
|
446
1445
|
return {
|
|
447
1446
|
...plan,
|
|
@@ -462,6 +1461,61 @@ function flattenObject(value, prefix = "") {
|
|
|
462
1461
|
}, {});
|
|
463
1462
|
}
|
|
464
1463
|
|
|
1464
|
+
// ../core/src/utils/envNaming.ts
|
|
1465
|
+
function normalizeMappingConfig(config = {}) {
|
|
1466
|
+
return {
|
|
1467
|
+
convention: config.convention,
|
|
1468
|
+
explicit: config.explicit ?? {}
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
function toScreamingSnakeSegment(segment) {
|
|
1472
|
+
return segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
1473
|
+
}
|
|
1474
|
+
function toScreamingSnake(path10) {
|
|
1475
|
+
return path10.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
|
|
1476
|
+
}
|
|
1477
|
+
function fromScreamingSnake(path10) {
|
|
1478
|
+
return path10.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
|
|
1479
|
+
}
|
|
1480
|
+
function logicalKeyToEnvVar(key, config = {}) {
|
|
1481
|
+
const normalized = normalizeMappingConfig(config);
|
|
1482
|
+
const explicitEntry = Object.entries(normalized.explicit).find(([, logicalKey]) => logicalKey === key);
|
|
1483
|
+
if (explicitEntry) {
|
|
1484
|
+
return explicitEntry[0];
|
|
1485
|
+
}
|
|
1486
|
+
if (normalized.convention !== "SCREAMING_SNAKE") {
|
|
1487
|
+
return void 0;
|
|
1488
|
+
}
|
|
1489
|
+
if (key.startsWith("value.")) {
|
|
1490
|
+
return toScreamingSnake(key.slice("value.".length));
|
|
1491
|
+
}
|
|
1492
|
+
if (key.startsWith("secret.")) {
|
|
1493
|
+
return `SECRET_${toScreamingSnake(key.slice("secret.".length))}`;
|
|
1494
|
+
}
|
|
1495
|
+
return void 0;
|
|
1496
|
+
}
|
|
1497
|
+
function envVarToLogicalKey(envVar, config = {}) {
|
|
1498
|
+
const normalized = normalizeMappingConfig(config);
|
|
1499
|
+
const explicitMatch = normalized.explicit[envVar];
|
|
1500
|
+
if (explicitMatch) {
|
|
1501
|
+
return explicitMatch;
|
|
1502
|
+
}
|
|
1503
|
+
if (normalized.convention !== "SCREAMING_SNAKE") {
|
|
1504
|
+
return void 0;
|
|
1505
|
+
}
|
|
1506
|
+
if (envVar.startsWith("SECRET_")) {
|
|
1507
|
+
const stripped = envVar.slice("SECRET_".length);
|
|
1508
|
+
if (!stripped) {
|
|
1509
|
+
return void 0;
|
|
1510
|
+
}
|
|
1511
|
+
return `secret.${fromScreamingSnake(stripped)}`;
|
|
1512
|
+
}
|
|
1513
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(envVar)) {
|
|
1514
|
+
return void 0;
|
|
1515
|
+
}
|
|
1516
|
+
return `value.${fromScreamingSnake(envVar)}`;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
465
1519
|
// ../core/src/validation/envMapping.ts
|
|
466
1520
|
function fallbackLogicalKeyToEnvVar(key) {
|
|
467
1521
|
return key.replace(/^(value|secret)\./, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
@@ -475,7 +1529,8 @@ function validateEnvMappingCollisions(manifest, graph) {
|
|
|
475
1529
|
]);
|
|
476
1530
|
const collisions = /* @__PURE__ */ new Map();
|
|
477
1531
|
for (const key of candidates) {
|
|
478
|
-
|
|
1532
|
+
const definition = getNamespaceDefinition(manifest, key);
|
|
1533
|
+
if (definition.kind !== "data") {
|
|
479
1534
|
continue;
|
|
480
1535
|
}
|
|
481
1536
|
const envVar = logicalKeyToEnvVar(key, manifest.envMapping) ?? (key.startsWith("value.") || key.startsWith("secret.") ? fallbackLogicalKeyToEnvVar(key) : void 0);
|
|
@@ -494,11 +1549,7 @@ function validateEnvMappingCollisions(manifest, graph) {
|
|
|
494
1549
|
|
|
495
1550
|
// ../core/src/validation/publicSafety.ts
|
|
496
1551
|
function validatePublicSafety(manifest) {
|
|
497
|
-
return manifest.public.promote.
|
|
498
|
-
code: "public.invalid-promotion",
|
|
499
|
-
key,
|
|
500
|
-
message: `public.promote may only include value.* keys: ${key}`
|
|
501
|
-
}));
|
|
1552
|
+
return manifest.public.promote.map((key) => validateProjectionIssue(manifest, key, "public")).filter((issue) => Boolean(issue));
|
|
502
1553
|
}
|
|
503
1554
|
|
|
504
1555
|
// ../core/src/validation/workspaceSafety.ts
|
|
@@ -542,58 +1593,25 @@ async function validateRuntime(runtime) {
|
|
|
542
1593
|
issues: validatePublicSafety(runtime.manifest)
|
|
543
1594
|
},
|
|
544
1595
|
{
|
|
545
|
-
pluginId: "env-mapping",
|
|
546
|
-
valid: true,
|
|
547
|
-
issues: validateEnvMappingCollisions(runtime.manifest, runtime.graph)
|
|
548
|
-
},
|
|
549
|
-
{
|
|
550
|
-
pluginId: "workspace-safety",
|
|
551
|
-
valid: true,
|
|
552
|
-
issues: validateWorkspaceSafety(runtime.manifest, runtime.graph)
|
|
553
|
-
}
|
|
554
|
-
].map((result) => ({
|
|
555
|
-
...result,
|
|
556
|
-
valid: result.issues.length === 0
|
|
557
|
-
}));
|
|
558
|
-
const results = [...pluginResults, ...builtInResults];
|
|
559
|
-
const issues = results.flatMap((result) => result.issues);
|
|
560
|
-
return {
|
|
561
|
-
valid: issues.length === 0,
|
|
562
|
-
issues,
|
|
563
|
-
results
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// ../core/src/runtime/inspect.ts
|
|
568
|
-
function inspectValue(graph, key) {
|
|
569
|
-
const entry = graph.entries.get(key);
|
|
570
|
-
if (!entry) {
|
|
571
|
-
throw new CnosKeyNotFoundError(key);
|
|
572
|
-
}
|
|
573
|
-
return {
|
|
574
|
-
key: entry.key,
|
|
575
|
-
value: entry.value,
|
|
576
|
-
namespace: entry.namespace,
|
|
577
|
-
profile: graph.profile,
|
|
578
|
-
profileSource: graph.profileSource,
|
|
579
|
-
workspace: {
|
|
580
|
-
id: graph.workspace.workspaceId,
|
|
581
|
-
source: graph.workspace.workspaceSource,
|
|
582
|
-
chain: graph.workspace.workspaceChain
|
|
583
|
-
},
|
|
584
|
-
winner: {
|
|
585
|
-
sourceId: entry.winner.sourceId,
|
|
586
|
-
pluginId: entry.winner.pluginId,
|
|
587
|
-
workspaceId: entry.winner.workspaceId,
|
|
588
|
-
...entry.winner.origin ? { origin: entry.winner.origin } : {}
|
|
1596
|
+
pluginId: "env-mapping",
|
|
1597
|
+
valid: true,
|
|
1598
|
+
issues: validateEnvMappingCollisions(runtime.manifest, runtime.graph)
|
|
589
1599
|
},
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
1600
|
+
{
|
|
1601
|
+
pluginId: "workspace-safety",
|
|
1602
|
+
valid: true,
|
|
1603
|
+
issues: validateWorkspaceSafety(runtime.manifest, runtime.graph)
|
|
1604
|
+
}
|
|
1605
|
+
].map((result) => ({
|
|
1606
|
+
...result,
|
|
1607
|
+
valid: result.issues.length === 0
|
|
1608
|
+
}));
|
|
1609
|
+
const results = [...pluginResults, ...builtInResults];
|
|
1610
|
+
const issues = results.flatMap((result) => result.issues);
|
|
1611
|
+
return {
|
|
1612
|
+
valid: issues.length === 0,
|
|
1613
|
+
issues,
|
|
1614
|
+
results
|
|
597
1615
|
};
|
|
598
1616
|
}
|
|
599
1617
|
|
|
@@ -608,174 +1626,13 @@ function createProvenanceInspector() {
|
|
|
608
1626
|
};
|
|
609
1627
|
}
|
|
610
1628
|
|
|
611
|
-
// ../core/src/manifest/loadManifest.ts
|
|
612
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
613
|
-
import path4 from "path";
|
|
614
|
-
|
|
615
|
-
// ../core/src/manifest/normalizeManifest.ts
|
|
616
|
-
var DEFAULT_RESOLVE_FROM = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
617
|
-
var DEFAULT_LOADERS = [
|
|
618
|
-
"filesystem-values",
|
|
619
|
-
"filesystem-secrets",
|
|
620
|
-
"dotenv",
|
|
621
|
-
"process-env",
|
|
622
|
-
"cli-args"
|
|
623
|
-
];
|
|
624
|
-
var DEFAULT_VALIDATORS = ["basic-schema"];
|
|
625
|
-
var DEFAULT_EXPORTERS = ["env", "public-env"];
|
|
626
|
-
var DEFAULT_INSPECTORS = ["provenance"];
|
|
627
|
-
var DEFAULT_FRAMEWORK_PREFIXES = {
|
|
628
|
-
next: "NEXT_PUBLIC_",
|
|
629
|
-
vite: "VITE_",
|
|
630
|
-
nuxt: "NUXT_PUBLIC_"
|
|
631
|
-
};
|
|
632
|
-
function validateResolveFrom(resolveFrom) {
|
|
633
|
-
const validValues = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
634
|
-
for (const entry of resolveFrom) {
|
|
635
|
-
if (!validValues.includes(entry)) {
|
|
636
|
-
throw new CnosManifestError(`Unsupported profiles.resolveFrom entry: ${entry}`);
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
return resolveFrom;
|
|
640
|
-
}
|
|
641
|
-
function normalizeWorkspaceItems(items) {
|
|
642
|
-
return Object.fromEntries(
|
|
643
|
-
Object.entries(items ?? {}).map(([workspaceId, item]) => [
|
|
644
|
-
workspaceId,
|
|
645
|
-
{
|
|
646
|
-
extends: Array.isArray(item?.extends) ? item.extends.map((entry) => entry.trim()).filter(Boolean) : item?.extends ? [item.extends.trim()].filter(Boolean) : [],
|
|
647
|
-
...item?.globalId?.trim() ? { globalId: item.globalId.trim() } : {}
|
|
648
|
-
}
|
|
649
|
-
])
|
|
650
|
-
);
|
|
651
|
-
}
|
|
652
|
-
function normalizeManifest(manifest) {
|
|
653
|
-
const version = manifest.version ?? 1;
|
|
654
|
-
if (version !== 1) {
|
|
655
|
-
throw new CnosManifestError(`Unsupported CNOS manifest version: ${version}`);
|
|
656
|
-
}
|
|
657
|
-
const projectName = manifest.project?.name?.trim();
|
|
658
|
-
if (!projectName) {
|
|
659
|
-
throw new CnosManifestError("Manifest requires project.name");
|
|
660
|
-
}
|
|
661
|
-
const defaultProfile = manifest.profiles?.default?.trim() || "base";
|
|
662
|
-
const workspaceItems = normalizeWorkspaceItems(manifest.workspaces?.items);
|
|
663
|
-
const resolveFrom = validateResolveFrom(manifest.profiles?.resolveFrom ?? DEFAULT_RESOLVE_FROM);
|
|
664
|
-
const filesystemValues = {
|
|
665
|
-
root: "./",
|
|
666
|
-
format: "yaml",
|
|
667
|
-
...manifest.sources?.["filesystem-values"] ?? {}
|
|
668
|
-
};
|
|
669
|
-
const filesystemSecrets = {
|
|
670
|
-
root: "./",
|
|
671
|
-
format: "yaml",
|
|
672
|
-
...manifest.sources?.["filesystem-secrets"] ?? {}
|
|
673
|
-
};
|
|
674
|
-
const dotenv = {
|
|
675
|
-
root: "./env",
|
|
676
|
-
...manifest.sources?.dotenv ?? {}
|
|
677
|
-
};
|
|
678
|
-
return {
|
|
679
|
-
version: 1,
|
|
680
|
-
project: {
|
|
681
|
-
name: projectName
|
|
682
|
-
},
|
|
683
|
-
workspaces: {
|
|
684
|
-
...manifest.workspaces?.default?.trim() ? {
|
|
685
|
-
default: manifest.workspaces.default.trim()
|
|
686
|
-
} : {},
|
|
687
|
-
global: {
|
|
688
|
-
enabled: manifest.workspaces?.global?.enabled ?? false,
|
|
689
|
-
...manifest.workspaces?.global?.root?.trim() ? {
|
|
690
|
-
root: manifest.workspaces.global.root.trim()
|
|
691
|
-
} : {},
|
|
692
|
-
allowWrite: manifest.workspaces?.global?.allowWrite ?? false
|
|
693
|
-
},
|
|
694
|
-
items: workspaceItems
|
|
695
|
-
},
|
|
696
|
-
profiles: {
|
|
697
|
-
default: defaultProfile,
|
|
698
|
-
resolveFrom
|
|
699
|
-
},
|
|
700
|
-
plugins: {
|
|
701
|
-
loaders: manifest.plugins?.loaders ?? DEFAULT_LOADERS,
|
|
702
|
-
resolver: manifest.plugins?.resolver ?? "profile-aware",
|
|
703
|
-
validators: manifest.plugins?.validators ?? DEFAULT_VALIDATORS,
|
|
704
|
-
exporters: manifest.plugins?.exporters ?? DEFAULT_EXPORTERS,
|
|
705
|
-
inspectors: manifest.plugins?.inspectors ?? DEFAULT_INSPECTORS
|
|
706
|
-
},
|
|
707
|
-
sources: {
|
|
708
|
-
...manifest.sources ?? {},
|
|
709
|
-
"filesystem-values": filesystemValues,
|
|
710
|
-
"filesystem-secrets": filesystemSecrets,
|
|
711
|
-
dotenv
|
|
712
|
-
},
|
|
713
|
-
resolution: {
|
|
714
|
-
precedence: manifest.resolution?.precedence ?? [
|
|
715
|
-
"filesystem-values",
|
|
716
|
-
"filesystem-secrets",
|
|
717
|
-
"dotenv",
|
|
718
|
-
"process-env",
|
|
719
|
-
"cli-args"
|
|
720
|
-
],
|
|
721
|
-
arrayPolicy: manifest.resolution?.arrayPolicy ?? "replace"
|
|
722
|
-
},
|
|
723
|
-
envMapping: {
|
|
724
|
-
...manifest.envMapping?.convention ? {
|
|
725
|
-
convention: manifest.envMapping.convention
|
|
726
|
-
} : {},
|
|
727
|
-
explicit: manifest.envMapping?.explicit ?? {}
|
|
728
|
-
},
|
|
729
|
-
public: {
|
|
730
|
-
promote: manifest.public?.promote ?? [],
|
|
731
|
-
frameworks: {
|
|
732
|
-
...DEFAULT_FRAMEWORK_PREFIXES,
|
|
733
|
-
...manifest.public?.frameworks ?? {}
|
|
734
|
-
}
|
|
735
|
-
},
|
|
736
|
-
writePolicy: {
|
|
737
|
-
define: {
|
|
738
|
-
defaultProfile: manifest.writePolicy?.define?.defaultProfile ?? defaultProfile,
|
|
739
|
-
targets: {
|
|
740
|
-
value: manifest.writePolicy?.define?.targets?.value ?? "./values/app.yml",
|
|
741
|
-
secret: manifest.writePolicy?.define?.targets?.secret ?? "./secrets/app.yml"
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
},
|
|
745
|
-
schema: manifest.schema ?? {}
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// ../core/src/manifest/loadManifest.ts
|
|
750
|
-
async function loadManifest(options = {}) {
|
|
751
|
-
const manifestRoot = await resolveManifestRoot(options.root);
|
|
752
|
-
const manifestPath = path4.join(manifestRoot, "cnos.yml");
|
|
753
|
-
let source;
|
|
754
|
-
try {
|
|
755
|
-
source = await readFile2(manifestPath, "utf8");
|
|
756
|
-
} catch {
|
|
757
|
-
throw new CnosManifestError("Unable to read CNOS manifest", manifestPath);
|
|
758
|
-
}
|
|
759
|
-
const rawManifest = parseYaml(source);
|
|
760
|
-
if (!rawManifest || typeof rawManifest !== "object") {
|
|
761
|
-
throw new CnosManifestError("CNOS manifest must be a YAML object", manifestPath);
|
|
762
|
-
}
|
|
763
|
-
return {
|
|
764
|
-
manifestRoot,
|
|
765
|
-
repoRoot: path4.dirname(manifestRoot),
|
|
766
|
-
manifestPath,
|
|
767
|
-
manifest: normalizeManifest(rawManifest),
|
|
768
|
-
rawManifest
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
|
|
772
1629
|
// ../core/src/manifest/loadWorkspaceFile.ts
|
|
773
|
-
import { readFile as
|
|
774
|
-
import
|
|
1630
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
1631
|
+
import path7 from "path";
|
|
775
1632
|
async function loadWorkspaceFile(repoRoot) {
|
|
776
|
-
const workspaceFilePath =
|
|
1633
|
+
const workspaceFilePath = path7.join(repoRoot, ".cnos-workspace.yml");
|
|
777
1634
|
try {
|
|
778
|
-
const source = await
|
|
1635
|
+
const source = await readFile4(workspaceFilePath, "utf8");
|
|
779
1636
|
const parsed = parseYaml(source);
|
|
780
1637
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
781
1638
|
throw new CnosManifestError(".cnos-workspace.yml must be a YAML object", workspaceFilePath);
|
|
@@ -798,8 +1655,8 @@ async function loadWorkspaceFile(repoRoot) {
|
|
|
798
1655
|
}
|
|
799
1656
|
|
|
800
1657
|
// ../core/src/profiles/expandProfileChain.ts
|
|
801
|
-
import { access as access2, readFile as
|
|
802
|
-
import
|
|
1658
|
+
import { access as access2, readFile as readFile5 } from "fs/promises";
|
|
1659
|
+
import path8 from "path";
|
|
803
1660
|
async function fileExists(targetPath) {
|
|
804
1661
|
try {
|
|
805
1662
|
await access2(targetPath);
|
|
@@ -836,11 +1693,11 @@ async function loadProfileDefinition(profileName, options) {
|
|
|
836
1693
|
return normalizeProfileDefinition(profileName, void 0);
|
|
837
1694
|
}
|
|
838
1695
|
for (const workspaceRoot of [...workspaceRoots].reverse()) {
|
|
839
|
-
const profilePath =
|
|
1696
|
+
const profilePath = path8.join(workspaceRoot.path, "profiles", `${profileName}.yml`);
|
|
840
1697
|
if (!await fileExists(profilePath)) {
|
|
841
1698
|
continue;
|
|
842
1699
|
}
|
|
843
|
-
const document = await
|
|
1700
|
+
const document = await readFile5(profilePath, "utf8");
|
|
844
1701
|
const parsed = parseYaml(document);
|
|
845
1702
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
846
1703
|
throw new CnosManifestError("Profile definition must be a YAML object", profilePath);
|
|
@@ -848,7 +1705,7 @@ async function loadProfileDefinition(profileName, options) {
|
|
|
848
1705
|
const definition = normalizeProfileDefinition(
|
|
849
1706
|
profileName,
|
|
850
1707
|
parsed,
|
|
851
|
-
options.manifestRoot ? toPortablePath(
|
|
1708
|
+
options.manifestRoot ? toPortablePath(path8.relative(path8.dirname(options.manifestRoot), profilePath)) : toPortablePath(profilePath)
|
|
852
1709
|
);
|
|
853
1710
|
if (definition.name !== profileName) {
|
|
854
1711
|
throw new CnosManifestError(
|
|
@@ -936,6 +1793,50 @@ async function expandProfileChain(activeProfile, options = {}) {
|
|
|
936
1793
|
};
|
|
937
1794
|
}
|
|
938
1795
|
|
|
1796
|
+
// ../core/src/promotions/promoteToPublic.ts
|
|
1797
|
+
function toPublicKey(key) {
|
|
1798
|
+
const namespace = getNamespaceNameForKey(key);
|
|
1799
|
+
return namespace === "value" ? `public.${stripNamespace(key)}` : `public.${key}`;
|
|
1800
|
+
}
|
|
1801
|
+
function toPromotedConfigEntry(entry, key, promotedFrom) {
|
|
1802
|
+
return {
|
|
1803
|
+
...entry,
|
|
1804
|
+
key,
|
|
1805
|
+
namespace: "public",
|
|
1806
|
+
sourceId: "public-promote",
|
|
1807
|
+
pluginId: "core",
|
|
1808
|
+
metadata: {
|
|
1809
|
+
...entry.metadata ?? {},
|
|
1810
|
+
promotedFrom
|
|
1811
|
+
}
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
function toPromotedResolvedEntry(entry) {
|
|
1815
|
+
const key = toPublicKey(entry.key);
|
|
1816
|
+
return {
|
|
1817
|
+
key,
|
|
1818
|
+
value: entry.value,
|
|
1819
|
+
namespace: "public",
|
|
1820
|
+
winner: toPromotedConfigEntry(entry.winner, key, entry.key),
|
|
1821
|
+
overridden: entry.overridden.map((override) => toPromotedConfigEntry(override, key, entry.key))
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
function promoteToPublic(graph, manifest) {
|
|
1825
|
+
const entries = new Map(graph.entries);
|
|
1826
|
+
for (const key of manifest.public.promote) {
|
|
1827
|
+
ensureProjectionAllowed(manifest, key, "public");
|
|
1828
|
+
const resolved = graph.entries.get(key);
|
|
1829
|
+
if (!resolved) {
|
|
1830
|
+
continue;
|
|
1831
|
+
}
|
|
1832
|
+
entries.set(toPublicKey(key), toPromotedResolvedEntry(resolved));
|
|
1833
|
+
}
|
|
1834
|
+
return {
|
|
1835
|
+
...graph,
|
|
1836
|
+
entries
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
|
|
939
1840
|
// ../core/src/profiles/resolveActiveProfile.ts
|
|
940
1841
|
function resolveActiveProfile(manifest, options = {}) {
|
|
941
1842
|
for (const source of manifest.profiles.resolveFrom) {
|
|
@@ -1051,7 +1952,7 @@ function createProfileAwareResolver() {
|
|
|
1051
1952
|
|
|
1052
1953
|
// ../core/src/workspaces/resolveWorkspaceContext.ts
|
|
1053
1954
|
import { access as access3 } from "fs/promises";
|
|
1054
|
-
import
|
|
1955
|
+
import path9 from "path";
|
|
1055
1956
|
|
|
1056
1957
|
// ../core/src/workspaces/expandWorkspaceChain.ts
|
|
1057
1958
|
function expandWorkspaceChain(workspaceId, items) {
|
|
@@ -1088,7 +1989,7 @@ function expandWorkspaceChain(workspaceId, items) {
|
|
|
1088
1989
|
}
|
|
1089
1990
|
|
|
1090
1991
|
// ../core/src/workspaces/resolveWorkspaceContext.ts
|
|
1091
|
-
async function
|
|
1992
|
+
async function exists3(targetPath) {
|
|
1092
1993
|
try {
|
|
1093
1994
|
await access3(targetPath);
|
|
1094
1995
|
return true;
|
|
@@ -1097,14 +1998,14 @@ async function exists2(targetPath) {
|
|
|
1097
1998
|
}
|
|
1098
1999
|
}
|
|
1099
2000
|
async function resolveLocalWorkspaceRoot(manifestRoot, workspaceId) {
|
|
1100
|
-
const workspaceRoot =
|
|
1101
|
-
if (await
|
|
2001
|
+
const workspaceRoot = path9.join(manifestRoot, "workspaces", workspaceId);
|
|
2002
|
+
if (await exists3(workspaceRoot)) {
|
|
1102
2003
|
return workspaceRoot;
|
|
1103
2004
|
}
|
|
1104
2005
|
const legacyMarkers = ["values", "secrets", "env", "profiles"].map(
|
|
1105
|
-
(segment) =>
|
|
2006
|
+
(segment) => path9.join(manifestRoot, segment)
|
|
1106
2007
|
);
|
|
1107
|
-
if ((await Promise.all(legacyMarkers.map((marker) =>
|
|
2008
|
+
if ((await Promise.all(legacyMarkers.map((marker) => exists3(marker)))).some(Boolean)) {
|
|
1108
2009
|
return manifestRoot;
|
|
1109
2010
|
}
|
|
1110
2011
|
return workspaceRoot;
|
|
@@ -1144,26 +2045,26 @@ function resolveGlobalRoot(manifest, workspaceFile, options) {
|
|
|
1144
2045
|
}
|
|
1145
2046
|
if (options.globalRoot) {
|
|
1146
2047
|
return {
|
|
1147
|
-
value:
|
|
2048
|
+
value: path9.resolve(expandHomePath(options.globalRoot)),
|
|
1148
2049
|
source: "cli"
|
|
1149
2050
|
};
|
|
1150
2051
|
}
|
|
1151
2052
|
if (workspaceFile?.globalRoot) {
|
|
1152
2053
|
return {
|
|
1153
|
-
value:
|
|
2054
|
+
value: path9.resolve(expandHomePath(workspaceFile.globalRoot)),
|
|
1154
2055
|
source: "workspace-file"
|
|
1155
2056
|
};
|
|
1156
2057
|
}
|
|
1157
2058
|
if (manifest.workspaces.global.root) {
|
|
1158
2059
|
return {
|
|
1159
|
-
value:
|
|
2060
|
+
value: path9.resolve(expandHomePath(manifest.workspaces.global.root)),
|
|
1160
2061
|
source: "manifest"
|
|
1161
2062
|
};
|
|
1162
2063
|
}
|
|
1163
2064
|
const cnosHome = options.processEnv?.CNOS_HOME;
|
|
1164
2065
|
if (cnosHome) {
|
|
1165
2066
|
return {
|
|
1166
|
-
value:
|
|
2067
|
+
value: path9.resolve(expandHomePath(cnosHome)),
|
|
1167
2068
|
source: "CNOS_HOME"
|
|
1168
2069
|
};
|
|
1169
2070
|
}
|
|
@@ -1180,7 +2081,7 @@ async function resolveWorkspaceContext(manifest, options) {
|
|
|
1180
2081
|
workspaceRoots.push({
|
|
1181
2082
|
scope: "global",
|
|
1182
2083
|
workspaceId: chainWorkspaceId,
|
|
1183
|
-
path:
|
|
2084
|
+
path: path9.join(globalRoot.value, "workspaces", globalWorkspaceId)
|
|
1184
2085
|
});
|
|
1185
2086
|
}
|
|
1186
2087
|
}
|
|
@@ -1370,49 +2271,120 @@ async function runPipeline(options) {
|
|
|
1370
2271
|
return collectedEntries.flat();
|
|
1371
2272
|
}
|
|
1372
2273
|
|
|
1373
|
-
// ../core/src/
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
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
|
+
};
|
|
1377
2304
|
|
|
1378
|
-
// ../core/src/
|
|
1379
|
-
function
|
|
1380
|
-
|
|
1381
|
-
|
|
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
|
+
}));
|
|
1382
2311
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
const
|
|
1387
|
-
|
|
1388
|
-
|
|
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
|
+
);
|
|
1389
2340
|
}
|
|
1390
|
-
return
|
|
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;
|
|
1391
2349
|
}
|
|
1392
2350
|
|
|
1393
2351
|
// ../core/src/orchestrator/runtime.ts
|
|
1394
|
-
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
|
+
}
|
|
1395
2363
|
return {
|
|
1396
2364
|
manifest,
|
|
1397
2365
|
plugins,
|
|
1398
2366
|
graph,
|
|
1399
2367
|
read(key) {
|
|
1400
|
-
return
|
|
2368
|
+
return readLogicalKey(key);
|
|
1401
2369
|
},
|
|
1402
2370
|
require(key) {
|
|
1403
|
-
|
|
2371
|
+
const value = readLogicalKey(key);
|
|
2372
|
+
if (value === void 0) {
|
|
2373
|
+
return requireValue(graph, key);
|
|
2374
|
+
}
|
|
2375
|
+
return value;
|
|
1404
2376
|
},
|
|
1405
2377
|
readOr(key, fallback) {
|
|
1406
2378
|
return readOrValue(graph, key, fallback);
|
|
1407
2379
|
},
|
|
1408
|
-
value(
|
|
1409
|
-
return
|
|
2380
|
+
value(path10) {
|
|
2381
|
+
return readLogicalKey(toLogicalKey("value", path10));
|
|
1410
2382
|
},
|
|
1411
|
-
secret(
|
|
1412
|
-
return
|
|
2383
|
+
secret(path10) {
|
|
2384
|
+
return readLogicalKey(toLogicalKey("secret", path10));
|
|
1413
2385
|
},
|
|
1414
|
-
meta(
|
|
1415
|
-
return
|
|
2386
|
+
meta(path10) {
|
|
2387
|
+
return readLogicalKey(toLogicalKey("meta", path10));
|
|
1416
2388
|
},
|
|
1417
2389
|
inspect(key) {
|
|
1418
2390
|
return inspectValue(graph, key);
|
|
@@ -1527,6 +2499,9 @@ function appendMetaEntries(graph, cnosVersion) {
|
|
|
1527
2499
|
}
|
|
1528
2500
|
async function createCnos(options = {}) {
|
|
1529
2501
|
const loadedManifest = await loadManifest(options.root ? { root: options.root } : {});
|
|
2502
|
+
for (const key of loadedManifest.manifest.public.promote) {
|
|
2503
|
+
ensureProjectionAllowed(loadedManifest.manifest, key, "public");
|
|
2504
|
+
}
|
|
1530
2505
|
const workspaceFile = await loadWorkspaceFile(loadedManifest.repoRoot);
|
|
1531
2506
|
const workspace = await resolveWorkspaceContext(loadedManifest.manifest, {
|
|
1532
2507
|
manifestRoot: loadedManifest.manifestRoot,
|
|
@@ -1566,40 +2541,73 @@ async function createCnos(options = {}) {
|
|
|
1566
2541
|
workspace
|
|
1567
2542
|
});
|
|
1568
2543
|
const schemaApplied = applySchemaRules(graph, loadedManifest.manifest.schema);
|
|
2544
|
+
const promotedGraph = promoteToPublic(schemaApplied.graph, loadedManifest.manifest);
|
|
2545
|
+
const secretCache = options.secretResolution === "lazy" ? void 0 : await batchResolveSecrets(promotedGraph, loadedManifest.manifest, options.processEnv);
|
|
1569
2546
|
return createRuntime(
|
|
1570
2547
|
loadedManifest.manifest,
|
|
1571
2548
|
appendMetaEntries({
|
|
1572
|
-
...
|
|
2549
|
+
...promotedGraph,
|
|
1573
2550
|
profileSource: activeProfile.source
|
|
1574
2551
|
}, options.cnosVersion),
|
|
1575
|
-
plugins
|
|
2552
|
+
plugins,
|
|
2553
|
+
secretCache
|
|
1576
2554
|
);
|
|
1577
2555
|
}
|
|
1578
2556
|
|
|
1579
2557
|
export {
|
|
1580
2558
|
CnosManifestError,
|
|
2559
|
+
CnosSecurityError,
|
|
2560
|
+
CnosAuthenticationError,
|
|
2561
|
+
inspectValue,
|
|
1581
2562
|
createProvenanceInspector,
|
|
2563
|
+
readKeychain,
|
|
2564
|
+
writeKeychain,
|
|
2565
|
+
resolveManifestRoot,
|
|
1582
2566
|
resolveWorkspaceScopedPath,
|
|
1583
2567
|
resolveConfigDocumentPath,
|
|
1584
2568
|
toPortablePath,
|
|
1585
2569
|
joinConfigPath,
|
|
2570
|
+
toLogicalKey,
|
|
1586
2571
|
parseYaml,
|
|
1587
2572
|
stringifyYaml,
|
|
2573
|
+
loadManifest,
|
|
2574
|
+
ensureProjectionAllowed,
|
|
1588
2575
|
applySchemaRules,
|
|
2576
|
+
writeVaultSessionKey,
|
|
2577
|
+
clearVaultSessionKey,
|
|
2578
|
+
clearAllVaultSessionKeys,
|
|
1589
2579
|
isSecretReference,
|
|
1590
2580
|
resolveSecretStoreRoot,
|
|
1591
|
-
|
|
2581
|
+
getVaultPassphraseEnvVar,
|
|
2582
|
+
isPassphraseEnvRef,
|
|
2583
|
+
getVaultSessionKeyEnvVar,
|
|
1592
2584
|
resolveSecretPassphrase,
|
|
1593
|
-
|
|
2585
|
+
deriveVaultKey,
|
|
2586
|
+
resolveSecretVaultFile,
|
|
2587
|
+
detectLegacyVaultFormat,
|
|
2588
|
+
readVaultMetadata,
|
|
1594
2589
|
listSecretVaults,
|
|
2590
|
+
createSecretVault,
|
|
2591
|
+
resolveConfiguredVaultPassphrase,
|
|
2592
|
+
resolveVaultAccessKey,
|
|
1595
2593
|
writeLocalSecret,
|
|
2594
|
+
deleteLocalSecret,
|
|
1596
2595
|
readLocalSecret,
|
|
2596
|
+
listLocalSecrets,
|
|
2597
|
+
resolveVaultDefinition,
|
|
2598
|
+
removeLocalVaultFiles,
|
|
2599
|
+
createSecretVaultProvider,
|
|
2600
|
+
resolveVaultAuth,
|
|
2601
|
+
toNamespaceObject,
|
|
2602
|
+
readValue,
|
|
2603
|
+
readOrValue,
|
|
2604
|
+
requireValue,
|
|
1597
2605
|
toEnv,
|
|
1598
|
-
envVarToLogicalKey,
|
|
1599
2606
|
toPublicEnv,
|
|
1600
2607
|
createCnos,
|
|
1601
2608
|
planDump,
|
|
1602
2609
|
writeDump,
|
|
2610
|
+
envVarToLogicalKey,
|
|
1603
2611
|
flattenObject,
|
|
1604
2612
|
validateRuntime
|
|
1605
2613
|
};
|