@kitsy/cnos 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/build/index.cjs +1003 -121
- package/dist/build/index.d.cts +1 -1
- package/dist/build/index.d.ts +1 -1
- package/dist/build/index.js +22 -10
- package/dist/{chunk-APCTXRUN.js → chunk-APIU4GTB.js} +1012 -195
- package/dist/chunk-EQSKV3DP.js +105 -0
- package/dist/{chunk-MLQGYCO7.js → chunk-FWJC4Y2D.js} +1 -1
- package/dist/{chunk-RD5WMHPM.js → chunk-HMM76UYZ.js} +1 -1
- package/dist/{chunk-EIN55XXA.js → chunk-J4K4JUJL.js} +1 -1
- package/dist/{chunk-SO5XREEU.js → chunk-JSBVYK2T.js} +32 -11
- package/dist/chunk-LJD4SM32.js +189 -0
- package/dist/{chunk-SXTMTACL.js → chunk-T6Y57KTT.js} +20 -31
- package/dist/chunk-WCHX2QFY.js +115 -0
- package/dist/{chunk-ZA74BO47.js → chunk-ZTPSFXWP.js} +1 -1
- package/dist/configure/index.cjs +3021 -0
- package/dist/configure/index.d.cts +12 -0
- package/dist/configure/index.d.ts +12 -0
- package/dist/configure/index.js +24 -0
- package/dist/{envNaming-CcsqAel3.d.ts → envNaming-Dvm_LP2D.d.ts} +1 -1
- package/dist/{envNaming-BTJpH93W.d.cts → envNaming-S4B-dHUx.d.cts} +1 -1
- package/dist/index.cjs +1243 -186
- package/dist/index.d.cts +2 -13
- package/dist/index.d.ts +2 -13
- package/dist/index.js +13 -25
- package/dist/internal.cjs +1525 -81
- package/dist/internal.d.cts +171 -14
- package/dist/internal.d.ts +171 -14
- package/dist/internal.js +652 -5
- package/dist/plugin/basic-schema.cjs +29 -2
- package/dist/plugin/basic-schema.d.cts +1 -1
- package/dist/plugin/basic-schema.d.ts +1 -1
- package/dist/plugin/basic-schema.js +2 -2
- package/dist/plugin/cli-args.cjs +29 -2
- package/dist/plugin/cli-args.d.cts +1 -1
- package/dist/plugin/cli-args.d.ts +1 -1
- package/dist/plugin/cli-args.js +2 -2
- package/dist/plugin/dotenv.cjs +36 -9
- package/dist/plugin/dotenv.d.cts +2 -2
- package/dist/plugin/dotenv.d.ts +2 -2
- package/dist/plugin/dotenv.js +2 -2
- package/dist/plugin/env-export.cjs +31 -2
- package/dist/plugin/env-export.d.cts +2 -2
- package/dist/plugin/env-export.d.ts +2 -2
- package/dist/plugin/env-export.js +2 -2
- package/dist/plugin/filesystem.cjs +65 -91
- package/dist/plugin/filesystem.d.cts +1 -1
- package/dist/plugin/filesystem.d.ts +1 -1
- package/dist/plugin/filesystem.js +2 -2
- package/dist/plugin/process-env.cjs +105 -11
- package/dist/plugin/process-env.d.cts +4 -3
- package/dist/plugin/process-env.d.ts +4 -3
- package/dist/plugin/process-env.js +6 -4
- package/dist/{plugin-DkOIT5uI.d.cts → plugin-B4xwySxw.d.cts} +15 -2
- package/dist/{plugin-DkOIT5uI.d.ts → plugin-B4xwySxw.d.ts} +15 -2
- package/dist/runtime/index.cjs +1057 -136
- package/dist/runtime/index.d.cts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +11 -186
- package/dist/{toPublicEnv-C9clvXLo.d.ts → toPublicEnv-CvhGAfsB.d.ts} +1 -1
- package/dist/{toPublicEnv-DvFeV3qG.d.cts → toPublicEnv-ggmphZFs.d.cts} +1 -1
- package/package.json +11 -1
- package/dist/chunk-JUHPBAEH.js +0 -20
- package/dist/chunk-PQ4KSV76.js +0 -50
- package/dist/chunk-WHUGFPE4.js +0 -49
package/dist/build/index.cjs
CHANGED
|
@@ -53,6 +53,11 @@ var CnosSecurityError = class extends CnosError {
|
|
|
53
53
|
super(message);
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
|
+
var CnosAuthenticationError = class extends CnosError {
|
|
57
|
+
constructor(message) {
|
|
58
|
+
super(message);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
56
61
|
var CnosKeyNotFoundError = class extends CnosError {
|
|
57
62
|
constructor(key) {
|
|
58
63
|
super(`Missing required CNOS config key: ${key}`);
|
|
@@ -105,6 +110,68 @@ function createProvenanceInspector() {
|
|
|
105
110
|
};
|
|
106
111
|
}
|
|
107
112
|
|
|
113
|
+
// ../core/src/keychain/linux.ts
|
|
114
|
+
var import_node_child_process = require("child_process");
|
|
115
|
+
var import_node_util = require("util");
|
|
116
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
117
|
+
async function readLinuxKeychain(entry) {
|
|
118
|
+
try {
|
|
119
|
+
const { stdout } = await execFileAsync("secret-tool", ["lookup", "service", "cnos", "account", entry]);
|
|
120
|
+
const value = stdout.trim();
|
|
121
|
+
return value.length > 0 ? value : void 0;
|
|
122
|
+
} catch {
|
|
123
|
+
return void 0;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ../core/src/keychain/macos.ts
|
|
128
|
+
var import_node_child_process2 = require("child_process");
|
|
129
|
+
var import_node_util2 = require("util");
|
|
130
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
131
|
+
async function readMacosKeychain(entry) {
|
|
132
|
+
try {
|
|
133
|
+
const { stdout } = await execFileAsync2("security", ["find-generic-password", "-a", "cnos", "-s", entry, "-w"]);
|
|
134
|
+
const value = stdout.trim();
|
|
135
|
+
return value.length > 0 ? value : void 0;
|
|
136
|
+
} catch {
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ../core/src/keychain/windows.ts
|
|
142
|
+
var import_node_child_process3 = require("child_process");
|
|
143
|
+
var import_node_util3 = require("util");
|
|
144
|
+
var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
|
|
145
|
+
function wrap(script) {
|
|
146
|
+
return ["-NoProfile", "-Command", script];
|
|
147
|
+
}
|
|
148
|
+
async function readWindowsKeychain(entry) {
|
|
149
|
+
try {
|
|
150
|
+
const { stdout } = await execFileAsync3(
|
|
151
|
+
"powershell",
|
|
152
|
+
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`)
|
|
153
|
+
);
|
|
154
|
+
const value = stdout.trim();
|
|
155
|
+
return value.length > 0 ? value : void 0;
|
|
156
|
+
} catch {
|
|
157
|
+
return void 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ../core/src/keychain/index.ts
|
|
162
|
+
async function readKeychain(entry) {
|
|
163
|
+
if (process.platform === "win32") {
|
|
164
|
+
return readWindowsKeychain(entry);
|
|
165
|
+
}
|
|
166
|
+
if (process.platform === "darwin") {
|
|
167
|
+
return readMacosKeychain(entry);
|
|
168
|
+
}
|
|
169
|
+
if (process.platform === "linux") {
|
|
170
|
+
return readLinuxKeychain(entry);
|
|
171
|
+
}
|
|
172
|
+
return void 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
108
175
|
// ../core/src/manifest/loadManifest.ts
|
|
109
176
|
var import_promises2 = require("fs/promises");
|
|
110
177
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
@@ -191,6 +258,9 @@ var import_yaml = require("yaml");
|
|
|
191
258
|
function parseYaml(source) {
|
|
192
259
|
return (0, import_yaml.parse)(source);
|
|
193
260
|
}
|
|
261
|
+
function stringifyYaml(value) {
|
|
262
|
+
return (0, import_yaml.stringify)(value);
|
|
263
|
+
}
|
|
194
264
|
|
|
195
265
|
// ../core/src/manifest/normalizeManifest.ts
|
|
196
266
|
var DEFAULT_RESOLVE_FROM = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
@@ -224,6 +294,11 @@ var DEFAULT_NAMESPACES = {
|
|
|
224
294
|
shareable: false,
|
|
225
295
|
readonly: true
|
|
226
296
|
},
|
|
297
|
+
process: {
|
|
298
|
+
kind: "system",
|
|
299
|
+
shareable: false,
|
|
300
|
+
readonly: true
|
|
301
|
+
},
|
|
227
302
|
public: {
|
|
228
303
|
kind: "projection",
|
|
229
304
|
source: "promote",
|
|
@@ -278,22 +353,82 @@ function normalizeNamespaces(namespaces) {
|
|
|
278
353
|
function normalizeVaults(vaults) {
|
|
279
354
|
return Object.fromEntries(
|
|
280
355
|
Object.entries(vaults ?? {}).map(([name, definition]) => {
|
|
356
|
+
const legacyPassphrase = definition.passphrase;
|
|
357
|
+
if (legacyPassphrase !== void 0) {
|
|
358
|
+
throw new CnosManifestError(
|
|
359
|
+
`Vault "${name}" uses legacy passphrase configuration. Use vaults.${name}.auth instead.`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
281
362
|
const provider = definition.provider?.trim();
|
|
282
363
|
if (!provider) {
|
|
283
364
|
throw new CnosManifestError(`Vault "${name}" requires a provider`);
|
|
284
365
|
}
|
|
366
|
+
const normalizedAuth = normalizeVaultAuth(name, provider, definition.auth);
|
|
367
|
+
const normalizedMapping = Object.fromEntries(
|
|
368
|
+
Object.entries(definition.mapping ?? {}).filter(
|
|
369
|
+
(entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
|
|
370
|
+
).map(([envVar, logicalRef]) => [envVar.trim(), logicalRef.trim()]).filter(([envVar, logicalRef]) => envVar.length > 0 && logicalRef.length > 0)
|
|
371
|
+
);
|
|
285
372
|
return [
|
|
286
373
|
name,
|
|
287
374
|
{
|
|
288
375
|
provider,
|
|
289
|
-
|
|
290
|
-
|
|
376
|
+
auth: normalizedAuth,
|
|
377
|
+
...Object.keys(normalizedMapping).length > 0 ? {
|
|
378
|
+
mapping: normalizedMapping
|
|
291
379
|
} : {}
|
|
292
380
|
}
|
|
293
381
|
];
|
|
294
382
|
})
|
|
295
383
|
);
|
|
296
384
|
}
|
|
385
|
+
function normalizeAuthSources(value) {
|
|
386
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
387
|
+
return void 0;
|
|
388
|
+
}
|
|
389
|
+
const sources = Array.isArray(value.from) ? value.from : void 0;
|
|
390
|
+
const normalized = (sources ?? []).map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
391
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
392
|
+
}
|
|
393
|
+
function normalizeVaultAuth(vaultName, provider, auth) {
|
|
394
|
+
if (provider === "local") {
|
|
395
|
+
const passphraseSources = normalizeAuthSources(auth?.passphrase);
|
|
396
|
+
const defaultToken = vaultName.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
397
|
+
const defaultSources = [
|
|
398
|
+
...defaultToken ? [`env:CNOS_SECRET_PASSPHRASE_${defaultToken}`] : [],
|
|
399
|
+
"env:CNOS_SECRET_PASSPHRASE",
|
|
400
|
+
`keychain:cnos/${vaultName}`,
|
|
401
|
+
"prompt"
|
|
402
|
+
];
|
|
403
|
+
return {
|
|
404
|
+
method: auth?.method ?? "passphrase",
|
|
405
|
+
passphrase: {
|
|
406
|
+
from: passphraseSources ?? defaultSources
|
|
407
|
+
},
|
|
408
|
+
...auth?.config ? { config: auth.config } : {}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
if (provider === "github-secrets") {
|
|
412
|
+
return {
|
|
413
|
+
method: auth?.method ?? "environment",
|
|
414
|
+
...auth?.config ? { config: auth.config } : {}
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
...auth?.method ? { method: auth.method } : {},
|
|
419
|
+
...normalizeAuthSources(auth?.passphrase) ? {
|
|
420
|
+
passphrase: {
|
|
421
|
+
from: normalizeAuthSources(auth?.passphrase) ?? []
|
|
422
|
+
}
|
|
423
|
+
} : {},
|
|
424
|
+
...normalizeAuthSources(auth?.token) ? {
|
|
425
|
+
token: {
|
|
426
|
+
from: normalizeAuthSources(auth?.token) ?? []
|
|
427
|
+
}
|
|
428
|
+
} : {},
|
|
429
|
+
...auth?.config ? { config: auth.config } : {}
|
|
430
|
+
};
|
|
431
|
+
}
|
|
297
432
|
function normalizeManifest(manifest) {
|
|
298
433
|
const version = manifest.version ?? 1;
|
|
299
434
|
if (version !== 1) {
|
|
@@ -823,12 +958,15 @@ async function exists2(targetPath) {
|
|
|
823
958
|
return false;
|
|
824
959
|
}
|
|
825
960
|
}
|
|
826
|
-
async function resolveLocalWorkspaceRoot(manifestRoot, workspaceId) {
|
|
961
|
+
async function resolveLocalWorkspaceRoot(manifestRoot, workspaceId, manifest) {
|
|
827
962
|
const workspaceRoot = import_node_path5.default.join(manifestRoot, "workspaces", workspaceId);
|
|
828
963
|
if (await exists2(workspaceRoot)) {
|
|
829
964
|
return workspaceRoot;
|
|
830
965
|
}
|
|
831
|
-
const
|
|
966
|
+
const customDataNamespaceRoots = Object.entries(manifest.namespaces).filter(
|
|
967
|
+
([namespace, definition]) => namespace !== "value" && namespace !== "secret" && definition.kind === "data" && !definition.sensitive
|
|
968
|
+
).map(([namespace]) => namespace);
|
|
969
|
+
const legacyMarkers = ["values", "secrets", "env", "profiles", ...customDataNamespaceRoots].map(
|
|
832
970
|
(segment) => import_node_path5.default.join(manifestRoot, segment)
|
|
833
971
|
);
|
|
834
972
|
if ((await Promise.all(legacyMarkers.map((marker) => exists2(marker)))).some(Boolean)) {
|
|
@@ -915,7 +1053,7 @@ async function resolveWorkspaceContext(manifest, options) {
|
|
|
915
1053
|
workspaceRoots.push({
|
|
916
1054
|
scope: "local",
|
|
917
1055
|
workspaceId: chainWorkspaceId,
|
|
918
|
-
path: await resolveLocalWorkspaceRoot(options.manifestRoot, chainWorkspaceId)
|
|
1056
|
+
path: await resolveLocalWorkspaceRoot(options.manifestRoot, chainWorkspaceId, manifest)
|
|
919
1057
|
});
|
|
920
1058
|
}
|
|
921
1059
|
return {
|
|
@@ -1080,6 +1218,7 @@ async function runPipeline(options) {
|
|
|
1080
1218
|
const collectedEntries = await Promise.all(
|
|
1081
1219
|
options.plugins.map(
|
|
1082
1220
|
(plugin) => plugin.load({
|
|
1221
|
+
manifest: options.manifest,
|
|
1083
1222
|
manifestConfig: {
|
|
1084
1223
|
...options.manifest.sources[plugin.id] ?? {},
|
|
1085
1224
|
envMapping: options.manifest.envMapping
|
|
@@ -1097,6 +1236,712 @@ async function runPipeline(options) {
|
|
|
1097
1236
|
return collectedEntries.flat();
|
|
1098
1237
|
}
|
|
1099
1238
|
|
|
1239
|
+
// ../core/src/secrets/auditLog.ts
|
|
1240
|
+
var import_promises8 = require("fs/promises");
|
|
1241
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1242
|
+
|
|
1243
|
+
// ../core/src/utils/secretStore.ts
|
|
1244
|
+
var import_node_crypto = require("crypto");
|
|
1245
|
+
var import_promises7 = require("fs/promises");
|
|
1246
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1247
|
+
|
|
1248
|
+
// ../core/src/secrets/sessionStore.ts
|
|
1249
|
+
var import_promises6 = require("fs/promises");
|
|
1250
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
1251
|
+
function buildSessionRoot(processEnv = process.env) {
|
|
1252
|
+
return import_node_path6.default.join(import_node_path6.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets")), "sessions");
|
|
1253
|
+
}
|
|
1254
|
+
function buildSessionPath(vault, processEnv) {
|
|
1255
|
+
return import_node_path6.default.join(buildSessionRoot(processEnv), `${vault}.json`);
|
|
1256
|
+
}
|
|
1257
|
+
async function readVaultSessionKey(vault, processEnv) {
|
|
1258
|
+
try {
|
|
1259
|
+
const source = await (0, import_promises6.readFile)(buildSessionPath(vault, processEnv), "utf8");
|
|
1260
|
+
const document = JSON.parse(source);
|
|
1261
|
+
if (document.version !== 1 || typeof document.derivedKey !== "string") {
|
|
1262
|
+
return void 0;
|
|
1263
|
+
}
|
|
1264
|
+
const key = Buffer.from(document.derivedKey, "hex");
|
|
1265
|
+
return key.length > 0 ? key : void 0;
|
|
1266
|
+
} catch {
|
|
1267
|
+
return void 0;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// ../core/src/utils/secretStore.ts
|
|
1272
|
+
var KEY_LENGTH = 32;
|
|
1273
|
+
var SALT_LENGTH = 32;
|
|
1274
|
+
var IV_LENGTH = 12;
|
|
1275
|
+
var AUTH_TAG_LENGTH = 16;
|
|
1276
|
+
var PBKDF2_ITERATIONS = 6e5;
|
|
1277
|
+
var KEYSTORE_VERSION = 1;
|
|
1278
|
+
var METADATA_VERSION = 1;
|
|
1279
|
+
var META_FILENAME = "meta.yml";
|
|
1280
|
+
var KEYSTORE_FILENAME = "keystore.enc";
|
|
1281
|
+
function isObject(value) {
|
|
1282
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1283
|
+
}
|
|
1284
|
+
function isSecretReference(value) {
|
|
1285
|
+
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));
|
|
1286
|
+
}
|
|
1287
|
+
function resolveSecretStoreRoot(processEnv = process.env) {
|
|
1288
|
+
return import_node_path7.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
|
|
1289
|
+
}
|
|
1290
|
+
function normalizeVaultToken(vault = "default") {
|
|
1291
|
+
return vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
1292
|
+
}
|
|
1293
|
+
function getVaultPassphraseEnvVar(vault = "default") {
|
|
1294
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
1295
|
+
return vaultToken && vaultToken !== "DEFAULT" ? `CNOS_SECRET_PASSPHRASE_${vaultToken}` : "CNOS_SECRET_PASSPHRASE";
|
|
1296
|
+
}
|
|
1297
|
+
function getVaultSessionKeyEnvVar(vault = "default") {
|
|
1298
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
1299
|
+
return `__CNOS_VAULT_KEY_${vaultToken || "DEFAULT"}__`;
|
|
1300
|
+
}
|
|
1301
|
+
function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
|
|
1302
|
+
return processEnv[getVaultPassphraseEnvVar(vault)] ?? processEnv.CNOS_SECRET_PASSPHRASE;
|
|
1303
|
+
}
|
|
1304
|
+
function resolveVaultSessionKey(vault = "default", processEnv = process.env) {
|
|
1305
|
+
const encoded = processEnv[getVaultSessionKeyEnvVar(vault)];
|
|
1306
|
+
if (!encoded) {
|
|
1307
|
+
return readVaultSessionKey(vault, processEnv);
|
|
1308
|
+
}
|
|
1309
|
+
try {
|
|
1310
|
+
const key = Buffer.from(encoded, "hex");
|
|
1311
|
+
return key.length === KEY_LENGTH ? key : void 0;
|
|
1312
|
+
} catch {
|
|
1313
|
+
return void 0;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
function deriveVaultKey(passphrase, salt, iterations = PBKDF2_ITERATIONS) {
|
|
1317
|
+
return (0, import_node_crypto.pbkdf2Sync)(passphrase, salt, iterations, KEY_LENGTH, "sha512");
|
|
1318
|
+
}
|
|
1319
|
+
function buildMetaPath(storeRoot, vault = "default") {
|
|
1320
|
+
return import_node_path7.default.join(storeRoot, "vaults", vault, META_FILENAME);
|
|
1321
|
+
}
|
|
1322
|
+
function buildKeystorePath(storeRoot, vault = "default") {
|
|
1323
|
+
return import_node_path7.default.join(storeRoot, "vaults", vault, KEYSTORE_FILENAME);
|
|
1324
|
+
}
|
|
1325
|
+
function buildLegacyVaultFile(storeRoot, vault = "default") {
|
|
1326
|
+
return import_node_path7.default.join(storeRoot, "vaults", `${vault}.json`);
|
|
1327
|
+
}
|
|
1328
|
+
function buildLegacyVaultStoreRoot(storeRoot, vault = "default") {
|
|
1329
|
+
return import_node_path7.default.join(storeRoot, "vaults", vault, "store");
|
|
1330
|
+
}
|
|
1331
|
+
function assertVaultMetadata(value, filePath) {
|
|
1332
|
+
if (!isObject(value)) {
|
|
1333
|
+
throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
|
|
1334
|
+
}
|
|
1335
|
+
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") {
|
|
1336
|
+
throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
|
|
1337
|
+
}
|
|
1338
|
+
return value;
|
|
1339
|
+
}
|
|
1340
|
+
async function exists3(targetPath) {
|
|
1341
|
+
try {
|
|
1342
|
+
await (0, import_promises7.stat)(targetPath);
|
|
1343
|
+
return true;
|
|
1344
|
+
} catch {
|
|
1345
|
+
return false;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
async function detectLegacyVaultFormat(storeRoot, vault = "default") {
|
|
1349
|
+
const legacyFile = buildLegacyVaultFile(storeRoot, vault);
|
|
1350
|
+
const legacyStore = buildLegacyVaultStoreRoot(storeRoot, vault);
|
|
1351
|
+
if (await exists3(legacyFile)) {
|
|
1352
|
+
return legacyFile;
|
|
1353
|
+
}
|
|
1354
|
+
if (await exists3(legacyStore)) {
|
|
1355
|
+
return legacyStore;
|
|
1356
|
+
}
|
|
1357
|
+
return void 0;
|
|
1358
|
+
}
|
|
1359
|
+
async function assertNoLegacyVaultFormat(storeRoot, vault = "default") {
|
|
1360
|
+
const legacyPath = await detectLegacyVaultFormat(storeRoot, vault);
|
|
1361
|
+
if (!legacyPath) {
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
throw new CnosSecurityError(
|
|
1365
|
+
`Legacy CNOS local vault format detected for vault "${vault}" at ${legacyPath}. CNOS 1.4 requires the new keystore format. Remove and recreate the vault.`
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
function encryptPayload(payload, key) {
|
|
1369
|
+
const iv = (0, import_node_crypto.randomBytes)(IV_LENGTH);
|
|
1370
|
+
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", key, iv);
|
|
1371
|
+
const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
|
|
1372
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
1373
|
+
const tag = cipher.getAuthTag();
|
|
1374
|
+
return Buffer.concat([
|
|
1375
|
+
Buffer.from(Uint32Array.of(KEYSTORE_VERSION).buffer),
|
|
1376
|
+
iv,
|
|
1377
|
+
tag,
|
|
1378
|
+
ciphertext
|
|
1379
|
+
]);
|
|
1380
|
+
}
|
|
1381
|
+
function decryptPayload(buffer, key) {
|
|
1382
|
+
if (buffer.length < 4 + IV_LENGTH + AUTH_TAG_LENGTH) {
|
|
1383
|
+
throw new CnosSecurityError("Invalid CNOS local vault keystore");
|
|
1384
|
+
}
|
|
1385
|
+
const version = buffer.readUInt32LE(0);
|
|
1386
|
+
if (version !== KEYSTORE_VERSION) {
|
|
1387
|
+
throw new CnosSecurityError(`Unsupported CNOS local vault keystore version: ${version}`);
|
|
1388
|
+
}
|
|
1389
|
+
const ivOffset = 4;
|
|
1390
|
+
const tagOffset = ivOffset + IV_LENGTH;
|
|
1391
|
+
const cipherOffset = tagOffset + AUTH_TAG_LENGTH;
|
|
1392
|
+
const iv = buffer.subarray(ivOffset, tagOffset);
|
|
1393
|
+
const tag = buffer.subarray(tagOffset, cipherOffset);
|
|
1394
|
+
const ciphertext = buffer.subarray(cipherOffset);
|
|
1395
|
+
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", key, iv);
|
|
1396
|
+
decipher.setAuthTag(tag);
|
|
1397
|
+
try {
|
|
1398
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
1399
|
+
const payload = JSON.parse(plaintext);
|
|
1400
|
+
if (!payload || !isObject(payload.secrets) || !isObject(payload.metadata)) {
|
|
1401
|
+
throw new Error("invalid");
|
|
1402
|
+
}
|
|
1403
|
+
return {
|
|
1404
|
+
secrets: Object.fromEntries(
|
|
1405
|
+
Object.entries(payload.secrets).filter((entry) => typeof entry[1] === "string")
|
|
1406
|
+
),
|
|
1407
|
+
metadata: Object.fromEntries(
|
|
1408
|
+
Object.entries(payload.metadata).filter(
|
|
1409
|
+
(entry) => isObject(entry[1]) && typeof entry[1].createdAt === "string" && typeof entry[1].updatedAt === "string"
|
|
1410
|
+
)
|
|
1411
|
+
)
|
|
1412
|
+
};
|
|
1413
|
+
} catch {
|
|
1414
|
+
throw new CnosAuthenticationError("Failed to decrypt CNOS local vault. Check vault authentication.");
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
function buildInitialPayload() {
|
|
1418
|
+
return {
|
|
1419
|
+
secrets: {},
|
|
1420
|
+
metadata: {}
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
async function writeVaultFiles(storeRoot, vault, meta, payload, key) {
|
|
1424
|
+
const metaPath = buildMetaPath(storeRoot, vault);
|
|
1425
|
+
const keystorePath = buildKeystorePath(storeRoot, vault);
|
|
1426
|
+
await (0, import_promises7.mkdir)(import_node_path7.default.dirname(metaPath), { recursive: true });
|
|
1427
|
+
await (0, import_promises7.writeFile)(metaPath, stringifyYaml(meta), "utf8");
|
|
1428
|
+
await (0, import_promises7.writeFile)(keystorePath, encryptPayload(payload, key));
|
|
1429
|
+
}
|
|
1430
|
+
async function readVaultMetadata(storeRoot, vault = "default") {
|
|
1431
|
+
await assertNoLegacyVaultFormat(storeRoot, vault);
|
|
1432
|
+
const metaPath = buildMetaPath(storeRoot, vault);
|
|
1433
|
+
try {
|
|
1434
|
+
const source = await (0, import_promises7.readFile)(metaPath, "utf8");
|
|
1435
|
+
return assertVaultMetadata(parseYaml(source), metaPath);
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
if (error.code === "ENOENT") {
|
|
1438
|
+
return void 0;
|
|
1439
|
+
}
|
|
1440
|
+
throw error;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
async function createSecretVault(storeRoot, vault, passphrase) {
|
|
1444
|
+
const normalizedVault = vault.trim() || "default";
|
|
1445
|
+
await assertNoLegacyVaultFormat(storeRoot, normalizedVault);
|
|
1446
|
+
const salt = (0, import_node_crypto.randomBytes)(SALT_LENGTH);
|
|
1447
|
+
const key = deriveVaultKey(passphrase, salt, PBKDF2_ITERATIONS);
|
|
1448
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1449
|
+
const meta = {
|
|
1450
|
+
version: METADATA_VERSION,
|
|
1451
|
+
algorithm: "aes-256-gcm",
|
|
1452
|
+
kdf: "pbkdf2-sha512",
|
|
1453
|
+
iterations: PBKDF2_ITERATIONS,
|
|
1454
|
+
salt: salt.toString("base64"),
|
|
1455
|
+
createdAt,
|
|
1456
|
+
secretCount: 0
|
|
1457
|
+
};
|
|
1458
|
+
await writeVaultFiles(storeRoot, normalizedVault, meta, buildInitialPayload(), key);
|
|
1459
|
+
return buildMetaPath(storeRoot, normalizedVault);
|
|
1460
|
+
}
|
|
1461
|
+
async function ensureSecretVault(storeRoot, vault, passphrase) {
|
|
1462
|
+
const normalizedVault = vault.trim() || "default";
|
|
1463
|
+
const meta = await readVaultMetadata(storeRoot, normalizedVault);
|
|
1464
|
+
if (meta) {
|
|
1465
|
+
return buildMetaPath(storeRoot, normalizedVault);
|
|
1466
|
+
}
|
|
1467
|
+
return createSecretVault(storeRoot, normalizedVault, passphrase);
|
|
1468
|
+
}
|
|
1469
|
+
function resolveConfiguredVaultPassphrase(definition, vault = "default", processEnv = process.env) {
|
|
1470
|
+
if (definition?.provider !== "local") {
|
|
1471
|
+
return void 0;
|
|
1472
|
+
}
|
|
1473
|
+
const configuredSources = definition.auth?.passphrase?.from ?? [];
|
|
1474
|
+
for (const source of configuredSources) {
|
|
1475
|
+
if (source.startsWith("env:")) {
|
|
1476
|
+
const value = processEnv[source.slice(4)];
|
|
1477
|
+
if (value) {
|
|
1478
|
+
return value;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
return resolveSecretPassphrase(vault, processEnv);
|
|
1483
|
+
}
|
|
1484
|
+
async function resolveVaultAccessKey(storeRoot, definition, vault = "default", processEnv = process.env) {
|
|
1485
|
+
if (definition?.provider !== "local") {
|
|
1486
|
+
return definition?.provider === "github-secrets" ? {
|
|
1487
|
+
method: definition.auth?.method ?? "environment",
|
|
1488
|
+
...definition?.auth?.config ? { config: definition.auth.config } : {}
|
|
1489
|
+
} : void 0;
|
|
1490
|
+
}
|
|
1491
|
+
const sessionKey = await resolveVaultSessionKey(vault, processEnv);
|
|
1492
|
+
if (sessionKey) {
|
|
1493
|
+
return {
|
|
1494
|
+
derivedKey: sessionKey,
|
|
1495
|
+
method: "keychain",
|
|
1496
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
const passphrase = resolveConfiguredVaultPassphrase(definition, vault, processEnv);
|
|
1500
|
+
if (passphrase) {
|
|
1501
|
+
return {
|
|
1502
|
+
passphrase,
|
|
1503
|
+
method: "passphrase",
|
|
1504
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
const metadata = await readVaultMetadata(storeRoot, vault);
|
|
1508
|
+
if (!metadata) {
|
|
1509
|
+
return void 0;
|
|
1510
|
+
}
|
|
1511
|
+
throw new CnosAuthenticationError(
|
|
1512
|
+
`Cannot authenticate to vault "${vault}". Set ${getVaultPassphraseEnvVar(vault)} or run cnos vault auth ${vault}.`
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
async function loadVaultPayload(storeRoot, vault, auth) {
|
|
1516
|
+
const meta = await readVaultMetadata(storeRoot, vault);
|
|
1517
|
+
if (!meta) {
|
|
1518
|
+
throw new CnosManifestError(`Missing CNOS vault metadata for "${vault}"`);
|
|
1519
|
+
}
|
|
1520
|
+
const salt = Buffer.from(meta.salt, "base64");
|
|
1521
|
+
const key = auth.derivedKey ?? (auth.passphrase ? deriveVaultKey(auth.passphrase, salt, meta.iterations) : void 0);
|
|
1522
|
+
if (!key) {
|
|
1523
|
+
throw new CnosAuthenticationError(`Vault "${vault}" requires authentication before access.`);
|
|
1524
|
+
}
|
|
1525
|
+
const buffer = await (0, import_promises7.readFile)(buildKeystorePath(storeRoot, vault));
|
|
1526
|
+
return {
|
|
1527
|
+
meta,
|
|
1528
|
+
payload: decryptPayload(buffer, key),
|
|
1529
|
+
key
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
async function writeLocalSecret(storeRoot, ref, value, authOrPassphrase, vault = "default") {
|
|
1533
|
+
const auth = typeof authOrPassphrase === "string" ? {
|
|
1534
|
+
passphrase: authOrPassphrase,
|
|
1535
|
+
method: "passphrase"
|
|
1536
|
+
} : authOrPassphrase;
|
|
1537
|
+
if (auth.passphrase) {
|
|
1538
|
+
await ensureSecretVault(storeRoot, vault, auth.passphrase);
|
|
1539
|
+
} else {
|
|
1540
|
+
const meta2 = await readVaultMetadata(storeRoot, vault);
|
|
1541
|
+
if (!meta2) {
|
|
1542
|
+
throw new CnosAuthenticationError(`Vault "${vault}" requires passphrase-based authentication for initial creation.`);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1546
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1547
|
+
const existing = payload.metadata[ref];
|
|
1548
|
+
payload.secrets[ref] = value;
|
|
1549
|
+
payload.metadata[ref] = {
|
|
1550
|
+
createdAt: existing?.createdAt ?? now,
|
|
1551
|
+
updatedAt: now
|
|
1552
|
+
};
|
|
1553
|
+
const nextMeta = {
|
|
1554
|
+
...meta,
|
|
1555
|
+
secretCount: Object.keys(payload.secrets).length
|
|
1556
|
+
};
|
|
1557
|
+
await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
|
|
1558
|
+
return buildKeystorePath(storeRoot, vault);
|
|
1559
|
+
}
|
|
1560
|
+
async function deleteLocalSecret(storeRoot, ref, auth, vault = "default") {
|
|
1561
|
+
const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1562
|
+
if (!(ref in payload.secrets)) {
|
|
1563
|
+
return false;
|
|
1564
|
+
}
|
|
1565
|
+
delete payload.secrets[ref];
|
|
1566
|
+
delete payload.metadata[ref];
|
|
1567
|
+
const nextMeta = {
|
|
1568
|
+
...meta,
|
|
1569
|
+
secretCount: Object.keys(payload.secrets).length
|
|
1570
|
+
};
|
|
1571
|
+
await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
|
|
1572
|
+
return true;
|
|
1573
|
+
}
|
|
1574
|
+
async function readLocalSecret(storeRoot, ref, auth, vault = "default") {
|
|
1575
|
+
const { payload } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1576
|
+
const value = payload.secrets[ref];
|
|
1577
|
+
if (value === void 0) {
|
|
1578
|
+
throw new CnosManifestError(`Missing local secret ref "${ref}" in vault "${vault}"`);
|
|
1579
|
+
}
|
|
1580
|
+
return value;
|
|
1581
|
+
}
|
|
1582
|
+
async function listLocalSecrets(storeRoot, auth, vault = "default") {
|
|
1583
|
+
const { payload } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1584
|
+
return Object.keys(payload.secrets).sort((left, right) => left.localeCompare(right));
|
|
1585
|
+
}
|
|
1586
|
+
function resolveVaultDefinition(vaults, vault = "default") {
|
|
1587
|
+
const definition = vaults?.[vault];
|
|
1588
|
+
const provider = definition?.provider ?? "local";
|
|
1589
|
+
return {
|
|
1590
|
+
name: vault,
|
|
1591
|
+
provider,
|
|
1592
|
+
...definition?.auth ? { auth: definition.auth } : {},
|
|
1593
|
+
...definition?.mapping ? { mapping: definition.mapping } : {},
|
|
1594
|
+
requiresAuthentication: provider === "local"
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// ../core/src/secrets/auditLog.ts
|
|
1599
|
+
async function appendAuditEvent(event, processEnv = process.env) {
|
|
1600
|
+
const auditFile = processEnv.CNOS_AUDIT_FILE ?? import_node_path8.default.join(resolveSecretStoreRoot(processEnv), "audit", "access.log");
|
|
1601
|
+
await (0, import_promises8.mkdir)(import_node_path8.default.dirname(auditFile), { recursive: true });
|
|
1602
|
+
await (0, import_promises8.appendFile)(
|
|
1603
|
+
auditFile,
|
|
1604
|
+
`${JSON.stringify({
|
|
1605
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1606
|
+
...event
|
|
1607
|
+
})}
|
|
1608
|
+
`,
|
|
1609
|
+
"utf8"
|
|
1610
|
+
);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
// ../core/src/secrets/secretCache.ts
|
|
1614
|
+
var SecretCache = class {
|
|
1615
|
+
cache = /* @__PURE__ */ new Map();
|
|
1616
|
+
authenticated = /* @__PURE__ */ new Set();
|
|
1617
|
+
load(vaultId, secrets) {
|
|
1618
|
+
this.authenticated.add(vaultId);
|
|
1619
|
+
for (const [ref, value] of secrets) {
|
|
1620
|
+
this.cache.set(`${vaultId}:${ref}`, value);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
isVaultAuthenticated(vaultId) {
|
|
1624
|
+
return this.authenticated.has(vaultId);
|
|
1625
|
+
}
|
|
1626
|
+
get(vaultId, ref) {
|
|
1627
|
+
return this.cache.get(`${vaultId}:${ref}`);
|
|
1628
|
+
}
|
|
1629
|
+
clear(vaultId) {
|
|
1630
|
+
if (!vaultId) {
|
|
1631
|
+
this.cache.clear();
|
|
1632
|
+
this.authenticated.clear();
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
this.authenticated.delete(vaultId);
|
|
1636
|
+
for (const key of Array.from(this.cache.keys())) {
|
|
1637
|
+
if (key.startsWith(`${vaultId}:`)) {
|
|
1638
|
+
this.cache.delete(key);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
};
|
|
1643
|
+
|
|
1644
|
+
// ../core/src/secrets/providers/github.ts
|
|
1645
|
+
var GithubSecretsVaultProvider = class {
|
|
1646
|
+
constructor(vaultId, definition, processEnv = process.env) {
|
|
1647
|
+
this.vaultId = vaultId;
|
|
1648
|
+
this.definition = definition;
|
|
1649
|
+
this.processEnv = processEnv;
|
|
1650
|
+
}
|
|
1651
|
+
vaultId;
|
|
1652
|
+
definition;
|
|
1653
|
+
processEnv;
|
|
1654
|
+
authenticated = false;
|
|
1655
|
+
async authenticate(_authConfig) {
|
|
1656
|
+
void _authConfig;
|
|
1657
|
+
this.authenticated = true;
|
|
1658
|
+
}
|
|
1659
|
+
isAuthenticated() {
|
|
1660
|
+
return this.authenticated;
|
|
1661
|
+
}
|
|
1662
|
+
resolveEnvVar(ref) {
|
|
1663
|
+
if (this.processEnv[ref] !== void 0) {
|
|
1664
|
+
return ref;
|
|
1665
|
+
}
|
|
1666
|
+
return Object.entries(this.definition.mapping ?? {}).find(([, logicalRef]) => logicalRef === ref)?.[0];
|
|
1667
|
+
}
|
|
1668
|
+
async batchGet(refs) {
|
|
1669
|
+
this.authenticated = true;
|
|
1670
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1671
|
+
for (const ref of Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right))) {
|
|
1672
|
+
const envVar = this.resolveEnvVar(ref);
|
|
1673
|
+
const value = envVar ? this.processEnv[envVar] : void 0;
|
|
1674
|
+
if (value !== void 0) {
|
|
1675
|
+
resolved.set(ref, value);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
return resolved;
|
|
1679
|
+
}
|
|
1680
|
+
async get(ref) {
|
|
1681
|
+
const envVar = this.resolveEnvVar(ref);
|
|
1682
|
+
this.authenticated = true;
|
|
1683
|
+
return envVar ? this.processEnv[envVar] : void 0;
|
|
1684
|
+
}
|
|
1685
|
+
async set(ref, value) {
|
|
1686
|
+
void ref;
|
|
1687
|
+
void value;
|
|
1688
|
+
throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be written by CNOS.`);
|
|
1689
|
+
}
|
|
1690
|
+
async delete(ref) {
|
|
1691
|
+
void ref;
|
|
1692
|
+
throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be mutated by CNOS.`);
|
|
1693
|
+
}
|
|
1694
|
+
async list() {
|
|
1695
|
+
return Object.values(this.definition.mapping ?? {}).sort((left, right) => left.localeCompare(right));
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
|
|
1699
|
+
// ../core/src/secrets/providers/local.ts
|
|
1700
|
+
var LocalSecretVaultProvider = class _LocalSecretVaultProvider {
|
|
1701
|
+
constructor(vaultId, definition, processEnv = process.env, storeRoot = resolveSecretStoreRoot(processEnv)) {
|
|
1702
|
+
this.vaultId = vaultId;
|
|
1703
|
+
this.processEnv = processEnv;
|
|
1704
|
+
this.storeRoot = storeRoot;
|
|
1705
|
+
this.definition = definition;
|
|
1706
|
+
}
|
|
1707
|
+
vaultId;
|
|
1708
|
+
processEnv;
|
|
1709
|
+
storeRoot;
|
|
1710
|
+
authConfig;
|
|
1711
|
+
definition;
|
|
1712
|
+
static fromVaults(vaults, vaultId, processEnv) {
|
|
1713
|
+
const definition = resolveVaultDefinition(vaults, vaultId);
|
|
1714
|
+
return new _LocalSecretVaultProvider(vaultId, definition, processEnv);
|
|
1715
|
+
}
|
|
1716
|
+
async authenticate(authConfig) {
|
|
1717
|
+
this.authConfig = authConfig;
|
|
1718
|
+
await this.list();
|
|
1719
|
+
}
|
|
1720
|
+
isAuthenticated() {
|
|
1721
|
+
return Boolean(this.authConfig);
|
|
1722
|
+
}
|
|
1723
|
+
async requireAuth() {
|
|
1724
|
+
if (this.authConfig) {
|
|
1725
|
+
return this.authConfig;
|
|
1726
|
+
}
|
|
1727
|
+
const resolved = await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
|
|
1728
|
+
if (!resolved) {
|
|
1729
|
+
throw new CnosAuthenticationError(
|
|
1730
|
+
`Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
this.authConfig = resolved;
|
|
1734
|
+
return resolved;
|
|
1735
|
+
}
|
|
1736
|
+
async batchGet(refs) {
|
|
1737
|
+
const auth = await this.requireAuth();
|
|
1738
|
+
const entries = await Promise.all(
|
|
1739
|
+
Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right)).map(async (ref) => [ref, await readLocalSecret(this.storeRoot, ref, auth, this.vaultId)])
|
|
1740
|
+
);
|
|
1741
|
+
return new Map(entries);
|
|
1742
|
+
}
|
|
1743
|
+
async get(ref) {
|
|
1744
|
+
const auth = await this.requireAuth();
|
|
1745
|
+
try {
|
|
1746
|
+
return await readLocalSecret(this.storeRoot, ref, auth, this.vaultId);
|
|
1747
|
+
} catch {
|
|
1748
|
+
return void 0;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
async set(ref, value) {
|
|
1752
|
+
const auth = await this.requireAuth();
|
|
1753
|
+
await writeLocalSecret(this.storeRoot, ref, value, auth, this.vaultId);
|
|
1754
|
+
await appendAuditEvent(
|
|
1755
|
+
{
|
|
1756
|
+
action: "write",
|
|
1757
|
+
vault: this.vaultId,
|
|
1758
|
+
ref,
|
|
1759
|
+
caller: "cli"
|
|
1760
|
+
},
|
|
1761
|
+
this.processEnv
|
|
1762
|
+
);
|
|
1763
|
+
}
|
|
1764
|
+
async delete(ref) {
|
|
1765
|
+
const auth = await this.requireAuth();
|
|
1766
|
+
await deleteLocalSecret(this.storeRoot, ref, auth, this.vaultId);
|
|
1767
|
+
await appendAuditEvent(
|
|
1768
|
+
{
|
|
1769
|
+
action: "delete",
|
|
1770
|
+
vault: this.vaultId,
|
|
1771
|
+
ref,
|
|
1772
|
+
caller: "cli"
|
|
1773
|
+
},
|
|
1774
|
+
this.processEnv
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
async list() {
|
|
1778
|
+
const auth = this.authConfig ?? await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
|
|
1779
|
+
if (!auth) {
|
|
1780
|
+
throw new CnosAuthenticationError(
|
|
1781
|
+
`Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
|
|
1782
|
+
);
|
|
1783
|
+
}
|
|
1784
|
+
this.authConfig = auth;
|
|
1785
|
+
return listLocalSecrets(this.storeRoot, auth, this.vaultId);
|
|
1786
|
+
}
|
|
1787
|
+
};
|
|
1788
|
+
|
|
1789
|
+
// ../core/src/secrets/providers/registry.ts
|
|
1790
|
+
function createSecretVaultProvider(vaultId, definition, processEnv) {
|
|
1791
|
+
if (definition.provider === "local") {
|
|
1792
|
+
return new LocalSecretVaultProvider(vaultId, definition, processEnv);
|
|
1793
|
+
}
|
|
1794
|
+
if (definition.provider === "github-secrets") {
|
|
1795
|
+
return new GithubSecretsVaultProvider(vaultId, definition, processEnv);
|
|
1796
|
+
}
|
|
1797
|
+
throw new CnosManifestError(`Unsupported vault provider: ${definition.provider}`);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
// ../core/src/secrets/prompt.ts
|
|
1801
|
+
var import_node_readline = __toESM(require("readline"), 1);
|
|
1802
|
+
var import_node_stream = require("stream");
|
|
1803
|
+
async function promptHidden(message) {
|
|
1804
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1805
|
+
return void 0;
|
|
1806
|
+
}
|
|
1807
|
+
const mutableStdout = new WritableMask();
|
|
1808
|
+
const rl = import_node_readline.default.createInterface({
|
|
1809
|
+
input: process.stdin,
|
|
1810
|
+
output: mutableStdout,
|
|
1811
|
+
terminal: true
|
|
1812
|
+
});
|
|
1813
|
+
try {
|
|
1814
|
+
mutableStdout.muted = true;
|
|
1815
|
+
const value = await new Promise((resolve) => {
|
|
1816
|
+
rl.question(message, resolve);
|
|
1817
|
+
});
|
|
1818
|
+
process.stdout.write("\n");
|
|
1819
|
+
return value;
|
|
1820
|
+
} finally {
|
|
1821
|
+
rl.close();
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
var WritableMask = class extends import_node_stream.Writable {
|
|
1825
|
+
muted = false;
|
|
1826
|
+
_write(chunk, _encoding, callback) {
|
|
1827
|
+
if (!this.muted) {
|
|
1828
|
+
process.stdout.write(chunk);
|
|
1829
|
+
}
|
|
1830
|
+
callback();
|
|
1831
|
+
}
|
|
1832
|
+
};
|
|
1833
|
+
|
|
1834
|
+
// ../core/src/secrets/resolveAuth.ts
|
|
1835
|
+
function toAuthError(vaultId, sources) {
|
|
1836
|
+
return new CnosAuthenticationError(
|
|
1837
|
+
`Cannot authenticate to vault "${vaultId}". Tried: ${sources.join(", ")}. Set ${getVaultPassphraseEnvVar(vaultId)} or run cnos vault auth ${vaultId}.`
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
|
|
1841
|
+
const sessionKey = await resolveVaultSessionKey(vaultId, processEnv);
|
|
1842
|
+
if (sessionKey) {
|
|
1843
|
+
return {
|
|
1844
|
+
derivedKey: sessionKey,
|
|
1845
|
+
method: "keychain",
|
|
1846
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
if (definition.provider === "github-secrets") {
|
|
1850
|
+
return {
|
|
1851
|
+
method: definition.auth?.method ?? "environment",
|
|
1852
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
const sources = definition.auth?.passphrase?.from ?? [getVaultPassphraseEnvVar(vaultId)];
|
|
1856
|
+
for (const source of sources) {
|
|
1857
|
+
if (source.startsWith("env:")) {
|
|
1858
|
+
const value = processEnv[source.slice(4)];
|
|
1859
|
+
if (value) {
|
|
1860
|
+
return {
|
|
1861
|
+
passphrase: value,
|
|
1862
|
+
method: "passphrase",
|
|
1863
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
if (source.startsWith("keychain:")) {
|
|
1868
|
+
const value = await readKeychain(source.slice("keychain:".length));
|
|
1869
|
+
if (value) {
|
|
1870
|
+
return {
|
|
1871
|
+
derivedKey: Buffer.from(value, "hex"),
|
|
1872
|
+
method: "keychain",
|
|
1873
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
if (source === "prompt") {
|
|
1878
|
+
const value = await promptHidden(`Enter passphrase for vault "${vaultId}": `);
|
|
1879
|
+
if (value) {
|
|
1880
|
+
return {
|
|
1881
|
+
passphrase: value,
|
|
1882
|
+
method: "passphrase",
|
|
1883
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
const fallback = resolveSecretPassphrase(vaultId, processEnv);
|
|
1889
|
+
if (fallback) {
|
|
1890
|
+
return {
|
|
1891
|
+
passphrase: fallback,
|
|
1892
|
+
method: "passphrase",
|
|
1893
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
throw toAuthError(vaultId, [getVaultSessionKeyEnvVar(vaultId), ...sources]);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
// ../core/src/secrets/batchResolve.ts
|
|
1900
|
+
function collectSecretDescriptors(graph) {
|
|
1901
|
+
return Array.from(graph.entries.values()).filter((entry) => entry.namespace === "secret" && isSecretReference(entry.value)).map((entry) => ({
|
|
1902
|
+
logicalKey: entry.key,
|
|
1903
|
+
ref: entry.value
|
|
1904
|
+
}));
|
|
1905
|
+
}
|
|
1906
|
+
async function batchResolveSecrets(graph, manifest, processEnv = process.env) {
|
|
1907
|
+
const cache = new SecretCache();
|
|
1908
|
+
const descriptors = collectSecretDescriptors(graph);
|
|
1909
|
+
const grouped = descriptors.reduce((accumulator, descriptor) => {
|
|
1910
|
+
const vaultId = descriptor.ref.vault ?? "default";
|
|
1911
|
+
const bucket = accumulator.get(vaultId) ?? [];
|
|
1912
|
+
bucket.push(descriptor);
|
|
1913
|
+
accumulator.set(vaultId, bucket);
|
|
1914
|
+
return accumulator;
|
|
1915
|
+
}, /* @__PURE__ */ new Map());
|
|
1916
|
+
for (const [vaultId, refs] of grouped) {
|
|
1917
|
+
const definition = manifest.vaults[vaultId] ?? { provider: "local", auth: { passphrase: { from: [] } } };
|
|
1918
|
+
const provider = createSecretVaultProvider(vaultId, definition, processEnv);
|
|
1919
|
+
const auth = await resolveVaultAuth(vaultId, definition, processEnv);
|
|
1920
|
+
await provider.authenticate(auth);
|
|
1921
|
+
const resolved = await provider.batchGet(refs.map((entry) => entry.ref.ref));
|
|
1922
|
+
cache.load(vaultId, resolved);
|
|
1923
|
+
await appendAuditEvent(
|
|
1924
|
+
{
|
|
1925
|
+
action: "batch_read",
|
|
1926
|
+
vault: vaultId,
|
|
1927
|
+
refs: Array.from(resolved.keys()).sort((left, right) => left.localeCompare(right)),
|
|
1928
|
+
caller: "runtime",
|
|
1929
|
+
workspace: graph.workspace.workspaceId,
|
|
1930
|
+
profile: graph.profile
|
|
1931
|
+
},
|
|
1932
|
+
processEnv
|
|
1933
|
+
);
|
|
1934
|
+
}
|
|
1935
|
+
return cache;
|
|
1936
|
+
}
|
|
1937
|
+
function resolveSecretEntryValue(key, value, cache) {
|
|
1938
|
+
if (!key.startsWith("secret.") || !isSecretReference(value)) {
|
|
1939
|
+
return value;
|
|
1940
|
+
}
|
|
1941
|
+
const vaultId = value.vault ?? "default";
|
|
1942
|
+
return cache.get(vaultId, value.ref) ?? value;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1100
1945
|
// ../core/src/runtime/projection.ts
|
|
1101
1946
|
function setNestedValue(target, pathSegments, value) {
|
|
1102
1947
|
const [head, ...tail] = pathSegments;
|
|
@@ -1147,55 +1992,6 @@ function requireValue(graph, key) {
|
|
|
1147
1992
|
return value;
|
|
1148
1993
|
}
|
|
1149
1994
|
|
|
1150
|
-
// ../core/src/utils/secretStore.ts
|
|
1151
|
-
var import_node_crypto = require("crypto");
|
|
1152
|
-
var import_promises6 = require("fs/promises");
|
|
1153
|
-
var import_node_path6 = __toESM(require("path"), 1);
|
|
1154
|
-
function isObject(value) {
|
|
1155
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1156
|
-
}
|
|
1157
|
-
function isSecretReference(value) {
|
|
1158
|
-
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));
|
|
1159
|
-
}
|
|
1160
|
-
function resolveSecretStoreRoot(processEnv = process.env) {
|
|
1161
|
-
return import_node_path6.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
|
|
1162
|
-
}
|
|
1163
|
-
function resolveSecretStoreFile(storeRoot, ref, vault = "default") {
|
|
1164
|
-
return import_node_path6.default.join(storeRoot, "vaults", vault, "store", ...ref.split("/")).concat(".json");
|
|
1165
|
-
}
|
|
1166
|
-
function deriveKey(passphrase, salt) {
|
|
1167
|
-
return (0, import_node_crypto.scryptSync)(passphrase, salt, 32);
|
|
1168
|
-
}
|
|
1169
|
-
function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
|
|
1170
|
-
const vaultToken = vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
1171
|
-
return processEnv[`CNOS_SECRET_PASSPHRASE_${vaultToken}`] ?? processEnv.CNOS_SECRET_PASSPHRASE;
|
|
1172
|
-
}
|
|
1173
|
-
function decryptDocument(document, passphrase) {
|
|
1174
|
-
const salt = Buffer.from(document.salt, "base64");
|
|
1175
|
-
const iv = Buffer.from(document.iv, "base64");
|
|
1176
|
-
const tag = Buffer.from(document.tag, "base64");
|
|
1177
|
-
const ciphertext = Buffer.from(document.ciphertext, "base64");
|
|
1178
|
-
const key = deriveKey(passphrase, salt);
|
|
1179
|
-
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", key, iv);
|
|
1180
|
-
decipher.setAuthTag(tag);
|
|
1181
|
-
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
1182
|
-
return plaintext.toString("utf8");
|
|
1183
|
-
}
|
|
1184
|
-
async function readLocalSecret(storeRoot, ref, passphrase, vault = "default") {
|
|
1185
|
-
if (!passphrase) {
|
|
1186
|
-
throw new CnosManifestError(
|
|
1187
|
-
`Missing CNOS secret passphrase for local secret ref "${ref}". Set CNOS_SECRET_PASSPHRASE or pass processEnv explicitly.`
|
|
1188
|
-
);
|
|
1189
|
-
}
|
|
1190
|
-
const filePath = resolveSecretStoreFile(storeRoot, ref, vault);
|
|
1191
|
-
const source = await (0, import_promises6.readFile)(filePath, "utf8");
|
|
1192
|
-
const document = JSON.parse(source);
|
|
1193
|
-
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") {
|
|
1194
|
-
throw new CnosManifestError("Invalid local secret document", filePath);
|
|
1195
|
-
}
|
|
1196
|
-
return decryptDocument(document, passphrase);
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
1995
|
// ../core/src/runtime/toEnv.ts
|
|
1200
1996
|
function normalizeEnvValue(value) {
|
|
1201
1997
|
if (value === void 0 || value === null) {
|
|
@@ -1277,28 +2073,42 @@ function toPublicEnv(graph, manifest, options = {}) {
|
|
|
1277
2073
|
}
|
|
1278
2074
|
|
|
1279
2075
|
// ../core/src/orchestrator/runtime.ts
|
|
1280
|
-
function createRuntime(manifest, graph, plugins = []) {
|
|
2076
|
+
function createRuntime(manifest, graph, plugins = [], secretCache) {
|
|
2077
|
+
function readLogicalKey(key) {
|
|
2078
|
+
const entry = graph.entries.get(key);
|
|
2079
|
+
if (!entry) {
|
|
2080
|
+
return void 0;
|
|
2081
|
+
}
|
|
2082
|
+
if (!secretCache) {
|
|
2083
|
+
return entry.value;
|
|
2084
|
+
}
|
|
2085
|
+
return resolveSecretEntryValue(key, entry.value, secretCache);
|
|
2086
|
+
}
|
|
1281
2087
|
return {
|
|
1282
2088
|
manifest,
|
|
1283
2089
|
plugins,
|
|
1284
2090
|
graph,
|
|
1285
2091
|
read(key) {
|
|
1286
|
-
return
|
|
2092
|
+
return readLogicalKey(key);
|
|
1287
2093
|
},
|
|
1288
2094
|
require(key) {
|
|
1289
|
-
|
|
2095
|
+
const value = readLogicalKey(key);
|
|
2096
|
+
if (value === void 0) {
|
|
2097
|
+
return requireValue(graph, key);
|
|
2098
|
+
}
|
|
2099
|
+
return value;
|
|
1290
2100
|
},
|
|
1291
2101
|
readOr(key, fallback) {
|
|
1292
2102
|
return readOrValue(graph, key, fallback);
|
|
1293
2103
|
},
|
|
1294
|
-
value(
|
|
1295
|
-
return
|
|
2104
|
+
value(path12) {
|
|
2105
|
+
return readLogicalKey(toLogicalKey("value", path12));
|
|
1296
2106
|
},
|
|
1297
|
-
secret(
|
|
1298
|
-
return
|
|
2107
|
+
secret(path12) {
|
|
2108
|
+
return readLogicalKey(toLogicalKey("secret", path12));
|
|
1299
2109
|
},
|
|
1300
|
-
meta(
|
|
1301
|
-
return
|
|
2110
|
+
meta(path12) {
|
|
2111
|
+
return readLogicalKey(toLogicalKey("meta", path12));
|
|
1302
2112
|
},
|
|
1303
2113
|
inspect(key) {
|
|
1304
2114
|
return inspectValue(graph, key);
|
|
@@ -1456,19 +2266,21 @@ async function createCnos(options = {}) {
|
|
|
1456
2266
|
});
|
|
1457
2267
|
const schemaApplied = applySchemaRules(graph, loadedManifest.manifest.schema);
|
|
1458
2268
|
const promotedGraph = promoteToPublic(schemaApplied.graph, loadedManifest.manifest);
|
|
2269
|
+
const secretCache = options.secretResolution === "lazy" ? void 0 : await batchResolveSecrets(promotedGraph, loadedManifest.manifest, options.processEnv);
|
|
1459
2270
|
return createRuntime(
|
|
1460
2271
|
loadedManifest.manifest,
|
|
1461
2272
|
appendMetaEntries({
|
|
1462
2273
|
...promotedGraph,
|
|
1463
2274
|
profileSource: activeProfile.source
|
|
1464
2275
|
}, options.cnosVersion),
|
|
1465
|
-
plugins
|
|
2276
|
+
plugins,
|
|
2277
|
+
secretCache
|
|
1466
2278
|
);
|
|
1467
2279
|
}
|
|
1468
2280
|
|
|
1469
2281
|
// ../core/src/runtime/dump.ts
|
|
1470
|
-
var
|
|
1471
|
-
var
|
|
2282
|
+
var import_promises9 = require("fs/promises");
|
|
2283
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1472
2284
|
|
|
1473
2285
|
// ../core/src/utils/envNaming.ts
|
|
1474
2286
|
function normalizeMappingConfig(config = {}) {
|
|
@@ -1477,8 +2289,8 @@ function normalizeMappingConfig(config = {}) {
|
|
|
1477
2289
|
explicit: config.explicit ?? {}
|
|
1478
2290
|
};
|
|
1479
2291
|
}
|
|
1480
|
-
function fromScreamingSnake(
|
|
1481
|
-
return
|
|
2292
|
+
function fromScreamingSnake(path12) {
|
|
2293
|
+
return path12.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
|
|
1482
2294
|
}
|
|
1483
2295
|
function envVarToLogicalKey(envVar, config = {}) {
|
|
1484
2296
|
const normalized = normalizeMappingConfig(config);
|
|
@@ -1505,7 +2317,7 @@ function envVarToLogicalKey(envVar, config = {}) {
|
|
|
1505
2317
|
// package.json
|
|
1506
2318
|
var package_default = {
|
|
1507
2319
|
name: "@kitsy/cnos",
|
|
1508
|
-
version: "1.
|
|
2320
|
+
version: "1.4.0",
|
|
1509
2321
|
description: "Batteries-included CNOS runtime package wired with the official plugins.",
|
|
1510
2322
|
type: "module",
|
|
1511
2323
|
main: "./dist/index.cjs",
|
|
@@ -1517,6 +2329,16 @@ var package_default = {
|
|
|
1517
2329
|
import: "./dist/index.js",
|
|
1518
2330
|
require: "./dist/index.cjs"
|
|
1519
2331
|
},
|
|
2332
|
+
"./configure": {
|
|
2333
|
+
types: "./dist/configure/index.d.ts",
|
|
2334
|
+
import: "./dist/configure/index.js",
|
|
2335
|
+
require: "./dist/configure/index.cjs"
|
|
2336
|
+
},
|
|
2337
|
+
"./create": {
|
|
2338
|
+
types: "./dist/configure/index.d.ts",
|
|
2339
|
+
import: "./dist/configure/index.js",
|
|
2340
|
+
require: "./dist/configure/index.cjs"
|
|
2341
|
+
},
|
|
1520
2342
|
"./internal": {
|
|
1521
2343
|
types: "./dist/internal.d.ts",
|
|
1522
2344
|
import: "./dist/internal.js",
|
|
@@ -1694,8 +2516,8 @@ function createCliArgsPlugin() {
|
|
|
1694
2516
|
}
|
|
1695
2517
|
|
|
1696
2518
|
// ../../plugins/dotenv/src/index.ts
|
|
1697
|
-
var
|
|
1698
|
-
var
|
|
2519
|
+
var import_promises10 = require("fs/promises");
|
|
2520
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
1699
2521
|
var DOTENV_PLUGIN_ID = "@kitsy/cnos/plugins/dotenv";
|
|
1700
2522
|
function parseDoubleQuoted(value) {
|
|
1701
2523
|
return value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
@@ -1752,7 +2574,7 @@ function dotenvEntriesFromObject(values, mapping = {}, originFile, workspaceId =
|
|
|
1752
2574
|
}
|
|
1753
2575
|
async function readIfPresent(filePath) {
|
|
1754
2576
|
try {
|
|
1755
|
-
return await (0,
|
|
2577
|
+
return await (0, import_promises10.readFile)(filePath, "utf8");
|
|
1756
2578
|
} catch {
|
|
1757
2579
|
return void 0;
|
|
1758
2580
|
}
|
|
@@ -1771,7 +2593,7 @@ function createDotenvPlugin() {
|
|
|
1771
2593
|
workspace: workspaceRoot.workspaceId
|
|
1772
2594
|
});
|
|
1773
2595
|
for (const fileName of fileNames) {
|
|
1774
|
-
const absolutePath =
|
|
2596
|
+
const absolutePath = import_node_path10.default.join(envRoot, fileName);
|
|
1775
2597
|
const document = await readIfPresent(absolutePath);
|
|
1776
2598
|
if (!document) {
|
|
1777
2599
|
continue;
|
|
@@ -1780,7 +2602,7 @@ function createDotenvPlugin() {
|
|
|
1780
2602
|
...dotenvEntriesFromObject(
|
|
1781
2603
|
parseDotenv(document),
|
|
1782
2604
|
config.envMapping,
|
|
1783
|
-
toPortablePath(
|
|
2605
|
+
toPortablePath(import_node_path10.default.relative(import_node_path10.default.dirname(context.manifestRoot), absolutePath)),
|
|
1784
2606
|
workspaceRoot.workspaceId
|
|
1785
2607
|
)
|
|
1786
2608
|
);
|
|
@@ -1818,32 +2640,32 @@ function createPublicEnvExportPlugin() {
|
|
|
1818
2640
|
}
|
|
1819
2641
|
|
|
1820
2642
|
// ../../plugins/filesystem/src/filesystemSecretsReader.ts
|
|
1821
|
-
var
|
|
2643
|
+
var import_promises12 = require("fs/promises");
|
|
1822
2644
|
|
|
1823
2645
|
// ../../plugins/filesystem/src/helpers.ts
|
|
1824
|
-
var
|
|
1825
|
-
var
|
|
2646
|
+
var import_promises11 = require("fs/promises");
|
|
2647
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
1826
2648
|
var YAML_EXTENSIONS = /* @__PURE__ */ new Set([".yml", ".yaml"]);
|
|
1827
2649
|
var FILESYSTEM_PLUGIN_ID = "@kitsy/cnos/plugins/filesystem";
|
|
1828
2650
|
async function existsDirectory(targetPath) {
|
|
1829
2651
|
try {
|
|
1830
|
-
const
|
|
1831
|
-
void
|
|
2652
|
+
const stat2 = await (0, import_promises11.readdir)(targetPath);
|
|
2653
|
+
void stat2;
|
|
1832
2654
|
return true;
|
|
1833
2655
|
} catch {
|
|
1834
2656
|
return false;
|
|
1835
2657
|
}
|
|
1836
2658
|
}
|
|
1837
2659
|
async function collectYamlFiles(root) {
|
|
1838
|
-
const entries = await (0,
|
|
2660
|
+
const entries = await (0, import_promises11.readdir)(root, { withFileTypes: true });
|
|
1839
2661
|
const results = [];
|
|
1840
2662
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
1841
|
-
const absolutePath =
|
|
2663
|
+
const absolutePath = import_node_path11.default.join(root, entry.name);
|
|
1842
2664
|
if (entry.isDirectory()) {
|
|
1843
2665
|
results.push(...await collectYamlFiles(absolutePath));
|
|
1844
2666
|
continue;
|
|
1845
2667
|
}
|
|
1846
|
-
if (entry.isFile() && YAML_EXTENSIONS.has(
|
|
2668
|
+
if (entry.isFile() && YAML_EXTENSIONS.has(import_node_path11.default.extname(entry.name).toLowerCase())) {
|
|
1847
2669
|
results.push(absolutePath);
|
|
1848
2670
|
}
|
|
1849
2671
|
}
|
|
@@ -1851,16 +2673,16 @@ async function collectYamlFiles(root) {
|
|
|
1851
2673
|
}
|
|
1852
2674
|
async function collectFilesystemLayerFiles(manifestRoot, workspaceRoots, sourceRoot, activeLayers) {
|
|
1853
2675
|
const files = [];
|
|
1854
|
-
const repoRoot =
|
|
2676
|
+
const repoRoot = import_node_path11.default.dirname(manifestRoot);
|
|
1855
2677
|
for (const workspaceRoot of workspaceRoots) {
|
|
1856
|
-
const resolvedRoot =
|
|
2678
|
+
const resolvedRoot = import_node_path11.default.resolve(workspaceRoot.path, sourceRoot);
|
|
1857
2679
|
for (const layer of activeLayers) {
|
|
1858
|
-
const layerRoot =
|
|
2680
|
+
const layerRoot = import_node_path11.default.join(resolvedRoot, layer);
|
|
1859
2681
|
if (!await existsDirectory(layerRoot)) {
|
|
1860
2682
|
continue;
|
|
1861
2683
|
}
|
|
1862
2684
|
for (const absolutePath of await collectYamlFiles(layerRoot)) {
|
|
1863
|
-
const relativePath =
|
|
2685
|
+
const relativePath = import_node_path11.default.relative(repoRoot, absolutePath);
|
|
1864
2686
|
files.push({
|
|
1865
2687
|
absolutePath,
|
|
1866
2688
|
relativePath: toPortablePath(relativePath.startsWith("..") ? absolutePath : relativePath),
|
|
@@ -1910,31 +2732,6 @@ function yamlObjectToEntries(document, filePath, namespace, sourceId, workspaceI
|
|
|
1910
2732
|
}
|
|
1911
2733
|
}));
|
|
1912
2734
|
}
|
|
1913
|
-
async function resolveSecretValue(value, processEnv) {
|
|
1914
|
-
if (!isSecretReference(value)) {
|
|
1915
|
-
return value;
|
|
1916
|
-
}
|
|
1917
|
-
if (value.provider === "local") {
|
|
1918
|
-
const passphrase = resolveSecretPassphrase(value.vault, processEnv);
|
|
1919
|
-
if (!passphrase) {
|
|
1920
|
-
return value;
|
|
1921
|
-
}
|
|
1922
|
-
return readLocalSecret(
|
|
1923
|
-
resolveSecretStoreRoot(processEnv),
|
|
1924
|
-
value.ref,
|
|
1925
|
-
passphrase,
|
|
1926
|
-
value.vault
|
|
1927
|
-
);
|
|
1928
|
-
}
|
|
1929
|
-
if (value.provider === "env" || value.provider === "github-secrets") {
|
|
1930
|
-
const resolved = processEnv?.[value.ref];
|
|
1931
|
-
if (resolved === void 0) {
|
|
1932
|
-
return value;
|
|
1933
|
-
}
|
|
1934
|
-
return resolved;
|
|
1935
|
-
}
|
|
1936
|
-
return value;
|
|
1937
|
-
}
|
|
1938
2735
|
function toSecretReferenceMetadata(value) {
|
|
1939
2736
|
if (!isSecretReference(value)) {
|
|
1940
2737
|
return void 0;
|
|
@@ -1962,14 +2759,12 @@ function createFilesystemSecretsPlugin() {
|
|
|
1962
2759
|
);
|
|
1963
2760
|
const entries = [];
|
|
1964
2761
|
for (const file of files) {
|
|
1965
|
-
const document = await (0,
|
|
2762
|
+
const document = await (0, import_promises12.readFile)(file.absolutePath, "utf8");
|
|
1966
2763
|
const fileEntries = filesystemSecretsReader(file.relativePath, document, file.workspaceId);
|
|
1967
2764
|
for (const entry of fileEntries) {
|
|
1968
2765
|
const metadata = toSecretReferenceMetadata(entry.value);
|
|
1969
|
-
const resolvedValue = await resolveSecretValue(entry.value, context.processEnv);
|
|
1970
2766
|
entries.push({
|
|
1971
2767
|
...entry,
|
|
1972
|
-
value: resolvedValue,
|
|
1973
2768
|
...metadata ? { metadata } : {}
|
|
1974
2769
|
});
|
|
1975
2770
|
}
|
|
@@ -1980,7 +2775,7 @@ function createFilesystemSecretsPlugin() {
|
|
|
1980
2775
|
}
|
|
1981
2776
|
|
|
1982
2777
|
// ../../plugins/filesystem/src/filesystemValuesReader.ts
|
|
1983
|
-
var
|
|
2778
|
+
var import_promises13 = require("fs/promises");
|
|
1984
2779
|
function filesystemValuesReader(filePath, document, workspaceId = "default") {
|
|
1985
2780
|
return yamlObjectToEntries(document, filePath, "value", "filesystem-values", workspaceId);
|
|
1986
2781
|
}
|
|
@@ -1996,11 +2791,30 @@ function createFilesystemValuesPlugin() {
|
|
|
1996
2791
|
sourceRoot,
|
|
1997
2792
|
context.profileActivation.values
|
|
1998
2793
|
);
|
|
2794
|
+
const customNamespaces = Object.entries(context.manifest.namespaces).filter(
|
|
2795
|
+
([namespace, definition]) => namespace !== "value" && namespace !== "secret" && definition.kind === "data" && !definition.sensitive
|
|
2796
|
+
).map(([namespace]) => namespace);
|
|
1999
2797
|
const entries = [];
|
|
2000
2798
|
for (const file of files) {
|
|
2001
|
-
const document = await (0,
|
|
2799
|
+
const document = await (0, import_promises13.readFile)(file.absolutePath, "utf8");
|
|
2002
2800
|
entries.push(...filesystemValuesReader(file.relativePath, document, file.workspaceId));
|
|
2003
2801
|
}
|
|
2802
|
+
for (const namespace of customNamespaces) {
|
|
2803
|
+
const layers = [
|
|
2804
|
+
namespace,
|
|
2805
|
+
...context.profileChain.filter((profile) => profile !== "base").map((profile) => `profiles/${profile}/${namespace}`)
|
|
2806
|
+
];
|
|
2807
|
+
const namespaceFiles = await collectFilesystemLayerFiles(
|
|
2808
|
+
context.manifestRoot,
|
|
2809
|
+
context.workspace.workspaceRoots,
|
|
2810
|
+
sourceRoot,
|
|
2811
|
+
layers
|
|
2812
|
+
);
|
|
2813
|
+
for (const file of namespaceFiles) {
|
|
2814
|
+
const document = await (0, import_promises13.readFile)(file.absolutePath, "utf8");
|
|
2815
|
+
entries.push(...yamlObjectToEntries(document, file.relativePath, namespace, "filesystem-values", file.workspaceId));
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2004
2818
|
return entries;
|
|
2005
2819
|
}
|
|
2006
2820
|
};
|
|
@@ -2008,6 +2822,11 @@ function createFilesystemValuesPlugin() {
|
|
|
2008
2822
|
|
|
2009
2823
|
// ../../plugins/process-env/src/index.ts
|
|
2010
2824
|
var PROCESS_ENV_PLUGIN_ID = "@kitsy/cnos/plugins/process-env";
|
|
2825
|
+
var PROCESS_GRAPH_OMIT = /* @__PURE__ */ new Set([
|
|
2826
|
+
"__CNOS_GRAPH__",
|
|
2827
|
+
"__CNOS_SECRET_PAYLOAD__",
|
|
2828
|
+
"__CNOS_SESSION_KEY__"
|
|
2829
|
+
]);
|
|
2011
2830
|
function processEnvEntriesFromObject(env, mapping = {}, workspaceId = "default") {
|
|
2012
2831
|
return Object.entries(env).flatMap(([envVar, value]) => {
|
|
2013
2832
|
if (typeof value !== "string") {
|
|
@@ -2032,17 +2851,77 @@ function processEnvEntriesFromObject(env, mapping = {}, workspaceId = "default")
|
|
|
2032
2851
|
];
|
|
2033
2852
|
});
|
|
2034
2853
|
}
|
|
2854
|
+
function processNamespaceEntriesFromContext(env, workspaceId = "default") {
|
|
2855
|
+
const envEntries = Object.entries(env).filter((entry) => typeof entry[1] === "string").filter(([envVar]) => !PROCESS_GRAPH_OMIT.has(envVar)).map(([envVar, value]) => ({
|
|
2856
|
+
key: `process.env.${envVar}`,
|
|
2857
|
+
value,
|
|
2858
|
+
namespace: "process",
|
|
2859
|
+
sourceId: "process-runtime",
|
|
2860
|
+
pluginId: PROCESS_ENV_PLUGIN_ID,
|
|
2861
|
+
workspaceId,
|
|
2862
|
+
origin: {
|
|
2863
|
+
envVar
|
|
2864
|
+
}
|
|
2865
|
+
}));
|
|
2866
|
+
const runtimeEntries = [
|
|
2867
|
+
{
|
|
2868
|
+
key: "process.cwd",
|
|
2869
|
+
value: process.cwd(),
|
|
2870
|
+
namespace: "process",
|
|
2871
|
+
sourceId: "process-runtime",
|
|
2872
|
+
pluginId: PROCESS_ENV_PLUGIN_ID,
|
|
2873
|
+
workspaceId
|
|
2874
|
+
},
|
|
2875
|
+
{
|
|
2876
|
+
key: "process.platform",
|
|
2877
|
+
value: process.platform,
|
|
2878
|
+
namespace: "process",
|
|
2879
|
+
sourceId: "process-runtime",
|
|
2880
|
+
pluginId: PROCESS_ENV_PLUGIN_ID,
|
|
2881
|
+
workspaceId
|
|
2882
|
+
},
|
|
2883
|
+
{
|
|
2884
|
+
key: "process.arch",
|
|
2885
|
+
value: process.arch,
|
|
2886
|
+
namespace: "process",
|
|
2887
|
+
sourceId: "process-runtime",
|
|
2888
|
+
pluginId: PROCESS_ENV_PLUGIN_ID,
|
|
2889
|
+
workspaceId
|
|
2890
|
+
},
|
|
2891
|
+
{
|
|
2892
|
+
key: "process.node.version",
|
|
2893
|
+
value: process.version,
|
|
2894
|
+
namespace: "process",
|
|
2895
|
+
sourceId: "process-runtime",
|
|
2896
|
+
pluginId: PROCESS_ENV_PLUGIN_ID,
|
|
2897
|
+
workspaceId
|
|
2898
|
+
},
|
|
2899
|
+
{
|
|
2900
|
+
key: "process.args.raw",
|
|
2901
|
+
value: process.argv.slice(2),
|
|
2902
|
+
namespace: "process",
|
|
2903
|
+
sourceId: "process-runtime",
|
|
2904
|
+
pluginId: PROCESS_ENV_PLUGIN_ID,
|
|
2905
|
+
workspaceId
|
|
2906
|
+
}
|
|
2907
|
+
];
|
|
2908
|
+
return [...runtimeEntries, ...envEntries];
|
|
2909
|
+
}
|
|
2035
2910
|
function createProcessEnvPlugin() {
|
|
2036
2911
|
return {
|
|
2037
2912
|
id: "process-env",
|
|
2038
2913
|
kind: "loader",
|
|
2039
2914
|
async load(context) {
|
|
2040
2915
|
const config = context.manifestConfig;
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2916
|
+
const env = context.processEnv ?? process.env;
|
|
2917
|
+
return [
|
|
2918
|
+
...processEnvEntriesFromObject(
|
|
2919
|
+
env,
|
|
2920
|
+
config.envMapping,
|
|
2921
|
+
context.workspace.workspaceId
|
|
2922
|
+
),
|
|
2923
|
+
...processNamespaceEntriesFromContext(env, context.workspace.workspaceId)
|
|
2924
|
+
];
|
|
2046
2925
|
}
|
|
2047
2926
|
};
|
|
2048
2927
|
}
|
|
@@ -2065,9 +2944,11 @@ function defaultPlugins() {
|
|
|
2065
2944
|
// src/runtime/state.ts
|
|
2066
2945
|
var singletonRuntime;
|
|
2067
2946
|
var singletonReady;
|
|
2947
|
+
var bootstrappedSecretHydrationRequired = false;
|
|
2068
2948
|
function setSingletonRuntime(runtime) {
|
|
2069
2949
|
singletonRuntime = runtime;
|
|
2070
2950
|
singletonReady = Promise.resolve(runtime);
|
|
2951
|
+
bootstrappedSecretHydrationRequired = false;
|
|
2071
2952
|
return runtime;
|
|
2072
2953
|
}
|
|
2073
2954
|
|
|
@@ -2075,6 +2956,7 @@ function setSingletonRuntime(runtime) {
|
|
|
2075
2956
|
async function createCnos2(options = {}) {
|
|
2076
2957
|
const runtime = await createCnos({
|
|
2077
2958
|
...options,
|
|
2959
|
+
processEnv: options.processEnv ?? process.env,
|
|
2078
2960
|
cnosVersion: package_default.version,
|
|
2079
2961
|
plugins: [...defaultPlugins(), ...options.plugins ?? []]
|
|
2080
2962
|
});
|