@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.
- 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 +97 -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 +29 -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 +8 -0
- package/dist/apps/cli/src/localAccess/declared.js +20 -0
- package/dist/apps/cli/src/localAccess/declared.js.map +1 -0
- package/dist/apps/cli/src/localAccess/keyFiles.d.ts +26 -0
- package/dist/apps/cli/src/localAccess/keyFiles.js +87 -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 +390 -31
- package/dist/bundles/cli/index.js +737 -35
- package/dist/bundles/skills/catladder-secrets/SKILL.md +24 -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 +46 -0
- package/dist/packages/pipeline/src/localAccess/index.js +149 -0
- package/dist/packages/pipeline/src/localAccess/index.js.map +1 -0
- package/dist/packages/pipeline/src/types/config.d.ts +57 -0
- package/dist/packages/pipeline/src/types/config.js.map +1 -1
- package/dist/skills/catladder-secrets/SKILL.md +24 -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/checkCloudRun.ts +5 -1
- package/src/apps/cli/commands/project/doctor/checkLocalAccess.test.ts +112 -0
- package/src/apps/cli/commands/project/doctor/checkLocalAccess.ts +144 -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 +41 -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 +107 -0
- package/src/localAccess/declared.ts +19 -0
- package/src/localAccess/keyFiles.ts +114 -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
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
@@ -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;
|
|
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
|
|
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
|
|
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
|
|
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
|