@catladder/cli 0.0.0-main-2355fd7c → 0.0.0-main-018e2dee
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/dist/apps/cli/src/apps/catenv/catenv.js +3 -0
- package/dist/apps/cli/src/apps/catenv/catenv.js.map +1 -1
- package/dist/apps/cli/src/apps/cli/commands/project/doctor/GcloudProjectInspector.d.ts +4 -0
- package/dist/apps/cli/src/apps/cli/commands/project/doctor/GcloudProjectInspector.js +12 -0
- package/dist/apps/cli/src/apps/cli/commands/project/doctor/GcloudProjectInspector.js.map +1 -1
- package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkCloudRun.d.ts +2 -2
- package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkCloudRun.js +3 -1
- package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkCloudRun.js.map +1 -1
- package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkLocalAccess.d.ts +10 -0
- package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkLocalAccess.js +98 -0
- package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkLocalAccess.js.map +1 -0
- package/dist/apps/cli/src/apps/cli/commands/project/doctor/index.js +1 -1
- package/dist/apps/cli/src/apps/cli/commands/project/doctor/index.js.map +1 -1
- package/dist/apps/cli/src/apps/cli/commands/project/setup/setupContext.js +2 -0
- package/dist/apps/cli/src/apps/cli/commands/project/setup/setupContext.js.map +1 -1
- package/dist/apps/cli/src/apps/cli/commands/project/setup/setupLocalAccess.d.ts +9 -0
- package/dist/apps/cli/src/apps/cli/commands/project/setup/setupLocalAccess.js +31 -0
- package/dist/apps/cli/src/apps/cli/commands/project/setup/setupLocalAccess.js.map +1 -0
- package/dist/apps/cli/src/config/getProjectConfig.js +9 -1
- package/dist/apps/cli/src/config/getProjectConfig.js.map +1 -1
- package/dist/apps/cli/src/gcloud/localAccess.d.ts +53 -0
- package/dist/apps/cli/src/gcloud/localAccess.js +176 -0
- package/dist/apps/cli/src/gcloud/localAccess.js.map +1 -0
- package/dist/apps/cli/src/gcloud/serviceAccounts.js +9 -2
- package/dist/apps/cli/src/gcloud/serviceAccounts.js.map +1 -1
- package/dist/apps/cli/src/localAccess/declared.d.ts +14 -0
- package/dist/apps/cli/src/localAccess/declared.js +34 -0
- package/dist/apps/cli/src/localAccess/declared.js.map +1 -0
- package/dist/apps/cli/src/localAccess/keyFiles.d.ts +28 -0
- package/dist/apps/cli/src/localAccess/keyFiles.js +91 -0
- package/dist/apps/cli/src/localAccess/keyFiles.js.map +1 -0
- package/dist/apps/cli/src/utils/writePrivateFile.d.ts +7 -0
- package/dist/apps/cli/src/utils/writePrivateFile.js +25 -0
- package/dist/apps/cli/src/utils/writePrivateFile.js.map +1 -0
- package/dist/apps/cli/src/vault/BitwardenVault.js +13 -22
- package/dist/apps/cli/src/vault/BitwardenVault.js.map +1 -1
- package/dist/apps/cli/src/vault/VaultManager.d.ts +6 -0
- package/dist/apps/cli/src/vault/VaultManager.js +18 -0
- package/dist/apps/cli/src/vault/VaultManager.js.map +1 -1
- package/dist/apps/cli/src/vault/bw.d.ts +6 -2
- package/dist/apps/cli/src/vault/bw.js +32 -6
- package/dist/apps/cli/src/vault/bw.js.map +1 -1
- package/dist/apps/cli/src/vault/cache.js +5 -2
- package/dist/apps/cli/src/vault/cache.js.map +1 -1
- package/dist/bundles/catenv/index.js +436 -31
- package/dist/bundles/cli/index.js +786 -35
- package/dist/bundles/skills/catladder-secrets/SKILL.md +35 -3
- package/dist/packages/pipeline/src/index.d.ts +1 -0
- package/dist/packages/pipeline/src/index.js +1 -0
- package/dist/packages/pipeline/src/index.js.map +1 -1
- package/dist/packages/pipeline/src/localAccess/index.d.ts +53 -0
- package/dist/packages/pipeline/src/localAccess/index.js +177 -0
- package/dist/packages/pipeline/src/localAccess/index.js.map +1 -0
- package/dist/packages/pipeline/src/types/config.d.ts +75 -0
- package/dist/packages/pipeline/src/types/config.js.map +1 -1
- package/dist/skills/catladder-secrets/SKILL.md +35 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/apps/catenv/catenv.ts +4 -0
- package/src/apps/cli/commands/project/doctor/GcloudProjectInspector.ts +25 -1
- package/src/apps/cli/commands/project/doctor/__tests__/checkLocalAccess.test.ts +127 -0
- package/src/apps/cli/commands/project/doctor/checkCloudRun.ts +5 -1
- package/src/apps/cli/commands/project/doctor/checkLocalAccess.ts +146 -0
- package/src/apps/cli/commands/project/doctor/index.ts +1 -1
- package/src/apps/cli/commands/project/setup/setupContext.ts +2 -0
- package/src/apps/cli/commands/project/setup/setupLocalAccess.ts +44 -0
- package/src/config/__tests__/getEnvVarsResolved.test.ts +45 -0
- package/src/config/getProjectConfig.ts +11 -1
- package/src/gcloud/__tests__/localAccess.test.ts +82 -0
- package/src/gcloud/__tests__/syncLocalAccess.test.ts +196 -0
- package/src/gcloud/localAccess.ts +257 -0
- package/src/gcloud/serviceAccounts.ts +8 -2
- package/src/localAccess/__tests__/keyFiles.test.ts +166 -0
- package/src/localAccess/declared.ts +38 -0
- package/src/localAccess/keyFiles.ts +126 -0
- package/src/utils/__tests__/writePrivateFile.test.ts +40 -0
- package/src/utils/writePrivateFile.ts +20 -0
- package/src/vault/BitwardenVault.ts +8 -9
- package/src/vault/VaultManager.ts +28 -0
- package/src/vault/__tests__/BitwardenVault.test.ts +4 -4
- package/src/vault/__tests__/cache.test.ts +37 -0
- package/src/vault/bw.ts +34 -9
- package/src/vault/cache.ts +5 -2
|
@@ -331,6 +331,7 @@ exports.createVaultManagerGetter = createVaultManagerGetter;
|
|
|
331
331
|
|
|
332
332
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
333
333
|
const getProjectConfig_1 = __nccwpck_require__(70478);
|
|
334
|
+
const keyFiles_1 = __nccwpck_require__(70180);
|
|
334
335
|
const printVariables_1 = __nccwpck_require__(32507);
|
|
335
336
|
const verboseBanner_1 = __nccwpck_require__(95423);
|
|
336
337
|
const writeDotEnvFiles_1 = __nccwpck_require__(16699);
|
|
@@ -356,6 +357,8 @@ exports["default"] = async (choice, options) => {
|
|
|
356
357
|
if (options === null || options === void 0 ? void 0 : options.verbose) {
|
|
357
358
|
(0, verboseBanner_1.printVerboseBanner)();
|
|
358
359
|
}
|
|
360
|
+
// before the env vars are resolved: they point at the key files
|
|
361
|
+
await (0, keyFiles_1.writeLocalAccessKeyFiles)(io, config);
|
|
359
362
|
await Promise.all([
|
|
360
363
|
(0, pipeline_1.generatePipelineFiles)(context),
|
|
361
364
|
(0, writeDotEnvFiles_1.writeDotEnvFiles)(context, choice),
|
|
@@ -671,6 +674,7 @@ program.parse();
|
|
|
671
674
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
672
675
|
exports.getJobOnlyEnvVarsResolved = exports.getEnvVarsResolved = exports.getGitlabVar = exports.getEnvironment = exports.getAllPipelineContexts = exports.getAllComponentsWithAllEnvsHierarchical = exports.getAllComponentsWithAllEnvsFlat = exports.getPipelineContextByChoice = exports.getProjectComponents = exports.invalidateProjectConfig = exports.getProjectConfig = exports.parseChoice = void 0;
|
|
673
676
|
const pipeline_1 = __nccwpck_require__(53695);
|
|
677
|
+
const keyFiles_1 = __nccwpck_require__(70180);
|
|
674
678
|
const gitlab_1 = __nccwpck_require__(17697);
|
|
675
679
|
const projects_1 = __nccwpck_require__(20575);
|
|
676
680
|
var parseChoice_1 = __nccwpck_require__(70769);
|
|
@@ -791,12 +795,19 @@ const getEnvVarsResolved = async (io, env, componentName) => {
|
|
|
791
795
|
const envionment = await (0, exports.getEnvironment)(env, componentName);
|
|
792
796
|
// in the pipeline the secrets alreadyy exists and bash will expand them
|
|
793
797
|
// but here we need to manually load them
|
|
794
|
-
|
|
798
|
+
const resolved = await resolveSecrets(io, [
|
|
795
799
|
{
|
|
796
800
|
envVars: envionment.envVars,
|
|
797
801
|
secretEnvVarKeys: envionment.secretEnvVarKeys,
|
|
798
802
|
},
|
|
799
803
|
]);
|
|
804
|
+
if (env !== "local")
|
|
805
|
+
return resolved;
|
|
806
|
+
// paths of the local access key files (written by catenv)
|
|
807
|
+
return {
|
|
808
|
+
...resolved,
|
|
809
|
+
...(await (0, keyFiles_1.getLocalAccessKeyFileVars)(await (0, exports.getProjectConfig)(), componentName)),
|
|
810
|
+
};
|
|
800
811
|
};
|
|
801
812
|
exports.getEnvVarsResolved = getEnvVarsResolved;
|
|
802
813
|
/**
|
|
@@ -931,6 +942,145 @@ exports.gitProjectInformation = gitProjectInformation;
|
|
|
931
942
|
|
|
932
943
|
/***/ }),
|
|
933
944
|
|
|
945
|
+
/***/ 83228:
|
|
946
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
947
|
+
|
|
948
|
+
"use strict";
|
|
949
|
+
|
|
950
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
951
|
+
exports.getDeclaredLocalAccessCredentialsEnvVars = exports.getDeclaredLocalAccessEnvs = void 0;
|
|
952
|
+
const pipeline_1 = __nccwpck_require__(53695);
|
|
953
|
+
/**
|
|
954
|
+
* the envs of a component declaring gcp local access. An invalid
|
|
955
|
+
* declaration yields none instead of throwing: catenv and the vault
|
|
956
|
+
* cache must keep working (`project doctor` and `project setup` report
|
|
957
|
+
* the invalid declaration loudly).
|
|
958
|
+
*/
|
|
959
|
+
const getDeclaredLocalAccessEnvs = (config, componentName) => {
|
|
960
|
+
try {
|
|
961
|
+
return (0, pipeline_1.getLocalAccessGcpEnvs)(config, componentName);
|
|
962
|
+
}
|
|
963
|
+
catch (_a) {
|
|
964
|
+
return [];
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
exports.getDeclaredLocalAccessEnvs = getDeclaredLocalAccessEnvs;
|
|
968
|
+
/**
|
|
969
|
+
* the declared `credentialsEnvVar` names of a component, mapped to their
|
|
970
|
+
* env. Empty instead of throwing on an invalid declaration, see
|
|
971
|
+
* {@link getDeclaredLocalAccessEnvs}.
|
|
972
|
+
*/
|
|
973
|
+
const getDeclaredLocalAccessCredentialsEnvVars = (config, componentName) => {
|
|
974
|
+
try {
|
|
975
|
+
return (0, pipeline_1.getLocalAccessGcpCredentialsEnvVars)(config, componentName);
|
|
976
|
+
}
|
|
977
|
+
catch (_a) {
|
|
978
|
+
return {};
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
exports.getDeclaredLocalAccessCredentialsEnvVars = getDeclaredLocalAccessCredentialsEnvVars;
|
|
982
|
+
//# sourceMappingURL=declared.js.map
|
|
983
|
+
|
|
984
|
+
/***/ }),
|
|
985
|
+
|
|
986
|
+
/***/ 70180:
|
|
987
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
988
|
+
|
|
989
|
+
"use strict";
|
|
990
|
+
|
|
991
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
992
|
+
exports.writeLocalAccessKeyFiles = exports.getLocalAccessKeyFileVars = exports.getLocalAccessKeyFileVarName = exports.getLocalAccessKeyFileName = void 0;
|
|
993
|
+
const pipeline_1 = __nccwpck_require__(53695);
|
|
994
|
+
const promises_1 = __nccwpck_require__(91943);
|
|
995
|
+
const fs_1 = __nccwpck_require__(79896);
|
|
996
|
+
const path_1 = __nccwpck_require__(16928);
|
|
997
|
+
const writePrivateFile_1 = __nccwpck_require__(14550);
|
|
998
|
+
const declared_1 = __nccwpck_require__(83228);
|
|
999
|
+
const gitProjectInformation_1 = __nccwpck_require__(86884);
|
|
1000
|
+
const sanitize = (s) => s.replace(/[^A-Za-z0-9]+/g, "_");
|
|
1001
|
+
/** file name of a component/env key, relative to the local access folder */
|
|
1002
|
+
const getLocalAccessKeyFileName = (env, componentName) => `gcp-${sanitize(env)}-${sanitize(componentName)}.json`;
|
|
1003
|
+
exports.getLocalAccessKeyFileName = getLocalAccessKeyFileName;
|
|
1004
|
+
/**
|
|
1005
|
+
* env var exposing the key file path. Deliberately NOT
|
|
1006
|
+
* GOOGLE_APPLICATION_CREDENTIALS: the key must only be used where a
|
|
1007
|
+
* command opts in (isolated gcloud config, or a declared
|
|
1008
|
+
* `credentialsEnvVar`), never by ADC implicitly.
|
|
1009
|
+
*/
|
|
1010
|
+
const getLocalAccessKeyFileVarName = (env) => `CATLADDER_LOCAL_ACCESS_GCP_${sanitize(env).toUpperCase()}_KEY_FILE`;
|
|
1011
|
+
exports.getLocalAccessKeyFileVarName = getLocalAccessKeyFileVarName;
|
|
1012
|
+
// inside .catladder/, which ignores itself (see vault/cache.ts)
|
|
1013
|
+
const getFolder = async () => (0, path_1.join)(await (0, gitProjectInformation_1.getProjectRootPath)(), ".catladder", "local-access");
|
|
1014
|
+
/**
|
|
1015
|
+
* env vars pointing at the key files that exist for the component
|
|
1016
|
+
* (only paths, no secrets): one per env, plus the declared
|
|
1017
|
+
* `credentialsEnvVar` names
|
|
1018
|
+
*/
|
|
1019
|
+
const getLocalAccessKeyFileVars = async (config, componentName) => {
|
|
1020
|
+
const folder = await getFolder();
|
|
1021
|
+
const getPath = (env) => (0, path_1.join)(folder, (0, exports.getLocalAccessKeyFileName)(env, componentName));
|
|
1022
|
+
return Object.fromEntries([
|
|
1023
|
+
...(0, declared_1.getDeclaredLocalAccessEnvs)(config, componentName).map((env) => [
|
|
1024
|
+
(0, exports.getLocalAccessKeyFileVarName)(env),
|
|
1025
|
+
getPath(env),
|
|
1026
|
+
]),
|
|
1027
|
+
...Object.entries((0, declared_1.getDeclaredLocalAccessCredentialsEnvVars)(config, componentName)).map(([name, env]) => [name, getPath(env)]),
|
|
1028
|
+
].filter(([, path]) => (0, fs_1.existsSync)(path)));
|
|
1029
|
+
};
|
|
1030
|
+
exports.getLocalAccessKeyFileVars = getLocalAccessKeyFileVars;
|
|
1031
|
+
/**
|
|
1032
|
+
* writes the stored local access service account keys to gitignored
|
|
1033
|
+
* files (mode 0600) and removes files of declarations that are gone.
|
|
1034
|
+
* A no-op without any declaration (nothing is created). Never throws:
|
|
1035
|
+
* a vault that cannot be reached or an invalid declaration (reported by
|
|
1036
|
+
* `project doctor`) must not break catenv.
|
|
1037
|
+
*
|
|
1038
|
+
* The key is only read through the cache; if setup rotated it on another
|
|
1039
|
+
* machine, refresh with `catenv --vault-mode refresh`.
|
|
1040
|
+
*/
|
|
1041
|
+
const writeLocalAccessKeyFiles = async (io, config) => {
|
|
1042
|
+
const wanted = Object.keys(config.components).flatMap((componentName) => (0, declared_1.getDeclaredLocalAccessEnvs)(config, componentName).map((env) => ({
|
|
1043
|
+
env,
|
|
1044
|
+
componentName,
|
|
1045
|
+
secretName: (0, pipeline_1.getSecretVarName)("local", componentName, (0, pipeline_1.getLocalAccessGcpSecretName)(env)),
|
|
1046
|
+
fileName: (0, exports.getLocalAccessKeyFileName)(env, componentName),
|
|
1047
|
+
})));
|
|
1048
|
+
const folder = await getFolder();
|
|
1049
|
+
if (wanted.length === 0 && !(0, fs_1.existsSync)(folder))
|
|
1050
|
+
return;
|
|
1051
|
+
try {
|
|
1052
|
+
const secrets = wanted.length > 0
|
|
1053
|
+
? await (await io.getVaultManager()).readSecrets(wanted.map((w) => w.secretName), io)
|
|
1054
|
+
: {};
|
|
1055
|
+
await (0, promises_1.mkdir)(folder, { recursive: true, mode: 0o700 });
|
|
1056
|
+
// the parent folder ignores itself
|
|
1057
|
+
await (0, promises_1.writeFile)((0, path_1.join)(folder, "..", ".gitignore"), "*\n");
|
|
1058
|
+
const written = new Set();
|
|
1059
|
+
for (const { secretName, fileName, env, componentName } of wanted) {
|
|
1060
|
+
const key = secrets[secretName];
|
|
1061
|
+
if (!key) {
|
|
1062
|
+
io.log(`⚠️ no local access key for ${env}:${componentName} in the secret store — run \`catladder project setup\` (or \`catenv --vault-mode refresh\` if it was set up on another machine)`);
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
const path = (0, path_1.join)(folder, fileName);
|
|
1066
|
+
await (0, writePrivateFile_1.writePrivateFile)(path, key);
|
|
1067
|
+
written.add(fileName);
|
|
1068
|
+
}
|
|
1069
|
+
for (const file of await (0, promises_1.readdir)(folder)) {
|
|
1070
|
+
if (!written.has(file)) {
|
|
1071
|
+
await (0, promises_1.rm)((0, path_1.join)(folder, file), { force: true });
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
catch (e) {
|
|
1076
|
+
io.log(`⚠️ could not write local access keys: ${e.message}`);
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
exports.writeLocalAccessKeyFiles = writeLocalAccessKeyFiles;
|
|
1080
|
+
//# sourceMappingURL=keyFiles.js.map
|
|
1081
|
+
|
|
1082
|
+
/***/ }),
|
|
1083
|
+
|
|
934
1084
|
/***/ 55689:
|
|
935
1085
|
/***/ ((__unused_webpack_module, exports) => {
|
|
936
1086
|
|
|
@@ -1347,6 +1497,38 @@ exports.getProjectNamespace = getProjectNamespace;
|
|
|
1347
1497
|
|
|
1348
1498
|
/***/ }),
|
|
1349
1499
|
|
|
1500
|
+
/***/ 14550:
|
|
1501
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
1502
|
+
|
|
1503
|
+
"use strict";
|
|
1504
|
+
|
|
1505
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1506
|
+
exports.writePrivateFile = void 0;
|
|
1507
|
+
const promises_1 = __nccwpck_require__(91943);
|
|
1508
|
+
/**
|
|
1509
|
+
* writes a file readable by the owner only. Goes through a 0600 temp
|
|
1510
|
+
* file and a rename, so the content is never visible with looser
|
|
1511
|
+
* permissions (writing first and chmod-ing afterwards would leave an
|
|
1512
|
+
* existing 0644 file readable until the chmod).
|
|
1513
|
+
*/
|
|
1514
|
+
const writePrivateFile = async (path, content) => {
|
|
1515
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
1516
|
+
try {
|
|
1517
|
+
await (0, promises_1.writeFile)(tmp, content, { mode: 0o600 });
|
|
1518
|
+
// the mode is only applied when the file is created
|
|
1519
|
+
await (0, promises_1.chmod)(tmp, 0o600);
|
|
1520
|
+
await (0, promises_1.rename)(tmp, path);
|
|
1521
|
+
}
|
|
1522
|
+
catch (e) {
|
|
1523
|
+
await (0, promises_1.rm)(tmp, { force: true });
|
|
1524
|
+
throw e;
|
|
1525
|
+
}
|
|
1526
|
+
};
|
|
1527
|
+
exports.writePrivateFile = writePrivateFile;
|
|
1528
|
+
//# sourceMappingURL=writePrivateFile.js.map
|
|
1529
|
+
|
|
1530
|
+
/***/ }),
|
|
1531
|
+
|
|
1350
1532
|
/***/ 42827:
|
|
1351
1533
|
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
1352
1534
|
|
|
@@ -1453,30 +1635,21 @@ class BitwardenVault {
|
|
|
1453
1635
|
: {};
|
|
1454
1636
|
const notes = (0, yaml_1.stringify)({ ...previous, ...secrets });
|
|
1455
1637
|
if (existing) {
|
|
1456
|
-
await (0, bw_1.runBw)(bwSession, [
|
|
1457
|
-
"edit",
|
|
1458
|
-
"item",
|
|
1459
|
-
existing.id,
|
|
1460
|
-
encodePayload({ ...existing, notes }),
|
|
1461
|
-
]);
|
|
1638
|
+
await (0, bw_1.runBw)(bwSession, ["edit", "item", existing.id], encodePayload({ ...existing, notes }));
|
|
1462
1639
|
}
|
|
1463
1640
|
else {
|
|
1464
|
-
await (0, bw_1.runBw)(bwSession, [
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
}
|
|
1477
|
-
: {}),
|
|
1478
|
-
}),
|
|
1479
|
-
]);
|
|
1641
|
+
await (0, bw_1.runBw)(bwSession, ["create", "item"], encodePayload({
|
|
1642
|
+
type: SECURE_NOTE_TYPE,
|
|
1643
|
+
secureNote: { type: 0 },
|
|
1644
|
+
name,
|
|
1645
|
+
notes,
|
|
1646
|
+
...(collection.organizationId
|
|
1647
|
+
? {
|
|
1648
|
+
organizationId: collection.organizationId,
|
|
1649
|
+
collectionIds: [collection.id],
|
|
1650
|
+
}
|
|
1651
|
+
: {}),
|
|
1652
|
+
}));
|
|
1480
1653
|
}
|
|
1481
1654
|
}
|
|
1482
1655
|
}
|
|
@@ -1523,6 +1696,7 @@ exports.GitlabVault = GitlabVault;
|
|
|
1523
1696
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1524
1697
|
exports.VaultManager = void 0;
|
|
1525
1698
|
const pipeline_1 = __nccwpck_require__(53695);
|
|
1699
|
+
const declared_1 = __nccwpck_require__(83228);
|
|
1526
1700
|
const BitwardenVault_1 = __nccwpck_require__(42827);
|
|
1527
1701
|
const cache_1 = __nccwpck_require__(57011);
|
|
1528
1702
|
const GitlabVault_1 = __nccwpck_require__(6020);
|
|
@@ -1605,6 +1779,15 @@ class VaultManager {
|
|
|
1605
1779
|
])));
|
|
1606
1780
|
}
|
|
1607
1781
|
}
|
|
1782
|
+
/**
|
|
1783
|
+
* updates the local cache only (the vault already holds the values),
|
|
1784
|
+
* "local" env only
|
|
1785
|
+
*/
|
|
1786
|
+
async cacheSecrets(env, componentName, secrets) {
|
|
1787
|
+
if (env !== CACHED_ENV)
|
|
1788
|
+
return;
|
|
1789
|
+
await (0, cache_1.updateSecretsCache)(this.vault.id, env, componentName, secrets);
|
|
1790
|
+
}
|
|
1608
1791
|
/**
|
|
1609
1792
|
* the subset of the vault secrets that may be cached: the declared
|
|
1610
1793
|
* secrets of the "local" env, per component, as a hierarchy
|
|
@@ -1628,6 +1811,14 @@ class VaultManager {
|
|
|
1628
1811
|
return Object.keys(local).length > 0 ? { [CACHED_ENV]: local } : {};
|
|
1629
1812
|
}
|
|
1630
1813
|
async declaredSecretKeys(componentName) {
|
|
1814
|
+
return [
|
|
1815
|
+
...(await this.declaredEnvSecretKeys(componentName)),
|
|
1816
|
+
// the local access keys `project setup` provisions are not part
|
|
1817
|
+
// of any env's vars, but belong to the cached local secrets
|
|
1818
|
+
...(0, declared_1.getDeclaredLocalAccessEnvs)(this.config, componentName).map(pipeline_1.getLocalAccessGcpSecretName),
|
|
1819
|
+
];
|
|
1820
|
+
}
|
|
1821
|
+
async declaredEnvSecretKeys(componentName) {
|
|
1631
1822
|
try {
|
|
1632
1823
|
const { secretEnvVarKeys, jobOnlyVars } = await (0, pipeline_1.getEnvironment)({
|
|
1633
1824
|
config: this.config,
|
|
@@ -1737,14 +1928,40 @@ const getBwSession = async (io, { allowPrompt }) => {
|
|
|
1737
1928
|
};
|
|
1738
1929
|
exports.getBwSession = getBwSession;
|
|
1739
1930
|
/**
|
|
1740
|
-
* runs a bw command with the given session and returns stdout
|
|
1931
|
+
* runs a bw command with the given session and returns stdout.
|
|
1932
|
+
*
|
|
1933
|
+
* `input` is written to stdin: use it for payloads (e.g. the secrets of
|
|
1934
|
+
* `bw create item`), which would otherwise be visible in `ps` and be
|
|
1935
|
+
* echoed by node's "Command failed" error.
|
|
1741
1936
|
*/
|
|
1742
|
-
const runBw = async (session, args) => {
|
|
1743
|
-
const
|
|
1744
|
-
|
|
1745
|
-
|
|
1937
|
+
const runBw = async (session, args, input) => {
|
|
1938
|
+
const fullArgs = [...args, "--raw", "--nointeraction"];
|
|
1939
|
+
const env = { ...process.env, BW_SESSION: session };
|
|
1940
|
+
if (input === undefined) {
|
|
1941
|
+
const { stdout } = await execFile("bw", fullArgs, {
|
|
1942
|
+
env,
|
|
1943
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
1944
|
+
});
|
|
1945
|
+
return stdout;
|
|
1946
|
+
}
|
|
1947
|
+
return new Promise((resolve, reject) => {
|
|
1948
|
+
const child = (0, child_process_1.spawn)("bw", fullArgs, {
|
|
1949
|
+
env,
|
|
1950
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1951
|
+
});
|
|
1952
|
+
let stdout = "";
|
|
1953
|
+
let stderr = "";
|
|
1954
|
+
child.stdout.on("data", (data) => (stdout += data.toString("utf-8")));
|
|
1955
|
+
child.stderr.on("data", (data) => (stderr += data.toString("utf-8")));
|
|
1956
|
+
child.on("error", reject);
|
|
1957
|
+
child.on("close", (code) => code === 0
|
|
1958
|
+
? resolve(stdout)
|
|
1959
|
+
: reject(new Error(`bw ${args[0]} failed: ${stderr.trim()}`)));
|
|
1960
|
+
child.stdin.on("error", () => {
|
|
1961
|
+
// bw exiting early closes the pipe, the close handler reports why
|
|
1962
|
+
});
|
|
1963
|
+
child.stdin.end(input);
|
|
1746
1964
|
});
|
|
1747
|
-
return stdout;
|
|
1748
1965
|
};
|
|
1749
1966
|
exports.runBw = runBw;
|
|
1750
1967
|
const runBwJson = async (session, args) => JSON.parse(await (0, exports.runBw)(session, args));
|
|
@@ -1762,6 +1979,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
1762
1979
|
exports.updateSecretsCache = exports.writeSecretsCache = exports.readSecretsCache = void 0;
|
|
1763
1980
|
const promises_1 = __nccwpck_require__(91943);
|
|
1764
1981
|
const path_1 = __nccwpck_require__(16928);
|
|
1982
|
+
const writePrivateFile_1 = __nccwpck_require__(14550);
|
|
1765
1983
|
const gitProjectInformation_1 = __nccwpck_require__(86884);
|
|
1766
1984
|
const getCacheFolder = async () => (0, path_1.join)(await (0, gitProjectInformation_1.getProjectRootPath)(), ".catladder");
|
|
1767
1985
|
const getCachePath = async () => (0, path_1.join)(await getCacheFolder(), "secrets.json");
|
|
@@ -1776,7 +1994,7 @@ const readSecretsCache = async () => {
|
|
|
1776
1994
|
exports.readSecretsCache = readSecretsCache;
|
|
1777
1995
|
const writeSecretsCache = async (vaultId, secrets) => {
|
|
1778
1996
|
const folder = await getCacheFolder();
|
|
1779
|
-
await (0, promises_1.mkdir)(folder, { recursive: true });
|
|
1997
|
+
await (0, promises_1.mkdir)(folder, { recursive: true, mode: 0o700 });
|
|
1780
1998
|
// the folder ignores itself, so the cache can never be committed
|
|
1781
1999
|
await (0, promises_1.writeFile)((0, path_1.join)(folder, ".gitignore"), "*\n");
|
|
1782
2000
|
const cache = {
|
|
@@ -1784,7 +2002,9 @@ const writeSecretsCache = async (vaultId, secrets) => {
|
|
|
1784
2002
|
updatedAt: new Date().toISOString(),
|
|
1785
2003
|
secrets,
|
|
1786
2004
|
};
|
|
1787
|
-
|
|
2005
|
+
const path = await getCachePath();
|
|
2006
|
+
// may hold key material (local access keys): owner only
|
|
2007
|
+
await (0, writePrivateFile_1.writePrivateFile)(path, JSON.stringify(cache, null, 2));
|
|
1788
2008
|
};
|
|
1789
2009
|
exports.writeSecretsCache = writeSecretsCache;
|
|
1790
2010
|
/**
|
|
@@ -12677,6 +12897,191 @@ __exportStar(__nccwpck_require__(91804), exports);
|
|
|
12677
12897
|
__exportStar(__nccwpck_require__(94143), exports);
|
|
12678
12898
|
__exportStar(__nccwpck_require__(98011), exports);
|
|
12679
12899
|
__exportStar(__nccwpck_require__(61459), exports);
|
|
12900
|
+
__exportStar(__nccwpck_require__(54165), exports);
|
|
12901
|
+
//# sourceMappingURL=index.js.map
|
|
12902
|
+
|
|
12903
|
+
/***/ }),
|
|
12904
|
+
|
|
12905
|
+
/***/ 54165:
|
|
12906
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
12907
|
+
|
|
12908
|
+
"use strict";
|
|
12909
|
+
|
|
12910
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
12911
|
+
exports.getLocalAccessGcpCredentialsEnvVars = exports.diffRoleBindings = exports.getLocalAccessGcpSecretName = exports.getLocalAccessGcpEnvs = exports.getLocalAccessGcpRoles = exports.getLocalAccessConfig = exports.getDeniedLocalAccessRoleReason = exports.LOCAL_ACCESS_GCP_SA_NAME = exports.LOCAL_ACCESS_GCP_PRESETS = void 0;
|
|
12912
|
+
const configruedEnvs_1 = __nccwpck_require__(38866);
|
|
12913
|
+
const getEnvConfig_1 = __nccwpck_require__(69578);
|
|
12914
|
+
const types_1 = __nccwpck_require__(79689);
|
|
12915
|
+
exports.LOCAL_ACCESS_GCP_PRESETS = {
|
|
12916
|
+
// no run.viewer on purpose, see LocalAccessGcpPreset
|
|
12917
|
+
logs: ["roles/logging.viewer"],
|
|
12918
|
+
// not read-only, see LocalAccessGcpPreset; only reachable as a
|
|
12919
|
+
// preset, getDeniedLocalAccessRoleReason rejects it in `roles`
|
|
12920
|
+
vertex: ["roles/aiplatform.user"],
|
|
12921
|
+
};
|
|
12922
|
+
/** name prefix of the service account `project setup` creates (see getGcloudServiceAccountNames) */
|
|
12923
|
+
exports.LOCAL_ACCESS_GCP_SA_NAME = "local-diag";
|
|
12924
|
+
const ROLE_FORMAT = /^(roles\/[A-Za-z0-9_.]+|projects\/[a-z0-9-]+\/roles\/[A-Za-z0-9_.]+|organizations\/\d+\/roles\/[A-Za-z0-9_.]+)$/;
|
|
12925
|
+
/**
|
|
12926
|
+
* roles that are read-only on paper but expose secrets: cloud run
|
|
12927
|
+
* services/jobs carry their secrets as plain env vars, so anything that
|
|
12928
|
+
* may read them reads every deployed secret. Denied even with
|
|
12929
|
+
* `allowAnyRole`.
|
|
12930
|
+
*/
|
|
12931
|
+
const SECRET_EXPOSING_ROLES = {
|
|
12932
|
+
"roles/viewer": "roles/viewer can read cloud run env vars (secrets)",
|
|
12933
|
+
"roles/run.viewer": "roles/run.viewer can read cloud run env vars (secrets)",
|
|
12934
|
+
"roles/secretmanager.secretAccessor": "this role reads secret values",
|
|
12935
|
+
"roles/secretmanager.admin": "this role reads secret values",
|
|
12936
|
+
};
|
|
12937
|
+
/**
|
|
12938
|
+
* roles granted without `allowAnyRole`. An explicit list instead of a
|
|
12939
|
+
* name pattern: a role called `*.viewer` / `*.reader` says nothing about
|
|
12940
|
+
* its permissions (custom roles can hold anything, and predefined ones
|
|
12941
|
+
* like storage.objectViewer, cloudfunctions.viewer, compute.viewer or
|
|
12942
|
+
* logging.privateLogViewer expose data or secrets).
|
|
12943
|
+
*/
|
|
12944
|
+
const SAFE_ROLES = [
|
|
12945
|
+
"roles/logging.viewer",
|
|
12946
|
+
"roles/monitoring.viewer",
|
|
12947
|
+
"roles/errorreporting.viewer",
|
|
12948
|
+
];
|
|
12949
|
+
/** `roles/foo.bar.v1` -> `foo.bar` (version suffixes must not defeat the checks) */
|
|
12950
|
+
const getRoleName = (role) => { var _a; return ((_a = role.split("/").pop()) !== null && _a !== void 0 ? _a : role).replace(/(\.v\d+)+$/i, ""); };
|
|
12951
|
+
/**
|
|
12952
|
+
* why a role may not be granted for local access, or undefined if it
|
|
12953
|
+
* is acceptable.
|
|
12954
|
+
*
|
|
12955
|
+
* By default only the roles in {@link SAFE_ROLES} pass; custom roles
|
|
12956
|
+
* and everything else need `allowAnyRole`. `allowAnyRole` lifts the
|
|
12957
|
+
* allow-list, but the obvious write/admin/impersonation roles and the
|
|
12958
|
+
* secret exposing roles stay denied.
|
|
12959
|
+
*/
|
|
12960
|
+
const getDeniedLocalAccessRoleReason = (role, options = {}) => {
|
|
12961
|
+
var _a;
|
|
12962
|
+
const secretReason = SECRET_EXPOSING_ROLES[role];
|
|
12963
|
+
if (secretReason)
|
|
12964
|
+
return secretReason;
|
|
12965
|
+
const name = getRoleName(role);
|
|
12966
|
+
if (name === "owner" || name === "editor") {
|
|
12967
|
+
return "owner/editor are full write roles";
|
|
12968
|
+
}
|
|
12969
|
+
const last = (_a = name.split(".").pop()) !== null && _a !== void 0 ? _a : name;
|
|
12970
|
+
if (/admin$/i.test(last)) {
|
|
12971
|
+
return "admin roles can change resources";
|
|
12972
|
+
}
|
|
12973
|
+
if (/(editor|developer|writer|creator|deployer|invoker|builder|publisher|client|user|objectUser)$/i.test(last) &&
|
|
12974
|
+
!/^iam\./.test(name)) {
|
|
12975
|
+
return "this role can change resources";
|
|
12976
|
+
}
|
|
12977
|
+
if (/^iam\./.test(name) &&
|
|
12978
|
+
/(serviceAccountUser|serviceAccountTokenCreator|serviceAccountKeyAdmin|workloadIdentityUser)$/.test(name)) {
|
|
12979
|
+
return "this role allows impersonating service accounts";
|
|
12980
|
+
}
|
|
12981
|
+
if (!options.allowAnyRole && !SAFE_ROLES.includes(role)) {
|
|
12982
|
+
return `only these roles are allowed: ${SAFE_ROLES.join(", ")} (set allowAnyRole: true to grant others at your own risk)`;
|
|
12983
|
+
}
|
|
12984
|
+
return undefined;
|
|
12985
|
+
};
|
|
12986
|
+
exports.getDeniedLocalAccessRoleReason = getDeniedLocalAccessRoleReason;
|
|
12987
|
+
/**
|
|
12988
|
+
* the effective local access declaration of an env: the env's own
|
|
12989
|
+
* declaration replaces the project default entirely
|
|
12990
|
+
*/
|
|
12991
|
+
const getLocalAccessConfig = (config, env) => {
|
|
12992
|
+
var _a;
|
|
12993
|
+
if (env === "local")
|
|
12994
|
+
return undefined;
|
|
12995
|
+
const envEntry = (_a = config.environments) === null || _a === void 0 ? void 0 : _a[env];
|
|
12996
|
+
const declared = envEntry && envEntry.localAccess !== undefined
|
|
12997
|
+
? envEntry.localAccess
|
|
12998
|
+
: config.localAccess;
|
|
12999
|
+
return declared || undefined;
|
|
13000
|
+
};
|
|
13001
|
+
exports.getLocalAccessConfig = getLocalAccessConfig;
|
|
13002
|
+
/**
|
|
13003
|
+
* the gcp roles local access grants in `env`: preset roles plus the
|
|
13004
|
+
* explicit ones, deduplicated and sorted. Throws on an unknown preset,
|
|
13005
|
+
* a malformed role or a denied (write/admin) role.
|
|
13006
|
+
*/
|
|
13007
|
+
const getLocalAccessGcpRoles = (config, env) => {
|
|
13008
|
+
var _a, _b, _c;
|
|
13009
|
+
const gcp = (_a = (0, exports.getLocalAccessConfig)(config, env)) === null || _a === void 0 ? void 0 : _a.gcp;
|
|
13010
|
+
if (!gcp)
|
|
13011
|
+
return [];
|
|
13012
|
+
const roles = new Set();
|
|
13013
|
+
for (const preset of (_b = gcp.presets) !== null && _b !== void 0 ? _b : []) {
|
|
13014
|
+
const presetRoles = exports.LOCAL_ACCESS_GCP_PRESETS[preset];
|
|
13015
|
+
if (!presetRoles) {
|
|
13016
|
+
throw new Error(`localAccess (${env}): unknown gcp preset '${preset}', known: ${Object.keys(exports.LOCAL_ACCESS_GCP_PRESETS).join(", ")}`);
|
|
13017
|
+
}
|
|
13018
|
+
presetRoles.forEach((role) => roles.add(role));
|
|
13019
|
+
}
|
|
13020
|
+
for (const role of (_c = gcp.roles) !== null && _c !== void 0 ? _c : []) {
|
|
13021
|
+
if (!ROLE_FORMAT.test(role)) {
|
|
13022
|
+
throw new Error(`localAccess (${env}): '${role}' is not a valid role (expected e.g. roles/logging.viewer)`);
|
|
13023
|
+
}
|
|
13024
|
+
const reason = (0, exports.getDeniedLocalAccessRoleReason)(role, {
|
|
13025
|
+
allowAnyRole: gcp.allowAnyRole,
|
|
13026
|
+
});
|
|
13027
|
+
if (reason) {
|
|
13028
|
+
throw new Error(`localAccess (${env}): role '${role}' is not allowed (${reason})`);
|
|
13029
|
+
}
|
|
13030
|
+
roles.add(role);
|
|
13031
|
+
}
|
|
13032
|
+
return [...roles].sort();
|
|
13033
|
+
};
|
|
13034
|
+
exports.getLocalAccessGcpRoles = getLocalAccessGcpRoles;
|
|
13035
|
+
/**
|
|
13036
|
+
* the envs of a component that declare gcp local access. Empty for
|
|
13037
|
+
* components that do not deploy to google cloud run.
|
|
13038
|
+
*/
|
|
13039
|
+
const getLocalAccessGcpEnvs = (config, componentName) => (0, configruedEnvs_1.getAllEnvs)(config, componentName).filter((env) =>
|
|
13040
|
+
// only google cloud run envs have a project to grant roles in; the
|
|
13041
|
+
// env's own `deploy` overrides the component's
|
|
13042
|
+
(0, types_1.isOfDeployType)((0, getEnvConfig_1.getEnvConfig)(config, componentName, env).deploy, "google-cloudrun") && (0, exports.getLocalAccessGcpRoles)(config, env).length > 0);
|
|
13043
|
+
exports.getLocalAccessGcpEnvs = getLocalAccessGcpEnvs;
|
|
13044
|
+
/**
|
|
13045
|
+
* name of the secret (stored under the `local` env of the component)
|
|
13046
|
+
* holding the service account key of `env`'s local access
|
|
13047
|
+
*/
|
|
13048
|
+
const getLocalAccessGcpSecretName = (env) => `LOCAL_ACCESS_GCP_${env.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase()}_CREDENTIALS`;
|
|
13049
|
+
exports.getLocalAccessGcpSecretName = getLocalAccessGcpSecretName;
|
|
13050
|
+
/**
|
|
13051
|
+
* the bindings to add and remove so that `current` matches `desired`.
|
|
13052
|
+
* The local access account is dedicated, so every binding it carries
|
|
13053
|
+
* beyond the declaration is extra.
|
|
13054
|
+
*/
|
|
13055
|
+
const diffRoleBindings = (desired, current) => ({
|
|
13056
|
+
missing: desired.filter((role) => !current.includes(role)),
|
|
13057
|
+
extra: current.filter((role) => !desired.includes(role)),
|
|
13058
|
+
});
|
|
13059
|
+
exports.diffRoleBindings = diffRoleBindings;
|
|
13060
|
+
const ENV_VAR_NAME_FORMAT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
13061
|
+
/**
|
|
13062
|
+
* the extra env var names (`credentialsEnvVar`) under which the local
|
|
13063
|
+
* env of a component exports the key file path, mapped to the env whose
|
|
13064
|
+
* key they point at. Throws on a malformed name or on two envs
|
|
13065
|
+
* exporting the same name (e.g. a project default declaring it).
|
|
13066
|
+
*/
|
|
13067
|
+
const getLocalAccessGcpCredentialsEnvVars = (config, componentName) => {
|
|
13068
|
+
var _a, _b;
|
|
13069
|
+
const vars = {};
|
|
13070
|
+
for (const env of (0, exports.getLocalAccessGcpEnvs)(config, componentName)) {
|
|
13071
|
+
const name = (_b = (_a = (0, exports.getLocalAccessConfig)(config, env)) === null || _a === void 0 ? void 0 : _a.gcp) === null || _b === void 0 ? void 0 : _b.credentialsEnvVar;
|
|
13072
|
+
if (!name)
|
|
13073
|
+
continue;
|
|
13074
|
+
if (!ENV_VAR_NAME_FORMAT.test(name)) {
|
|
13075
|
+
throw new Error(`localAccess (${env}): credentialsEnvVar '${name}' is not a valid env var name`);
|
|
13076
|
+
}
|
|
13077
|
+
if (vars[name]) {
|
|
13078
|
+
throw new Error(`localAccess (${componentName}): credentialsEnvVar '${name}' is declared by both ${vars[name]} and ${env}; declare it in one env only (environments.<env>.localAccess)`);
|
|
13079
|
+
}
|
|
13080
|
+
vars[name] = env;
|
|
13081
|
+
}
|
|
13082
|
+
return vars;
|
|
13083
|
+
};
|
|
13084
|
+
exports.getLocalAccessGcpCredentialsEnvVars = getLocalAccessGcpCredentialsEnvVars;
|
|
12680
13085
|
//# sourceMappingURL=index.js.map
|
|
12681
13086
|
|
|
12682
13087
|
/***/ }),
|