@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
@@ -6,7 +6,7 @@ description: Managing environment variables and secrets in a catladder project.
6
6
  # Env vars and secrets with catladder
7
7
 
8
8
  Environment variables are declared per component in `catladder.ts`;
9
- secret *values* are never checked into the repo.
9
+ secret _values_ are never checked into the repo.
10
10
 
11
11
  ## Declaring variables
12
12
 
@@ -93,7 +93,7 @@ A few secrets are not typed in by a human but created by
93
93
  (`GCLOUD_DEPLOY_credentialsKey`) and the kubernetes deploy credentials
94
94
  (`KUBE_TOKEN`, `KUBE_CA_PEM`, `KUBE_URL`). They travel the same route as
95
95
  every other secret — vault first, then the enabled CI backends — but
96
- they are *hidden*: they never show up in `secrets-list`, in the editor
96
+ they are _hidden_: they never show up in `secrets-list`, in the editor
97
97
  or in a pulled document, and they must never be set by hand.
98
98
 
99
99
  Re-running `project setup` **rotates** them (a fresh credential is
@@ -105,7 +105,7 @@ over, so re-run `project setup` to provision them into the new vault.
105
105
  ## GitHub pipelines
106
106
 
107
107
  GitHub Actions cannot read the vault at runtime; secrets are mirrored
108
- into GitHub *environment* secrets (one GitHub environment per catladder
108
+ into GitHub _environment_ secrets (one GitHub environment per catladder
109
109
  env). Sync them with:
110
110
 
111
111
  ```sh
@@ -122,6 +122,38 @@ direnv) for the `local` environment. If a var is missing locally,
122
122
  check its declaration in `catladder.ts` and re-run `yarn catenv` before
123
123
  suspecting anything else.
124
124
 
125
+ ### Read-only cloud access for local use
126
+
127
+ For google cloud run components, `localAccess` in `catladder.ts`
128
+ (project default at the top level, per env in `environments.<env>`,
129
+ `false` to disable) declares read-only access for local use, e.g.
130
+ `localAccess: { gcp: { presets: ["logs"] } }`. `catladder project setup`
131
+ creates a service account per component/env with exactly those roles and
132
+ stores its key in the secret store under the `local` env (never mirrored
133
+ to CI); `catladder project doctor` reports drift. `catenv` writes the key
134
+ to `.catladder/local-access/gcp-<env>-<component>.json` (0600, gitignored)
135
+ and exports its path as `CATLADDER_LOCAL_ACCESS_GCP_<ENV>_KEY_FILE` — not
136
+ as `GOOGLE_APPLICATION_CREDENTIALS`, use it with an isolated
137
+ `CLOUDSDK_CONFIG`, never `gcloud auth` on the personal config. The
138
+ `logs` preset is `roles/logging.viewer` only (project-wide); extra `roles`
139
+ must be `roles/{logging,monitoring,errorreporting}.viewer` (never `roles/viewer`/`roles/run.viewer`:
140
+ cloud run revisions carry secrets as plain env vars), unless
141
+ `allowAnyRole: true` (also needed for custom roles); write/admin roles are always rejected. Needs a
142
+ non-CI vault (e.g. bitwarden) — `project setup` refuses with the gitlab
143
+ vault. After a key rotation on another machine, run
144
+ `catenv --vault-mode refresh`.
145
+
146
+ The `vertex` preset grants `roles/aiplatform.user` (to call Vertex AI
147
+ models, e.g. local evals). It is **not** read-only: it can use and bill
148
+ every Vertex AI API of the project and create/change/delete Vertex AI
149
+ resources; it is only accepted as a preset, never in `roles`. Add
150
+ `credentialsEnvVar: "GOOGLE_APPLICATION_CREDENTIALS"` (in one env only)
151
+ to have catenv export the key path under that name as well, so google
152
+ client libraries use it without `gcloud auth application-default login`.
153
+ The variable lands in the component's `.env` (e.g. `apps/web/.env`), not
154
+ in a root `.env`: scripts run from the repo root must load that file
155
+ (`node --env-file-if-exists=apps/web/.env …`).
156
+
125
157
  ## Related skills
126
158
 
127
159
  - `catladder-config` — general catladder.ts structure and regeneration
@@ -20,3 +20,4 @@ export * from "./utils/measureTime";
20
20
  export * from "../../bash/src/index.js";
21
21
  export * from "./catenv";
22
22
  export * from "./agentSkills";
23
+ export * from "./localAccess";
@@ -36,4 +36,5 @@ __exportStar(require("./utils/measureTime"), exports);
36
36
  __exportStar(require("../../bash/src/index.js"), exports);
37
37
  __exportStar(require("./catenv"), exports);
38
38
  __exportStar(require("./agentSkills"), exports);
39
+ __exportStar(require("./localAccess"), exports);
39
40
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/pipeline/src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA2B;AAC3B,6CAA2B;AAC3B,+CAA6B;AAC7B,6CAA2B;AAC3B,0CAAwB;AACxB,0CAAwB;AACxB,2CAAyB;AACzB,+DAA6C;AAC7C,+DAA6C;AAC7C,2CAAyB;AACzB,0CAAwB;AACxB,4CAA0B;AAC1B,0CAAwB;AACxB,2CAAyB;AACzB,2CAAyB;AACzB,kDAAgC;AAChC,4CAA0B;AAC1B,qDAAmC;AACnC,sDAAoC;AACpC,kDAAgC;AAChC,2CAAyB;AACzB,gDAA8B"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/pipeline/src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA2B;AAC3B,6CAA2B;AAC3B,+CAA6B;AAC7B,6CAA2B;AAC3B,0CAAwB;AACxB,0CAAwB;AACxB,2CAAyB;AACzB,+DAA6C;AAC7C,+DAA6C;AAC7C,2CAAyB;AACzB,0CAAwB;AACxB,4CAA0B;AAC1B,0CAAwB;AACxB,2CAAyB;AACzB,2CAAyB;AACzB,kDAAgC;AAChC,4CAA0B;AAC1B,qDAAmC;AACnC,sDAAoC;AACpC,kDAAgC;AAChC,2CAAyB;AACzB,gDAA8B;AAC9B,gDAA8B"}
@@ -0,0 +1,53 @@
1
+ import type { Config, LocalAccessConfig, LocalAccessGcpPreset } from "../types";
2
+ export declare const LOCAL_ACCESS_GCP_PRESETS: Record<LocalAccessGcpPreset, string[]>;
3
+ /** name prefix of the service account `project setup` creates (see getGcloudServiceAccountNames) */
4
+ export declare const LOCAL_ACCESS_GCP_SA_NAME = "local-diag";
5
+ /**
6
+ * why a role may not be granted for local access, or undefined if it
7
+ * is acceptable.
8
+ *
9
+ * By default only the roles in {@link SAFE_ROLES} pass; custom roles
10
+ * and everything else need `allowAnyRole`. `allowAnyRole` lifts the
11
+ * allow-list, but the obvious write/admin/impersonation roles and the
12
+ * secret exposing roles stay denied.
13
+ */
14
+ export declare const getDeniedLocalAccessRoleReason: (role: string, options?: {
15
+ allowAnyRole?: boolean;
16
+ }) => string | undefined;
17
+ /**
18
+ * the effective local access declaration of an env: the env's own
19
+ * declaration replaces the project default entirely
20
+ */
21
+ export declare const getLocalAccessConfig: (config: Pick<Config, "localAccess" | "environments">, env: string) => Exclude<LocalAccessConfig, false> | undefined;
22
+ /**
23
+ * the gcp roles local access grants in `env`: preset roles plus the
24
+ * explicit ones, deduplicated and sorted. Throws on an unknown preset,
25
+ * a malformed role or a denied (write/admin) role.
26
+ */
27
+ export declare const getLocalAccessGcpRoles: (config: Pick<Config, "localAccess" | "environments">, env: string) => string[];
28
+ /**
29
+ * the envs of a component that declare gcp local access. Empty for
30
+ * components that do not deploy to google cloud run.
31
+ */
32
+ export declare const getLocalAccessGcpEnvs: (config: Config, componentName: string) => string[];
33
+ /**
34
+ * name of the secret (stored under the `local` env of the component)
35
+ * holding the service account key of `env`'s local access
36
+ */
37
+ export declare const getLocalAccessGcpSecretName: (env: string) => string;
38
+ /**
39
+ * the bindings to add and remove so that `current` matches `desired`.
40
+ * The local access account is dedicated, so every binding it carries
41
+ * beyond the declaration is extra.
42
+ */
43
+ export declare const diffRoleBindings: (desired: string[], current: string[]) => {
44
+ missing: string[];
45
+ extra: string[];
46
+ };
47
+ /**
48
+ * the extra env var names (`credentialsEnvVar`) under which the local
49
+ * env of a component exports the key file path, mapped to the env whose
50
+ * key they point at. Throws on a malformed name or on two envs
51
+ * exporting the same name (e.g. a project default declaring it).
52
+ */
53
+ export declare const getLocalAccessGcpCredentialsEnvVars: (config: Config, componentName: string) => Record<string, string>;
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ 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;
4
+ const configruedEnvs_1 = require("../config/configruedEnvs");
5
+ const getEnvConfig_1 = require("../context/getEnvConfig");
6
+ const types_1 = require("../deploy/types");
7
+ exports.LOCAL_ACCESS_GCP_PRESETS = {
8
+ // no run.viewer on purpose, see LocalAccessGcpPreset
9
+ logs: ["roles/logging.viewer"],
10
+ // not read-only, see LocalAccessGcpPreset; only reachable as a
11
+ // preset, getDeniedLocalAccessRoleReason rejects it in `roles`
12
+ vertex: ["roles/aiplatform.user"],
13
+ };
14
+ /** name prefix of the service account `project setup` creates (see getGcloudServiceAccountNames) */
15
+ exports.LOCAL_ACCESS_GCP_SA_NAME = "local-diag";
16
+ const ROLE_FORMAT = /^(roles\/[A-Za-z0-9_.]+|projects\/[a-z0-9-]+\/roles\/[A-Za-z0-9_.]+|organizations\/\d+\/roles\/[A-Za-z0-9_.]+)$/;
17
+ /**
18
+ * roles that are read-only on paper but expose secrets: cloud run
19
+ * services/jobs carry their secrets as plain env vars, so anything that
20
+ * may read them reads every deployed secret. Denied even with
21
+ * `allowAnyRole`.
22
+ */
23
+ const SECRET_EXPOSING_ROLES = {
24
+ "roles/viewer": "roles/viewer can read cloud run env vars (secrets)",
25
+ "roles/run.viewer": "roles/run.viewer can read cloud run env vars (secrets)",
26
+ "roles/secretmanager.secretAccessor": "this role reads secret values",
27
+ "roles/secretmanager.admin": "this role reads secret values",
28
+ };
29
+ /**
30
+ * roles granted without `allowAnyRole`. An explicit list instead of a
31
+ * name pattern: a role called `*.viewer` / `*.reader` says nothing about
32
+ * its permissions (custom roles can hold anything, and predefined ones
33
+ * like storage.objectViewer, cloudfunctions.viewer, compute.viewer or
34
+ * logging.privateLogViewer expose data or secrets).
35
+ */
36
+ const SAFE_ROLES = [
37
+ "roles/logging.viewer",
38
+ "roles/monitoring.viewer",
39
+ "roles/errorreporting.viewer",
40
+ ];
41
+ /** `roles/foo.bar.v1` -> `foo.bar` (version suffixes must not defeat the checks) */
42
+ const getRoleName = (role) => { var _a; return ((_a = role.split("/").pop()) !== null && _a !== void 0 ? _a : role).replace(/(\.v\d+)+$/i, ""); };
43
+ /**
44
+ * why a role may not be granted for local access, or undefined if it
45
+ * is acceptable.
46
+ *
47
+ * By default only the roles in {@link SAFE_ROLES} pass; custom roles
48
+ * and everything else need `allowAnyRole`. `allowAnyRole` lifts the
49
+ * allow-list, but the obvious write/admin/impersonation roles and the
50
+ * secret exposing roles stay denied.
51
+ */
52
+ const getDeniedLocalAccessRoleReason = (role, options = {}) => {
53
+ var _a;
54
+ const secretReason = SECRET_EXPOSING_ROLES[role];
55
+ if (secretReason)
56
+ return secretReason;
57
+ const name = getRoleName(role);
58
+ if (name === "owner" || name === "editor") {
59
+ return "owner/editor are full write roles";
60
+ }
61
+ const last = (_a = name.split(".").pop()) !== null && _a !== void 0 ? _a : name;
62
+ if (/admin$/i.test(last)) {
63
+ return "admin roles can change resources";
64
+ }
65
+ if (/(editor|developer|writer|creator|deployer|invoker|builder|publisher|client|user|objectUser)$/i.test(last) &&
66
+ !/^iam\./.test(name)) {
67
+ return "this role can change resources";
68
+ }
69
+ if (/^iam\./.test(name) &&
70
+ /(serviceAccountUser|serviceAccountTokenCreator|serviceAccountKeyAdmin|workloadIdentityUser)$/.test(name)) {
71
+ return "this role allows impersonating service accounts";
72
+ }
73
+ if (!options.allowAnyRole && !SAFE_ROLES.includes(role)) {
74
+ return `only these roles are allowed: ${SAFE_ROLES.join(", ")} (set allowAnyRole: true to grant others at your own risk)`;
75
+ }
76
+ return undefined;
77
+ };
78
+ exports.getDeniedLocalAccessRoleReason = getDeniedLocalAccessRoleReason;
79
+ /**
80
+ * the effective local access declaration of an env: the env's own
81
+ * declaration replaces the project default entirely
82
+ */
83
+ const getLocalAccessConfig = (config, env) => {
84
+ var _a;
85
+ if (env === "local")
86
+ return undefined;
87
+ const envEntry = (_a = config.environments) === null || _a === void 0 ? void 0 : _a[env];
88
+ const declared = envEntry && envEntry.localAccess !== undefined
89
+ ? envEntry.localAccess
90
+ : config.localAccess;
91
+ return declared || undefined;
92
+ };
93
+ exports.getLocalAccessConfig = getLocalAccessConfig;
94
+ /**
95
+ * the gcp roles local access grants in `env`: preset roles plus the
96
+ * explicit ones, deduplicated and sorted. Throws on an unknown preset,
97
+ * a malformed role or a denied (write/admin) role.
98
+ */
99
+ const getLocalAccessGcpRoles = (config, env) => {
100
+ var _a, _b, _c;
101
+ const gcp = (_a = (0, exports.getLocalAccessConfig)(config, env)) === null || _a === void 0 ? void 0 : _a.gcp;
102
+ if (!gcp)
103
+ return [];
104
+ const roles = new Set();
105
+ for (const preset of (_b = gcp.presets) !== null && _b !== void 0 ? _b : []) {
106
+ const presetRoles = exports.LOCAL_ACCESS_GCP_PRESETS[preset];
107
+ if (!presetRoles) {
108
+ throw new Error(`localAccess (${env}): unknown gcp preset '${preset}', known: ${Object.keys(exports.LOCAL_ACCESS_GCP_PRESETS).join(", ")}`);
109
+ }
110
+ presetRoles.forEach((role) => roles.add(role));
111
+ }
112
+ for (const role of (_c = gcp.roles) !== null && _c !== void 0 ? _c : []) {
113
+ if (!ROLE_FORMAT.test(role)) {
114
+ throw new Error(`localAccess (${env}): '${role}' is not a valid role (expected e.g. roles/logging.viewer)`);
115
+ }
116
+ const reason = (0, exports.getDeniedLocalAccessRoleReason)(role, {
117
+ allowAnyRole: gcp.allowAnyRole,
118
+ });
119
+ if (reason) {
120
+ throw new Error(`localAccess (${env}): role '${role}' is not allowed (${reason})`);
121
+ }
122
+ roles.add(role);
123
+ }
124
+ return [...roles].sort();
125
+ };
126
+ exports.getLocalAccessGcpRoles = getLocalAccessGcpRoles;
127
+ /**
128
+ * the envs of a component that declare gcp local access. Empty for
129
+ * components that do not deploy to google cloud run.
130
+ */
131
+ const getLocalAccessGcpEnvs = (config, componentName) => (0, configruedEnvs_1.getAllEnvs)(config, componentName).filter((env) =>
132
+ // only google cloud run envs have a project to grant roles in; the
133
+ // env's own `deploy` overrides the component's
134
+ (0, types_1.isOfDeployType)((0, getEnvConfig_1.getEnvConfig)(config, componentName, env).deploy, "google-cloudrun") && (0, exports.getLocalAccessGcpRoles)(config, env).length > 0);
135
+ exports.getLocalAccessGcpEnvs = getLocalAccessGcpEnvs;
136
+ /**
137
+ * name of the secret (stored under the `local` env of the component)
138
+ * holding the service account key of `env`'s local access
139
+ */
140
+ const getLocalAccessGcpSecretName = (env) => `LOCAL_ACCESS_GCP_${env.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase()}_CREDENTIALS`;
141
+ exports.getLocalAccessGcpSecretName = getLocalAccessGcpSecretName;
142
+ /**
143
+ * the bindings to add and remove so that `current` matches `desired`.
144
+ * The local access account is dedicated, so every binding it carries
145
+ * beyond the declaration is extra.
146
+ */
147
+ const diffRoleBindings = (desired, current) => ({
148
+ missing: desired.filter((role) => !current.includes(role)),
149
+ extra: current.filter((role) => !desired.includes(role)),
150
+ });
151
+ exports.diffRoleBindings = diffRoleBindings;
152
+ const ENV_VAR_NAME_FORMAT = /^[A-Za-z_][A-Za-z0-9_]*$/;
153
+ /**
154
+ * the extra env var names (`credentialsEnvVar`) under which the local
155
+ * env of a component exports the key file path, mapped to the env whose
156
+ * key they point at. Throws on a malformed name or on two envs
157
+ * exporting the same name (e.g. a project default declaring it).
158
+ */
159
+ const getLocalAccessGcpCredentialsEnvVars = (config, componentName) => {
160
+ var _a, _b;
161
+ const vars = {};
162
+ for (const env of (0, exports.getLocalAccessGcpEnvs)(config, componentName)) {
163
+ 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;
164
+ if (!name)
165
+ continue;
166
+ if (!ENV_VAR_NAME_FORMAT.test(name)) {
167
+ throw new Error(`localAccess (${env}): credentialsEnvVar '${name}' is not a valid env var name`);
168
+ }
169
+ if (vars[name]) {
170
+ throw new Error(`localAccess (${componentName}): credentialsEnvVar '${name}' is declared by both ${vars[name]} and ${env}; declare it in one env only (environments.<env>.localAccess)`);
171
+ }
172
+ vars[name] = env;
173
+ }
174
+ return vars;
175
+ };
176
+ exports.getLocalAccessGcpCredentialsEnvVars = getLocalAccessGcpCredentialsEnvVars;
177
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../../packages/pipeline/src/localAccess/index.ts"],"names":[],"mappings":"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"}
@@ -201,6 +201,13 @@ export type EnvironmentConfig = {
201
201
  * axes separately.
202
202
  */
203
203
  inherit?: EnvironmentInheritConfig;
204
+ /**
205
+ * read-only access to this env's cloud resources for LOCAL use
206
+ * (agents, scripts), see {@link LocalAccessConfig}. Overrides the
207
+ * project default (top-level `localAccess`) entirely; `false`
208
+ * switches it off for this env.
209
+ */
210
+ localAccess?: LocalAccessConfig;
204
211
  };
205
212
  /**
206
213
  * what an environment inherits from another environment.
@@ -236,6 +243,65 @@ export type EnvironmentsConfig = Record<string, EnvironmentConfig | false>;
236
243
  */
237
244
  export declare const getEnvironmentConfig: (environments: EnvironmentsConfig | undefined, env: string) => EnvironmentConfig | undefined;
238
245
  export declare const isEnvironmentDisabled: (environments: EnvironmentsConfig | undefined, env: string) => boolean;
246
+ /**
247
+ * named bundles of gcp roles for {@link LocalAccessGcpConfig}
248
+ *
249
+ * - `logs`: `roles/logging.viewer` only. Deliberately NOT
250
+ * `roles/run.viewer`: cloud run services/jobs are deployed with
251
+ * their secrets as plain env vars, so anything with `run.*.get`
252
+ * can read every deployed secret.
253
+ * - `vertex`: `roles/aiplatform.user`, to call Vertex AI models (e.g.
254
+ * for local evals). NOT read-only: it may use (and bill) every Vertex
255
+ * AI API of the project and create, change and delete Vertex AI
256
+ * resources (datasets, endpoints, models, jobs). It reads no other
257
+ * service's data. Only available as a preset, `roles` rejects it.
258
+ */
259
+ export type LocalAccessGcpPreset = "logs" | "vertex";
260
+ export type LocalAccessGcpConfig = {
261
+ /** preset role bundles, see {@link LocalAccessGcpPreset} */
262
+ presets?: LocalAccessGcpPreset[];
263
+ /**
264
+ * additional explicit roles (e.g. `roles/monitoring.viewer`).
265
+ * Write/admin roles are rejected; everything else you grant here is
266
+ * your responsibility — the key file lives on developer machines.
267
+ */
268
+ roles?: string[];
269
+ /**
270
+ * lifts the default allow-list (only a few known-safe roles:
271
+ * logging/monitoring/errorreporting viewers; custom roles need this).
272
+ * Write/admin/impersonation roles and roles exposing secrets
273
+ * (`roles/viewer`, `roles/run.viewer`, secret accessors) stay denied.
274
+ * What you grant beyond the allow-list is your responsibility.
275
+ */
276
+ allowAnyRole?: boolean;
277
+ /**
278
+ * also export the key file path under this env var name in the
279
+ * `local` env of the component (in addition to
280
+ * `CATLADDER_LOCAL_ACCESS_GCP_<ENV>_KEY_FILE`), e.g.
281
+ * `GOOGLE_APPLICATION_CREDENTIALS` so google client libraries use the
282
+ * key instead of a personal ADC login. Every process that loads the
283
+ * component's env then acts as the service account, so declare it in
284
+ * one env only (two envs exporting the same name are rejected).
285
+ * Overrides a var of the same name declared in `vars` while the key
286
+ * file exists.
287
+ */
288
+ credentialsEnvVar?: string;
289
+ };
290
+ /**
291
+ * which access LOCAL use gets for an env (read-only, unless a preset
292
+ * like `vertex` says otherwise). `project setup`
293
+ * creates a dedicated service account per component/env, grants exactly
294
+ * these bindings (and removes bindings no longer declared) and stores
295
+ * its key in the secret store under the `local` env — never in CI
296
+ * variables. catenv writes the key to a gitignored file.
297
+ *
298
+ * `false` disables it. Without a declaration nothing is created: the
299
+ * env stays reachable with the personal gcloud login only.
300
+ */
301
+ export type LocalAccessConfig = {
302
+ /** google cloud (only for components deploying to google cloud run) */
303
+ gcp?: LocalAccessGcpConfig;
304
+ } | false;
239
305
  export type EnvironmentInheritConfig = string | {
240
306
  /** env whose per-component config overrides are inherited */
241
307
  config?: string;
@@ -451,6 +517,15 @@ export type Config<C extends ConfigProps = never> = {
451
517
  * A single component opts out with `env.<name>: false` instead.
452
518
  */
453
519
  environments?: EnvironmentsConfig;
520
+ /**
521
+ * project default for the read-only access LOCAL use gets per env
522
+ * (see {@link LocalAccessConfig}), overridable per env in
523
+ * `environments.<env>.localAccess`. Never applies to the `local` env.
524
+ *
525
+ * @example
526
+ * localAccess: { gcp: { presets: ["logs"] } }
527
+ */
528
+ localAccess?: LocalAccessConfig;
454
529
  /**
455
530
  * components (sub apps)
456
531
  */
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../../../../packages/pipeline/src/types/config.ts"],"names":[],"mappings":";;;AAmBa,QAAA,qBAAqB,GAAG;IACnC,YAAY;IACZ,IAAI;IACJ,eAAe;CACP,CAAC;AAoBJ,MAAM,eAAe,GAAG,CAC7B,OAA2B,EACO,EAAE,CACpC,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,QAAQ,IAAI,OAAO,CAAC;AAH5D,QAAA,eAAe,mBAG6C;AAElE,MAAM,gBAAgB,GAAG,CAC9B,CAA0B,EAC1B,CAAqB,EACZ,EAAE;IACX,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IACjD,IAAI,IAAA,uBAAe,EAAC,CAAC,CAAC;QAAE,OAAO,IAAA,uBAAe,EAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC;IAC3E,OAAO,CAAC,KAAK,CAAC,CAAC;AACjB,CAAC,CAAC;AAPW,QAAA,gBAAgB,oBAO3B;AAEF;;;GAGG;AACU,QAAA,SAAS,GAAG;IACvB,GAAG,EAAE;QACH,QAAQ,EAAE,CAAC,YAAY,CAAC;KACzB;IACD,MAAM,EAAE;QACN,QAAQ,EAAE,CAAC,IAAI,CAAC;KACjB;IACD,KAAK,EAAE;QACL,QAAQ,EAAE,CAAC,eAAe,CAAC;KAC5B;IACD,IAAI,EAAE;QACJ,QAAQ,EAAE,CAAC,eAAe,CAAC;KAC5B;IACD,KAAK,EAAE;QACL,QAAQ,EAAE,EAAE;KACb;CACO,CAAC;AAEE,QAAA,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAS,CAAC,CAAC;AACtC,QAAA,iBAAiB,GAAG,oBAAyB,CAAC;AAGpD,MAAM,aAAa,GAAG,CAAC,GAAW,EAAkB,EAAE;IAC3D,OAAO,GAAG,IAAI,iBAAS,CAAC;AAC1B,CAAC,CAAC;AAFW,QAAA,aAAa,iBAExB;AAmMF;;;GAGG;AACI,MAAM,oBAAoB,GAAG,CAClC,YAA4C,EAC5C,GAAW,EACoB,EAAE,CAAC,CAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,GAAG,CAAC,KAAI,SAAS,CAAC;AAHxD,QAAA,oBAAoB,wBAGoC;AAE9D,MAAM,qBAAqB,GAAG,CACnC,YAA4C,EAC5C,GAAW,EACF,EAAE,CAAC,CAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,GAAG,CAAC,MAAK,KAAK,CAAC;AAH/B,QAAA,qBAAqB,yBAGU"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../../../../packages/pipeline/src/types/config.ts"],"names":[],"mappings":";;;AAmBa,QAAA,qBAAqB,GAAG;IACnC,YAAY;IACZ,IAAI;IACJ,eAAe;CACP,CAAC;AAoBJ,MAAM,eAAe,GAAG,CAC7B,OAA2B,EACO,EAAE,CACpC,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,QAAQ,IAAI,OAAO,CAAC;AAH5D,QAAA,eAAe,mBAG6C;AAElE,MAAM,gBAAgB,GAAG,CAC9B,CAA0B,EAC1B,CAAqB,EACZ,EAAE;IACX,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IACjD,IAAI,IAAA,uBAAe,EAAC,CAAC,CAAC;QAAE,OAAO,IAAA,uBAAe,EAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC;IAC3E,OAAO,CAAC,KAAK,CAAC,CAAC;AACjB,CAAC,CAAC;AAPW,QAAA,gBAAgB,oBAO3B;AAEF;;;GAGG;AACU,QAAA,SAAS,GAAG;IACvB,GAAG,EAAE;QACH,QAAQ,EAAE,CAAC,YAAY,CAAC;KACzB;IACD,MAAM,EAAE;QACN,QAAQ,EAAE,CAAC,IAAI,CAAC;KACjB;IACD,KAAK,EAAE;QACL,QAAQ,EAAE,CAAC,eAAe,CAAC;KAC5B;IACD,IAAI,EAAE;QACJ,QAAQ,EAAE,CAAC,eAAe,CAAC;KAC5B;IACD,KAAK,EAAE;QACL,QAAQ,EAAE,EAAE;KACb;CACO,CAAC;AAEE,QAAA,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAS,CAAC,CAAC;AACtC,QAAA,iBAAiB,GAAG,oBAAyB,CAAC;AAGpD,MAAM,aAAa,GAAG,CAAC,GAAW,EAAkB,EAAE;IAC3D,OAAO,GAAG,IAAI,iBAAS,CAAC;AAC1B,CAAC,CAAC;AAFW,QAAA,aAAa,iBAExB;AA2MF;;;GAGG;AACI,MAAM,oBAAoB,GAAG,CAClC,YAA4C,EAC5C,GAAW,EACoB,EAAE,CAAC,CAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,GAAG,CAAC,KAAI,SAAS,CAAC;AAHxD,QAAA,oBAAoB,wBAGoC;AAE9D,MAAM,qBAAqB,GAAG,CACnC,YAA4C,EAC5C,GAAW,EACF,EAAE,CAAC,CAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,GAAG,CAAC,MAAK,KAAK,CAAC;AAH/B,QAAA,qBAAqB,yBAGU"}
@@ -6,7 +6,7 @@ description: Managing environment variables and secrets in a catladder project.
6
6
  # Env vars and secrets with catladder
7
7
 
8
8
  Environment variables are declared per component in `catladder.ts`;
9
- secret *values* are never checked into the repo.
9
+ secret _values_ are never checked into the repo.
10
10
 
11
11
  ## Declaring variables
12
12
 
@@ -93,7 +93,7 @@ A few secrets are not typed in by a human but created by
93
93
  (`GCLOUD_DEPLOY_credentialsKey`) and the kubernetes deploy credentials
94
94
  (`KUBE_TOKEN`, `KUBE_CA_PEM`, `KUBE_URL`). They travel the same route as
95
95
  every other secret — vault first, then the enabled CI backends — but
96
- they are *hidden*: they never show up in `secrets-list`, in the editor
96
+ they are _hidden_: they never show up in `secrets-list`, in the editor
97
97
  or in a pulled document, and they must never be set by hand.
98
98
 
99
99
  Re-running `project setup` **rotates** them (a fresh credential is
@@ -105,7 +105,7 @@ over, so re-run `project setup` to provision them into the new vault.
105
105
  ## GitHub pipelines
106
106
 
107
107
  GitHub Actions cannot read the vault at runtime; secrets are mirrored
108
- into GitHub *environment* secrets (one GitHub environment per catladder
108
+ into GitHub _environment_ secrets (one GitHub environment per catladder
109
109
  env). Sync them with:
110
110
 
111
111
  ```sh
@@ -122,6 +122,38 @@ direnv) for the `local` environment. If a var is missing locally,
122
122
  check its declaration in `catladder.ts` and re-run `yarn catenv` before
123
123
  suspecting anything else.
124
124
 
125
+ ### Read-only cloud access for local use
126
+
127
+ For google cloud run components, `localAccess` in `catladder.ts`
128
+ (project default at the top level, per env in `environments.<env>`,
129
+ `false` to disable) declares read-only access for local use, e.g.
130
+ `localAccess: { gcp: { presets: ["logs"] } }`. `catladder project setup`
131
+ creates a service account per component/env with exactly those roles and
132
+ stores its key in the secret store under the `local` env (never mirrored
133
+ to CI); `catladder project doctor` reports drift. `catenv` writes the key
134
+ to `.catladder/local-access/gcp-<env>-<component>.json` (0600, gitignored)
135
+ and exports its path as `CATLADDER_LOCAL_ACCESS_GCP_<ENV>_KEY_FILE` — not
136
+ as `GOOGLE_APPLICATION_CREDENTIALS`, use it with an isolated
137
+ `CLOUDSDK_CONFIG`, never `gcloud auth` on the personal config. The
138
+ `logs` preset is `roles/logging.viewer` only (project-wide); extra `roles`
139
+ must be `roles/{logging,monitoring,errorreporting}.viewer` (never `roles/viewer`/`roles/run.viewer`:
140
+ cloud run revisions carry secrets as plain env vars), unless
141
+ `allowAnyRole: true` (also needed for custom roles); write/admin roles are always rejected. Needs a
142
+ non-CI vault (e.g. bitwarden) — `project setup` refuses with the gitlab
143
+ vault. After a key rotation on another machine, run
144
+ `catenv --vault-mode refresh`.
145
+
146
+ The `vertex` preset grants `roles/aiplatform.user` (to call Vertex AI
147
+ models, e.g. local evals). It is **not** read-only: it can use and bill
148
+ every Vertex AI API of the project and create/change/delete Vertex AI
149
+ resources; it is only accepted as a preset, never in `roles`. Add
150
+ `credentialsEnvVar: "GOOGLE_APPLICATION_CREDENTIALS"` (in one env only)
151
+ to have catenv export the key path under that name as well, so google
152
+ client libraries use it without `gcloud auth application-default login`.
153
+ The variable lands in the component's `.env` (e.g. `apps/web/.env`), not
154
+ in a root `.env`: scripts run from the repo root must load that file
155
+ (`node --env-file-if-exists=apps/web/.env …`).
156
+
125
157
  ## Related skills
126
158
 
127
159
  - `catladder-config` — general catladder.ts structure and regeneration