@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
|
@@ -475,6 +475,18 @@ class GcloudProjectInspector {
|
|
|
475
475
|
.filter((binding) => { var _a; return (_a = binding.members) === null || _a === void 0 ? void 0 : _a.includes(member); })
|
|
476
476
|
.map((binding) => binding.role);
|
|
477
477
|
}
|
|
478
|
+
/** roles bound to the member without an iam condition */
|
|
479
|
+
async getUnconditionalRolesOfMember(projectId, member) {
|
|
480
|
+
return (await this.getIamBindings(projectId))
|
|
481
|
+
.filter((b) => { var _a; return !b.condition && ((_a = b.members) === null || _a === void 0 ? void 0 : _a.includes(member)); })
|
|
482
|
+
.map((b) => b.role);
|
|
483
|
+
}
|
|
484
|
+
/** roles bound to the member under an iam condition */
|
|
485
|
+
async getConditionalRolesOfMember(projectId, member) {
|
|
486
|
+
return (await this.getIamBindings(projectId))
|
|
487
|
+
.filter((b) => { var _a; return b.condition && ((_a = b.members) === null || _a === void 0 ? void 0 : _a.includes(member)); })
|
|
488
|
+
.map((b) => b.role);
|
|
489
|
+
}
|
|
478
490
|
getEnabledServices(projectId) {
|
|
479
491
|
if (!this.enabledServices.has(projectId)) {
|
|
480
492
|
this.enabledServices.set(projectId, (0, child_process_promise_1.exec)(`gcloud services list --enabled --project=${projectId} --format="value(config.name)"`).then((result) => new Set(result.stdout
|
|
@@ -512,6 +524,7 @@ const serviceAccountNames_1 = __nccwpck_require__(67851);
|
|
|
512
524
|
const serviceAccounts_1 = __nccwpck_require__(18190);
|
|
513
525
|
const concurrency_1 = __nccwpck_require__(59723);
|
|
514
526
|
const setupCloudRun_1 = __nccwpck_require__(92526);
|
|
527
|
+
const checkLocalAccess_1 = __nccwpck_require__(80206);
|
|
515
528
|
const GcloudProjectInspector_1 = __nccwpck_require__(50379);
|
|
516
529
|
const getCloudRunConfig = (context) => {
|
|
517
530
|
var _a;
|
|
@@ -531,7 +544,7 @@ const getCloudRunConfig = (context) => {
|
|
|
531
544
|
* setup ran — deploys keep working and only the review teardown hits
|
|
532
545
|
* the missing `roles/cloudsql.admin` as a 403 in ci.
|
|
533
546
|
*/
|
|
534
|
-
const checkCloudRun = async (report, contexts) => {
|
|
547
|
+
const checkCloudRun = async (report, contexts, config) => {
|
|
535
548
|
var _a;
|
|
536
549
|
const cloudRunContexts = contexts.filter((context) => { var _a; return (0, pipeline_1.isOfDeployType)((_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config, "google-cloudrun"); });
|
|
537
550
|
if (cloudRunContexts.length === 0)
|
|
@@ -602,6 +615,7 @@ const checkCloudRun = async (report, contexts) => {
|
|
|
602
615
|
report.ok(`${label}: service account exists with all config-implied roles`);
|
|
603
616
|
}
|
|
604
617
|
}
|
|
618
|
+
await (0, checkLocalAccess_1.checkLocalAccess)(report, config, cloudRunContexts, inspector);
|
|
605
619
|
};
|
|
606
620
|
exports.checkCloudRun = checkCloudRun;
|
|
607
621
|
//# sourceMappingURL=checkCloudRun.js.map
|
|
@@ -1052,6 +1066,111 @@ exports.checkGitlab = checkGitlab;
|
|
|
1052
1066
|
|
|
1053
1067
|
/***/ }),
|
|
1054
1068
|
|
|
1069
|
+
/***/ 80206:
|
|
1070
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
1071
|
+
|
|
1072
|
+
"use strict";
|
|
1073
|
+
|
|
1074
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1075
|
+
exports.checkLocalAccess = void 0;
|
|
1076
|
+
const pipeline_1 = __nccwpck_require__(53695);
|
|
1077
|
+
const localAccess_1 = __nccwpck_require__(94216);
|
|
1078
|
+
const serviceAccounts_1 = __nccwpck_require__(18190);
|
|
1079
|
+
const concurrency_1 = __nccwpck_require__(59723);
|
|
1080
|
+
/**
|
|
1081
|
+
* compares the read-only local access service accounts (`localAccess`
|
|
1082
|
+
* in catladder.ts) with their declaration: missing account, missing and
|
|
1083
|
+
* extra bindings, and an account that exists although nothing is
|
|
1084
|
+
* declared any more. Expects an authenticated inspector.
|
|
1085
|
+
*/
|
|
1086
|
+
const checkLocalAccess = async (report, config, contexts, inspector) => {
|
|
1087
|
+
const results = await (0, concurrency_1.mapWithConcurrency)(contexts, 5, async (context) => {
|
|
1088
|
+
var _a;
|
|
1089
|
+
const deployConfig = (_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config;
|
|
1090
|
+
if (!(0, pipeline_1.isOfDeployType)(deployConfig, "google-cloudrun"))
|
|
1091
|
+
return null;
|
|
1092
|
+
const label = `${context.env}:${context.name}`;
|
|
1093
|
+
let declared;
|
|
1094
|
+
try {
|
|
1095
|
+
declared = (0, pipeline_1.getLocalAccessGcpRoles)(config, context.env);
|
|
1096
|
+
(0, pipeline_1.getLocalAccessGcpCredentialsEnvVars)(config, context.name);
|
|
1097
|
+
}
|
|
1098
|
+
catch (e) {
|
|
1099
|
+
return { label, invalid: e.message };
|
|
1100
|
+
}
|
|
1101
|
+
const { fullIdentifier } = (0, localAccess_1.getLocalAccessServiceAccountNames)(context, deployConfig.projectId);
|
|
1102
|
+
const exists = await (0, serviceAccounts_1.accountExists)(fullIdentifier);
|
|
1103
|
+
const member = `serviceAccount:${fullIdentifier}`;
|
|
1104
|
+
const bound = exists
|
|
1105
|
+
? await inspector.getUnconditionalRolesOfMember(deployConfig.projectId, member)
|
|
1106
|
+
: [];
|
|
1107
|
+
const conditional = exists
|
|
1108
|
+
? await inspector.getConditionalRolesOfMember(deployConfig.projectId, member)
|
|
1109
|
+
: [];
|
|
1110
|
+
const keyCount = exists
|
|
1111
|
+
? (await (0, localAccess_1.listUserManagedKeyIds)(fullIdentifier)).length
|
|
1112
|
+
: 0;
|
|
1113
|
+
return {
|
|
1114
|
+
label,
|
|
1115
|
+
declared,
|
|
1116
|
+
fullIdentifier,
|
|
1117
|
+
exists,
|
|
1118
|
+
bound,
|
|
1119
|
+
conditional,
|
|
1120
|
+
keyCount,
|
|
1121
|
+
};
|
|
1122
|
+
});
|
|
1123
|
+
const relevant = results.filter((r) => r && ("invalid" in r || r.declared.length > 0 || r.exists));
|
|
1124
|
+
if (relevant.length === 0)
|
|
1125
|
+
return;
|
|
1126
|
+
report.section("local access (read-only service accounts)");
|
|
1127
|
+
for (const result of relevant) {
|
|
1128
|
+
if (!result)
|
|
1129
|
+
continue;
|
|
1130
|
+
if ("invalid" in result) {
|
|
1131
|
+
report.fail(`${result.label}: ${result.invalid}`);
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
const { label, declared, fullIdentifier, exists, bound, conditional, keyCount, } = result;
|
|
1135
|
+
if (declared.length > 0 && (0, pipeline_1.getVaultConfig)(config).type === "gitlab") {
|
|
1136
|
+
report.fail(`${label}: localAccess needs a vault that is not a CI variable store (the gitlab vault would expose the key to every CI job)`);
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
const heal = `run: catladder project setup`;
|
|
1140
|
+
if (declared.length === 0) {
|
|
1141
|
+
report.warn(`${label}: ${fullIdentifier} exists although no local access is declared`, heal);
|
|
1142
|
+
}
|
|
1143
|
+
else if (!exists) {
|
|
1144
|
+
report.fail(`${label}: ${fullIdentifier} does not exist`, heal);
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
const { missing, extra } = (0, pipeline_1.diffRoleBindings)(declared, bound);
|
|
1148
|
+
if (missing.length > 0) {
|
|
1149
|
+
report.fail(`${label}: local access account is missing roles: ${missing.join(", ")}`, heal);
|
|
1150
|
+
}
|
|
1151
|
+
if (extra.length > 0) {
|
|
1152
|
+
report.fail(`${label}: local access account has undeclared roles: ${extra.join(", ")}`, heal);
|
|
1153
|
+
}
|
|
1154
|
+
if (conditional.length > 0) {
|
|
1155
|
+
report.fail(`${label}: local access account has conditional roles (${conditional.join(", ")}) that setup does not remove`, "remove them by hand");
|
|
1156
|
+
}
|
|
1157
|
+
if (keyCount !== 1) {
|
|
1158
|
+
report.fail(`${label}: local access account has ${keyCount} user-managed keys, expected exactly 1`, heal);
|
|
1159
|
+
}
|
|
1160
|
+
if (missing.length === 0 &&
|
|
1161
|
+
extra.length === 0 &&
|
|
1162
|
+
conditional.length === 0 &&
|
|
1163
|
+
keyCount === 1) {
|
|
1164
|
+
report.ok(`${label}: local access account has exactly the declared roles`);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
exports.checkLocalAccess = checkLocalAccess;
|
|
1170
|
+
//# sourceMappingURL=checkLocalAccess.js.map
|
|
1171
|
+
|
|
1172
|
+
/***/ }),
|
|
1173
|
+
|
|
1055
1174
|
/***/ 62878:
|
|
1056
1175
|
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
1057
1176
|
|
|
@@ -1145,7 +1264,7 @@ const doctorProject = async (instance, onlyComponents) => {
|
|
|
1145
1264
|
if ((0, pipeline_1.getEnabledPipelineTypes)(config).includes("gitlab")) {
|
|
1146
1265
|
await (0, checkGitlab_1.checkGitlab)(instance, report, contexts);
|
|
1147
1266
|
}
|
|
1148
|
-
await (0, checkCloudRun_1.checkCloudRun)(report, contexts);
|
|
1267
|
+
await (0, checkCloudRun_1.checkCloudRun)(report, contexts, config);
|
|
1149
1268
|
await (0, checkGithubImageCasing_1.checkGithubImageCasing)(report, config);
|
|
1150
1269
|
await (0, checkGithubReleaseWorkflow_1.checkGithubReleaseWorkflow)(report, config);
|
|
1151
1270
|
await (0, checkGithub_1.checkGithub)(report, config);
|
|
@@ -1704,6 +1823,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
1704
1823
|
exports.setupContext = void 0;
|
|
1705
1824
|
const pipeline_1 = __nccwpck_require__(53695);
|
|
1706
1825
|
const setupCloudRun_1 = __nccwpck_require__(92526);
|
|
1826
|
+
const setupLocalAccess_1 = __nccwpck_require__(3299);
|
|
1707
1827
|
const setupKubernetes_1 = __nccwpck_require__(51796);
|
|
1708
1828
|
const logSection_1 = __nccwpck_require__(74120);
|
|
1709
1829
|
const setupContext = async (instance, context) => {
|
|
@@ -1711,6 +1831,7 @@ const setupContext = async (instance, context) => {
|
|
|
1711
1831
|
var _a, _b;
|
|
1712
1832
|
if ((0, pipeline_1.isOfDeployType)((_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config, "google-cloudrun")) {
|
|
1713
1833
|
await (0, setupCloudRun_1.setupCloudRun)(instance, context);
|
|
1834
|
+
await (0, setupLocalAccess_1.setupLocalAccess)(instance, context);
|
|
1714
1835
|
}
|
|
1715
1836
|
const deployConfig = (_b = context.deploy) === null || _b === void 0 ? void 0 : _b.config;
|
|
1716
1837
|
if ((0, pipeline_1.isOfDeployType)(deployConfig, "kubernetes")) {
|
|
@@ -2067,6 +2188,44 @@ exports.setupKubernetes = setupKubernetes;
|
|
|
2067
2188
|
|
|
2068
2189
|
/***/ }),
|
|
2069
2190
|
|
|
2191
|
+
/***/ 3299:
|
|
2192
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
2193
|
+
|
|
2194
|
+
"use strict";
|
|
2195
|
+
|
|
2196
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2197
|
+
exports.setupLocalAccess = void 0;
|
|
2198
|
+
const pipeline_1 = __nccwpck_require__(53695);
|
|
2199
|
+
const getProjectConfig_1 = __nccwpck_require__(70478);
|
|
2200
|
+
const localAccess_1 = __nccwpck_require__(94216);
|
|
2201
|
+
/**
|
|
2202
|
+
* provisions the local access (`localAccess` in catladder.ts)
|
|
2203
|
+
* of a google cloud run component/env: a dedicated service account with
|
|
2204
|
+
* exactly the declared bindings and its key in the secret store. Also
|
|
2205
|
+
* removes what is no longer declared.
|
|
2206
|
+
*/
|
|
2207
|
+
const setupLocalAccess = async (instance, context) => {
|
|
2208
|
+
var _a;
|
|
2209
|
+
const config = (_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config;
|
|
2210
|
+
if (!(0, pipeline_1.isOfDeployType)(config, "google-cloudrun"))
|
|
2211
|
+
return;
|
|
2212
|
+
const projectConfig = await (0, getProjectConfig_1.getProjectConfig)();
|
|
2213
|
+
const roles = (0, pipeline_1.getLocalAccessGcpRoles)(projectConfig, context.env);
|
|
2214
|
+
// validates `credentialsEnvVar` (catenv silently skips invalid ones)
|
|
2215
|
+
(0, pipeline_1.getLocalAccessGcpCredentialsEnvVars)(projectConfig, context.name);
|
|
2216
|
+
if (roles.length > 0 && (0, pipeline_1.getVaultConfig)(projectConfig).type === "gitlab") {
|
|
2217
|
+
// the gitlab vault stores secrets as CI variables available to every
|
|
2218
|
+
// job on every branch: the key would leak to anyone who can push
|
|
2219
|
+
throw new Error(`localAccess (${context.env}:${context.name}) needs a vault that is not a CI variable store (e.g. bitwarden): the gitlab vault would expose the service account key to every CI job. Configure \`vault\` or remove the localAccess declaration.`);
|
|
2220
|
+
}
|
|
2221
|
+
instance.log("sync local access service account...");
|
|
2222
|
+
await (0, localAccess_1.syncLocalAccessServiceAccount)(instance, context, config.projectId, roles);
|
|
2223
|
+
};
|
|
2224
|
+
exports.setupLocalAccess = setupLocalAccess;
|
|
2225
|
+
//# sourceMappingURL=setupLocalAccess.js.map
|
|
2226
|
+
|
|
2227
|
+
/***/ }),
|
|
2228
|
+
|
|
2070
2229
|
/***/ 63361:
|
|
2071
2230
|
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
2072
2231
|
|
|
@@ -5790,6 +5949,7 @@ exports.uninstallCompletions = uninstallCompletions;
|
|
|
5790
5949
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
5791
5950
|
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;
|
|
5792
5951
|
const pipeline_1 = __nccwpck_require__(53695);
|
|
5952
|
+
const keyFiles_1 = __nccwpck_require__(70180);
|
|
5793
5953
|
const gitlab_1 = __nccwpck_require__(17697);
|
|
5794
5954
|
const projects_1 = __nccwpck_require__(20575);
|
|
5795
5955
|
var parseChoice_1 = __nccwpck_require__(70769);
|
|
@@ -5910,12 +6070,19 @@ const getEnvVarsResolved = async (io, env, componentName) => {
|
|
|
5910
6070
|
const envionment = await (0, exports.getEnvironment)(env, componentName);
|
|
5911
6071
|
// in the pipeline the secrets alreadyy exists and bash will expand them
|
|
5912
6072
|
// but here we need to manually load them
|
|
5913
|
-
|
|
6073
|
+
const resolved = await resolveSecrets(io, [
|
|
5914
6074
|
{
|
|
5915
6075
|
envVars: envionment.envVars,
|
|
5916
6076
|
secretEnvVarKeys: envionment.secretEnvVarKeys,
|
|
5917
6077
|
},
|
|
5918
6078
|
]);
|
|
6079
|
+
if (env !== "local")
|
|
6080
|
+
return resolved;
|
|
6081
|
+
// paths of the local access key files (written by catenv)
|
|
6082
|
+
return {
|
|
6083
|
+
...resolved,
|
|
6084
|
+
...(await (0, keyFiles_1.getLocalAccessKeyFileVars)(await (0, exports.getProjectConfig)(), componentName)),
|
|
6085
|
+
};
|
|
5919
6086
|
};
|
|
5920
6087
|
exports.getEnvVarsResolved = getEnvVarsResolved;
|
|
5921
6088
|
/**
|
|
@@ -6230,6 +6397,189 @@ exports.fetchGcloudProjectNumber = fetchGcloudProjectNumber;
|
|
|
6230
6397
|
|
|
6231
6398
|
/***/ }),
|
|
6232
6399
|
|
|
6400
|
+
/***/ 94216:
|
|
6401
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
6402
|
+
|
|
6403
|
+
"use strict";
|
|
6404
|
+
|
|
6405
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
6406
|
+
exports.syncLocalAccessServiceAccount = exports.listUserManagedKeyIds = exports.parseServiceAccountKey = exports.getConditionalRolesOfMemberInBindings = exports.getRolesOfMemberInBindings = exports.getLocalAccessServiceAccountNames = void 0;
|
|
6407
|
+
const pipeline_1 = __nccwpck_require__(53695);
|
|
6408
|
+
const child_process_promise_1 = __nccwpck_require__(11542);
|
|
6409
|
+
const promises_1 = __nccwpck_require__(91943);
|
|
6410
|
+
const os_1 = __nccwpck_require__(70857);
|
|
6411
|
+
const path_1 = __nccwpck_require__(16928);
|
|
6412
|
+
const serviceAccountNames_1 = __nccwpck_require__(67851);
|
|
6413
|
+
const serviceAccounts_1 = __nccwpck_require__(18190);
|
|
6414
|
+
/**
|
|
6415
|
+
* the dedicated read-only service account of a component/env that
|
|
6416
|
+
* `project setup` provisions from the `localAccess` declaration.
|
|
6417
|
+
* Shared with `project doctor` and catenv.
|
|
6418
|
+
*/
|
|
6419
|
+
const getLocalAccessServiceAccountNames = (context, projectId) => (0, serviceAccountNames_1.getGcloudServiceAccountNames)(context, {
|
|
6420
|
+
name: pipeline_1.LOCAL_ACCESS_GCP_SA_NAME,
|
|
6421
|
+
projectId,
|
|
6422
|
+
});
|
|
6423
|
+
exports.getLocalAccessServiceAccountNames = getLocalAccessServiceAccountNames;
|
|
6424
|
+
/**
|
|
6425
|
+
* all unconditional roles bound to `member` in the given iam policy
|
|
6426
|
+
* bindings. Conditional bindings are reported separately, see
|
|
6427
|
+
* {@link getConditionalRolesOfMemberInBindings}.
|
|
6428
|
+
*/
|
|
6429
|
+
const getRolesOfMemberInBindings = (bindings, member) => bindings
|
|
6430
|
+
.filter((binding) => !binding.condition)
|
|
6431
|
+
.filter((binding) => { var _a; return (_a = binding.members) === null || _a === void 0 ? void 0 : _a.includes(member); })
|
|
6432
|
+
.map((binding) => binding.role)
|
|
6433
|
+
.sort();
|
|
6434
|
+
exports.getRolesOfMemberInBindings = getRolesOfMemberInBindings;
|
|
6435
|
+
/**
|
|
6436
|
+
* roles bound to `member` under an iam condition. These cannot be
|
|
6437
|
+
* removed by role name alone, so they are never touched by `project
|
|
6438
|
+
* setup` — only reported.
|
|
6439
|
+
*/
|
|
6440
|
+
const getConditionalRolesOfMemberInBindings = (bindings, member) => bindings
|
|
6441
|
+
.filter((binding) => binding.condition)
|
|
6442
|
+
.filter((binding) => { var _a; return (_a = binding.members) === null || _a === void 0 ? void 0 : _a.includes(member); })
|
|
6443
|
+
.map((binding) => binding.role)
|
|
6444
|
+
.sort();
|
|
6445
|
+
exports.getConditionalRolesOfMemberInBindings = getConditionalRolesOfMemberInBindings;
|
|
6446
|
+
/**
|
|
6447
|
+
* parses the output of `gcloud iam service-accounts keys create` and
|
|
6448
|
+
* makes sure it is a usable service account key
|
|
6449
|
+
*/
|
|
6450
|
+
const parseServiceAccountKey = (raw) => {
|
|
6451
|
+
let parsed;
|
|
6452
|
+
try {
|
|
6453
|
+
parsed = JSON.parse(raw);
|
|
6454
|
+
}
|
|
6455
|
+
catch (_a) {
|
|
6456
|
+
throw new Error("gcloud did not return a valid service account key");
|
|
6457
|
+
}
|
|
6458
|
+
if (typeof parsed.private_key_id !== "string" ||
|
|
6459
|
+
typeof parsed.private_key !== "string") {
|
|
6460
|
+
throw new Error("gcloud did not return a valid service account key");
|
|
6461
|
+
}
|
|
6462
|
+
return { key: raw, privateKeyId: parsed.private_key_id };
|
|
6463
|
+
};
|
|
6464
|
+
exports.parseServiceAccountKey = parseServiceAccountKey;
|
|
6465
|
+
const getProjectBindings = async (projectId) => {
|
|
6466
|
+
var _a;
|
|
6467
|
+
return (_a = JSON.parse((await (0, child_process_promise_1.exec)(`gcloud projects get-iam-policy ${projectId} --format=json`))
|
|
6468
|
+
.stdout).bindings) !== null && _a !== void 0 ? _a : [];
|
|
6469
|
+
};
|
|
6470
|
+
const listUserManagedKeyIds = async (fullIdentifier) => {
|
|
6471
|
+
const keys = JSON.parse((await (0, child_process_promise_1.exec)(`gcloud iam service-accounts keys list --iam-account=${fullIdentifier} --managed-by=user --format=json`)).stdout);
|
|
6472
|
+
return keys.map((key) => key.name.split("/").pop());
|
|
6473
|
+
};
|
|
6474
|
+
exports.listUserManagedKeyIds = listUserManagedKeyIds;
|
|
6475
|
+
/**
|
|
6476
|
+
* makes the account's bindings equal to `roles`: adds missing, removes
|
|
6477
|
+
* everything else (the account is dedicated, every other binding is
|
|
6478
|
+
* drift). Idempotent — a no-op when nothing differs.
|
|
6479
|
+
*/
|
|
6480
|
+
const syncBindings = async (io, projectId, fullIdentifier, roles) => {
|
|
6481
|
+
const member = `serviceAccount:${fullIdentifier}`;
|
|
6482
|
+
const bindings = await getProjectBindings(projectId);
|
|
6483
|
+
const conditional = (0, exports.getConditionalRolesOfMemberInBindings)(bindings, member);
|
|
6484
|
+
if (conditional.length > 0) {
|
|
6485
|
+
io.log(`⚠️ ${fullIdentifier} has conditional bindings (${conditional.join(", ")}) — setup does not touch them, remove them by hand`);
|
|
6486
|
+
}
|
|
6487
|
+
const { missing, extra } = (0, pipeline_1.diffRoleBindings)(roles, (0, exports.getRolesOfMemberInBindings)(bindings, member));
|
|
6488
|
+
for (const role of missing) {
|
|
6489
|
+
io.log(` + ${role}`);
|
|
6490
|
+
await (0, child_process_promise_1.exec)(`gcloud projects add-iam-policy-binding ${projectId} --member=${member} --role=${role} --condition=None`);
|
|
6491
|
+
}
|
|
6492
|
+
for (const role of extra) {
|
|
6493
|
+
io.log(` - ${role}`);
|
|
6494
|
+
await (0, child_process_promise_1.exec)(`gcloud projects remove-iam-policy-binding ${projectId} --member=${member} --role=${role} --condition=None`);
|
|
6495
|
+
}
|
|
6496
|
+
};
|
|
6497
|
+
/**
|
|
6498
|
+
* brings the local access service account of a component/env in line
|
|
6499
|
+
* with the declared `roles`:
|
|
6500
|
+
*
|
|
6501
|
+
* - roles declared: account exists, carries exactly these bindings,
|
|
6502
|
+
* and a key for it is in the secret store (under the `local` env, so
|
|
6503
|
+
* it is never mirrored to ci). The key is only created when the
|
|
6504
|
+
* stored one is missing or no longer valid — re-running is a no-op.
|
|
6505
|
+
* - no roles declared: bindings are removed and the account (with its
|
|
6506
|
+
* keys) deleted if it exists.
|
|
6507
|
+
*/
|
|
6508
|
+
const syncLocalAccessServiceAccount = async (io, context, projectId, roles) => {
|
|
6509
|
+
const { fullName, fullIdentifier } = (0, exports.getLocalAccessServiceAccountNames)(context, projectId);
|
|
6510
|
+
const exists = await (0, serviceAccounts_1.accountExists)(fullIdentifier);
|
|
6511
|
+
if (roles.length === 0) {
|
|
6512
|
+
if (exists) {
|
|
6513
|
+
io.log(`local access no longer declared, removing ${fullIdentifier}...`);
|
|
6514
|
+
await syncBindings(io, projectId, fullIdentifier, []);
|
|
6515
|
+
await (0, child_process_promise_1.exec)(`gcloud iam service-accounts delete ${fullIdentifier} --quiet --project="${projectId}"`);
|
|
6516
|
+
}
|
|
6517
|
+
return;
|
|
6518
|
+
}
|
|
6519
|
+
if (!exists) {
|
|
6520
|
+
io.log(`creating local access service account ${fullIdentifier}...`);
|
|
6521
|
+
await (0, child_process_promise_1.exec)(`gcloud iam service-accounts create ${fullName} --display-name="${context.fullConfig.customerName}-${context.fullConfig.appName} ${context.env}:${context.name} | Catladder local access (read-only)" --project="${projectId}" --description="Read-only access for local use (agents, scripts)"`);
|
|
6522
|
+
}
|
|
6523
|
+
await syncBindings(io, projectId, fullIdentifier, roles);
|
|
6524
|
+
const secretName = (0, pipeline_1.getLocalAccessGcpSecretName)(context.env);
|
|
6525
|
+
const vaultManager = await io.getVaultManager();
|
|
6526
|
+
const rawName = (0, pipeline_1.getSecretVarName)("local", context.name, secretName);
|
|
6527
|
+
// straight from the vault, NOT through the local cache: a stale cache
|
|
6528
|
+
// (older than the first setup, or holding an older key) would make us
|
|
6529
|
+
// believe the key is gone, delete the valid one and lock out every
|
|
6530
|
+
// other machine.
|
|
6531
|
+
const stored = (await vaultManager.vault.readAllSecrets(io))[rawName];
|
|
6532
|
+
const validKeyIds = await (0, exports.listUserManagedKeyIds)(fullIdentifier);
|
|
6533
|
+
let storedKeyId;
|
|
6534
|
+
try {
|
|
6535
|
+
storedKeyId = JSON.parse(stored).private_key_id;
|
|
6536
|
+
}
|
|
6537
|
+
catch (_a) {
|
|
6538
|
+
// no (or unparsable) stored key
|
|
6539
|
+
}
|
|
6540
|
+
if (storedKeyId && validKeyIds.includes(storedKeyId)) {
|
|
6541
|
+
// keep the cache in line with the vault
|
|
6542
|
+
await vaultManager.cacheSecrets("local", context.name, {
|
|
6543
|
+
[secretName]: stored,
|
|
6544
|
+
});
|
|
6545
|
+
// at most one key per account: also drop leftovers of a failed run
|
|
6546
|
+
// or keys added by hand
|
|
6547
|
+
await deleteKeys(fullIdentifier, validKeyIds.filter((id) => id !== storedKeyId));
|
|
6548
|
+
return;
|
|
6549
|
+
}
|
|
6550
|
+
io.log("creating local access key...");
|
|
6551
|
+
// create first: a failed create must not leave the vault empty
|
|
6552
|
+
const { key, privateKeyId } = await createServiceAccountKey(fullIdentifier);
|
|
6553
|
+
// straight into the vault, NOT through writeSecretsAndMirror: this
|
|
6554
|
+
// key is for local use only and must never reach ci variables.
|
|
6555
|
+
// A fresh key replaces the old one, a backup would be dead.
|
|
6556
|
+
await vaultManager.writeSecrets("local", context.name, { [secretName]: key }, io, { backup: false });
|
|
6557
|
+
// only now drop the keys that are not the new one
|
|
6558
|
+
await deleteKeys(fullIdentifier, validKeyIds.filter((id) => id !== privateKeyId));
|
|
6559
|
+
};
|
|
6560
|
+
exports.syncLocalAccessServiceAccount = syncLocalAccessServiceAccount;
|
|
6561
|
+
const deleteKeys = async (fullIdentifier, keyIds) => {
|
|
6562
|
+
for (const keyId of keyIds) {
|
|
6563
|
+
await (0, child_process_promise_1.exec)(`gcloud iam service-accounts keys delete ${keyId} --quiet --iam-account=${fullIdentifier}`);
|
|
6564
|
+
}
|
|
6565
|
+
};
|
|
6566
|
+
const createServiceAccountKey = async (fullIdentifier) => {
|
|
6567
|
+
// a private temp file (0700 folder) instead of /dev/stdout | cat:
|
|
6568
|
+
// the pipe hid gcloud's exit code
|
|
6569
|
+
const folder = await (0, promises_1.mkdtemp)((0, path_1.join)((0, os_1.tmpdir)(), "catladder-local-access-"));
|
|
6570
|
+
try {
|
|
6571
|
+
const file = (0, path_1.join)(folder, "key.json");
|
|
6572
|
+
await (0, child_process_promise_1.exec)(`gcloud iam service-accounts keys create "${file}" --iam-account=${fullIdentifier}`);
|
|
6573
|
+
return (0, exports.parseServiceAccountKey)(await (0, promises_1.readFile)(file, "utf-8"));
|
|
6574
|
+
}
|
|
6575
|
+
finally {
|
|
6576
|
+
await (0, promises_1.rm)(folder, { recursive: true, force: true });
|
|
6577
|
+
}
|
|
6578
|
+
};
|
|
6579
|
+
//# sourceMappingURL=localAccess.js.map
|
|
6580
|
+
|
|
6581
|
+
/***/ }),
|
|
6582
|
+
|
|
6233
6583
|
/***/ 74241:
|
|
6234
6584
|
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
6235
6585
|
|
|
@@ -6319,12 +6669,19 @@ const child_process_promise_1 = __nccwpck_require__(11542);
|
|
|
6319
6669
|
const secrets_1 = __nccwpck_require__(85200);
|
|
6320
6670
|
const serviceAccountNames_1 = __nccwpck_require__(67851);
|
|
6321
6671
|
const accountExists = async (fullIdentifier) => {
|
|
6672
|
+
var _a, _b;
|
|
6322
6673
|
try {
|
|
6323
6674
|
await (0, child_process_promise_1.exec)(`gcloud iam service-accounts describe ${fullIdentifier}`);
|
|
6324
6675
|
return true;
|
|
6325
6676
|
}
|
|
6326
|
-
catch (
|
|
6327
|
-
|
|
6677
|
+
catch (e) {
|
|
6678
|
+
// only a missing account means "does not exist": an auth or
|
|
6679
|
+
// permission error must not look like one (setup would skip the
|
|
6680
|
+
// deletion, doctor would report it missing)
|
|
6681
|
+
if (`${(_a = e === null || e === void 0 ? void 0 : e.stderr) !== null && _a !== void 0 ? _a : ""}${(_b = e === null || e === void 0 ? void 0 : e.message) !== null && _b !== void 0 ? _b : ""}`.includes("NOT_FOUND")) {
|
|
6682
|
+
return false;
|
|
6683
|
+
}
|
|
6684
|
+
throw e;
|
|
6328
6685
|
}
|
|
6329
6686
|
};
|
|
6330
6687
|
exports.accountExists = accountExists;
|
|
@@ -6622,6 +6979,145 @@ exports.startKubePortForward = startKubePortForward;
|
|
|
6622
6979
|
|
|
6623
6980
|
/***/ }),
|
|
6624
6981
|
|
|
6982
|
+
/***/ 83228:
|
|
6983
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
6984
|
+
|
|
6985
|
+
"use strict";
|
|
6986
|
+
|
|
6987
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
6988
|
+
exports.getDeclaredLocalAccessCredentialsEnvVars = exports.getDeclaredLocalAccessEnvs = void 0;
|
|
6989
|
+
const pipeline_1 = __nccwpck_require__(53695);
|
|
6990
|
+
/**
|
|
6991
|
+
* the envs of a component declaring gcp local access. An invalid
|
|
6992
|
+
* declaration yields none instead of throwing: catenv and the vault
|
|
6993
|
+
* cache must keep working (`project doctor` and `project setup` report
|
|
6994
|
+
* the invalid declaration loudly).
|
|
6995
|
+
*/
|
|
6996
|
+
const getDeclaredLocalAccessEnvs = (config, componentName) => {
|
|
6997
|
+
try {
|
|
6998
|
+
return (0, pipeline_1.getLocalAccessGcpEnvs)(config, componentName);
|
|
6999
|
+
}
|
|
7000
|
+
catch (_a) {
|
|
7001
|
+
return [];
|
|
7002
|
+
}
|
|
7003
|
+
};
|
|
7004
|
+
exports.getDeclaredLocalAccessEnvs = getDeclaredLocalAccessEnvs;
|
|
7005
|
+
/**
|
|
7006
|
+
* the declared `credentialsEnvVar` names of a component, mapped to their
|
|
7007
|
+
* env. Empty instead of throwing on an invalid declaration, see
|
|
7008
|
+
* {@link getDeclaredLocalAccessEnvs}.
|
|
7009
|
+
*/
|
|
7010
|
+
const getDeclaredLocalAccessCredentialsEnvVars = (config, componentName) => {
|
|
7011
|
+
try {
|
|
7012
|
+
return (0, pipeline_1.getLocalAccessGcpCredentialsEnvVars)(config, componentName);
|
|
7013
|
+
}
|
|
7014
|
+
catch (_a) {
|
|
7015
|
+
return {};
|
|
7016
|
+
}
|
|
7017
|
+
};
|
|
7018
|
+
exports.getDeclaredLocalAccessCredentialsEnvVars = getDeclaredLocalAccessCredentialsEnvVars;
|
|
7019
|
+
//# sourceMappingURL=declared.js.map
|
|
7020
|
+
|
|
7021
|
+
/***/ }),
|
|
7022
|
+
|
|
7023
|
+
/***/ 70180:
|
|
7024
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
7025
|
+
|
|
7026
|
+
"use strict";
|
|
7027
|
+
|
|
7028
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
7029
|
+
exports.writeLocalAccessKeyFiles = exports.getLocalAccessKeyFileVars = exports.getLocalAccessKeyFileVarName = exports.getLocalAccessKeyFileName = void 0;
|
|
7030
|
+
const pipeline_1 = __nccwpck_require__(53695);
|
|
7031
|
+
const promises_1 = __nccwpck_require__(91943);
|
|
7032
|
+
const fs_1 = __nccwpck_require__(79896);
|
|
7033
|
+
const path_1 = __nccwpck_require__(16928);
|
|
7034
|
+
const writePrivateFile_1 = __nccwpck_require__(14550);
|
|
7035
|
+
const declared_1 = __nccwpck_require__(83228);
|
|
7036
|
+
const gitProjectInformation_1 = __nccwpck_require__(86884);
|
|
7037
|
+
const sanitize = (s) => s.replace(/[^A-Za-z0-9]+/g, "_");
|
|
7038
|
+
/** file name of a component/env key, relative to the local access folder */
|
|
7039
|
+
const getLocalAccessKeyFileName = (env, componentName) => `gcp-${sanitize(env)}-${sanitize(componentName)}.json`;
|
|
7040
|
+
exports.getLocalAccessKeyFileName = getLocalAccessKeyFileName;
|
|
7041
|
+
/**
|
|
7042
|
+
* env var exposing the key file path. Deliberately NOT
|
|
7043
|
+
* GOOGLE_APPLICATION_CREDENTIALS: the key must only be used where a
|
|
7044
|
+
* command opts in (isolated gcloud config, or a declared
|
|
7045
|
+
* `credentialsEnvVar`), never by ADC implicitly.
|
|
7046
|
+
*/
|
|
7047
|
+
const getLocalAccessKeyFileVarName = (env) => `CATLADDER_LOCAL_ACCESS_GCP_${sanitize(env).toUpperCase()}_KEY_FILE`;
|
|
7048
|
+
exports.getLocalAccessKeyFileVarName = getLocalAccessKeyFileVarName;
|
|
7049
|
+
// inside .catladder/, which ignores itself (see vault/cache.ts)
|
|
7050
|
+
const getFolder = async () => (0, path_1.join)(await (0, gitProjectInformation_1.getProjectRootPath)(), ".catladder", "local-access");
|
|
7051
|
+
/**
|
|
7052
|
+
* env vars pointing at the key files that exist for the component
|
|
7053
|
+
* (only paths, no secrets): one per env, plus the declared
|
|
7054
|
+
* `credentialsEnvVar` names
|
|
7055
|
+
*/
|
|
7056
|
+
const getLocalAccessKeyFileVars = async (config, componentName) => {
|
|
7057
|
+
const folder = await getFolder();
|
|
7058
|
+
const getPath = (env) => (0, path_1.join)(folder, (0, exports.getLocalAccessKeyFileName)(env, componentName));
|
|
7059
|
+
return Object.fromEntries([
|
|
7060
|
+
...(0, declared_1.getDeclaredLocalAccessEnvs)(config, componentName).map((env) => [
|
|
7061
|
+
(0, exports.getLocalAccessKeyFileVarName)(env),
|
|
7062
|
+
getPath(env),
|
|
7063
|
+
]),
|
|
7064
|
+
...Object.entries((0, declared_1.getDeclaredLocalAccessCredentialsEnvVars)(config, componentName)).map(([name, env]) => [name, getPath(env)]),
|
|
7065
|
+
].filter(([, path]) => (0, fs_1.existsSync)(path)));
|
|
7066
|
+
};
|
|
7067
|
+
exports.getLocalAccessKeyFileVars = getLocalAccessKeyFileVars;
|
|
7068
|
+
/**
|
|
7069
|
+
* writes the stored local access service account keys to gitignored
|
|
7070
|
+
* files (mode 0600) and removes files of declarations that are gone.
|
|
7071
|
+
* A no-op without any declaration (nothing is created). Never throws:
|
|
7072
|
+
* a vault that cannot be reached or an invalid declaration (reported by
|
|
7073
|
+
* `project doctor`) must not break catenv.
|
|
7074
|
+
*
|
|
7075
|
+
* The key is only read through the cache; if setup rotated it on another
|
|
7076
|
+
* machine, refresh with `catenv --vault-mode refresh`.
|
|
7077
|
+
*/
|
|
7078
|
+
const writeLocalAccessKeyFiles = async (io, config) => {
|
|
7079
|
+
const wanted = Object.keys(config.components).flatMap((componentName) => (0, declared_1.getDeclaredLocalAccessEnvs)(config, componentName).map((env) => ({
|
|
7080
|
+
env,
|
|
7081
|
+
componentName,
|
|
7082
|
+
secretName: (0, pipeline_1.getSecretVarName)("local", componentName, (0, pipeline_1.getLocalAccessGcpSecretName)(env)),
|
|
7083
|
+
fileName: (0, exports.getLocalAccessKeyFileName)(env, componentName),
|
|
7084
|
+
})));
|
|
7085
|
+
const folder = await getFolder();
|
|
7086
|
+
if (wanted.length === 0 && !(0, fs_1.existsSync)(folder))
|
|
7087
|
+
return;
|
|
7088
|
+
try {
|
|
7089
|
+
const secrets = wanted.length > 0
|
|
7090
|
+
? await (await io.getVaultManager()).readSecrets(wanted.map((w) => w.secretName), io)
|
|
7091
|
+
: {};
|
|
7092
|
+
await (0, promises_1.mkdir)(folder, { recursive: true, mode: 0o700 });
|
|
7093
|
+
// the parent folder ignores itself
|
|
7094
|
+
await (0, promises_1.writeFile)((0, path_1.join)(folder, "..", ".gitignore"), "*\n");
|
|
7095
|
+
const written = new Set();
|
|
7096
|
+
for (const { secretName, fileName, env, componentName } of wanted) {
|
|
7097
|
+
const key = secrets[secretName];
|
|
7098
|
+
if (!key) {
|
|
7099
|
+
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)`);
|
|
7100
|
+
continue;
|
|
7101
|
+
}
|
|
7102
|
+
const path = (0, path_1.join)(folder, fileName);
|
|
7103
|
+
await (0, writePrivateFile_1.writePrivateFile)(path, key);
|
|
7104
|
+
written.add(fileName);
|
|
7105
|
+
}
|
|
7106
|
+
for (const file of await (0, promises_1.readdir)(folder)) {
|
|
7107
|
+
if (!written.has(file)) {
|
|
7108
|
+
await (0, promises_1.rm)((0, path_1.join)(folder, file), { force: true });
|
|
7109
|
+
}
|
|
7110
|
+
}
|
|
7111
|
+
}
|
|
7112
|
+
catch (e) {
|
|
7113
|
+
io.log(`⚠️ could not write local access keys: ${e.message}`);
|
|
7114
|
+
}
|
|
7115
|
+
};
|
|
7116
|
+
exports.writeLocalAccessKeyFiles = writeLocalAccessKeyFiles;
|
|
7117
|
+
//# sourceMappingURL=keyFiles.js.map
|
|
7118
|
+
|
|
7119
|
+
/***/ }),
|
|
7120
|
+
|
|
6625
7121
|
/***/ 55689:
|
|
6626
7122
|
/***/ ((__unused_webpack_module, exports) => {
|
|
6627
7123
|
|
|
@@ -8034,6 +8530,38 @@ exports.getShell = getShell;
|
|
|
8034
8530
|
|
|
8035
8531
|
/***/ }),
|
|
8036
8532
|
|
|
8533
|
+
/***/ 14550:
|
|
8534
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
8535
|
+
|
|
8536
|
+
"use strict";
|
|
8537
|
+
|
|
8538
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
8539
|
+
exports.writePrivateFile = void 0;
|
|
8540
|
+
const promises_1 = __nccwpck_require__(91943);
|
|
8541
|
+
/**
|
|
8542
|
+
* writes a file readable by the owner only. Goes through a 0600 temp
|
|
8543
|
+
* file and a rename, so the content is never visible with looser
|
|
8544
|
+
* permissions (writing first and chmod-ing afterwards would leave an
|
|
8545
|
+
* existing 0644 file readable until the chmod).
|
|
8546
|
+
*/
|
|
8547
|
+
const writePrivateFile = async (path, content) => {
|
|
8548
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
8549
|
+
try {
|
|
8550
|
+
await (0, promises_1.writeFile)(tmp, content, { mode: 0o600 });
|
|
8551
|
+
// the mode is only applied when the file is created
|
|
8552
|
+
await (0, promises_1.chmod)(tmp, 0o600);
|
|
8553
|
+
await (0, promises_1.rename)(tmp, path);
|
|
8554
|
+
}
|
|
8555
|
+
catch (e) {
|
|
8556
|
+
await (0, promises_1.rm)(tmp, { force: true });
|
|
8557
|
+
throw e;
|
|
8558
|
+
}
|
|
8559
|
+
};
|
|
8560
|
+
exports.writePrivateFile = writePrivateFile;
|
|
8561
|
+
//# sourceMappingURL=writePrivateFile.js.map
|
|
8562
|
+
|
|
8563
|
+
/***/ }),
|
|
8564
|
+
|
|
8037
8565
|
/***/ 42827:
|
|
8038
8566
|
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
8039
8567
|
|
|
@@ -8140,30 +8668,21 @@ class BitwardenVault {
|
|
|
8140
8668
|
: {};
|
|
8141
8669
|
const notes = (0, yaml_1.stringify)({ ...previous, ...secrets });
|
|
8142
8670
|
if (existing) {
|
|
8143
|
-
await (0, bw_1.runBw)(bwSession, [
|
|
8144
|
-
"edit",
|
|
8145
|
-
"item",
|
|
8146
|
-
existing.id,
|
|
8147
|
-
encodePayload({ ...existing, notes }),
|
|
8148
|
-
]);
|
|
8671
|
+
await (0, bw_1.runBw)(bwSession, ["edit", "item", existing.id], encodePayload({ ...existing, notes }));
|
|
8149
8672
|
}
|
|
8150
8673
|
else {
|
|
8151
|
-
await (0, bw_1.runBw)(bwSession, [
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
}
|
|
8164
|
-
: {}),
|
|
8165
|
-
}),
|
|
8166
|
-
]);
|
|
8674
|
+
await (0, bw_1.runBw)(bwSession, ["create", "item"], encodePayload({
|
|
8675
|
+
type: SECURE_NOTE_TYPE,
|
|
8676
|
+
secureNote: { type: 0 },
|
|
8677
|
+
name,
|
|
8678
|
+
notes,
|
|
8679
|
+
...(collection.organizationId
|
|
8680
|
+
? {
|
|
8681
|
+
organizationId: collection.organizationId,
|
|
8682
|
+
collectionIds: [collection.id],
|
|
8683
|
+
}
|
|
8684
|
+
: {}),
|
|
8685
|
+
}));
|
|
8167
8686
|
}
|
|
8168
8687
|
}
|
|
8169
8688
|
}
|
|
@@ -8210,6 +8729,7 @@ exports.GitlabVault = GitlabVault;
|
|
|
8210
8729
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
8211
8730
|
exports.VaultManager = void 0;
|
|
8212
8731
|
const pipeline_1 = __nccwpck_require__(53695);
|
|
8732
|
+
const declared_1 = __nccwpck_require__(83228);
|
|
8213
8733
|
const BitwardenVault_1 = __nccwpck_require__(42827);
|
|
8214
8734
|
const cache_1 = __nccwpck_require__(57011);
|
|
8215
8735
|
const GitlabVault_1 = __nccwpck_require__(6020);
|
|
@@ -8292,6 +8812,15 @@ class VaultManager {
|
|
|
8292
8812
|
])));
|
|
8293
8813
|
}
|
|
8294
8814
|
}
|
|
8815
|
+
/**
|
|
8816
|
+
* updates the local cache only (the vault already holds the values),
|
|
8817
|
+
* "local" env only
|
|
8818
|
+
*/
|
|
8819
|
+
async cacheSecrets(env, componentName, secrets) {
|
|
8820
|
+
if (env !== CACHED_ENV)
|
|
8821
|
+
return;
|
|
8822
|
+
await (0, cache_1.updateSecretsCache)(this.vault.id, env, componentName, secrets);
|
|
8823
|
+
}
|
|
8295
8824
|
/**
|
|
8296
8825
|
* the subset of the vault secrets that may be cached: the declared
|
|
8297
8826
|
* secrets of the "local" env, per component, as a hierarchy
|
|
@@ -8315,6 +8844,14 @@ class VaultManager {
|
|
|
8315
8844
|
return Object.keys(local).length > 0 ? { [CACHED_ENV]: local } : {};
|
|
8316
8845
|
}
|
|
8317
8846
|
async declaredSecretKeys(componentName) {
|
|
8847
|
+
return [
|
|
8848
|
+
...(await this.declaredEnvSecretKeys(componentName)),
|
|
8849
|
+
// the local access keys `project setup` provisions are not part
|
|
8850
|
+
// of any env's vars, but belong to the cached local secrets
|
|
8851
|
+
...(0, declared_1.getDeclaredLocalAccessEnvs)(this.config, componentName).map(pipeline_1.getLocalAccessGcpSecretName),
|
|
8852
|
+
];
|
|
8853
|
+
}
|
|
8854
|
+
async declaredEnvSecretKeys(componentName) {
|
|
8318
8855
|
try {
|
|
8319
8856
|
const { secretEnvVarKeys, jobOnlyVars } = await (0, pipeline_1.getEnvironment)({
|
|
8320
8857
|
config: this.config,
|
|
@@ -8424,14 +8961,40 @@ const getBwSession = async (io, { allowPrompt }) => {
|
|
|
8424
8961
|
};
|
|
8425
8962
|
exports.getBwSession = getBwSession;
|
|
8426
8963
|
/**
|
|
8427
|
-
* runs a bw command with the given session and returns stdout
|
|
8964
|
+
* runs a bw command with the given session and returns stdout.
|
|
8965
|
+
*
|
|
8966
|
+
* `input` is written to stdin: use it for payloads (e.g. the secrets of
|
|
8967
|
+
* `bw create item`), which would otherwise be visible in `ps` and be
|
|
8968
|
+
* echoed by node's "Command failed" error.
|
|
8428
8969
|
*/
|
|
8429
|
-
const runBw = async (session, args) => {
|
|
8430
|
-
const
|
|
8431
|
-
|
|
8432
|
-
|
|
8970
|
+
const runBw = async (session, args, input) => {
|
|
8971
|
+
const fullArgs = [...args, "--raw", "--nointeraction"];
|
|
8972
|
+
const env = { ...process.env, BW_SESSION: session };
|
|
8973
|
+
if (input === undefined) {
|
|
8974
|
+
const { stdout } = await execFile("bw", fullArgs, {
|
|
8975
|
+
env,
|
|
8976
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
8977
|
+
});
|
|
8978
|
+
return stdout;
|
|
8979
|
+
}
|
|
8980
|
+
return new Promise((resolve, reject) => {
|
|
8981
|
+
const child = (0, child_process_1.spawn)("bw", fullArgs, {
|
|
8982
|
+
env,
|
|
8983
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
8984
|
+
});
|
|
8985
|
+
let stdout = "";
|
|
8986
|
+
let stderr = "";
|
|
8987
|
+
child.stdout.on("data", (data) => (stdout += data.toString("utf-8")));
|
|
8988
|
+
child.stderr.on("data", (data) => (stderr += data.toString("utf-8")));
|
|
8989
|
+
child.on("error", reject);
|
|
8990
|
+
child.on("close", (code) => code === 0
|
|
8991
|
+
? resolve(stdout)
|
|
8992
|
+
: reject(new Error(`bw ${args[0]} failed: ${stderr.trim()}`)));
|
|
8993
|
+
child.stdin.on("error", () => {
|
|
8994
|
+
// bw exiting early closes the pipe, the close handler reports why
|
|
8995
|
+
});
|
|
8996
|
+
child.stdin.end(input);
|
|
8433
8997
|
});
|
|
8434
|
-
return stdout;
|
|
8435
8998
|
};
|
|
8436
8999
|
exports.runBw = runBw;
|
|
8437
9000
|
const runBwJson = async (session, args) => JSON.parse(await (0, exports.runBw)(session, args));
|
|
@@ -8449,6 +9012,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
8449
9012
|
exports.updateSecretsCache = exports.writeSecretsCache = exports.readSecretsCache = void 0;
|
|
8450
9013
|
const promises_1 = __nccwpck_require__(91943);
|
|
8451
9014
|
const path_1 = __nccwpck_require__(16928);
|
|
9015
|
+
const writePrivateFile_1 = __nccwpck_require__(14550);
|
|
8452
9016
|
const gitProjectInformation_1 = __nccwpck_require__(86884);
|
|
8453
9017
|
const getCacheFolder = async () => (0, path_1.join)(await (0, gitProjectInformation_1.getProjectRootPath)(), ".catladder");
|
|
8454
9018
|
const getCachePath = async () => (0, path_1.join)(await getCacheFolder(), "secrets.json");
|
|
@@ -8463,7 +9027,7 @@ const readSecretsCache = async () => {
|
|
|
8463
9027
|
exports.readSecretsCache = readSecretsCache;
|
|
8464
9028
|
const writeSecretsCache = async (vaultId, secrets) => {
|
|
8465
9029
|
const folder = await getCacheFolder();
|
|
8466
|
-
await (0, promises_1.mkdir)(folder, { recursive: true });
|
|
9030
|
+
await (0, promises_1.mkdir)(folder, { recursive: true, mode: 0o700 });
|
|
8467
9031
|
// the folder ignores itself, so the cache can never be committed
|
|
8468
9032
|
await (0, promises_1.writeFile)((0, path_1.join)(folder, ".gitignore"), "*\n");
|
|
8469
9033
|
const cache = {
|
|
@@ -8471,7 +9035,9 @@ const writeSecretsCache = async (vaultId, secrets) => {
|
|
|
8471
9035
|
updatedAt: new Date().toISOString(),
|
|
8472
9036
|
secrets,
|
|
8473
9037
|
};
|
|
8474
|
-
|
|
9038
|
+
const path = await getCachePath();
|
|
9039
|
+
// may hold key material (local access keys): owner only
|
|
9040
|
+
await (0, writePrivateFile_1.writePrivateFile)(path, JSON.stringify(cache, null, 2));
|
|
8475
9041
|
};
|
|
8476
9042
|
exports.writeSecretsCache = writeSecretsCache;
|
|
8477
9043
|
/**
|
|
@@ -19364,6 +19930,191 @@ __exportStar(__nccwpck_require__(91804), exports);
|
|
|
19364
19930
|
__exportStar(__nccwpck_require__(94143), exports);
|
|
19365
19931
|
__exportStar(__nccwpck_require__(98011), exports);
|
|
19366
19932
|
__exportStar(__nccwpck_require__(61459), exports);
|
|
19933
|
+
__exportStar(__nccwpck_require__(54165), exports);
|
|
19934
|
+
//# sourceMappingURL=index.js.map
|
|
19935
|
+
|
|
19936
|
+
/***/ }),
|
|
19937
|
+
|
|
19938
|
+
/***/ 54165:
|
|
19939
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
19940
|
+
|
|
19941
|
+
"use strict";
|
|
19942
|
+
|
|
19943
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
19944
|
+
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;
|
|
19945
|
+
const configruedEnvs_1 = __nccwpck_require__(38866);
|
|
19946
|
+
const getEnvConfig_1 = __nccwpck_require__(69578);
|
|
19947
|
+
const types_1 = __nccwpck_require__(79689);
|
|
19948
|
+
exports.LOCAL_ACCESS_GCP_PRESETS = {
|
|
19949
|
+
// no run.viewer on purpose, see LocalAccessGcpPreset
|
|
19950
|
+
logs: ["roles/logging.viewer"],
|
|
19951
|
+
// not read-only, see LocalAccessGcpPreset; only reachable as a
|
|
19952
|
+
// preset, getDeniedLocalAccessRoleReason rejects it in `roles`
|
|
19953
|
+
vertex: ["roles/aiplatform.user"],
|
|
19954
|
+
};
|
|
19955
|
+
/** name prefix of the service account `project setup` creates (see getGcloudServiceAccountNames) */
|
|
19956
|
+
exports.LOCAL_ACCESS_GCP_SA_NAME = "local-diag";
|
|
19957
|
+
const ROLE_FORMAT = /^(roles\/[A-Za-z0-9_.]+|projects\/[a-z0-9-]+\/roles\/[A-Za-z0-9_.]+|organizations\/\d+\/roles\/[A-Za-z0-9_.]+)$/;
|
|
19958
|
+
/**
|
|
19959
|
+
* roles that are read-only on paper but expose secrets: cloud run
|
|
19960
|
+
* services/jobs carry their secrets as plain env vars, so anything that
|
|
19961
|
+
* may read them reads every deployed secret. Denied even with
|
|
19962
|
+
* `allowAnyRole`.
|
|
19963
|
+
*/
|
|
19964
|
+
const SECRET_EXPOSING_ROLES = {
|
|
19965
|
+
"roles/viewer": "roles/viewer can read cloud run env vars (secrets)",
|
|
19966
|
+
"roles/run.viewer": "roles/run.viewer can read cloud run env vars (secrets)",
|
|
19967
|
+
"roles/secretmanager.secretAccessor": "this role reads secret values",
|
|
19968
|
+
"roles/secretmanager.admin": "this role reads secret values",
|
|
19969
|
+
};
|
|
19970
|
+
/**
|
|
19971
|
+
* roles granted without `allowAnyRole`. An explicit list instead of a
|
|
19972
|
+
* name pattern: a role called `*.viewer` / `*.reader` says nothing about
|
|
19973
|
+
* its permissions (custom roles can hold anything, and predefined ones
|
|
19974
|
+
* like storage.objectViewer, cloudfunctions.viewer, compute.viewer or
|
|
19975
|
+
* logging.privateLogViewer expose data or secrets).
|
|
19976
|
+
*/
|
|
19977
|
+
const SAFE_ROLES = [
|
|
19978
|
+
"roles/logging.viewer",
|
|
19979
|
+
"roles/monitoring.viewer",
|
|
19980
|
+
"roles/errorreporting.viewer",
|
|
19981
|
+
];
|
|
19982
|
+
/** `roles/foo.bar.v1` -> `foo.bar` (version suffixes must not defeat the checks) */
|
|
19983
|
+
const getRoleName = (role) => { var _a; return ((_a = role.split("/").pop()) !== null && _a !== void 0 ? _a : role).replace(/(\.v\d+)+$/i, ""); };
|
|
19984
|
+
/**
|
|
19985
|
+
* why a role may not be granted for local access, or undefined if it
|
|
19986
|
+
* is acceptable.
|
|
19987
|
+
*
|
|
19988
|
+
* By default only the roles in {@link SAFE_ROLES} pass; custom roles
|
|
19989
|
+
* and everything else need `allowAnyRole`. `allowAnyRole` lifts the
|
|
19990
|
+
* allow-list, but the obvious write/admin/impersonation roles and the
|
|
19991
|
+
* secret exposing roles stay denied.
|
|
19992
|
+
*/
|
|
19993
|
+
const getDeniedLocalAccessRoleReason = (role, options = {}) => {
|
|
19994
|
+
var _a;
|
|
19995
|
+
const secretReason = SECRET_EXPOSING_ROLES[role];
|
|
19996
|
+
if (secretReason)
|
|
19997
|
+
return secretReason;
|
|
19998
|
+
const name = getRoleName(role);
|
|
19999
|
+
if (name === "owner" || name === "editor") {
|
|
20000
|
+
return "owner/editor are full write roles";
|
|
20001
|
+
}
|
|
20002
|
+
const last = (_a = name.split(".").pop()) !== null && _a !== void 0 ? _a : name;
|
|
20003
|
+
if (/admin$/i.test(last)) {
|
|
20004
|
+
return "admin roles can change resources";
|
|
20005
|
+
}
|
|
20006
|
+
if (/(editor|developer|writer|creator|deployer|invoker|builder|publisher|client|user|objectUser)$/i.test(last) &&
|
|
20007
|
+
!/^iam\./.test(name)) {
|
|
20008
|
+
return "this role can change resources";
|
|
20009
|
+
}
|
|
20010
|
+
if (/^iam\./.test(name) &&
|
|
20011
|
+
/(serviceAccountUser|serviceAccountTokenCreator|serviceAccountKeyAdmin|workloadIdentityUser)$/.test(name)) {
|
|
20012
|
+
return "this role allows impersonating service accounts";
|
|
20013
|
+
}
|
|
20014
|
+
if (!options.allowAnyRole && !SAFE_ROLES.includes(role)) {
|
|
20015
|
+
return `only these roles are allowed: ${SAFE_ROLES.join(", ")} (set allowAnyRole: true to grant others at your own risk)`;
|
|
20016
|
+
}
|
|
20017
|
+
return undefined;
|
|
20018
|
+
};
|
|
20019
|
+
exports.getDeniedLocalAccessRoleReason = getDeniedLocalAccessRoleReason;
|
|
20020
|
+
/**
|
|
20021
|
+
* the effective local access declaration of an env: the env's own
|
|
20022
|
+
* declaration replaces the project default entirely
|
|
20023
|
+
*/
|
|
20024
|
+
const getLocalAccessConfig = (config, env) => {
|
|
20025
|
+
var _a;
|
|
20026
|
+
if (env === "local")
|
|
20027
|
+
return undefined;
|
|
20028
|
+
const envEntry = (_a = config.environments) === null || _a === void 0 ? void 0 : _a[env];
|
|
20029
|
+
const declared = envEntry && envEntry.localAccess !== undefined
|
|
20030
|
+
? envEntry.localAccess
|
|
20031
|
+
: config.localAccess;
|
|
20032
|
+
return declared || undefined;
|
|
20033
|
+
};
|
|
20034
|
+
exports.getLocalAccessConfig = getLocalAccessConfig;
|
|
20035
|
+
/**
|
|
20036
|
+
* the gcp roles local access grants in `env`: preset roles plus the
|
|
20037
|
+
* explicit ones, deduplicated and sorted. Throws on an unknown preset,
|
|
20038
|
+
* a malformed role or a denied (write/admin) role.
|
|
20039
|
+
*/
|
|
20040
|
+
const getLocalAccessGcpRoles = (config, env) => {
|
|
20041
|
+
var _a, _b, _c;
|
|
20042
|
+
const gcp = (_a = (0, exports.getLocalAccessConfig)(config, env)) === null || _a === void 0 ? void 0 : _a.gcp;
|
|
20043
|
+
if (!gcp)
|
|
20044
|
+
return [];
|
|
20045
|
+
const roles = new Set();
|
|
20046
|
+
for (const preset of (_b = gcp.presets) !== null && _b !== void 0 ? _b : []) {
|
|
20047
|
+
const presetRoles = exports.LOCAL_ACCESS_GCP_PRESETS[preset];
|
|
20048
|
+
if (!presetRoles) {
|
|
20049
|
+
throw new Error(`localAccess (${env}): unknown gcp preset '${preset}', known: ${Object.keys(exports.LOCAL_ACCESS_GCP_PRESETS).join(", ")}`);
|
|
20050
|
+
}
|
|
20051
|
+
presetRoles.forEach((role) => roles.add(role));
|
|
20052
|
+
}
|
|
20053
|
+
for (const role of (_c = gcp.roles) !== null && _c !== void 0 ? _c : []) {
|
|
20054
|
+
if (!ROLE_FORMAT.test(role)) {
|
|
20055
|
+
throw new Error(`localAccess (${env}): '${role}' is not a valid role (expected e.g. roles/logging.viewer)`);
|
|
20056
|
+
}
|
|
20057
|
+
const reason = (0, exports.getDeniedLocalAccessRoleReason)(role, {
|
|
20058
|
+
allowAnyRole: gcp.allowAnyRole,
|
|
20059
|
+
});
|
|
20060
|
+
if (reason) {
|
|
20061
|
+
throw new Error(`localAccess (${env}): role '${role}' is not allowed (${reason})`);
|
|
20062
|
+
}
|
|
20063
|
+
roles.add(role);
|
|
20064
|
+
}
|
|
20065
|
+
return [...roles].sort();
|
|
20066
|
+
};
|
|
20067
|
+
exports.getLocalAccessGcpRoles = getLocalAccessGcpRoles;
|
|
20068
|
+
/**
|
|
20069
|
+
* the envs of a component that declare gcp local access. Empty for
|
|
20070
|
+
* components that do not deploy to google cloud run.
|
|
20071
|
+
*/
|
|
20072
|
+
const getLocalAccessGcpEnvs = (config, componentName) => (0, configruedEnvs_1.getAllEnvs)(config, componentName).filter((env) =>
|
|
20073
|
+
// only google cloud run envs have a project to grant roles in; the
|
|
20074
|
+
// env's own `deploy` overrides the component's
|
|
20075
|
+
(0, types_1.isOfDeployType)((0, getEnvConfig_1.getEnvConfig)(config, componentName, env).deploy, "google-cloudrun") && (0, exports.getLocalAccessGcpRoles)(config, env).length > 0);
|
|
20076
|
+
exports.getLocalAccessGcpEnvs = getLocalAccessGcpEnvs;
|
|
20077
|
+
/**
|
|
20078
|
+
* name of the secret (stored under the `local` env of the component)
|
|
20079
|
+
* holding the service account key of `env`'s local access
|
|
20080
|
+
*/
|
|
20081
|
+
const getLocalAccessGcpSecretName = (env) => `LOCAL_ACCESS_GCP_${env.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase()}_CREDENTIALS`;
|
|
20082
|
+
exports.getLocalAccessGcpSecretName = getLocalAccessGcpSecretName;
|
|
20083
|
+
/**
|
|
20084
|
+
* the bindings to add and remove so that `current` matches `desired`.
|
|
20085
|
+
* The local access account is dedicated, so every binding it carries
|
|
20086
|
+
* beyond the declaration is extra.
|
|
20087
|
+
*/
|
|
20088
|
+
const diffRoleBindings = (desired, current) => ({
|
|
20089
|
+
missing: desired.filter((role) => !current.includes(role)),
|
|
20090
|
+
extra: current.filter((role) => !desired.includes(role)),
|
|
20091
|
+
});
|
|
20092
|
+
exports.diffRoleBindings = diffRoleBindings;
|
|
20093
|
+
const ENV_VAR_NAME_FORMAT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
20094
|
+
/**
|
|
20095
|
+
* the extra env var names (`credentialsEnvVar`) under which the local
|
|
20096
|
+
* env of a component exports the key file path, mapped to the env whose
|
|
20097
|
+
* key they point at. Throws on a malformed name or on two envs
|
|
20098
|
+
* exporting the same name (e.g. a project default declaring it).
|
|
20099
|
+
*/
|
|
20100
|
+
const getLocalAccessGcpCredentialsEnvVars = (config, componentName) => {
|
|
20101
|
+
var _a, _b;
|
|
20102
|
+
const vars = {};
|
|
20103
|
+
for (const env of (0, exports.getLocalAccessGcpEnvs)(config, componentName)) {
|
|
20104
|
+
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;
|
|
20105
|
+
if (!name)
|
|
20106
|
+
continue;
|
|
20107
|
+
if (!ENV_VAR_NAME_FORMAT.test(name)) {
|
|
20108
|
+
throw new Error(`localAccess (${env}): credentialsEnvVar '${name}' is not a valid env var name`);
|
|
20109
|
+
}
|
|
20110
|
+
if (vars[name]) {
|
|
20111
|
+
throw new Error(`localAccess (${componentName}): credentialsEnvVar '${name}' is declared by both ${vars[name]} and ${env}; declare it in one env only (environments.<env>.localAccess)`);
|
|
20112
|
+
}
|
|
20113
|
+
vars[name] = env;
|
|
20114
|
+
}
|
|
20115
|
+
return vars;
|
|
20116
|
+
};
|
|
20117
|
+
exports.getLocalAccessGcpCredentialsEnvVars = getLocalAccessGcpCredentialsEnvVars;
|
|
19367
20118
|
//# sourceMappingURL=index.js.map
|
|
19368
20119
|
|
|
19369
20120
|
/***/ }),
|