@catladder/cli 5.8.1 → 5.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +97 -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 +29 -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 +8 -0
  27. package/dist/apps/cli/src/localAccess/declared.js +20 -0
  28. package/dist/apps/cli/src/localAccess/declared.js.map +1 -0
  29. package/dist/apps/cli/src/localAccess/keyFiles.d.ts +26 -0
  30. package/dist/apps/cli/src/localAccess/keyFiles.js +87 -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 +390 -31
  46. package/dist/bundles/cli/index.js +737 -35
  47. package/dist/bundles/skills/catladder-secrets/SKILL.md +24 -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 +46 -0
  52. package/dist/packages/pipeline/src/localAccess/index.js +149 -0
  53. package/dist/packages/pipeline/src/localAccess/index.js.map +1 -0
  54. package/dist/packages/pipeline/src/types/config.d.ts +57 -0
  55. package/dist/packages/pipeline/src/types/config.js.map +1 -1
  56. package/dist/skills/catladder-secrets/SKILL.md +24 -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/checkCloudRun.ts +5 -1
  62. package/src/apps/cli/commands/project/doctor/checkLocalAccess.test.ts +112 -0
  63. package/src/apps/cli/commands/project/doctor/checkLocalAccess.ts +144 -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 +41 -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 +107 -0
  74. package/src/localAccess/declared.ts +19 -0
  75. package/src/localAccess/keyFiles.ts +114 -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,27 @@ 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
+
125
146
  ## Related skills
126
147
 
127
148
  - `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,46 @@
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
+ };
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ 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
+ };
11
+ /** name prefix of the service account `project setup` creates (see getGcloudServiceAccountNames) */
12
+ exports.LOCAL_ACCESS_GCP_SA_NAME = "local-diag";
13
+ const ROLE_FORMAT = /^(roles\/[A-Za-z0-9_.]+|projects\/[a-z0-9-]+\/roles\/[A-Za-z0-9_.]+|organizations\/\d+\/roles\/[A-Za-z0-9_.]+)$/;
14
+ /**
15
+ * roles that are read-only on paper but expose secrets: cloud run
16
+ * services/jobs carry their secrets as plain env vars, so anything that
17
+ * may read them reads every deployed secret. Denied even with
18
+ * `allowAnyRole`.
19
+ */
20
+ const SECRET_EXPOSING_ROLES = {
21
+ "roles/viewer": "roles/viewer can read cloud run env vars (secrets)",
22
+ "roles/run.viewer": "roles/run.viewer can read cloud run env vars (secrets)",
23
+ "roles/secretmanager.secretAccessor": "this role reads secret values",
24
+ "roles/secretmanager.admin": "this role reads secret values",
25
+ };
26
+ /**
27
+ * roles granted without `allowAnyRole`. An explicit list instead of a
28
+ * name pattern: a role called `*.viewer` / `*.reader` says nothing about
29
+ * its permissions (custom roles can hold anything, and predefined ones
30
+ * like storage.objectViewer, cloudfunctions.viewer, compute.viewer or
31
+ * logging.privateLogViewer expose data or secrets).
32
+ */
33
+ const SAFE_ROLES = [
34
+ "roles/logging.viewer",
35
+ "roles/monitoring.viewer",
36
+ "roles/errorreporting.viewer",
37
+ ];
38
+ /** `roles/foo.bar.v1` -> `foo.bar` (version suffixes must not defeat the checks) */
39
+ const getRoleName = (role) => { var _a; return ((_a = role.split("/").pop()) !== null && _a !== void 0 ? _a : role).replace(/(\.v\d+)+$/i, ""); };
40
+ /**
41
+ * why a role may not be granted for local access, or undefined if it
42
+ * is acceptable.
43
+ *
44
+ * By default only the roles in {@link SAFE_ROLES} pass; custom roles
45
+ * and everything else need `allowAnyRole`. `allowAnyRole` lifts the
46
+ * allow-list, but the obvious write/admin/impersonation roles and the
47
+ * secret exposing roles stay denied.
48
+ */
49
+ const getDeniedLocalAccessRoleReason = (role, options = {}) => {
50
+ var _a;
51
+ const secretReason = SECRET_EXPOSING_ROLES[role];
52
+ if (secretReason)
53
+ return secretReason;
54
+ const name = getRoleName(role);
55
+ if (name === "owner" || name === "editor") {
56
+ return "owner/editor are full write roles";
57
+ }
58
+ const last = (_a = name.split(".").pop()) !== null && _a !== void 0 ? _a : name;
59
+ if (/admin$/i.test(last)) {
60
+ return "admin roles can change resources";
61
+ }
62
+ if (/(editor|developer|writer|creator|deployer|invoker|builder|publisher|client|user|objectUser)$/i.test(last) &&
63
+ !/^iam\./.test(name)) {
64
+ return "this role can change resources";
65
+ }
66
+ if (/^iam\./.test(name) &&
67
+ /(serviceAccountUser|serviceAccountTokenCreator|serviceAccountKeyAdmin|workloadIdentityUser)$/.test(name)) {
68
+ return "this role allows impersonating service accounts";
69
+ }
70
+ if (!options.allowAnyRole && !SAFE_ROLES.includes(role)) {
71
+ return `only these roles are allowed: ${SAFE_ROLES.join(", ")} (set allowAnyRole: true to grant others at your own risk)`;
72
+ }
73
+ return undefined;
74
+ };
75
+ exports.getDeniedLocalAccessRoleReason = getDeniedLocalAccessRoleReason;
76
+ /**
77
+ * the effective local access declaration of an env: the env's own
78
+ * declaration replaces the project default entirely
79
+ */
80
+ const getLocalAccessConfig = (config, env) => {
81
+ var _a;
82
+ if (env === "local")
83
+ return undefined;
84
+ const envEntry = (_a = config.environments) === null || _a === void 0 ? void 0 : _a[env];
85
+ const declared = envEntry && envEntry.localAccess !== undefined
86
+ ? envEntry.localAccess
87
+ : config.localAccess;
88
+ return declared || undefined;
89
+ };
90
+ exports.getLocalAccessConfig = getLocalAccessConfig;
91
+ /**
92
+ * the gcp roles local access grants in `env`: preset roles plus the
93
+ * explicit ones, deduplicated and sorted. Throws on an unknown preset,
94
+ * a malformed role or a denied (write/admin) role.
95
+ */
96
+ const getLocalAccessGcpRoles = (config, env) => {
97
+ var _a, _b, _c;
98
+ const gcp = (_a = (0, exports.getLocalAccessConfig)(config, env)) === null || _a === void 0 ? void 0 : _a.gcp;
99
+ if (!gcp)
100
+ return [];
101
+ const roles = new Set();
102
+ for (const preset of (_b = gcp.presets) !== null && _b !== void 0 ? _b : []) {
103
+ const presetRoles = exports.LOCAL_ACCESS_GCP_PRESETS[preset];
104
+ if (!presetRoles) {
105
+ throw new Error(`localAccess (${env}): unknown gcp preset '${preset}', known: ${Object.keys(exports.LOCAL_ACCESS_GCP_PRESETS).join(", ")}`);
106
+ }
107
+ presetRoles.forEach((role) => roles.add(role));
108
+ }
109
+ for (const role of (_c = gcp.roles) !== null && _c !== void 0 ? _c : []) {
110
+ if (!ROLE_FORMAT.test(role)) {
111
+ throw new Error(`localAccess (${env}): '${role}' is not a valid role (expected e.g. roles/logging.viewer)`);
112
+ }
113
+ const reason = (0, exports.getDeniedLocalAccessRoleReason)(role, {
114
+ allowAnyRole: gcp.allowAnyRole,
115
+ });
116
+ if (reason) {
117
+ throw new Error(`localAccess (${env}): role '${role}' is not allowed (${reason})`);
118
+ }
119
+ roles.add(role);
120
+ }
121
+ return [...roles].sort();
122
+ };
123
+ exports.getLocalAccessGcpRoles = getLocalAccessGcpRoles;
124
+ /**
125
+ * the envs of a component that declare gcp local access. Empty for
126
+ * components that do not deploy to google cloud run.
127
+ */
128
+ const getLocalAccessGcpEnvs = (config, componentName) => (0, configruedEnvs_1.getAllEnvs)(config, componentName).filter((env) =>
129
+ // only google cloud run envs have a project to grant roles in; the
130
+ // env's own `deploy` overrides the component's
131
+ (0, types_1.isOfDeployType)((0, getEnvConfig_1.getEnvConfig)(config, componentName, env).deploy, "google-cloudrun") && (0, exports.getLocalAccessGcpRoles)(config, env).length > 0);
132
+ exports.getLocalAccessGcpEnvs = getLocalAccessGcpEnvs;
133
+ /**
134
+ * name of the secret (stored under the `local` env of the component)
135
+ * holding the service account key of `env`'s local access
136
+ */
137
+ const getLocalAccessGcpSecretName = (env) => `LOCAL_ACCESS_GCP_${env.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase()}_CREDENTIALS`;
138
+ exports.getLocalAccessGcpSecretName = getLocalAccessGcpSecretName;
139
+ /**
140
+ * the bindings to add and remove so that `current` matches `desired`.
141
+ * The local access account is dedicated, so every binding it carries
142
+ * beyond the declaration is extra.
143
+ */
144
+ const diffRoleBindings = (desired, current) => ({
145
+ missing: desired.filter((role) => !current.includes(role)),
146
+ extra: current.filter((role) => !desired.includes(role)),
147
+ });
148
+ exports.diffRoleBindings = diffRoleBindings;
149
+ //# 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,47 @@ 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
+ */
254
+ export type LocalAccessGcpPreset = "logs";
255
+ export type LocalAccessGcpConfig = {
256
+ /** preset role bundles, see {@link LocalAccessGcpPreset} */
257
+ presets?: LocalAccessGcpPreset[];
258
+ /**
259
+ * additional explicit roles (e.g. `roles/monitoring.viewer`).
260
+ * Write/admin roles are rejected; everything else you grant here is
261
+ * your responsibility — the key file lives on developer machines.
262
+ */
263
+ roles?: string[];
264
+ /**
265
+ * lifts the default allow-list (only a few known-safe roles:
266
+ * logging/monitoring/errorreporting viewers; custom roles need this).
267
+ * Write/admin/impersonation roles and roles exposing secrets
268
+ * (`roles/viewer`, `roles/run.viewer`, secret accessors) stay denied.
269
+ * What you grant beyond the allow-list is your responsibility.
270
+ */
271
+ allowAnyRole?: boolean;
272
+ };
273
+ /**
274
+ * which read-only access LOCAL use gets for an env. `project setup`
275
+ * creates a dedicated service account per component/env, grants exactly
276
+ * these bindings (and removes bindings no longer declared) and stores
277
+ * its key in the secret store under the `local` env — never in CI
278
+ * variables. catenv writes the key to a gitignored file.
279
+ *
280
+ * `false` disables it. Without a declaration nothing is created: the
281
+ * env stays reachable with the personal gcloud login only.
282
+ */
283
+ export type LocalAccessConfig = {
284
+ /** google cloud (only for components deploying to google cloud run) */
285
+ gcp?: LocalAccessGcpConfig;
286
+ } | false;
239
287
  export type EnvironmentInheritConfig = string | {
240
288
  /** env whose per-component config overrides are inherited */
241
289
  config?: string;
@@ -451,6 +499,15 @@ export type Config<C extends ConfigProps = never> = {
451
499
  * A single component opts out with `env.<name>: false` instead.
452
500
  */
453
501
  environments?: EnvironmentsConfig;
502
+ /**
503
+ * project default for the read-only access LOCAL use gets per env
504
+ * (see {@link LocalAccessConfig}), overridable per env in
505
+ * `environments.<env>.localAccess`. Never applies to the `local` env.
506
+ *
507
+ * @example
508
+ * localAccess: { gcp: { presets: ["logs"] } }
509
+ */
510
+ localAccess?: LocalAccessConfig;
454
511
  /**
455
512
  * components (sub apps)
456
513
  */
@@ -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,27 @@ 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
+
125
146
  ## Related skills
126
147
 
127
148
  - `catladder-config` — general catladder.ts structure and regeneration