@kitsy/cnos 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/build/index.cjs +902 -113
- package/dist/build/index.d.cts +1 -1
- package/dist/build/index.d.ts +1 -1
- package/dist/build/index.js +22 -10
- package/dist/{chunk-WHUGFPE4.js → chunk-CDXJISGB.js} +1 -1
- package/dist/{chunk-APCTXRUN.js → chunk-DRKDNY4I.js} +998 -191
- package/dist/chunk-E7SE6N26.js +189 -0
- package/dist/{chunk-SO5XREEU.js → chunk-EDCLLCNL.js} +32 -11
- package/dist/{chunk-SXTMTACL.js → chunk-FC3IV6A7.js} +1 -31
- package/dist/{chunk-MLQGYCO7.js → chunk-JDII6O72.js} +1 -1
- package/dist/chunk-K6QYI2T4.js +105 -0
- package/dist/{chunk-EIN55XXA.js → chunk-OOKFRWTN.js} +1 -1
- package/dist/{chunk-ZA74BO47.js → chunk-OWUZQ4OH.js} +1 -1
- package/dist/{chunk-RD5WMHPM.js → chunk-QTKXPY3N.js} +1 -1
- package/dist/configure/index.cjs +2928 -0
- package/dist/configure/index.d.cts +12 -0
- package/dist/configure/index.d.ts +12 -0
- package/dist/configure/index.js +24 -0
- package/dist/{envNaming-BTJpH93W.d.cts → envNaming-D6k66myh.d.cts} +1 -1
- package/dist/{envNaming-CcsqAel3.d.ts → envNaming-Dy3WYiGK.d.ts} +1 -1
- package/dist/index.cjs +1142 -178
- package/dist/index.d.cts +2 -13
- package/dist/index.d.ts +2 -13
- package/dist/index.js +13 -25
- package/dist/internal.cjs +1512 -80
- package/dist/internal.d.cts +170 -14
- package/dist/internal.d.ts +170 -14
- package/dist/internal.js +645 -5
- package/dist/plugin/basic-schema.cjs +29 -2
- package/dist/plugin/basic-schema.d.cts +1 -1
- package/dist/plugin/basic-schema.d.ts +1 -1
- package/dist/plugin/basic-schema.js +2 -2
- package/dist/plugin/cli-args.cjs +29 -2
- package/dist/plugin/cli-args.d.cts +1 -1
- package/dist/plugin/cli-args.d.ts +1 -1
- package/dist/plugin/cli-args.js +2 -2
- package/dist/plugin/dotenv.cjs +36 -9
- package/dist/plugin/dotenv.d.cts +2 -2
- package/dist/plugin/dotenv.d.ts +2 -2
- package/dist/plugin/dotenv.js +2 -2
- package/dist/plugin/env-export.cjs +31 -2
- package/dist/plugin/env-export.d.cts +2 -2
- package/dist/plugin/env-export.d.ts +2 -2
- package/dist/plugin/env-export.js +2 -2
- package/dist/plugin/filesystem.cjs +46 -91
- package/dist/plugin/filesystem.d.cts +1 -1
- package/dist/plugin/filesystem.d.ts +1 -1
- package/dist/plugin/filesystem.js +2 -2
- package/dist/plugin/process-env.cjs +31 -4
- package/dist/plugin/process-env.d.cts +2 -2
- package/dist/plugin/process-env.d.ts +2 -2
- package/dist/plugin/process-env.js +2 -2
- package/dist/{plugin-DkOIT5uI.d.cts → plugin-CyNkf7Dm.d.cts} +14 -2
- package/dist/{plugin-DkOIT5uI.d.ts → plugin-CyNkf7Dm.d.ts} +14 -2
- package/dist/runtime/index.cjs +956 -128
- package/dist/runtime/index.d.cts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +11 -186
- package/dist/{toPublicEnv-DvFeV3qG.d.cts → toPublicEnv-Cz72m6y0.d.cts} +1 -1
- package/dist/{toPublicEnv-C9clvXLo.d.ts → toPublicEnv-D2PZkaN-.d.ts} +1 -1
- package/package.json +11 -1
- package/dist/chunk-JUHPBAEH.js +0 -20
- package/dist/chunk-PQ4KSV76.js +0 -50
package/dist/index.cjs
CHANGED
|
@@ -30,13 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
planDump: () => planDump,
|
|
36
|
-
resolveBrowserData: () => resolveBrowserData,
|
|
37
|
-
toEnv: () => toEnv,
|
|
38
|
-
toPublicEnv: () => toPublicEnv,
|
|
39
|
-
writeDump: () => writeDump
|
|
33
|
+
cnos: () => runtime_default,
|
|
34
|
+
default: () => runtime_default
|
|
40
35
|
});
|
|
41
36
|
module.exports = __toCommonJS(index_exports);
|
|
42
37
|
|
|
@@ -59,6 +54,11 @@ var CnosSecurityError = class extends CnosError {
|
|
|
59
54
|
super(message);
|
|
60
55
|
}
|
|
61
56
|
};
|
|
57
|
+
var CnosAuthenticationError = class extends CnosError {
|
|
58
|
+
constructor(message) {
|
|
59
|
+
super(message);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
62
|
var CnosKeyNotFoundError = class extends CnosError {
|
|
63
63
|
constructor(key) {
|
|
64
64
|
super(`Missing required CNOS config key: ${key}`);
|
|
@@ -111,6 +111,68 @@ function createProvenanceInspector() {
|
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// ../core/src/keychain/linux.ts
|
|
115
|
+
var import_node_child_process = require("child_process");
|
|
116
|
+
var import_node_util = require("util");
|
|
117
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
118
|
+
async function readLinuxKeychain(entry) {
|
|
119
|
+
try {
|
|
120
|
+
const { stdout } = await execFileAsync("secret-tool", ["lookup", "service", "cnos", "account", entry]);
|
|
121
|
+
const value = stdout.trim();
|
|
122
|
+
return value.length > 0 ? value : void 0;
|
|
123
|
+
} catch {
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ../core/src/keychain/macos.ts
|
|
129
|
+
var import_node_child_process2 = require("child_process");
|
|
130
|
+
var import_node_util2 = require("util");
|
|
131
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
132
|
+
async function readMacosKeychain(entry) {
|
|
133
|
+
try {
|
|
134
|
+
const { stdout } = await execFileAsync2("security", ["find-generic-password", "-a", "cnos", "-s", entry, "-w"]);
|
|
135
|
+
const value = stdout.trim();
|
|
136
|
+
return value.length > 0 ? value : void 0;
|
|
137
|
+
} catch {
|
|
138
|
+
return void 0;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ../core/src/keychain/windows.ts
|
|
143
|
+
var import_node_child_process3 = require("child_process");
|
|
144
|
+
var import_node_util3 = require("util");
|
|
145
|
+
var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
|
|
146
|
+
function wrap(script) {
|
|
147
|
+
return ["-NoProfile", "-Command", script];
|
|
148
|
+
}
|
|
149
|
+
async function readWindowsKeychain(entry) {
|
|
150
|
+
try {
|
|
151
|
+
const { stdout } = await execFileAsync3(
|
|
152
|
+
"powershell",
|
|
153
|
+
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`)
|
|
154
|
+
);
|
|
155
|
+
const value = stdout.trim();
|
|
156
|
+
return value.length > 0 ? value : void 0;
|
|
157
|
+
} catch {
|
|
158
|
+
return void 0;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ../core/src/keychain/index.ts
|
|
163
|
+
async function readKeychain(entry) {
|
|
164
|
+
if (process.platform === "win32") {
|
|
165
|
+
return readWindowsKeychain(entry);
|
|
166
|
+
}
|
|
167
|
+
if (process.platform === "darwin") {
|
|
168
|
+
return readMacosKeychain(entry);
|
|
169
|
+
}
|
|
170
|
+
if (process.platform === "linux") {
|
|
171
|
+
return readLinuxKeychain(entry);
|
|
172
|
+
}
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
114
176
|
// ../core/src/manifest/loadManifest.ts
|
|
115
177
|
var import_promises2 = require("fs/promises");
|
|
116
178
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
@@ -287,22 +349,82 @@ function normalizeNamespaces(namespaces) {
|
|
|
287
349
|
function normalizeVaults(vaults) {
|
|
288
350
|
return Object.fromEntries(
|
|
289
351
|
Object.entries(vaults ?? {}).map(([name, definition]) => {
|
|
352
|
+
const legacyPassphrase = definition.passphrase;
|
|
353
|
+
if (legacyPassphrase !== void 0) {
|
|
354
|
+
throw new CnosManifestError(
|
|
355
|
+
`Vault "${name}" uses legacy passphrase configuration. Use vaults.${name}.auth instead.`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
290
358
|
const provider = definition.provider?.trim();
|
|
291
359
|
if (!provider) {
|
|
292
360
|
throw new CnosManifestError(`Vault "${name}" requires a provider`);
|
|
293
361
|
}
|
|
362
|
+
const normalizedAuth = normalizeVaultAuth(name, provider, definition.auth);
|
|
363
|
+
const normalizedMapping = Object.fromEntries(
|
|
364
|
+
Object.entries(definition.mapping ?? {}).filter(
|
|
365
|
+
(entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
|
|
366
|
+
).map(([envVar, logicalRef]) => [envVar.trim(), logicalRef.trim()]).filter(([envVar, logicalRef]) => envVar.length > 0 && logicalRef.length > 0)
|
|
367
|
+
);
|
|
294
368
|
return [
|
|
295
369
|
name,
|
|
296
370
|
{
|
|
297
371
|
provider,
|
|
298
|
-
|
|
299
|
-
|
|
372
|
+
auth: normalizedAuth,
|
|
373
|
+
...Object.keys(normalizedMapping).length > 0 ? {
|
|
374
|
+
mapping: normalizedMapping
|
|
300
375
|
} : {}
|
|
301
376
|
}
|
|
302
377
|
];
|
|
303
378
|
})
|
|
304
379
|
);
|
|
305
380
|
}
|
|
381
|
+
function normalizeAuthSources(value) {
|
|
382
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
383
|
+
return void 0;
|
|
384
|
+
}
|
|
385
|
+
const sources = Array.isArray(value.from) ? value.from : void 0;
|
|
386
|
+
const normalized = (sources ?? []).map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
387
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
388
|
+
}
|
|
389
|
+
function normalizeVaultAuth(vaultName, provider, auth) {
|
|
390
|
+
if (provider === "local") {
|
|
391
|
+
const passphraseSources = normalizeAuthSources(auth?.passphrase);
|
|
392
|
+
const defaultToken = vaultName.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
393
|
+
const defaultSources = [
|
|
394
|
+
...defaultToken ? [`env:CNOS_SECRET_PASSPHRASE_${defaultToken}`] : [],
|
|
395
|
+
"env:CNOS_SECRET_PASSPHRASE",
|
|
396
|
+
`keychain:cnos/${vaultName}`,
|
|
397
|
+
"prompt"
|
|
398
|
+
];
|
|
399
|
+
return {
|
|
400
|
+
method: auth?.method ?? "passphrase",
|
|
401
|
+
passphrase: {
|
|
402
|
+
from: passphraseSources ?? defaultSources
|
|
403
|
+
},
|
|
404
|
+
...auth?.config ? { config: auth.config } : {}
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
if (provider === "github-secrets") {
|
|
408
|
+
return {
|
|
409
|
+
method: auth?.method ?? "environment",
|
|
410
|
+
...auth?.config ? { config: auth.config } : {}
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
...auth?.method ? { method: auth.method } : {},
|
|
415
|
+
...normalizeAuthSources(auth?.passphrase) ? {
|
|
416
|
+
passphrase: {
|
|
417
|
+
from: normalizeAuthSources(auth?.passphrase) ?? []
|
|
418
|
+
}
|
|
419
|
+
} : {},
|
|
420
|
+
...normalizeAuthSources(auth?.token) ? {
|
|
421
|
+
token: {
|
|
422
|
+
from: normalizeAuthSources(auth?.token) ?? []
|
|
423
|
+
}
|
|
424
|
+
} : {},
|
|
425
|
+
...auth?.config ? { config: auth.config } : {}
|
|
426
|
+
};
|
|
427
|
+
}
|
|
306
428
|
function normalizeManifest(manifest) {
|
|
307
429
|
const version = manifest.version ?? 1;
|
|
308
430
|
if (version !== 1) {
|
|
@@ -1106,6 +1228,712 @@ async function runPipeline(options) {
|
|
|
1106
1228
|
return collectedEntries.flat();
|
|
1107
1229
|
}
|
|
1108
1230
|
|
|
1231
|
+
// ../core/src/secrets/auditLog.ts
|
|
1232
|
+
var import_promises8 = require("fs/promises");
|
|
1233
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1234
|
+
|
|
1235
|
+
// ../core/src/utils/secretStore.ts
|
|
1236
|
+
var import_node_crypto = require("crypto");
|
|
1237
|
+
var import_promises7 = require("fs/promises");
|
|
1238
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1239
|
+
|
|
1240
|
+
// ../core/src/secrets/sessionStore.ts
|
|
1241
|
+
var import_promises6 = require("fs/promises");
|
|
1242
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
1243
|
+
function buildSessionRoot(processEnv = process.env) {
|
|
1244
|
+
return import_node_path6.default.join(import_node_path6.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets")), "sessions");
|
|
1245
|
+
}
|
|
1246
|
+
function buildSessionPath(vault, processEnv) {
|
|
1247
|
+
return import_node_path6.default.join(buildSessionRoot(processEnv), `${vault}.json`);
|
|
1248
|
+
}
|
|
1249
|
+
async function readVaultSessionKey(vault, processEnv) {
|
|
1250
|
+
try {
|
|
1251
|
+
const source = await (0, import_promises6.readFile)(buildSessionPath(vault, processEnv), "utf8");
|
|
1252
|
+
const document = JSON.parse(source);
|
|
1253
|
+
if (document.version !== 1 || typeof document.derivedKey !== "string") {
|
|
1254
|
+
return void 0;
|
|
1255
|
+
}
|
|
1256
|
+
const key = Buffer.from(document.derivedKey, "hex");
|
|
1257
|
+
return key.length > 0 ? key : void 0;
|
|
1258
|
+
} catch {
|
|
1259
|
+
return void 0;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// ../core/src/utils/secretStore.ts
|
|
1264
|
+
var KEY_LENGTH = 32;
|
|
1265
|
+
var SALT_LENGTH = 32;
|
|
1266
|
+
var IV_LENGTH = 12;
|
|
1267
|
+
var AUTH_TAG_LENGTH = 16;
|
|
1268
|
+
var PBKDF2_ITERATIONS = 6e5;
|
|
1269
|
+
var KEYSTORE_VERSION = 1;
|
|
1270
|
+
var METADATA_VERSION = 1;
|
|
1271
|
+
var META_FILENAME = "meta.yml";
|
|
1272
|
+
var KEYSTORE_FILENAME = "keystore.enc";
|
|
1273
|
+
function isObject(value) {
|
|
1274
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1275
|
+
}
|
|
1276
|
+
function isSecretReference(value) {
|
|
1277
|
+
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));
|
|
1278
|
+
}
|
|
1279
|
+
function resolveSecretStoreRoot(processEnv = process.env) {
|
|
1280
|
+
return import_node_path7.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
|
|
1281
|
+
}
|
|
1282
|
+
function normalizeVaultToken(vault = "default") {
|
|
1283
|
+
return vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
1284
|
+
}
|
|
1285
|
+
function getVaultPassphraseEnvVar(vault = "default") {
|
|
1286
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
1287
|
+
return vaultToken && vaultToken !== "DEFAULT" ? `CNOS_SECRET_PASSPHRASE_${vaultToken}` : "CNOS_SECRET_PASSPHRASE";
|
|
1288
|
+
}
|
|
1289
|
+
function getVaultSessionKeyEnvVar(vault = "default") {
|
|
1290
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
1291
|
+
return `__CNOS_VAULT_KEY_${vaultToken || "DEFAULT"}__`;
|
|
1292
|
+
}
|
|
1293
|
+
function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
|
|
1294
|
+
return processEnv[getVaultPassphraseEnvVar(vault)] ?? processEnv.CNOS_SECRET_PASSPHRASE;
|
|
1295
|
+
}
|
|
1296
|
+
function resolveVaultSessionKey(vault = "default", processEnv = process.env) {
|
|
1297
|
+
const encoded = processEnv[getVaultSessionKeyEnvVar(vault)];
|
|
1298
|
+
if (!encoded) {
|
|
1299
|
+
return readVaultSessionKey(vault, processEnv);
|
|
1300
|
+
}
|
|
1301
|
+
try {
|
|
1302
|
+
const key = Buffer.from(encoded, "hex");
|
|
1303
|
+
return key.length === KEY_LENGTH ? key : void 0;
|
|
1304
|
+
} catch {
|
|
1305
|
+
return void 0;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
function deriveVaultKey(passphrase, salt, iterations = PBKDF2_ITERATIONS) {
|
|
1309
|
+
return (0, import_node_crypto.pbkdf2Sync)(passphrase, salt, iterations, KEY_LENGTH, "sha512");
|
|
1310
|
+
}
|
|
1311
|
+
function buildMetaPath(storeRoot, vault = "default") {
|
|
1312
|
+
return import_node_path7.default.join(storeRoot, "vaults", vault, META_FILENAME);
|
|
1313
|
+
}
|
|
1314
|
+
function buildKeystorePath(storeRoot, vault = "default") {
|
|
1315
|
+
return import_node_path7.default.join(storeRoot, "vaults", vault, KEYSTORE_FILENAME);
|
|
1316
|
+
}
|
|
1317
|
+
function buildLegacyVaultFile(storeRoot, vault = "default") {
|
|
1318
|
+
return import_node_path7.default.join(storeRoot, "vaults", `${vault}.json`);
|
|
1319
|
+
}
|
|
1320
|
+
function buildLegacyVaultStoreRoot(storeRoot, vault = "default") {
|
|
1321
|
+
return import_node_path7.default.join(storeRoot, "vaults", vault, "store");
|
|
1322
|
+
}
|
|
1323
|
+
function assertVaultMetadata(value, filePath) {
|
|
1324
|
+
if (!isObject(value)) {
|
|
1325
|
+
throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
|
|
1326
|
+
}
|
|
1327
|
+
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") {
|
|
1328
|
+
throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
|
|
1329
|
+
}
|
|
1330
|
+
return value;
|
|
1331
|
+
}
|
|
1332
|
+
async function exists3(targetPath) {
|
|
1333
|
+
try {
|
|
1334
|
+
await (0, import_promises7.stat)(targetPath);
|
|
1335
|
+
return true;
|
|
1336
|
+
} catch {
|
|
1337
|
+
return false;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
async function detectLegacyVaultFormat(storeRoot, vault = "default") {
|
|
1341
|
+
const legacyFile = buildLegacyVaultFile(storeRoot, vault);
|
|
1342
|
+
const legacyStore = buildLegacyVaultStoreRoot(storeRoot, vault);
|
|
1343
|
+
if (await exists3(legacyFile)) {
|
|
1344
|
+
return legacyFile;
|
|
1345
|
+
}
|
|
1346
|
+
if (await exists3(legacyStore)) {
|
|
1347
|
+
return legacyStore;
|
|
1348
|
+
}
|
|
1349
|
+
return void 0;
|
|
1350
|
+
}
|
|
1351
|
+
async function assertNoLegacyVaultFormat(storeRoot, vault = "default") {
|
|
1352
|
+
const legacyPath = await detectLegacyVaultFormat(storeRoot, vault);
|
|
1353
|
+
if (!legacyPath) {
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
throw new CnosSecurityError(
|
|
1357
|
+
`Legacy CNOS local vault format detected for vault "${vault}" at ${legacyPath}. CNOS 1.4 requires the new keystore format. Remove and recreate the vault.`
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
function encryptPayload(payload, key) {
|
|
1361
|
+
const iv = (0, import_node_crypto.randomBytes)(IV_LENGTH);
|
|
1362
|
+
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", key, iv);
|
|
1363
|
+
const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
|
|
1364
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
1365
|
+
const tag = cipher.getAuthTag();
|
|
1366
|
+
return Buffer.concat([
|
|
1367
|
+
Buffer.from(Uint32Array.of(KEYSTORE_VERSION).buffer),
|
|
1368
|
+
iv,
|
|
1369
|
+
tag,
|
|
1370
|
+
ciphertext
|
|
1371
|
+
]);
|
|
1372
|
+
}
|
|
1373
|
+
function decryptPayload(buffer, key) {
|
|
1374
|
+
if (buffer.length < 4 + IV_LENGTH + AUTH_TAG_LENGTH) {
|
|
1375
|
+
throw new CnosSecurityError("Invalid CNOS local vault keystore");
|
|
1376
|
+
}
|
|
1377
|
+
const version = buffer.readUInt32LE(0);
|
|
1378
|
+
if (version !== KEYSTORE_VERSION) {
|
|
1379
|
+
throw new CnosSecurityError(`Unsupported CNOS local vault keystore version: ${version}`);
|
|
1380
|
+
}
|
|
1381
|
+
const ivOffset = 4;
|
|
1382
|
+
const tagOffset = ivOffset + IV_LENGTH;
|
|
1383
|
+
const cipherOffset = tagOffset + AUTH_TAG_LENGTH;
|
|
1384
|
+
const iv = buffer.subarray(ivOffset, tagOffset);
|
|
1385
|
+
const tag = buffer.subarray(tagOffset, cipherOffset);
|
|
1386
|
+
const ciphertext = buffer.subarray(cipherOffset);
|
|
1387
|
+
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", key, iv);
|
|
1388
|
+
decipher.setAuthTag(tag);
|
|
1389
|
+
try {
|
|
1390
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
1391
|
+
const payload = JSON.parse(plaintext);
|
|
1392
|
+
if (!payload || !isObject(payload.secrets) || !isObject(payload.metadata)) {
|
|
1393
|
+
throw new Error("invalid");
|
|
1394
|
+
}
|
|
1395
|
+
return {
|
|
1396
|
+
secrets: Object.fromEntries(
|
|
1397
|
+
Object.entries(payload.secrets).filter((entry) => typeof entry[1] === "string")
|
|
1398
|
+
),
|
|
1399
|
+
metadata: Object.fromEntries(
|
|
1400
|
+
Object.entries(payload.metadata).filter(
|
|
1401
|
+
(entry) => isObject(entry[1]) && typeof entry[1].createdAt === "string" && typeof entry[1].updatedAt === "string"
|
|
1402
|
+
)
|
|
1403
|
+
)
|
|
1404
|
+
};
|
|
1405
|
+
} catch {
|
|
1406
|
+
throw new CnosAuthenticationError("Failed to decrypt CNOS local vault. Check vault authentication.");
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
function buildInitialPayload() {
|
|
1410
|
+
return {
|
|
1411
|
+
secrets: {},
|
|
1412
|
+
metadata: {}
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
async function writeVaultFiles(storeRoot, vault, meta, payload, key) {
|
|
1416
|
+
const metaPath = buildMetaPath(storeRoot, vault);
|
|
1417
|
+
const keystorePath = buildKeystorePath(storeRoot, vault);
|
|
1418
|
+
await (0, import_promises7.mkdir)(import_node_path7.default.dirname(metaPath), { recursive: true });
|
|
1419
|
+
await (0, import_promises7.writeFile)(metaPath, stringifyYaml(meta), "utf8");
|
|
1420
|
+
await (0, import_promises7.writeFile)(keystorePath, encryptPayload(payload, key));
|
|
1421
|
+
}
|
|
1422
|
+
async function readVaultMetadata(storeRoot, vault = "default") {
|
|
1423
|
+
await assertNoLegacyVaultFormat(storeRoot, vault);
|
|
1424
|
+
const metaPath = buildMetaPath(storeRoot, vault);
|
|
1425
|
+
try {
|
|
1426
|
+
const source = await (0, import_promises7.readFile)(metaPath, "utf8");
|
|
1427
|
+
return assertVaultMetadata(parseYaml(source), metaPath);
|
|
1428
|
+
} catch (error) {
|
|
1429
|
+
if (error.code === "ENOENT") {
|
|
1430
|
+
return void 0;
|
|
1431
|
+
}
|
|
1432
|
+
throw error;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
async function createSecretVault(storeRoot, vault, passphrase) {
|
|
1436
|
+
const normalizedVault = vault.trim() || "default";
|
|
1437
|
+
await assertNoLegacyVaultFormat(storeRoot, normalizedVault);
|
|
1438
|
+
const salt = (0, import_node_crypto.randomBytes)(SALT_LENGTH);
|
|
1439
|
+
const key = deriveVaultKey(passphrase, salt, PBKDF2_ITERATIONS);
|
|
1440
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1441
|
+
const meta = {
|
|
1442
|
+
version: METADATA_VERSION,
|
|
1443
|
+
algorithm: "aes-256-gcm",
|
|
1444
|
+
kdf: "pbkdf2-sha512",
|
|
1445
|
+
iterations: PBKDF2_ITERATIONS,
|
|
1446
|
+
salt: salt.toString("base64"),
|
|
1447
|
+
createdAt,
|
|
1448
|
+
secretCount: 0
|
|
1449
|
+
};
|
|
1450
|
+
await writeVaultFiles(storeRoot, normalizedVault, meta, buildInitialPayload(), key);
|
|
1451
|
+
return buildMetaPath(storeRoot, normalizedVault);
|
|
1452
|
+
}
|
|
1453
|
+
async function ensureSecretVault(storeRoot, vault, passphrase) {
|
|
1454
|
+
const normalizedVault = vault.trim() || "default";
|
|
1455
|
+
const meta = await readVaultMetadata(storeRoot, normalizedVault);
|
|
1456
|
+
if (meta) {
|
|
1457
|
+
return buildMetaPath(storeRoot, normalizedVault);
|
|
1458
|
+
}
|
|
1459
|
+
return createSecretVault(storeRoot, normalizedVault, passphrase);
|
|
1460
|
+
}
|
|
1461
|
+
function resolveConfiguredVaultPassphrase(definition, vault = "default", processEnv = process.env) {
|
|
1462
|
+
if (definition?.provider !== "local") {
|
|
1463
|
+
return void 0;
|
|
1464
|
+
}
|
|
1465
|
+
const configuredSources = definition.auth?.passphrase?.from ?? [];
|
|
1466
|
+
for (const source of configuredSources) {
|
|
1467
|
+
if (source.startsWith("env:")) {
|
|
1468
|
+
const value = processEnv[source.slice(4)];
|
|
1469
|
+
if (value) {
|
|
1470
|
+
return value;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
return resolveSecretPassphrase(vault, processEnv);
|
|
1475
|
+
}
|
|
1476
|
+
async function resolveVaultAccessKey(storeRoot, definition, vault = "default", processEnv = process.env) {
|
|
1477
|
+
if (definition?.provider !== "local") {
|
|
1478
|
+
return definition?.provider === "github-secrets" ? {
|
|
1479
|
+
method: definition.auth?.method ?? "environment",
|
|
1480
|
+
...definition?.auth?.config ? { config: definition.auth.config } : {}
|
|
1481
|
+
} : void 0;
|
|
1482
|
+
}
|
|
1483
|
+
const sessionKey = await resolveVaultSessionKey(vault, processEnv);
|
|
1484
|
+
if (sessionKey) {
|
|
1485
|
+
return {
|
|
1486
|
+
derivedKey: sessionKey,
|
|
1487
|
+
method: "keychain",
|
|
1488
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
const passphrase = resolveConfiguredVaultPassphrase(definition, vault, processEnv);
|
|
1492
|
+
if (passphrase) {
|
|
1493
|
+
return {
|
|
1494
|
+
passphrase,
|
|
1495
|
+
method: "passphrase",
|
|
1496
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
const metadata = await readVaultMetadata(storeRoot, vault);
|
|
1500
|
+
if (!metadata) {
|
|
1501
|
+
return void 0;
|
|
1502
|
+
}
|
|
1503
|
+
throw new CnosAuthenticationError(
|
|
1504
|
+
`Cannot authenticate to vault "${vault}". Set ${getVaultPassphraseEnvVar(vault)} or run cnos vault auth ${vault}.`
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
async function loadVaultPayload(storeRoot, vault, auth) {
|
|
1508
|
+
const meta = await readVaultMetadata(storeRoot, vault);
|
|
1509
|
+
if (!meta) {
|
|
1510
|
+
throw new CnosManifestError(`Missing CNOS vault metadata for "${vault}"`);
|
|
1511
|
+
}
|
|
1512
|
+
const salt = Buffer.from(meta.salt, "base64");
|
|
1513
|
+
const key = auth.derivedKey ?? (auth.passphrase ? deriveVaultKey(auth.passphrase, salt, meta.iterations) : void 0);
|
|
1514
|
+
if (!key) {
|
|
1515
|
+
throw new CnosAuthenticationError(`Vault "${vault}" requires authentication before access.`);
|
|
1516
|
+
}
|
|
1517
|
+
const buffer = await (0, import_promises7.readFile)(buildKeystorePath(storeRoot, vault));
|
|
1518
|
+
return {
|
|
1519
|
+
meta,
|
|
1520
|
+
payload: decryptPayload(buffer, key),
|
|
1521
|
+
key
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
async function writeLocalSecret(storeRoot, ref, value, authOrPassphrase, vault = "default") {
|
|
1525
|
+
const auth = typeof authOrPassphrase === "string" ? {
|
|
1526
|
+
passphrase: authOrPassphrase,
|
|
1527
|
+
method: "passphrase"
|
|
1528
|
+
} : authOrPassphrase;
|
|
1529
|
+
if (auth.passphrase) {
|
|
1530
|
+
await ensureSecretVault(storeRoot, vault, auth.passphrase);
|
|
1531
|
+
} else {
|
|
1532
|
+
const meta2 = await readVaultMetadata(storeRoot, vault);
|
|
1533
|
+
if (!meta2) {
|
|
1534
|
+
throw new CnosAuthenticationError(`Vault "${vault}" requires passphrase-based authentication for initial creation.`);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1538
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1539
|
+
const existing = payload.metadata[ref];
|
|
1540
|
+
payload.secrets[ref] = value;
|
|
1541
|
+
payload.metadata[ref] = {
|
|
1542
|
+
createdAt: existing?.createdAt ?? now,
|
|
1543
|
+
updatedAt: now
|
|
1544
|
+
};
|
|
1545
|
+
const nextMeta = {
|
|
1546
|
+
...meta,
|
|
1547
|
+
secretCount: Object.keys(payload.secrets).length
|
|
1548
|
+
};
|
|
1549
|
+
await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
|
|
1550
|
+
return buildKeystorePath(storeRoot, vault);
|
|
1551
|
+
}
|
|
1552
|
+
async function deleteLocalSecret(storeRoot, ref, auth, vault = "default") {
|
|
1553
|
+
const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1554
|
+
if (!(ref in payload.secrets)) {
|
|
1555
|
+
return false;
|
|
1556
|
+
}
|
|
1557
|
+
delete payload.secrets[ref];
|
|
1558
|
+
delete payload.metadata[ref];
|
|
1559
|
+
const nextMeta = {
|
|
1560
|
+
...meta,
|
|
1561
|
+
secretCount: Object.keys(payload.secrets).length
|
|
1562
|
+
};
|
|
1563
|
+
await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
|
|
1564
|
+
return true;
|
|
1565
|
+
}
|
|
1566
|
+
async function readLocalSecret(storeRoot, ref, auth, vault = "default") {
|
|
1567
|
+
const { payload } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1568
|
+
const value = payload.secrets[ref];
|
|
1569
|
+
if (value === void 0) {
|
|
1570
|
+
throw new CnosManifestError(`Missing local secret ref "${ref}" in vault "${vault}"`);
|
|
1571
|
+
}
|
|
1572
|
+
return value;
|
|
1573
|
+
}
|
|
1574
|
+
async function listLocalSecrets(storeRoot, auth, vault = "default") {
|
|
1575
|
+
const { payload } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1576
|
+
return Object.keys(payload.secrets).sort((left, right) => left.localeCompare(right));
|
|
1577
|
+
}
|
|
1578
|
+
function resolveVaultDefinition(vaults, vault = "default") {
|
|
1579
|
+
const definition = vaults?.[vault];
|
|
1580
|
+
const provider = definition?.provider ?? "local";
|
|
1581
|
+
return {
|
|
1582
|
+
name: vault,
|
|
1583
|
+
provider,
|
|
1584
|
+
...definition?.auth ? { auth: definition.auth } : {},
|
|
1585
|
+
...definition?.mapping ? { mapping: definition.mapping } : {},
|
|
1586
|
+
requiresAuthentication: provider === "local"
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// ../core/src/secrets/auditLog.ts
|
|
1591
|
+
async function appendAuditEvent(event, processEnv = process.env) {
|
|
1592
|
+
const auditFile = processEnv.CNOS_AUDIT_FILE ?? import_node_path8.default.join(resolveSecretStoreRoot(processEnv), "audit", "access.log");
|
|
1593
|
+
await (0, import_promises8.mkdir)(import_node_path8.default.dirname(auditFile), { recursive: true });
|
|
1594
|
+
await (0, import_promises8.appendFile)(
|
|
1595
|
+
auditFile,
|
|
1596
|
+
`${JSON.stringify({
|
|
1597
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1598
|
+
...event
|
|
1599
|
+
})}
|
|
1600
|
+
`,
|
|
1601
|
+
"utf8"
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// ../core/src/secrets/secretCache.ts
|
|
1606
|
+
var SecretCache = class {
|
|
1607
|
+
cache = /* @__PURE__ */ new Map();
|
|
1608
|
+
authenticated = /* @__PURE__ */ new Set();
|
|
1609
|
+
load(vaultId, secrets) {
|
|
1610
|
+
this.authenticated.add(vaultId);
|
|
1611
|
+
for (const [ref, value] of secrets) {
|
|
1612
|
+
this.cache.set(`${vaultId}:${ref}`, value);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
isVaultAuthenticated(vaultId) {
|
|
1616
|
+
return this.authenticated.has(vaultId);
|
|
1617
|
+
}
|
|
1618
|
+
get(vaultId, ref) {
|
|
1619
|
+
return this.cache.get(`${vaultId}:${ref}`);
|
|
1620
|
+
}
|
|
1621
|
+
clear(vaultId) {
|
|
1622
|
+
if (!vaultId) {
|
|
1623
|
+
this.cache.clear();
|
|
1624
|
+
this.authenticated.clear();
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
this.authenticated.delete(vaultId);
|
|
1628
|
+
for (const key of Array.from(this.cache.keys())) {
|
|
1629
|
+
if (key.startsWith(`${vaultId}:`)) {
|
|
1630
|
+
this.cache.delete(key);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
// ../core/src/secrets/providers/github.ts
|
|
1637
|
+
var GithubSecretsVaultProvider = class {
|
|
1638
|
+
constructor(vaultId, definition, processEnv = process.env) {
|
|
1639
|
+
this.vaultId = vaultId;
|
|
1640
|
+
this.definition = definition;
|
|
1641
|
+
this.processEnv = processEnv;
|
|
1642
|
+
}
|
|
1643
|
+
vaultId;
|
|
1644
|
+
definition;
|
|
1645
|
+
processEnv;
|
|
1646
|
+
authenticated = false;
|
|
1647
|
+
async authenticate(_authConfig) {
|
|
1648
|
+
void _authConfig;
|
|
1649
|
+
this.authenticated = true;
|
|
1650
|
+
}
|
|
1651
|
+
isAuthenticated() {
|
|
1652
|
+
return this.authenticated;
|
|
1653
|
+
}
|
|
1654
|
+
resolveEnvVar(ref) {
|
|
1655
|
+
if (this.processEnv[ref] !== void 0) {
|
|
1656
|
+
return ref;
|
|
1657
|
+
}
|
|
1658
|
+
return Object.entries(this.definition.mapping ?? {}).find(([, logicalRef]) => logicalRef === ref)?.[0];
|
|
1659
|
+
}
|
|
1660
|
+
async batchGet(refs) {
|
|
1661
|
+
this.authenticated = true;
|
|
1662
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1663
|
+
for (const ref of Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right))) {
|
|
1664
|
+
const envVar = this.resolveEnvVar(ref);
|
|
1665
|
+
const value = envVar ? this.processEnv[envVar] : void 0;
|
|
1666
|
+
if (value !== void 0) {
|
|
1667
|
+
resolved.set(ref, value);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
return resolved;
|
|
1671
|
+
}
|
|
1672
|
+
async get(ref) {
|
|
1673
|
+
const envVar = this.resolveEnvVar(ref);
|
|
1674
|
+
this.authenticated = true;
|
|
1675
|
+
return envVar ? this.processEnv[envVar] : void 0;
|
|
1676
|
+
}
|
|
1677
|
+
async set(ref, value) {
|
|
1678
|
+
void ref;
|
|
1679
|
+
void value;
|
|
1680
|
+
throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be written by CNOS.`);
|
|
1681
|
+
}
|
|
1682
|
+
async delete(ref) {
|
|
1683
|
+
void ref;
|
|
1684
|
+
throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be mutated by CNOS.`);
|
|
1685
|
+
}
|
|
1686
|
+
async list() {
|
|
1687
|
+
return Object.values(this.definition.mapping ?? {}).sort((left, right) => left.localeCompare(right));
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
|
|
1691
|
+
// ../core/src/secrets/providers/local.ts
|
|
1692
|
+
var LocalSecretVaultProvider = class _LocalSecretVaultProvider {
|
|
1693
|
+
constructor(vaultId, definition, processEnv = process.env, storeRoot = resolveSecretStoreRoot(processEnv)) {
|
|
1694
|
+
this.vaultId = vaultId;
|
|
1695
|
+
this.processEnv = processEnv;
|
|
1696
|
+
this.storeRoot = storeRoot;
|
|
1697
|
+
this.definition = definition;
|
|
1698
|
+
}
|
|
1699
|
+
vaultId;
|
|
1700
|
+
processEnv;
|
|
1701
|
+
storeRoot;
|
|
1702
|
+
authConfig;
|
|
1703
|
+
definition;
|
|
1704
|
+
static fromVaults(vaults, vaultId, processEnv) {
|
|
1705
|
+
const definition = resolveVaultDefinition(vaults, vaultId);
|
|
1706
|
+
return new _LocalSecretVaultProvider(vaultId, definition, processEnv);
|
|
1707
|
+
}
|
|
1708
|
+
async authenticate(authConfig) {
|
|
1709
|
+
this.authConfig = authConfig;
|
|
1710
|
+
await this.list();
|
|
1711
|
+
}
|
|
1712
|
+
isAuthenticated() {
|
|
1713
|
+
return Boolean(this.authConfig);
|
|
1714
|
+
}
|
|
1715
|
+
async requireAuth() {
|
|
1716
|
+
if (this.authConfig) {
|
|
1717
|
+
return this.authConfig;
|
|
1718
|
+
}
|
|
1719
|
+
const resolved = await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
|
|
1720
|
+
if (!resolved) {
|
|
1721
|
+
throw new CnosAuthenticationError(
|
|
1722
|
+
`Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
this.authConfig = resolved;
|
|
1726
|
+
return resolved;
|
|
1727
|
+
}
|
|
1728
|
+
async batchGet(refs) {
|
|
1729
|
+
const auth = await this.requireAuth();
|
|
1730
|
+
const entries = await Promise.all(
|
|
1731
|
+
Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right)).map(async (ref) => [ref, await readLocalSecret(this.storeRoot, ref, auth, this.vaultId)])
|
|
1732
|
+
);
|
|
1733
|
+
return new Map(entries);
|
|
1734
|
+
}
|
|
1735
|
+
async get(ref) {
|
|
1736
|
+
const auth = await this.requireAuth();
|
|
1737
|
+
try {
|
|
1738
|
+
return await readLocalSecret(this.storeRoot, ref, auth, this.vaultId);
|
|
1739
|
+
} catch {
|
|
1740
|
+
return void 0;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
async set(ref, value) {
|
|
1744
|
+
const auth = await this.requireAuth();
|
|
1745
|
+
await writeLocalSecret(this.storeRoot, ref, value, auth, this.vaultId);
|
|
1746
|
+
await appendAuditEvent(
|
|
1747
|
+
{
|
|
1748
|
+
action: "write",
|
|
1749
|
+
vault: this.vaultId,
|
|
1750
|
+
ref,
|
|
1751
|
+
caller: "cli"
|
|
1752
|
+
},
|
|
1753
|
+
this.processEnv
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
async delete(ref) {
|
|
1757
|
+
const auth = await this.requireAuth();
|
|
1758
|
+
await deleteLocalSecret(this.storeRoot, ref, auth, this.vaultId);
|
|
1759
|
+
await appendAuditEvent(
|
|
1760
|
+
{
|
|
1761
|
+
action: "delete",
|
|
1762
|
+
vault: this.vaultId,
|
|
1763
|
+
ref,
|
|
1764
|
+
caller: "cli"
|
|
1765
|
+
},
|
|
1766
|
+
this.processEnv
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
async list() {
|
|
1770
|
+
const auth = this.authConfig ?? await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
|
|
1771
|
+
if (!auth) {
|
|
1772
|
+
throw new CnosAuthenticationError(
|
|
1773
|
+
`Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
this.authConfig = auth;
|
|
1777
|
+
return listLocalSecrets(this.storeRoot, auth, this.vaultId);
|
|
1778
|
+
}
|
|
1779
|
+
};
|
|
1780
|
+
|
|
1781
|
+
// ../core/src/secrets/providers/registry.ts
|
|
1782
|
+
function createSecretVaultProvider(vaultId, definition, processEnv) {
|
|
1783
|
+
if (definition.provider === "local") {
|
|
1784
|
+
return new LocalSecretVaultProvider(vaultId, definition, processEnv);
|
|
1785
|
+
}
|
|
1786
|
+
if (definition.provider === "github-secrets") {
|
|
1787
|
+
return new GithubSecretsVaultProvider(vaultId, definition, processEnv);
|
|
1788
|
+
}
|
|
1789
|
+
throw new CnosManifestError(`Unsupported vault provider: ${definition.provider}`);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
// ../core/src/secrets/prompt.ts
|
|
1793
|
+
var import_node_readline = __toESM(require("readline"), 1);
|
|
1794
|
+
var import_node_stream = require("stream");
|
|
1795
|
+
async function promptHidden(message) {
|
|
1796
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1797
|
+
return void 0;
|
|
1798
|
+
}
|
|
1799
|
+
const mutableStdout = new WritableMask();
|
|
1800
|
+
const rl = import_node_readline.default.createInterface({
|
|
1801
|
+
input: process.stdin,
|
|
1802
|
+
output: mutableStdout,
|
|
1803
|
+
terminal: true
|
|
1804
|
+
});
|
|
1805
|
+
try {
|
|
1806
|
+
mutableStdout.muted = true;
|
|
1807
|
+
const value = await new Promise((resolve) => {
|
|
1808
|
+
rl.question(message, resolve);
|
|
1809
|
+
});
|
|
1810
|
+
process.stdout.write("\n");
|
|
1811
|
+
return value;
|
|
1812
|
+
} finally {
|
|
1813
|
+
rl.close();
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
var WritableMask = class extends import_node_stream.Writable {
|
|
1817
|
+
muted = false;
|
|
1818
|
+
_write(chunk, _encoding, callback) {
|
|
1819
|
+
if (!this.muted) {
|
|
1820
|
+
process.stdout.write(chunk);
|
|
1821
|
+
}
|
|
1822
|
+
callback();
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
|
|
1826
|
+
// ../core/src/secrets/resolveAuth.ts
|
|
1827
|
+
function toAuthError(vaultId, sources) {
|
|
1828
|
+
return new CnosAuthenticationError(
|
|
1829
|
+
`Cannot authenticate to vault "${vaultId}". Tried: ${sources.join(", ")}. Set ${getVaultPassphraseEnvVar(vaultId)} or run cnos vault auth ${vaultId}.`
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
|
|
1833
|
+
const sessionKey = await resolveVaultSessionKey(vaultId, processEnv);
|
|
1834
|
+
if (sessionKey) {
|
|
1835
|
+
return {
|
|
1836
|
+
derivedKey: sessionKey,
|
|
1837
|
+
method: "keychain",
|
|
1838
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
if (definition.provider === "github-secrets") {
|
|
1842
|
+
return {
|
|
1843
|
+
method: definition.auth?.method ?? "environment",
|
|
1844
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
const sources = definition.auth?.passphrase?.from ?? [getVaultPassphraseEnvVar(vaultId)];
|
|
1848
|
+
for (const source of sources) {
|
|
1849
|
+
if (source.startsWith("env:")) {
|
|
1850
|
+
const value = processEnv[source.slice(4)];
|
|
1851
|
+
if (value) {
|
|
1852
|
+
return {
|
|
1853
|
+
passphrase: value,
|
|
1854
|
+
method: "passphrase",
|
|
1855
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
if (source.startsWith("keychain:")) {
|
|
1860
|
+
const value = await readKeychain(source.slice("keychain:".length));
|
|
1861
|
+
if (value) {
|
|
1862
|
+
return {
|
|
1863
|
+
derivedKey: Buffer.from(value, "hex"),
|
|
1864
|
+
method: "keychain",
|
|
1865
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
if (source === "prompt") {
|
|
1870
|
+
const value = await promptHidden(`Enter passphrase for vault "${vaultId}": `);
|
|
1871
|
+
if (value) {
|
|
1872
|
+
return {
|
|
1873
|
+
passphrase: value,
|
|
1874
|
+
method: "passphrase",
|
|
1875
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
const fallback = resolveSecretPassphrase(vaultId, processEnv);
|
|
1881
|
+
if (fallback) {
|
|
1882
|
+
return {
|
|
1883
|
+
passphrase: fallback,
|
|
1884
|
+
method: "passphrase",
|
|
1885
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
throw toAuthError(vaultId, [getVaultSessionKeyEnvVar(vaultId), ...sources]);
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
// ../core/src/secrets/batchResolve.ts
|
|
1892
|
+
function collectSecretDescriptors(graph) {
|
|
1893
|
+
return Array.from(graph.entries.values()).filter((entry) => entry.namespace === "secret" && isSecretReference(entry.value)).map((entry) => ({
|
|
1894
|
+
logicalKey: entry.key,
|
|
1895
|
+
ref: entry.value
|
|
1896
|
+
}));
|
|
1897
|
+
}
|
|
1898
|
+
async function batchResolveSecrets(graph, manifest, processEnv = process.env) {
|
|
1899
|
+
const cache = new SecretCache();
|
|
1900
|
+
const descriptors = collectSecretDescriptors(graph);
|
|
1901
|
+
const grouped = descriptors.reduce((accumulator, descriptor) => {
|
|
1902
|
+
const vaultId = descriptor.ref.vault ?? "default";
|
|
1903
|
+
const bucket = accumulator.get(vaultId) ?? [];
|
|
1904
|
+
bucket.push(descriptor);
|
|
1905
|
+
accumulator.set(vaultId, bucket);
|
|
1906
|
+
return accumulator;
|
|
1907
|
+
}, /* @__PURE__ */ new Map());
|
|
1908
|
+
for (const [vaultId, refs] of grouped) {
|
|
1909
|
+
const definition = manifest.vaults[vaultId] ?? { provider: "local", auth: { passphrase: { from: [] } } };
|
|
1910
|
+
const provider = createSecretVaultProvider(vaultId, definition, processEnv);
|
|
1911
|
+
const auth = await resolveVaultAuth(vaultId, definition, processEnv);
|
|
1912
|
+
await provider.authenticate(auth);
|
|
1913
|
+
const resolved = await provider.batchGet(refs.map((entry) => entry.ref.ref));
|
|
1914
|
+
cache.load(vaultId, resolved);
|
|
1915
|
+
await appendAuditEvent(
|
|
1916
|
+
{
|
|
1917
|
+
action: "batch_read",
|
|
1918
|
+
vault: vaultId,
|
|
1919
|
+
refs: Array.from(resolved.keys()).sort((left, right) => left.localeCompare(right)),
|
|
1920
|
+
caller: "runtime",
|
|
1921
|
+
workspace: graph.workspace.workspaceId,
|
|
1922
|
+
profile: graph.profile
|
|
1923
|
+
},
|
|
1924
|
+
processEnv
|
|
1925
|
+
);
|
|
1926
|
+
}
|
|
1927
|
+
return cache;
|
|
1928
|
+
}
|
|
1929
|
+
function resolveSecretEntryValue(key, value, cache) {
|
|
1930
|
+
if (!key.startsWith("secret.") || !isSecretReference(value)) {
|
|
1931
|
+
return value;
|
|
1932
|
+
}
|
|
1933
|
+
const vaultId = value.vault ?? "default";
|
|
1934
|
+
return cache.get(vaultId, value.ref) ?? value;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1109
1937
|
// ../core/src/runtime/projection.ts
|
|
1110
1938
|
function setNestedValue(target, pathSegments, value) {
|
|
1111
1939
|
const [head, ...tail] = pathSegments;
|
|
@@ -1156,55 +1984,6 @@ function requireValue(graph, key) {
|
|
|
1156
1984
|
return value;
|
|
1157
1985
|
}
|
|
1158
1986
|
|
|
1159
|
-
// ../core/src/utils/secretStore.ts
|
|
1160
|
-
var import_node_crypto = require("crypto");
|
|
1161
|
-
var import_promises6 = require("fs/promises");
|
|
1162
|
-
var import_node_path6 = __toESM(require("path"), 1);
|
|
1163
|
-
function isObject(value) {
|
|
1164
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1165
|
-
}
|
|
1166
|
-
function isSecretReference(value) {
|
|
1167
|
-
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));
|
|
1168
|
-
}
|
|
1169
|
-
function resolveSecretStoreRoot(processEnv = process.env) {
|
|
1170
|
-
return import_node_path6.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
|
|
1171
|
-
}
|
|
1172
|
-
function resolveSecretStoreFile(storeRoot, ref, vault = "default") {
|
|
1173
|
-
return import_node_path6.default.join(storeRoot, "vaults", vault, "store", ...ref.split("/")).concat(".json");
|
|
1174
|
-
}
|
|
1175
|
-
function deriveKey(passphrase, salt) {
|
|
1176
|
-
return (0, import_node_crypto.scryptSync)(passphrase, salt, 32);
|
|
1177
|
-
}
|
|
1178
|
-
function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
|
|
1179
|
-
const vaultToken = vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
1180
|
-
return processEnv[`CNOS_SECRET_PASSPHRASE_${vaultToken}`] ?? processEnv.CNOS_SECRET_PASSPHRASE;
|
|
1181
|
-
}
|
|
1182
|
-
function decryptDocument(document, passphrase) {
|
|
1183
|
-
const salt = Buffer.from(document.salt, "base64");
|
|
1184
|
-
const iv = Buffer.from(document.iv, "base64");
|
|
1185
|
-
const tag = Buffer.from(document.tag, "base64");
|
|
1186
|
-
const ciphertext = Buffer.from(document.ciphertext, "base64");
|
|
1187
|
-
const key = deriveKey(passphrase, salt);
|
|
1188
|
-
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", key, iv);
|
|
1189
|
-
decipher.setAuthTag(tag);
|
|
1190
|
-
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
1191
|
-
return plaintext.toString("utf8");
|
|
1192
|
-
}
|
|
1193
|
-
async function readLocalSecret(storeRoot, ref, passphrase, vault = "default") {
|
|
1194
|
-
if (!passphrase) {
|
|
1195
|
-
throw new CnosManifestError(
|
|
1196
|
-
`Missing CNOS secret passphrase for local secret ref "${ref}". Set CNOS_SECRET_PASSPHRASE or pass processEnv explicitly.`
|
|
1197
|
-
);
|
|
1198
|
-
}
|
|
1199
|
-
const filePath = resolveSecretStoreFile(storeRoot, ref, vault);
|
|
1200
|
-
const source = await (0, import_promises6.readFile)(filePath, "utf8");
|
|
1201
|
-
const document = JSON.parse(source);
|
|
1202
|
-
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") {
|
|
1203
|
-
throw new CnosManifestError("Invalid local secret document", filePath);
|
|
1204
|
-
}
|
|
1205
|
-
return decryptDocument(document, passphrase);
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
1987
|
// ../core/src/runtime/toEnv.ts
|
|
1209
1988
|
function normalizeEnvValue(value) {
|
|
1210
1989
|
if (value === void 0 || value === null) {
|
|
@@ -1286,28 +2065,42 @@ function toPublicEnv(graph, manifest, options = {}) {
|
|
|
1286
2065
|
}
|
|
1287
2066
|
|
|
1288
2067
|
// ../core/src/orchestrator/runtime.ts
|
|
1289
|
-
function createRuntime(manifest, graph, plugins = []) {
|
|
2068
|
+
function createRuntime(manifest, graph, plugins = [], secretCache) {
|
|
2069
|
+
function readLogicalKey(key) {
|
|
2070
|
+
const entry = graph.entries.get(key);
|
|
2071
|
+
if (!entry) {
|
|
2072
|
+
return void 0;
|
|
2073
|
+
}
|
|
2074
|
+
if (!secretCache) {
|
|
2075
|
+
return entry.value;
|
|
2076
|
+
}
|
|
2077
|
+
return resolveSecretEntryValue(key, entry.value, secretCache);
|
|
2078
|
+
}
|
|
1290
2079
|
return {
|
|
1291
2080
|
manifest,
|
|
1292
2081
|
plugins,
|
|
1293
2082
|
graph,
|
|
1294
2083
|
read(key) {
|
|
1295
|
-
return
|
|
2084
|
+
return readLogicalKey(key);
|
|
1296
2085
|
},
|
|
1297
2086
|
require(key) {
|
|
1298
|
-
|
|
2087
|
+
const value = readLogicalKey(key);
|
|
2088
|
+
if (value === void 0) {
|
|
2089
|
+
return requireValue(graph, key);
|
|
2090
|
+
}
|
|
2091
|
+
return value;
|
|
1299
2092
|
},
|
|
1300
2093
|
readOr(key, fallback) {
|
|
1301
2094
|
return readOrValue(graph, key, fallback);
|
|
1302
2095
|
},
|
|
1303
|
-
value(
|
|
1304
|
-
return
|
|
2096
|
+
value(path12) {
|
|
2097
|
+
return readLogicalKey(toLogicalKey("value", path12));
|
|
1305
2098
|
},
|
|
1306
|
-
secret(
|
|
1307
|
-
return
|
|
2099
|
+
secret(path12) {
|
|
2100
|
+
return readLogicalKey(toLogicalKey("secret", path12));
|
|
1308
2101
|
},
|
|
1309
|
-
meta(
|
|
1310
|
-
return
|
|
2102
|
+
meta(path12) {
|
|
2103
|
+
return readLogicalKey(toLogicalKey("meta", path12));
|
|
1311
2104
|
},
|
|
1312
2105
|
inspect(key) {
|
|
1313
2106
|
return inspectValue(graph, key);
|
|
@@ -1465,61 +2258,21 @@ async function createCnos(options = {}) {
|
|
|
1465
2258
|
});
|
|
1466
2259
|
const schemaApplied = applySchemaRules(graph, loadedManifest.manifest.schema);
|
|
1467
2260
|
const promotedGraph = promoteToPublic(schemaApplied.graph, loadedManifest.manifest);
|
|
2261
|
+
const secretCache = options.secretResolution === "lazy" ? void 0 : await batchResolveSecrets(promotedGraph, loadedManifest.manifest, options.processEnv);
|
|
1468
2262
|
return createRuntime(
|
|
1469
2263
|
loadedManifest.manifest,
|
|
1470
2264
|
appendMetaEntries({
|
|
1471
2265
|
...promotedGraph,
|
|
1472
2266
|
profileSource: activeProfile.source
|
|
1473
2267
|
}, options.cnosVersion),
|
|
1474
|
-
plugins
|
|
2268
|
+
plugins,
|
|
2269
|
+
secretCache
|
|
1475
2270
|
);
|
|
1476
2271
|
}
|
|
1477
2272
|
|
|
1478
2273
|
// ../core/src/runtime/dump.ts
|
|
1479
|
-
var
|
|
1480
|
-
var
|
|
1481
|
-
function buildDumpFiles(graph, options = {}) {
|
|
1482
|
-
const basePath = options.flatten ? "" : import_node_path7.default.posix.join("workspaces", graph.workspace.workspaceId);
|
|
1483
|
-
const values = toNamespaceObject(graph, "value");
|
|
1484
|
-
const secrets = toNamespaceObject(graph, "secret");
|
|
1485
|
-
const files = [];
|
|
1486
|
-
if (Object.keys(values).length > 0) {
|
|
1487
|
-
files.push({
|
|
1488
|
-
path: import_node_path7.default.posix.join(basePath, "values", graph.profile, "app.yml"),
|
|
1489
|
-
namespace: "value",
|
|
1490
|
-
content: stringifyYaml(values)
|
|
1491
|
-
});
|
|
1492
|
-
}
|
|
1493
|
-
if (Object.keys(secrets).length > 0) {
|
|
1494
|
-
files.push({
|
|
1495
|
-
path: import_node_path7.default.posix.join(basePath, "secrets", graph.profile, "app.yml"),
|
|
1496
|
-
namespace: "secret",
|
|
1497
|
-
content: stringifyYaml(secrets)
|
|
1498
|
-
});
|
|
1499
|
-
}
|
|
1500
|
-
return files.sort((left, right) => left.path.localeCompare(right.path));
|
|
1501
|
-
}
|
|
1502
|
-
function planDump(graph, options = {}) {
|
|
1503
|
-
return {
|
|
1504
|
-
workspaceId: graph.workspace.workspaceId,
|
|
1505
|
-
profile: graph.profile,
|
|
1506
|
-
flatten: options.flatten ?? false,
|
|
1507
|
-
files: buildDumpFiles(graph, options)
|
|
1508
|
-
};
|
|
1509
|
-
}
|
|
1510
|
-
async function writeDump(graph, options) {
|
|
1511
|
-
const root = import_node_path7.default.resolve(options.to);
|
|
1512
|
-
const plan = planDump(graph, options);
|
|
1513
|
-
for (const file of plan.files) {
|
|
1514
|
-
const destination = import_node_path7.default.join(root, file.path);
|
|
1515
|
-
await (0, import_promises7.mkdir)(import_node_path7.default.dirname(destination), { recursive: true });
|
|
1516
|
-
await (0, import_promises7.writeFile)(destination, file.content, "utf8");
|
|
1517
|
-
}
|
|
1518
|
-
return {
|
|
1519
|
-
...plan,
|
|
1520
|
-
root
|
|
1521
|
-
};
|
|
1522
|
-
}
|
|
2274
|
+
var import_promises9 = require("fs/promises");
|
|
2275
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1523
2276
|
|
|
1524
2277
|
// ../core/src/utils/envNaming.ts
|
|
1525
2278
|
function normalizeMappingConfig(config = {}) {
|
|
@@ -1528,8 +2281,8 @@ function normalizeMappingConfig(config = {}) {
|
|
|
1528
2281
|
explicit: config.explicit ?? {}
|
|
1529
2282
|
};
|
|
1530
2283
|
}
|
|
1531
|
-
function fromScreamingSnake(
|
|
1532
|
-
return
|
|
2284
|
+
function fromScreamingSnake(path12) {
|
|
2285
|
+
return path12.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
|
|
1533
2286
|
}
|
|
1534
2287
|
function envVarToLogicalKey(envVar, config = {}) {
|
|
1535
2288
|
const normalized = normalizeMappingConfig(config);
|
|
@@ -1556,7 +2309,7 @@ function envVarToLogicalKey(envVar, config = {}) {
|
|
|
1556
2309
|
// package.json
|
|
1557
2310
|
var package_default = {
|
|
1558
2311
|
name: "@kitsy/cnos",
|
|
1559
|
-
version: "1.
|
|
2312
|
+
version: "1.3.0",
|
|
1560
2313
|
description: "Batteries-included CNOS runtime package wired with the official plugins.",
|
|
1561
2314
|
type: "module",
|
|
1562
2315
|
main: "./dist/index.cjs",
|
|
@@ -1568,6 +2321,16 @@ var package_default = {
|
|
|
1568
2321
|
import: "./dist/index.js",
|
|
1569
2322
|
require: "./dist/index.cjs"
|
|
1570
2323
|
},
|
|
2324
|
+
"./configure": {
|
|
2325
|
+
types: "./dist/configure/index.d.ts",
|
|
2326
|
+
import: "./dist/configure/index.js",
|
|
2327
|
+
require: "./dist/configure/index.cjs"
|
|
2328
|
+
},
|
|
2329
|
+
"./create": {
|
|
2330
|
+
types: "./dist/configure/index.d.ts",
|
|
2331
|
+
import: "./dist/configure/index.js",
|
|
2332
|
+
require: "./dist/configure/index.cjs"
|
|
2333
|
+
},
|
|
1571
2334
|
"./internal": {
|
|
1572
2335
|
types: "./dist/internal.d.ts",
|
|
1573
2336
|
import: "./dist/internal.js",
|
|
@@ -1745,8 +2508,8 @@ function createCliArgsPlugin() {
|
|
|
1745
2508
|
}
|
|
1746
2509
|
|
|
1747
2510
|
// ../../plugins/dotenv/src/index.ts
|
|
1748
|
-
var
|
|
1749
|
-
var
|
|
2511
|
+
var import_promises10 = require("fs/promises");
|
|
2512
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
1750
2513
|
var DOTENV_PLUGIN_ID = "@kitsy/cnos/plugins/dotenv";
|
|
1751
2514
|
function parseDoubleQuoted(value) {
|
|
1752
2515
|
return value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
@@ -1803,7 +2566,7 @@ function dotenvEntriesFromObject(values, mapping = {}, originFile, workspaceId =
|
|
|
1803
2566
|
}
|
|
1804
2567
|
async function readIfPresent(filePath) {
|
|
1805
2568
|
try {
|
|
1806
|
-
return await (0,
|
|
2569
|
+
return await (0, import_promises10.readFile)(filePath, "utf8");
|
|
1807
2570
|
} catch {
|
|
1808
2571
|
return void 0;
|
|
1809
2572
|
}
|
|
@@ -1822,7 +2585,7 @@ function createDotenvPlugin() {
|
|
|
1822
2585
|
workspace: workspaceRoot.workspaceId
|
|
1823
2586
|
});
|
|
1824
2587
|
for (const fileName of fileNames) {
|
|
1825
|
-
const absolutePath =
|
|
2588
|
+
const absolutePath = import_node_path10.default.join(envRoot, fileName);
|
|
1826
2589
|
const document = await readIfPresent(absolutePath);
|
|
1827
2590
|
if (!document) {
|
|
1828
2591
|
continue;
|
|
@@ -1831,7 +2594,7 @@ function createDotenvPlugin() {
|
|
|
1831
2594
|
...dotenvEntriesFromObject(
|
|
1832
2595
|
parseDotenv(document),
|
|
1833
2596
|
config.envMapping,
|
|
1834
|
-
toPortablePath(
|
|
2597
|
+
toPortablePath(import_node_path10.default.relative(import_node_path10.default.dirname(context.manifestRoot), absolutePath)),
|
|
1835
2598
|
workspaceRoot.workspaceId
|
|
1836
2599
|
)
|
|
1837
2600
|
);
|
|
@@ -1869,32 +2632,32 @@ function createPublicEnvExportPlugin() {
|
|
|
1869
2632
|
}
|
|
1870
2633
|
|
|
1871
2634
|
// ../../plugins/filesystem/src/filesystemSecretsReader.ts
|
|
1872
|
-
var
|
|
2635
|
+
var import_promises12 = require("fs/promises");
|
|
1873
2636
|
|
|
1874
2637
|
// ../../plugins/filesystem/src/helpers.ts
|
|
1875
|
-
var
|
|
1876
|
-
var
|
|
2638
|
+
var import_promises11 = require("fs/promises");
|
|
2639
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
1877
2640
|
var YAML_EXTENSIONS = /* @__PURE__ */ new Set([".yml", ".yaml"]);
|
|
1878
2641
|
var FILESYSTEM_PLUGIN_ID = "@kitsy/cnos/plugins/filesystem";
|
|
1879
2642
|
async function existsDirectory(targetPath) {
|
|
1880
2643
|
try {
|
|
1881
|
-
const
|
|
1882
|
-
void
|
|
2644
|
+
const stat2 = await (0, import_promises11.readdir)(targetPath);
|
|
2645
|
+
void stat2;
|
|
1883
2646
|
return true;
|
|
1884
2647
|
} catch {
|
|
1885
2648
|
return false;
|
|
1886
2649
|
}
|
|
1887
2650
|
}
|
|
1888
2651
|
async function collectYamlFiles(root) {
|
|
1889
|
-
const entries = await (0,
|
|
2652
|
+
const entries = await (0, import_promises11.readdir)(root, { withFileTypes: true });
|
|
1890
2653
|
const results = [];
|
|
1891
2654
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
1892
|
-
const absolutePath =
|
|
2655
|
+
const absolutePath = import_node_path11.default.join(root, entry.name);
|
|
1893
2656
|
if (entry.isDirectory()) {
|
|
1894
2657
|
results.push(...await collectYamlFiles(absolutePath));
|
|
1895
2658
|
continue;
|
|
1896
2659
|
}
|
|
1897
|
-
if (entry.isFile() && YAML_EXTENSIONS.has(
|
|
2660
|
+
if (entry.isFile() && YAML_EXTENSIONS.has(import_node_path11.default.extname(entry.name).toLowerCase())) {
|
|
1898
2661
|
results.push(absolutePath);
|
|
1899
2662
|
}
|
|
1900
2663
|
}
|
|
@@ -1902,16 +2665,16 @@ async function collectYamlFiles(root) {
|
|
|
1902
2665
|
}
|
|
1903
2666
|
async function collectFilesystemLayerFiles(manifestRoot, workspaceRoots, sourceRoot, activeLayers) {
|
|
1904
2667
|
const files = [];
|
|
1905
|
-
const repoRoot =
|
|
2668
|
+
const repoRoot = import_node_path11.default.dirname(manifestRoot);
|
|
1906
2669
|
for (const workspaceRoot of workspaceRoots) {
|
|
1907
|
-
const resolvedRoot =
|
|
2670
|
+
const resolvedRoot = import_node_path11.default.resolve(workspaceRoot.path, sourceRoot);
|
|
1908
2671
|
for (const layer of activeLayers) {
|
|
1909
|
-
const layerRoot =
|
|
2672
|
+
const layerRoot = import_node_path11.default.join(resolvedRoot, layer);
|
|
1910
2673
|
if (!await existsDirectory(layerRoot)) {
|
|
1911
2674
|
continue;
|
|
1912
2675
|
}
|
|
1913
2676
|
for (const absolutePath of await collectYamlFiles(layerRoot)) {
|
|
1914
|
-
const relativePath =
|
|
2677
|
+
const relativePath = import_node_path11.default.relative(repoRoot, absolutePath);
|
|
1915
2678
|
files.push({
|
|
1916
2679
|
absolutePath,
|
|
1917
2680
|
relativePath: toPortablePath(relativePath.startsWith("..") ? absolutePath : relativePath),
|
|
@@ -1961,31 +2724,6 @@ function yamlObjectToEntries(document, filePath, namespace, sourceId, workspaceI
|
|
|
1961
2724
|
}
|
|
1962
2725
|
}));
|
|
1963
2726
|
}
|
|
1964
|
-
async function resolveSecretValue(value, processEnv) {
|
|
1965
|
-
if (!isSecretReference(value)) {
|
|
1966
|
-
return value;
|
|
1967
|
-
}
|
|
1968
|
-
if (value.provider === "local") {
|
|
1969
|
-
const passphrase = resolveSecretPassphrase(value.vault, processEnv);
|
|
1970
|
-
if (!passphrase) {
|
|
1971
|
-
return value;
|
|
1972
|
-
}
|
|
1973
|
-
return readLocalSecret(
|
|
1974
|
-
resolveSecretStoreRoot(processEnv),
|
|
1975
|
-
value.ref,
|
|
1976
|
-
passphrase,
|
|
1977
|
-
value.vault
|
|
1978
|
-
);
|
|
1979
|
-
}
|
|
1980
|
-
if (value.provider === "env" || value.provider === "github-secrets") {
|
|
1981
|
-
const resolved = processEnv?.[value.ref];
|
|
1982
|
-
if (resolved === void 0) {
|
|
1983
|
-
return value;
|
|
1984
|
-
}
|
|
1985
|
-
return resolved;
|
|
1986
|
-
}
|
|
1987
|
-
return value;
|
|
1988
|
-
}
|
|
1989
2727
|
function toSecretReferenceMetadata(value) {
|
|
1990
2728
|
if (!isSecretReference(value)) {
|
|
1991
2729
|
return void 0;
|
|
@@ -2013,14 +2751,12 @@ function createFilesystemSecretsPlugin() {
|
|
|
2013
2751
|
);
|
|
2014
2752
|
const entries = [];
|
|
2015
2753
|
for (const file of files) {
|
|
2016
|
-
const document = await (0,
|
|
2754
|
+
const document = await (0, import_promises12.readFile)(file.absolutePath, "utf8");
|
|
2017
2755
|
const fileEntries = filesystemSecretsReader(file.relativePath, document, file.workspaceId);
|
|
2018
2756
|
for (const entry of fileEntries) {
|
|
2019
2757
|
const metadata = toSecretReferenceMetadata(entry.value);
|
|
2020
|
-
const resolvedValue = await resolveSecretValue(entry.value, context.processEnv);
|
|
2021
2758
|
entries.push({
|
|
2022
2759
|
...entry,
|
|
2023
|
-
value: resolvedValue,
|
|
2024
2760
|
...metadata ? { metadata } : {}
|
|
2025
2761
|
});
|
|
2026
2762
|
}
|
|
@@ -2031,7 +2767,7 @@ function createFilesystemSecretsPlugin() {
|
|
|
2031
2767
|
}
|
|
2032
2768
|
|
|
2033
2769
|
// ../../plugins/filesystem/src/filesystemValuesReader.ts
|
|
2034
|
-
var
|
|
2770
|
+
var import_promises13 = require("fs/promises");
|
|
2035
2771
|
function filesystemValuesReader(filePath, document, workspaceId = "default") {
|
|
2036
2772
|
return yamlObjectToEntries(document, filePath, "value", "filesystem-values", workspaceId);
|
|
2037
2773
|
}
|
|
@@ -2049,7 +2785,7 @@ function createFilesystemValuesPlugin() {
|
|
|
2049
2785
|
);
|
|
2050
2786
|
const entries = [];
|
|
2051
2787
|
for (const file of files) {
|
|
2052
|
-
const document = await (0,
|
|
2788
|
+
const document = await (0, import_promises13.readFile)(file.absolutePath, "utf8");
|
|
2053
2789
|
entries.push(...filesystemValuesReader(file.relativePath, document, file.workspaceId));
|
|
2054
2790
|
}
|
|
2055
2791
|
return entries;
|
|
@@ -2116,16 +2852,35 @@ function defaultPlugins() {
|
|
|
2116
2852
|
// src/runtime/state.ts
|
|
2117
2853
|
var singletonRuntime;
|
|
2118
2854
|
var singletonReady;
|
|
2855
|
+
var bootstrappedSecretHydrationRequired = false;
|
|
2856
|
+
function getSingletonRuntime() {
|
|
2857
|
+
return singletonRuntime;
|
|
2858
|
+
}
|
|
2119
2859
|
function setSingletonRuntime(runtime) {
|
|
2120
2860
|
singletonRuntime = runtime;
|
|
2121
2861
|
singletonReady = Promise.resolve(runtime);
|
|
2862
|
+
bootstrappedSecretHydrationRequired = false;
|
|
2122
2863
|
return runtime;
|
|
2123
2864
|
}
|
|
2865
|
+
function getSingletonReady() {
|
|
2866
|
+
return singletonReady;
|
|
2867
|
+
}
|
|
2868
|
+
function setSingletonReady(promise) {
|
|
2869
|
+
singletonReady = promise;
|
|
2870
|
+
return promise;
|
|
2871
|
+
}
|
|
2872
|
+
function getBootstrappedSecretHydrationRequired() {
|
|
2873
|
+
return bootstrappedSecretHydrationRequired;
|
|
2874
|
+
}
|
|
2875
|
+
function setBootstrappedSecretHydrationRequired(value) {
|
|
2876
|
+
bootstrappedSecretHydrationRequired = value;
|
|
2877
|
+
}
|
|
2124
2878
|
|
|
2125
2879
|
// src/createCnos.ts
|
|
2126
2880
|
async function createCnos2(options = {}) {
|
|
2127
2881
|
const runtime = await createCnos({
|
|
2128
2882
|
...options,
|
|
2883
|
+
processEnv: options.processEnv ?? process.env,
|
|
2129
2884
|
cnosVersion: package_default.version,
|
|
2130
2885
|
plugins: [...defaultPlugins(), ...options.plugins ?? []]
|
|
2131
2886
|
});
|
|
@@ -2133,25 +2888,234 @@ async function createCnos2(options = {}) {
|
|
|
2133
2888
|
return runtime;
|
|
2134
2889
|
}
|
|
2135
2890
|
|
|
2136
|
-
// src/
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2891
|
+
// src/runtime/bootstrap.ts
|
|
2892
|
+
var import_node_crypto2 = require("crypto");
|
|
2893
|
+
var CNOS_GRAPH_ENV_VAR = "__CNOS_GRAPH__";
|
|
2894
|
+
var CNOS_SECRET_PAYLOAD_ENV_VAR = "__CNOS_SECRET_PAYLOAD__";
|
|
2895
|
+
var CNOS_SESSION_KEY_ENV_VAR = "__CNOS_SESSION_KEY__";
|
|
2896
|
+
function deserializeRuntimeGraph(source) {
|
|
2897
|
+
const payload = JSON.parse(source);
|
|
2898
|
+
if (!payload || !Array.isArray(payload.entries) || typeof payload.profile !== "string" || typeof payload.resolvedAt !== "string" || !payload.profileSource || !payload.workspace || typeof payload.workspace.workspaceId !== "string" || !Array.isArray(payload.workspace.workspaceChain) || !Array.isArray(payload.workspace.workspaceRoots)) {
|
|
2899
|
+
throw new Error("Invalid CNOS runtime bootstrap payload");
|
|
2900
|
+
}
|
|
2901
|
+
return {
|
|
2902
|
+
entries: new Map(
|
|
2903
|
+
payload.entries.map((entry) => [
|
|
2904
|
+
entry.key,
|
|
2905
|
+
{
|
|
2906
|
+
key: entry.key,
|
|
2907
|
+
value: entry.value,
|
|
2908
|
+
namespace: entry.namespace,
|
|
2909
|
+
winner: entry.winner,
|
|
2910
|
+
overridden: entry.overridden ?? []
|
|
2911
|
+
}
|
|
2912
|
+
])
|
|
2913
|
+
),
|
|
2914
|
+
profile: payload.profile,
|
|
2915
|
+
resolvedAt: payload.resolvedAt,
|
|
2916
|
+
profileSource: payload.profileSource,
|
|
2917
|
+
workspace: payload.workspace
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
function decryptSecretPayload(serialized, sessionKey) {
|
|
2921
|
+
const payload = JSON.parse(serialized);
|
|
2922
|
+
if (!payload || typeof payload.iv !== "string" || typeof payload.tag !== "string" || typeof payload.ciphertext !== "string") {
|
|
2923
|
+
throw new Error("Invalid CNOS secret payload");
|
|
2924
|
+
}
|
|
2925
|
+
const key = Buffer.from(sessionKey, "hex");
|
|
2926
|
+
const iv = Buffer.from(payload.iv, "base64");
|
|
2927
|
+
const tag = Buffer.from(payload.tag, "base64");
|
|
2928
|
+
const ciphertext = Buffer.from(payload.ciphertext, "base64");
|
|
2929
|
+
const decipher = (0, import_node_crypto2.createDecipheriv)("aes-256-gcm", key, iv);
|
|
2930
|
+
decipher.setAuthTag(tag);
|
|
2931
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
2932
|
+
return JSON.parse(plaintext);
|
|
2933
|
+
}
|
|
2934
|
+
function readRuntimeGraphFromEnv(processEnv = process.env) {
|
|
2935
|
+
const serialized = processEnv[CNOS_GRAPH_ENV_VAR];
|
|
2936
|
+
if (!serialized) {
|
|
2937
|
+
return void 0;
|
|
2938
|
+
}
|
|
2939
|
+
const graph = deserializeRuntimeGraph(serialized);
|
|
2940
|
+
const secretPayload = processEnv[CNOS_SECRET_PAYLOAD_ENV_VAR];
|
|
2941
|
+
const sessionKey = processEnv[CNOS_SESSION_KEY_ENV_VAR];
|
|
2942
|
+
if (secretPayload && sessionKey) {
|
|
2943
|
+
const decrypted = decryptSecretPayload(secretPayload, sessionKey);
|
|
2944
|
+
for (const [key, value] of Object.entries(decrypted)) {
|
|
2945
|
+
const entry = graph.entries.get(key);
|
|
2946
|
+
if (entry) {
|
|
2947
|
+
entry.value = value;
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
return graph;
|
|
2952
|
+
}
|
|
2953
|
+
function graphRequiresSecretHydration(graph) {
|
|
2954
|
+
return Array.from(graph.entries.values()).some((entry) => entry.namespace === "secret" && isSecretReference(entry.value));
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
// src/runtime/index.ts
|
|
2958
|
+
var NOT_READY_MESSAGE = "CNOS not initialized. Call await cnos.ready() or use cnos run.";
|
|
2959
|
+
function getRuntimeOrThrow() {
|
|
2960
|
+
const runtime = getSingletonRuntime();
|
|
2961
|
+
if (!runtime) {
|
|
2962
|
+
throw new Error(NOT_READY_MESSAGE);
|
|
2963
|
+
}
|
|
2964
|
+
return runtime;
|
|
2965
|
+
}
|
|
2966
|
+
function attachBootstrappedGraph(graph) {
|
|
2967
|
+
if (getSingletonRuntime()) {
|
|
2968
|
+
return;
|
|
2969
|
+
}
|
|
2970
|
+
const bootstrappedManifest = {
|
|
2971
|
+
version: 1,
|
|
2972
|
+
project: {
|
|
2973
|
+
name: "bootstrapped"
|
|
2974
|
+
},
|
|
2975
|
+
workspaces: {
|
|
2976
|
+
global: {
|
|
2977
|
+
enabled: Boolean(graph.workspace.globalRoot),
|
|
2978
|
+
...graph.workspace.globalRoot ? {
|
|
2979
|
+
root: graph.workspace.globalRoot
|
|
2980
|
+
} : {},
|
|
2981
|
+
allowWrite: false
|
|
2982
|
+
},
|
|
2983
|
+
items: {},
|
|
2984
|
+
...graph.workspace.workspaceSource === "implicit" ? {} : {
|
|
2985
|
+
default: graph.workspace.workspaceId
|
|
2986
|
+
}
|
|
2987
|
+
},
|
|
2988
|
+
profiles: {
|
|
2989
|
+
default: graph.profile,
|
|
2990
|
+
resolveFrom: ["default"]
|
|
2991
|
+
},
|
|
2992
|
+
plugins: {
|
|
2993
|
+
loaders: [],
|
|
2994
|
+
resolver: "profile-aware",
|
|
2995
|
+
validators: [],
|
|
2996
|
+
exporters: [],
|
|
2997
|
+
inspectors: []
|
|
2998
|
+
},
|
|
2999
|
+
sources: {},
|
|
3000
|
+
resolution: {
|
|
3001
|
+
precedence: [],
|
|
3002
|
+
arrayPolicy: "replace"
|
|
3003
|
+
},
|
|
3004
|
+
envMapping: {
|
|
3005
|
+
explicit: {}
|
|
3006
|
+
},
|
|
3007
|
+
public: {
|
|
3008
|
+
promote: [],
|
|
3009
|
+
frameworks: {}
|
|
3010
|
+
},
|
|
3011
|
+
namespaces: {},
|
|
3012
|
+
vaults: {},
|
|
3013
|
+
writePolicy: {
|
|
3014
|
+
define: {
|
|
3015
|
+
defaultProfile: graph.profile,
|
|
3016
|
+
targets: {
|
|
3017
|
+
value: "./values/app.yml",
|
|
3018
|
+
secret: "./secrets/app.yml"
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
},
|
|
3022
|
+
schema: {}
|
|
3023
|
+
};
|
|
3024
|
+
const runtime = {
|
|
3025
|
+
manifest: bootstrappedManifest,
|
|
3026
|
+
plugins: [],
|
|
3027
|
+
graph,
|
|
3028
|
+
read(key) {
|
|
3029
|
+
return readValue(graph, key);
|
|
3030
|
+
},
|
|
3031
|
+
require(key) {
|
|
3032
|
+
return requireValue(graph, key);
|
|
3033
|
+
},
|
|
3034
|
+
readOr(key, fallback) {
|
|
3035
|
+
return readOrValue(graph, key, fallback);
|
|
3036
|
+
},
|
|
3037
|
+
value(path12) {
|
|
3038
|
+
return readValue(graph, toLogicalKey("value", path12));
|
|
3039
|
+
},
|
|
3040
|
+
secret(path12) {
|
|
3041
|
+
return readValue(graph, toLogicalKey("secret", path12));
|
|
3042
|
+
},
|
|
3043
|
+
meta(path12) {
|
|
3044
|
+
return readValue(graph, toLogicalKey("meta", path12));
|
|
3045
|
+
},
|
|
3046
|
+
inspect(key) {
|
|
3047
|
+
return inspectValue(graph, key);
|
|
3048
|
+
},
|
|
3049
|
+
toObject() {
|
|
3050
|
+
return toNamespaceObject(graph);
|
|
3051
|
+
},
|
|
3052
|
+
toNamespace(namespace) {
|
|
3053
|
+
return toNamespaceObject(graph, namespace);
|
|
3054
|
+
},
|
|
3055
|
+
toEnv(options) {
|
|
3056
|
+
return toEnv(graph, bootstrappedManifest, options);
|
|
3057
|
+
},
|
|
3058
|
+
toPublicEnv(options) {
|
|
3059
|
+
return toPublicEnv(graph, bootstrappedManifest, options);
|
|
3060
|
+
}
|
|
3061
|
+
};
|
|
3062
|
+
setSingletonRuntime(runtime);
|
|
3063
|
+
setBootstrappedSecretHydrationRequired(graphRequiresSecretHydration(graph));
|
|
3064
|
+
}
|
|
3065
|
+
function bootstrapFromProcessEnv() {
|
|
3066
|
+
if (typeof process === "undefined") {
|
|
3067
|
+
return;
|
|
3068
|
+
}
|
|
3069
|
+
try {
|
|
3070
|
+
const graph = readRuntimeGraphFromEnv(process.env);
|
|
3071
|
+
if (graph) {
|
|
3072
|
+
attachBootstrappedGraph(graph);
|
|
2143
3073
|
}
|
|
2144
|
-
|
|
3074
|
+
} catch {
|
|
2145
3075
|
}
|
|
2146
|
-
return browserData;
|
|
2147
3076
|
}
|
|
3077
|
+
bootstrapFromProcessEnv();
|
|
3078
|
+
var cnos = Object.assign(
|
|
3079
|
+
((key) => readValue(getRuntimeOrThrow().graph, key)),
|
|
3080
|
+
{
|
|
3081
|
+
read(key) {
|
|
3082
|
+
return readValue(getRuntimeOrThrow().graph, key);
|
|
3083
|
+
},
|
|
3084
|
+
require(key) {
|
|
3085
|
+
return requireValue(getRuntimeOrThrow().graph, key);
|
|
3086
|
+
},
|
|
3087
|
+
readOr(key, fallback) {
|
|
3088
|
+
return readOrValue(getRuntimeOrThrow().graph, key, fallback);
|
|
3089
|
+
},
|
|
3090
|
+
value(path12) {
|
|
3091
|
+
return readValue(getRuntimeOrThrow().graph, toLogicalKey("value", path12));
|
|
3092
|
+
},
|
|
3093
|
+
secret(path12) {
|
|
3094
|
+
return readValue(getRuntimeOrThrow().graph, toLogicalKey("secret", path12));
|
|
3095
|
+
},
|
|
3096
|
+
meta(path12) {
|
|
3097
|
+
return readValue(getRuntimeOrThrow().graph, toLogicalKey("meta", path12));
|
|
3098
|
+
},
|
|
3099
|
+
async ready() {
|
|
3100
|
+
if (getSingletonRuntime() && !getBootstrappedSecretHydrationRequired()) {
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
const existing = getSingletonReady();
|
|
3104
|
+
if (existing && !getBootstrappedSecretHydrationRequired()) {
|
|
3105
|
+
await existing;
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
const readyPromise = createCnos2().then((runtime) => {
|
|
3109
|
+
setSingletonRuntime(runtime);
|
|
3110
|
+
return runtime;
|
|
3111
|
+
});
|
|
3112
|
+
setSingletonReady(readyPromise);
|
|
3113
|
+
await readyPromise;
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
);
|
|
3117
|
+
var runtime_default = cnos;
|
|
2148
3118
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2149
3119
|
0 && (module.exports = {
|
|
2150
|
-
|
|
2151
|
-
defaultPlugins,
|
|
2152
|
-
planDump,
|
|
2153
|
-
resolveBrowserData,
|
|
2154
|
-
toEnv,
|
|
2155
|
-
toPublicEnv,
|
|
2156
|
-
writeDump
|
|
3120
|
+
cnos
|
|
2157
3121
|
});
|