@caelo-cms/provisioning 0.11.11 → 0.11.13
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/admin-domain.d.ts +24 -0
- package/dist/admin-domain.d.ts.map +1 -1
- package/dist/admin-domain.js +43 -0
- package/dist/admin-domain.js.map +1 -1
- package/dist/agent-db.js +171 -15
- package/dist/converge-job-gcp.d.ts +7 -0
- package/dist/converge-job-gcp.d.ts.map +1 -1
- package/dist/converge-job-gcp.js +33 -1
- package/dist/converge-job-gcp.js.map +1 -1
- package/dist/converge-job-preflight.d.ts +34 -2
- package/dist/converge-job-preflight.d.ts.map +1 -1
- package/dist/converge-job-preflight.js +68 -1
- package/dist/converge-job-preflight.js.map +1 -1
- package/dist/converge-job-setup.d.ts.map +1 -1
- package/dist/converge-job-setup.js +4 -1
- package/dist/converge-job-setup.js.map +1 -1
- package/dist/converge-job.d.ts +27 -3
- package/dist/converge-job.d.ts.map +1 -1
- package/dist/converge-job.js +93 -11
- package/dist/converge-job.js.map +1 -1
- package/dist/infra-contract.d.ts +1 -1
- package/dist/infra-contract.d.ts.map +1 -1
- package/dist/infra-contract.js +5 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-job.d.ts +6 -0
- package/dist/install-job.d.ts.map +1 -1
- package/dist/install-job.js +6 -0
- package/dist/install-job.js.map +1 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +14 -4
- package/dist/lifecycle.js.map +1 -1
- package/dist/mcp-auth-proxy-setup.d.ts +3 -1
- package/dist/mcp-auth-proxy-setup.d.ts.map +1 -1
- package/dist/mcp-auth-proxy-setup.js +9 -4
- package/dist/mcp-auth-proxy-setup.js.map +1 -1
- package/dist/scheduler-setup.d.ts +5 -0
- package/dist/scheduler-setup.d.ts.map +1 -1
- package/dist/scheduler-setup.js +7 -0
- package/dist/scheduler-setup.js.map +1 -1
- package/dist/upgrader-iam-preflight.d.ts +8 -6
- package/dist/upgrader-iam-preflight.d.ts.map +1 -1
- package/dist/upgrader-iam-preflight.js +5 -1
- package/dist/upgrader-iam-preflight.js.map +1 -1
- package/dist/wizards/gcp-cost.js +1 -1
- package/dist/wizards/gcp-cost.js.map +1 -1
- package/package.json +2 -2
- package/src/admin-domain.test.ts +65 -0
- package/src/admin-domain.ts +53 -0
- package/src/converge-job-gcp.ts +33 -1
- package/src/converge-job-preflight.test.ts +101 -0
- package/src/converge-job-preflight.ts +89 -3
- package/src/converge-job-setup.ts +4 -1
- package/src/converge-job.test.ts +143 -0
- package/src/converge-job.ts +143 -19
- package/src/infra-contract.test.ts +4 -3
- package/src/infra-contract.ts +5 -1
- package/src/install-job.ts +6 -0
- package/src/lifecycle.ts +16 -6
- package/src/mcp-auth-proxy-setup.test.ts +33 -1
- package/src/mcp-auth-proxy-setup.ts +11 -4
- package/src/scheduler-setup.test.ts +6 -0
- package/src/scheduler-setup.ts +7 -0
- package/src/upgrader-iam-preflight.ts +15 -10
- package/src/wizards/gcp-cost.ts +1 -1
package/dist/wizards/gcp-cost.js
CHANGED
|
@@ -127,7 +127,7 @@ export function estimateGcpCost(inputs) {
|
|
|
127
127
|
{
|
|
128
128
|
name: "Quality checks (Cloud Run audit job)",
|
|
129
129
|
monthlyUsd: 2,
|
|
130
|
-
notes: "one container per Lighthouse run, all in parallel (up to 5 pages x 3 runs, ~90 s at 2 vCPU each): ~$0.05 per audited Stage, partly in the free tier; results bucket ~$0",
|
|
130
|
+
notes: "one container per Lighthouse run, all in parallel (up to 5 pages x 3 runs, ~90 s at 2 vCPU each; a page whose runs disagree gets 2 more): ~$0.05 per audited Stage, at most ~$0.08, partly in the free tier; results bucket ~$0",
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
name: "Screenshots (Cloud Run capture job)",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gcp-cost.js","sourceRoot":"","sources":["../../src/wizards/gcp-cost.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"gcp-cost.js","sourceRoot":"","sources":["../../src/wizards/gcp-cost.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caelo-cms/provisioning",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.13",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"description": "Pulumi-driven provisioning for Caelo CMS — `bunx @caelo-cms/provisioning --provider <self-hosted|gcp|aws|azure>` brings up a TLS-served install with Postgres + storage + secrets manager + the admin + the API gateway.",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"prepublishOnly": "tsc -b --force && bun build ../platform-updater/src/agent-db.ts --target=bun --outfile=dist/agent-db.js && tsc --project stacks/gcp/tsconfig.json && tsc --project stacks/gcp-firebase/tsconfig.json"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@caelo-cms/shared": "^0.11.
|
|
43
|
+
"@caelo-cms/shared": "^0.11.13",
|
|
44
44
|
"@clack/prompts": "1.8.1",
|
|
45
45
|
"@pulumi/command": "^1",
|
|
46
46
|
"@pulumi/gcp": "^10",
|
package/src/admin-domain.test.ts
CHANGED
|
@@ -340,3 +340,68 @@ describe("setAdminStagingHostEnv (#1058)", () => {
|
|
|
340
340
|
expect(b.status === "failed" && b.error).toContain("set CAELO_STAGING_HOST on the admin");
|
|
341
341
|
});
|
|
342
342
|
});
|
|
343
|
+
|
|
344
|
+
describe("stagingDomainState", () => {
|
|
345
|
+
const target = { projectId: "p", region: "europe-west1", stagingHost: "staging.example.com" };
|
|
346
|
+
|
|
347
|
+
it("reports an existing mapping as mapped", async () => {
|
|
348
|
+
const { run, calls } = fakeGcloud({
|
|
349
|
+
"beta run domain-mappings describe": [ok("staging.example.com\n")],
|
|
350
|
+
});
|
|
351
|
+
const { stagingDomainState } = await import("./admin-domain.js");
|
|
352
|
+
expect(await stagingDomainState({ ...target, run })).toEqual({ status: "mapped" });
|
|
353
|
+
expect(calls).toContainEqual([
|
|
354
|
+
"beta",
|
|
355
|
+
"run",
|
|
356
|
+
"domain-mappings",
|
|
357
|
+
"describe",
|
|
358
|
+
"--domain=staging.example.com",
|
|
359
|
+
"--region=europe-west1",
|
|
360
|
+
"--project=p",
|
|
361
|
+
"--format=value(metadata.name)",
|
|
362
|
+
]);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("reports gcloud's NOT_FOUND as unmapped", async () => {
|
|
366
|
+
const { run } = fakeGcloud({
|
|
367
|
+
"beta run domain-mappings describe": [
|
|
368
|
+
fail(
|
|
369
|
+
"ERROR: (gcloud.beta.run.domain-mappings.describe) NOT_FOUND: Resource 'staging.example.com' of kind 'DOMAIN_MAPPING' in region 'europe-west1' in project 'p' does not exist.",
|
|
370
|
+
),
|
|
371
|
+
],
|
|
372
|
+
});
|
|
373
|
+
const { stagingDomainState } = await import("./admin-domain.js");
|
|
374
|
+
expect(await stagingDomainState({ ...target, run })).toEqual({ status: "unmapped" });
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("does not guess on another error", async () => {
|
|
378
|
+
const { run } = fakeGcloud({
|
|
379
|
+
"beta run domain-mappings describe": [fail("PERMISSION_DENIED: run.domainmappings.get")],
|
|
380
|
+
});
|
|
381
|
+
const { stagingDomainState } = await import("./admin-domain.js");
|
|
382
|
+
expect(await stagingDomainState({ ...target, run })).toEqual({
|
|
383
|
+
status: "unknown",
|
|
384
|
+
reason: "PERMISSION_DENIED: run.domainmappings.get",
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("says why when gcloud cannot list its components", async () => {
|
|
389
|
+
const { run } = fakeGcloud({ "components list": [fail("component manager crashed")] });
|
|
390
|
+
const { stagingDomainState } = await import("./admin-domain.js");
|
|
391
|
+
expect(await stagingDomainState({ ...target, run })).toEqual({
|
|
392
|
+
status: "unknown",
|
|
393
|
+
reason: 'check the gcloud "beta" component: component manager crashed',
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("never installs the beta component to answer", async () => {
|
|
398
|
+
const { run, calls } = fakeGcloud({ "components list": [ok("")] });
|
|
399
|
+
const { stagingDomainState } = await import("./admin-domain.js");
|
|
400
|
+
expect(await stagingDomainState({ ...target, run })).toEqual({
|
|
401
|
+
status: "unknown",
|
|
402
|
+
reason: 'the gcloud "beta" component is not installed',
|
|
403
|
+
});
|
|
404
|
+
expect(calls.some((c) => c.join(" ").startsWith("components install"))).toBe(false);
|
|
405
|
+
expect(calls.some((c) => c[0] === "beta")).toBe(false);
|
|
406
|
+
});
|
|
407
|
+
});
|
package/src/admin-domain.ts
CHANGED
|
@@ -183,6 +183,59 @@ export async function enableStagingDomain(
|
|
|
183
183
|
return mapDomain(run, logOf(opts), scope, verifyDomain, opts.stagingHost, found.service);
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
/** What {@link stagingDomainState} found. */
|
|
187
|
+
export type StagingDomainState =
|
|
188
|
+
| { readonly status: "mapped" }
|
|
189
|
+
| { readonly status: "unmapped" }
|
|
190
|
+
| { readonly status: "unknown"; readonly reason: string };
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Whether the install's staging host already has its Cloud Run domain
|
|
194
|
+
* mapping, read-only. The upgrade asks before it tells the operator to run
|
|
195
|
+
* `staging-domain enable`, so an install that serves its staging host is
|
|
196
|
+
* not sent to set it up again. It never installs the gcloud `beta`
|
|
197
|
+
* component (an upgrade should not pause for a download to print a hint):
|
|
198
|
+
* without it, or on any error but gcloud's NOT_FOUND, the state is unknown
|
|
199
|
+
* and says why.
|
|
200
|
+
*/
|
|
201
|
+
export async function stagingDomainState(opts: {
|
|
202
|
+
projectId: string;
|
|
203
|
+
region: string;
|
|
204
|
+
stagingHost: string;
|
|
205
|
+
run?: Run;
|
|
206
|
+
}): Promise<StagingDomainState> {
|
|
207
|
+
const run = opts.run ?? defaultGcloud;
|
|
208
|
+
const listed = await run([
|
|
209
|
+
"components",
|
|
210
|
+
"list",
|
|
211
|
+
"--only-local-state",
|
|
212
|
+
"--filter=id=beta",
|
|
213
|
+
"--format=value(id)",
|
|
214
|
+
]);
|
|
215
|
+
if (!listed.ok) {
|
|
216
|
+
return {
|
|
217
|
+
status: "unknown",
|
|
218
|
+
reason: `check the gcloud "beta" component: ${listed.stderr.trim() || `exit ${listed.exitCode}`}`,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (!listed.stdout.split("\n").some((s) => s.trim() === "beta")) {
|
|
222
|
+
return { status: "unknown", reason: 'the gcloud "beta" component is not installed' };
|
|
223
|
+
}
|
|
224
|
+
const described = await run([
|
|
225
|
+
"beta",
|
|
226
|
+
"run",
|
|
227
|
+
"domain-mappings",
|
|
228
|
+
"describe",
|
|
229
|
+
`--domain=${opts.stagingHost}`,
|
|
230
|
+
`--region=${opts.region}`,
|
|
231
|
+
`--project=${opts.projectId}`,
|
|
232
|
+
"--format=value(metadata.name)",
|
|
233
|
+
]);
|
|
234
|
+
if (described.ok) return { status: "mapped" };
|
|
235
|
+
if (/\bNOT_FOUND\b/.test(described.stderr)) return { status: "unmapped" };
|
|
236
|
+
return { status: "unknown", reason: described.stderr.trim() };
|
|
237
|
+
}
|
|
238
|
+
|
|
186
239
|
/** What {@link setAdminStagingHostEnv} did. */
|
|
187
240
|
export type AdminStagingEnvResult =
|
|
188
241
|
| { readonly status: "set"; readonly service: string; readonly previous: string | null }
|
package/src/converge-job-gcp.ts
CHANGED
|
@@ -22,9 +22,10 @@ import { join } from "node:path";
|
|
|
22
22
|
import { CAELO_VERSION } from "@caelo-cms/shared";
|
|
23
23
|
import { readConvergeCheckpoint, writeConvergeCheckpoint } from "./converge-checkpoint.js";
|
|
24
24
|
import type { ConvergeJobDeps, ConvergeQueue, ConvergeUpgradeOutcome } from "./converge-job.js";
|
|
25
|
+
import { isSelfGrantableRole } from "./converge-job-preflight.js";
|
|
25
26
|
import { CONVERGE_TASK_TIMEOUT_MINUTES } from "./converge-job-setup.js";
|
|
26
27
|
import { gcloud } from "./gcloud.js";
|
|
27
|
-
import { type GcloudRunner, realSleep } from "./gcloud-retry.js";
|
|
28
|
+
import { type GcloudRunner, realSleep, runWithRetry } from "./gcloud-retry.js";
|
|
28
29
|
import {
|
|
29
30
|
convergeJobName,
|
|
30
31
|
GCP_STACK_ENV,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
} from "./install-state.js";
|
|
43
44
|
import { runMigrationsViaCloudRunJob } from "./migration-runner.js";
|
|
44
45
|
import { installIapAllowlist } from "./operator-access.js";
|
|
46
|
+
import { provisionerServiceAccountEmail } from "./provisioner-identity.js";
|
|
45
47
|
import { fetchVerifiedReleaseManifest, manifestServiceImages } from "./release-manifest.js";
|
|
46
48
|
import type { HttpFetch } from "./runtime-secrets.js";
|
|
47
49
|
import { publicSiteUrl, STAGING_HOST_ENV } from "./stack-contract.js";
|
|
@@ -203,6 +205,26 @@ export function startedExecutionName(stdout: string, stderr: string): string | n
|
|
|
203
205
|
return /^[a-z0-9-]+$/.test(name) ? name : null;
|
|
204
206
|
}
|
|
205
207
|
|
|
208
|
+
/**
|
|
209
|
+
* The `gcloud` argv that grants the install's provisioner — this job's
|
|
210
|
+
* own identity, never another principal — `role` on the project, or null
|
|
211
|
+
* when `role` is not on the self-grant allowlist (converge-job-preflight.ts).
|
|
212
|
+
* Exported for the tests that pin the boundary.
|
|
213
|
+
*/
|
|
214
|
+
export function selfGrantArgs(projectId: string, role: string): string[] | null {
|
|
215
|
+
if (!isSelfGrantableRole(role)) return null;
|
|
216
|
+
return [
|
|
217
|
+
"projects",
|
|
218
|
+
"add-iam-policy-binding",
|
|
219
|
+
projectId,
|
|
220
|
+
`--member=serviceAccount:${provisionerServiceAccountEmail(projectId)}`,
|
|
221
|
+
`--role=${role}`,
|
|
222
|
+
"--condition=None",
|
|
223
|
+
"--quiet",
|
|
224
|
+
"--format=none",
|
|
225
|
+
];
|
|
226
|
+
}
|
|
227
|
+
|
|
206
228
|
/**
|
|
207
229
|
* Every dependency of {@link runConvergeJob} except the database (the queue
|
|
208
230
|
* and how it is secured, #1373) and the log.
|
|
@@ -366,6 +388,16 @@ export function gcpConvergeDeps(
|
|
|
366
388
|
projectId,
|
|
367
389
|
region,
|
|
368
390
|
iam: gcloudIamTester(run, http),
|
|
391
|
+
grantOwnRole: async (role) => {
|
|
392
|
+
const args = selfGrantArgs(projectId, role);
|
|
393
|
+
if (!args)
|
|
394
|
+
return { ok: false, error: `${role} is not a role the converge job may grant itself` };
|
|
395
|
+
const r = await runWithRetry(run, realSleep, args);
|
|
396
|
+
return r.ok
|
|
397
|
+
? { ok: true }
|
|
398
|
+
: { ok: false, error: r.stderr.trim() || `gcloud exited ${r.exitCode}` };
|
|
399
|
+
},
|
|
400
|
+
sleep: realSleep,
|
|
369
401
|
execution: placement.execution,
|
|
370
402
|
ownVersion: CAELO_VERSION,
|
|
371
403
|
// The default deps: the fixed registry and signer, never overridden.
|
|
@@ -7,10 +7,15 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { describe, expect, it } from "bun:test";
|
|
10
|
+
import { selfGrantArgs } from "./converge-job-gcp.js";
|
|
10
11
|
import {
|
|
11
12
|
CONVERGE_PREFLIGHT_VOICE,
|
|
13
|
+
CONVERGE_SELF_GRANTABLE_ROLES,
|
|
12
14
|
convergeAccessChecks,
|
|
13
15
|
INSTALL_ROLE_USES,
|
|
16
|
+
isSelfGrantableRole,
|
|
17
|
+
SELF_GRANT_FORBIDDEN_ROLES,
|
|
18
|
+
selfGrantPlan,
|
|
14
19
|
} from "./converge-job-preflight.js";
|
|
15
20
|
import { PROVISIONER_ROLE_LIST } from "./gcloud.js";
|
|
16
21
|
import { INSTALL_JOB_ROLES } from "./install-job.js";
|
|
@@ -79,6 +84,7 @@ describe("convergeAccessChecks", () => {
|
|
|
79
84
|
for (const role of INSTALL_JOB_ROLES) {
|
|
80
85
|
expect(checks).toContainEqual({
|
|
81
86
|
resource: { kind: "project", projectId: "acme" },
|
|
87
|
+
role,
|
|
82
88
|
...INSTALL_ROLE_USES[role],
|
|
83
89
|
});
|
|
84
90
|
}
|
|
@@ -111,3 +117,98 @@ describe("convergeAccessChecks", () => {
|
|
|
111
117
|
expect(reason).toContain("Cloud Shell or the computer you installed from");
|
|
112
118
|
});
|
|
113
119
|
});
|
|
120
|
+
|
|
121
|
+
describe("the converge job's self-grant allowlist (2026-11-03)", () => {
|
|
122
|
+
it("is exactly the roles converge-job-setup.ts grants the provisioner", () => {
|
|
123
|
+
expect([...CONVERGE_SELF_GRANTABLE_ROLES]).toEqual([...INSTALL_JOB_ROLES]);
|
|
124
|
+
expect(CONVERGE_SELF_GRANTABLE_ROLES).toContain("roles/workflows.editor");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("names no ownership or IAM-policy role", () => {
|
|
128
|
+
for (const role of CONVERGE_SELF_GRANTABLE_ROLES) {
|
|
129
|
+
for (const f of SELF_GRANT_FORBIDDEN_ROLES) {
|
|
130
|
+
expect(role === f || (f.endsWith(".") && role.startsWith(f))).toBe(false);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("refuses every role outside the list, escalations first", () => {
|
|
136
|
+
for (const role of [
|
|
137
|
+
"roles/owner",
|
|
138
|
+
"roles/editor",
|
|
139
|
+
"roles/resourcemanager.projectIamAdmin",
|
|
140
|
+
"roles/iam.securityAdmin",
|
|
141
|
+
"roles/iam.serviceAccountKeyAdmin",
|
|
142
|
+
"roles/run.admin",
|
|
143
|
+
"roles/workflows.admin",
|
|
144
|
+
"roles/workflows.editor ",
|
|
145
|
+
"",
|
|
146
|
+
]) {
|
|
147
|
+
expect(isSelfGrantableRole(role)).toBe(false);
|
|
148
|
+
}
|
|
149
|
+
for (const role of INSTALL_JOB_ROLES) expect(isSelfGrantableRole(role)).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("the grant names the project and the provisioner only, and refuses other roles", () => {
|
|
153
|
+
expect(selfGrantArgs("acme", "roles/workflows.editor")).toEqual([
|
|
154
|
+
"projects",
|
|
155
|
+
"add-iam-policy-binding",
|
|
156
|
+
"acme",
|
|
157
|
+
"--member=serviceAccount:caelo-provisioner@acme.iam.gserviceaccount.com",
|
|
158
|
+
"--role=roles/workflows.editor",
|
|
159
|
+
"--condition=None",
|
|
160
|
+
"--quiet",
|
|
161
|
+
"--format=none",
|
|
162
|
+
]);
|
|
163
|
+
for (const role of [
|
|
164
|
+
"roles/owner",
|
|
165
|
+
"roles/resourcemanager.projectIamAdmin",
|
|
166
|
+
"roles/run.admin",
|
|
167
|
+
]) {
|
|
168
|
+
expect(selfGrantArgs("acme", role)).toBeNull();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("selfGrantPlan", () => {
|
|
174
|
+
it("an install from before 0.11.11 (no workflows.editor) needs one grant and nothing blocks", async () => {
|
|
175
|
+
const r = await runIamPreflight(
|
|
176
|
+
convergeAccessChecks(TARGET),
|
|
177
|
+
provisioner([
|
|
178
|
+
...PROVISIONER_ROLE_LIST,
|
|
179
|
+
...INSTALL_JOB_ROLES.filter((role) => role !== "roles/workflows.editor"),
|
|
180
|
+
]),
|
|
181
|
+
);
|
|
182
|
+
expect(r.kind).toBe("missing");
|
|
183
|
+
if (r.kind !== "missing") return;
|
|
184
|
+
expect(selfGrantPlan(r.missing)).toEqual({ grant: ["roles/workflows.editor"], blocking: [] });
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("every install-job role missing: all of them, in list order", async () => {
|
|
188
|
+
const r = await runIamPreflight(
|
|
189
|
+
convergeAccessChecks(TARGET),
|
|
190
|
+
provisioner(PROVISIONER_ROLE_LIST),
|
|
191
|
+
);
|
|
192
|
+
if (r.kind !== "missing") throw new Error("expected missing");
|
|
193
|
+
const plan = selfGrantPlan([...r.missing].reverse());
|
|
194
|
+
expect(plan.grant).toEqual([...INSTALL_JOB_ROLES]);
|
|
195
|
+
expect(plan.blocking).toEqual([]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("a check without a role, or with a role off the list, blocks", () => {
|
|
199
|
+
const actAs = convergeAccessChecks(TARGET).find((c) =>
|
|
200
|
+
c.permissions.includes("iam.serviceAccounts.actAs"),
|
|
201
|
+
);
|
|
202
|
+
if (!actAs) throw new Error("no actAs check");
|
|
203
|
+
const forged = {
|
|
204
|
+
...actAs,
|
|
205
|
+
role: "roles/owner" as unknown as (typeof INSTALL_JOB_ROLES)[number],
|
|
206
|
+
};
|
|
207
|
+
const plan = selfGrantPlan([
|
|
208
|
+
{ check: actAs, missing: ["iam.serviceAccounts.actAs"] },
|
|
209
|
+
{ check: forged, missing: ["iam.serviceAccounts.actAs"] },
|
|
210
|
+
]);
|
|
211
|
+
expect(plan.grant).toEqual([]);
|
|
212
|
+
expect(plan.blocking).toHaveLength(2);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -26,6 +26,23 @@
|
|
|
26
26
|
* - the Cloud SQL IAM login `ensureConvergeJob` creates (member of
|
|
27
27
|
* {@link UPGRADER_DB_ROLE}): the request row and the timeline.
|
|
28
28
|
*
|
|
29
|
+
* Self-grant (maintainer decision 2026-11-03). A release may add a role to
|
|
30
|
+
* {@link INSTALL_JOB_ROLES} (0.11.11 added roles/workflows.editor for the
|
|
31
|
+
* scheduler tick, #1140); an install set up before it lacks that role until
|
|
32
|
+
* a converge's `ensureConvergeJob` grants it, and the converge job used to
|
|
33
|
+
* stop here and send the Owner to Cloud Shell for exactly that grant. Now a
|
|
34
|
+
* missing role of {@link CONVERGE_SELF_GRANTABLE_ROLES} — this release's own
|
|
35
|
+
* compiled-in list, nothing a request, a record or the environment names —
|
|
36
|
+
* is not a reason to stop: stage 1 leaves it to stage 2, and stage 2 (the
|
|
37
|
+
* target release's verified ops image, under the state lock) grants it to
|
|
38
|
+
* the provisioner at project level, records each grant on the timeline and
|
|
39
|
+
* asks IAM again (converge-job.ts). Not an escalation: the provisioner holds
|
|
40
|
+
* roles/resourcemanager.projectIamAdmin since install, so it could grant
|
|
41
|
+
* itself any role already; the allowlist only narrows what the job does
|
|
42
|
+
* with that. {@link SELF_GRANT_FORBIDDEN_ROLES} pins that the list never
|
|
43
|
+
* names an ownership or IAM-policy role, and the upgrader job — which holds
|
|
44
|
+
* no IAM rights at all — has no grant step.
|
|
45
|
+
*
|
|
29
46
|
* Not checked: the permissions `pulumi up` needs for the stack. They are the
|
|
30
47
|
* provisioner's PROVISIONER_ROLE_LIST (gcloud.ts), granted at install by the
|
|
31
48
|
* Owner's own account; a converge cannot repair them and Pulumi names what
|
|
@@ -37,7 +54,7 @@ import { convergeJobName } from "./gcp-names.js";
|
|
|
37
54
|
import { INSTALL_JOB_ROLES, type InstallJobRole } from "./install-job.js";
|
|
38
55
|
import { provisionerServiceAccountEmail } from "./provisioner-identity.js";
|
|
39
56
|
import { OPS_INSTALL_PREFIX, opsBucketName } from "./state-backend.js";
|
|
40
|
-
import type { AccessCheck, PreflightVoice } from "./upgrader-iam-preflight.js";
|
|
57
|
+
import type { AccessCheck, MissingAccess, PreflightVoice } from "./upgrader-iam-preflight.js";
|
|
41
58
|
import { UPGRADER_DB_ROLE } from "./upgrader-setup.js";
|
|
42
59
|
|
|
43
60
|
/**
|
|
@@ -83,17 +100,86 @@ export const CONVERGE_PREFLIGHT_VOICE: PreflightVoice = {
|
|
|
83
100
|
"Run the update once from Cloud Shell or the computer you installed from (Settings → Updates shows the command); it repairs the converge job's access.",
|
|
84
101
|
};
|
|
85
102
|
|
|
103
|
+
/**
|
|
104
|
+
* The roles the converge job may grant its own account when the self-check
|
|
105
|
+
* finds them missing: exactly the roles `ensureConvergeJob` grants it, as
|
|
106
|
+
* compiled into this release (see the file comment).
|
|
107
|
+
*/
|
|
108
|
+
export const CONVERGE_SELF_GRANTABLE_ROLES: readonly InstallJobRole[] = INSTALL_JOB_ROLES;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Roles the converge job must never grant itself, whatever a release's list
|
|
112
|
+
* says — ownership and IAM-policy roles (prefix matches). The tests assert
|
|
113
|
+
* {@link CONVERGE_SELF_GRANTABLE_ROLES} names none of them, and the grant
|
|
114
|
+
* step refuses them again at run time.
|
|
115
|
+
*/
|
|
116
|
+
export const SELF_GRANT_FORBIDDEN_ROLES = [
|
|
117
|
+
"roles/owner",
|
|
118
|
+
"roles/editor",
|
|
119
|
+
"roles/resourcemanager.",
|
|
120
|
+
"roles/iam.securityAdmin",
|
|
121
|
+
"roles/iam.organizationRoleAdmin",
|
|
122
|
+
"roles/iam.serviceAccountKeyAdmin",
|
|
123
|
+
"roles/iam.workloadIdentityPoolAdmin",
|
|
124
|
+
"roles/orgpolicy.",
|
|
125
|
+
"roles/billing.",
|
|
126
|
+
] as const;
|
|
127
|
+
|
|
128
|
+
/** Whether the converge job may grant `role` to its own account (see the file comment). */
|
|
129
|
+
export function isSelfGrantableRole(role: string): role is InstallJobRole {
|
|
130
|
+
return (
|
|
131
|
+
(CONVERGE_SELF_GRANTABLE_ROLES as readonly string[]).includes(role) &&
|
|
132
|
+
!SELF_GRANT_FORBIDDEN_ROLES.some((f) => role === f || (f.endsWith(".") && role.startsWith(f)))
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** A converge self-check; `role` marks a check that granting that role repairs. */
|
|
137
|
+
export interface ConvergeAccessCheck extends AccessCheck {
|
|
138
|
+
readonly role?: InstallJobRole;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** What the converge job does about the self-check's missing permissions. */
|
|
142
|
+
export interface SelfGrantPlan {
|
|
143
|
+
/** Allowlisted roles to grant the provisioner, in list order. */
|
|
144
|
+
readonly grant: readonly InstallJobRole[];
|
|
145
|
+
/** What no grant of the job repairs: the run stops on any of these. */
|
|
146
|
+
readonly blocking: readonly MissingAccess<ConvergeAccessCheck>[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Split the self-check's missing permissions into roles the job grants
|
|
151
|
+
* itself and the rest. A check counts as repairable only when its whole
|
|
152
|
+
* shortfall is permissions of an allowlisted role it is marked with.
|
|
153
|
+
*/
|
|
154
|
+
export function selfGrantPlan(
|
|
155
|
+
missing: readonly MissingAccess<ConvergeAccessCheck>[],
|
|
156
|
+
): SelfGrantPlan {
|
|
157
|
+
const grant: InstallJobRole[] = [];
|
|
158
|
+
const blocking: MissingAccess<ConvergeAccessCheck>[] = [];
|
|
159
|
+
for (const m of missing) {
|
|
160
|
+
const role = m.check.role;
|
|
161
|
+
if (role && isSelfGrantableRole(role) && !(m.unchecked && m.unchecked.length > 0)) {
|
|
162
|
+
if (!grant.includes(role)) grant.push(role);
|
|
163
|
+
} else {
|
|
164
|
+
blocking.push(m);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const order = CONVERGE_SELF_GRANTABLE_ROLES as readonly string[];
|
|
168
|
+
grant.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
|
169
|
+
return { grant, blocking };
|
|
170
|
+
}
|
|
171
|
+
|
|
86
172
|
/** Every permission a converge run relies on after its decision (see the file comment). */
|
|
87
173
|
export function convergeAccessChecks(target: {
|
|
88
174
|
readonly projectId: string;
|
|
89
175
|
readonly region: string;
|
|
90
176
|
readonly env: string;
|
|
91
|
-
}):
|
|
177
|
+
}): ConvergeAccessCheck[] {
|
|
92
178
|
const { projectId, region, env } = target;
|
|
93
179
|
const project = { kind: "project", projectId } as const;
|
|
94
180
|
const bucket = opsBucketName(projectId);
|
|
95
181
|
return [
|
|
96
|
-
...INSTALL_JOB_ROLES.map((role) => ({ resource: project, ...INSTALL_ROLE_USES[role] })),
|
|
182
|
+
...INSTALL_JOB_ROLES.map((role) => ({ resource: project, role, ...INSTALL_ROLE_USES[role] })),
|
|
97
183
|
{
|
|
98
184
|
need: `read and write the install's records and state lock (${OPS_INSTALL_PREFIX}/) and the update's checkpoint (${CONVERGE_STATE_PREFIX}/) in the ops bucket`,
|
|
99
185
|
resource: { kind: "bucket", bucket },
|
|
@@ -41,7 +41,10 @@
|
|
|
41
41
|
* - {@link INSTALL_JOB_ROLES} (the operator-access step of a converge
|
|
42
42
|
* creates a custom role and a Cloud Scheduler job, and the job's lines
|
|
43
43
|
* must reach Cloud Logging) — what an install-job install already
|
|
44
|
-
* grants; not an escalation, the provisioner holds projectIamAdmin
|
|
44
|
+
* grants; not an escalation, the provisioner holds projectIamAdmin. The
|
|
45
|
+
* converge job of a release that adds a role here grants it itself when
|
|
46
|
+
* it finds it missing (converge-job-preflight.ts, the self-grant), so
|
|
47
|
+
* the role reaches an older install without a terminal run;
|
|
45
48
|
* - a Cloud SQL IAM database login that is a member of `platform_updater`
|
|
46
49
|
* only (migration 0267): it reads its request and writes the timeline,
|
|
47
50
|
* nothing else in the database.
|