@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.
Files changed (83) hide show
  1. package/dist/apps/cli/src/apps/catenv/catenv.js +3 -0
  2. package/dist/apps/cli/src/apps/catenv/catenv.js.map +1 -1
  3. package/dist/apps/cli/src/apps/cli/commands/project/doctor/GcloudProjectInspector.d.ts +4 -0
  4. package/dist/apps/cli/src/apps/cli/commands/project/doctor/GcloudProjectInspector.js +12 -0
  5. package/dist/apps/cli/src/apps/cli/commands/project/doctor/GcloudProjectInspector.js.map +1 -1
  6. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkCloudRun.d.ts +2 -2
  7. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkCloudRun.js +3 -1
  8. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkCloudRun.js.map +1 -1
  9. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkLocalAccess.d.ts +10 -0
  10. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkLocalAccess.js +98 -0
  11. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkLocalAccess.js.map +1 -0
  12. package/dist/apps/cli/src/apps/cli/commands/project/doctor/index.js +1 -1
  13. package/dist/apps/cli/src/apps/cli/commands/project/doctor/index.js.map +1 -1
  14. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupContext.js +2 -0
  15. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupContext.js.map +1 -1
  16. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupLocalAccess.d.ts +9 -0
  17. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupLocalAccess.js +31 -0
  18. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupLocalAccess.js.map +1 -0
  19. package/dist/apps/cli/src/config/getProjectConfig.js +9 -1
  20. package/dist/apps/cli/src/config/getProjectConfig.js.map +1 -1
  21. package/dist/apps/cli/src/gcloud/localAccess.d.ts +53 -0
  22. package/dist/apps/cli/src/gcloud/localAccess.js +176 -0
  23. package/dist/apps/cli/src/gcloud/localAccess.js.map +1 -0
  24. package/dist/apps/cli/src/gcloud/serviceAccounts.js +9 -2
  25. package/dist/apps/cli/src/gcloud/serviceAccounts.js.map +1 -1
  26. package/dist/apps/cli/src/localAccess/declared.d.ts +14 -0
  27. package/dist/apps/cli/src/localAccess/declared.js +34 -0
  28. package/dist/apps/cli/src/localAccess/declared.js.map +1 -0
  29. package/dist/apps/cli/src/localAccess/keyFiles.d.ts +28 -0
  30. package/dist/apps/cli/src/localAccess/keyFiles.js +91 -0
  31. package/dist/apps/cli/src/localAccess/keyFiles.js.map +1 -0
  32. package/dist/apps/cli/src/utils/writePrivateFile.d.ts +7 -0
  33. package/dist/apps/cli/src/utils/writePrivateFile.js +25 -0
  34. package/dist/apps/cli/src/utils/writePrivateFile.js.map +1 -0
  35. package/dist/apps/cli/src/vault/BitwardenVault.js +13 -22
  36. package/dist/apps/cli/src/vault/BitwardenVault.js.map +1 -1
  37. package/dist/apps/cli/src/vault/VaultManager.d.ts +6 -0
  38. package/dist/apps/cli/src/vault/VaultManager.js +18 -0
  39. package/dist/apps/cli/src/vault/VaultManager.js.map +1 -1
  40. package/dist/apps/cli/src/vault/bw.d.ts +6 -2
  41. package/dist/apps/cli/src/vault/bw.js +32 -6
  42. package/dist/apps/cli/src/vault/bw.js.map +1 -1
  43. package/dist/apps/cli/src/vault/cache.js +5 -2
  44. package/dist/apps/cli/src/vault/cache.js.map +1 -1
  45. package/dist/bundles/catenv/index.js +436 -31
  46. package/dist/bundles/cli/index.js +786 -35
  47. package/dist/bundles/skills/catladder-secrets/SKILL.md +35 -3
  48. package/dist/packages/pipeline/src/index.d.ts +1 -0
  49. package/dist/packages/pipeline/src/index.js +1 -0
  50. package/dist/packages/pipeline/src/index.js.map +1 -1
  51. package/dist/packages/pipeline/src/localAccess/index.d.ts +53 -0
  52. package/dist/packages/pipeline/src/localAccess/index.js +177 -0
  53. package/dist/packages/pipeline/src/localAccess/index.js.map +1 -0
  54. package/dist/packages/pipeline/src/types/config.d.ts +75 -0
  55. package/dist/packages/pipeline/src/types/config.js.map +1 -1
  56. package/dist/skills/catladder-secrets/SKILL.md +35 -3
  57. package/dist/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +1 -1
  59. package/src/apps/catenv/catenv.ts +4 -0
  60. package/src/apps/cli/commands/project/doctor/GcloudProjectInspector.ts +25 -1
  61. package/src/apps/cli/commands/project/doctor/__tests__/checkLocalAccess.test.ts +127 -0
  62. package/src/apps/cli/commands/project/doctor/checkCloudRun.ts +5 -1
  63. package/src/apps/cli/commands/project/doctor/checkLocalAccess.ts +146 -0
  64. package/src/apps/cli/commands/project/doctor/index.ts +1 -1
  65. package/src/apps/cli/commands/project/setup/setupContext.ts +2 -0
  66. package/src/apps/cli/commands/project/setup/setupLocalAccess.ts +44 -0
  67. package/src/config/__tests__/getEnvVarsResolved.test.ts +45 -0
  68. package/src/config/getProjectConfig.ts +11 -1
  69. package/src/gcloud/__tests__/localAccess.test.ts +82 -0
  70. package/src/gcloud/__tests__/syncLocalAccess.test.ts +196 -0
  71. package/src/gcloud/localAccess.ts +257 -0
  72. package/src/gcloud/serviceAccounts.ts +8 -2
  73. package/src/localAccess/__tests__/keyFiles.test.ts +166 -0
  74. package/src/localAccess/declared.ts +38 -0
  75. package/src/localAccess/keyFiles.ts +126 -0
  76. package/src/utils/__tests__/writePrivateFile.test.ts +40 -0
  77. package/src/utils/writePrivateFile.ts +20 -0
  78. package/src/vault/BitwardenVault.ts +8 -9
  79. package/src/vault/VaultManager.ts +28 -0
  80. package/src/vault/__tests__/BitwardenVault.test.ts +4 -4
  81. package/src/vault/__tests__/cache.test.ts +37 -0
  82. package/src/vault/bw.ts +34 -9
  83. 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
- return resolveSecrets(io, [
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
- "create",
1466
- "item",
1467
- encodePayload({
1468
- type: SECURE_NOTE_TYPE,
1469
- secureNote: { type: 0 },
1470
- name,
1471
- notes,
1472
- ...(collection.organizationId
1473
- ? {
1474
- organizationId: collection.organizationId,
1475
- collectionIds: [collection.id],
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 { stdout } = await execFile("bw", [...args, "--raw", "--nointeraction"], {
1744
- env: { ...process.env, BW_SESSION: session },
1745
- maxBuffer: 50 * 1024 * 1024,
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
- await (0, promises_1.writeFile)(await getCachePath(), JSON.stringify(cache, null, 2));
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
  /***/ }),