@caelo-cms/provisioning 0.11.8 → 0.11.10
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 +10 -1
- package/dist/admin-domain.d.ts.map +1 -1
- package/dist/admin-domain.js +13 -3
- package/dist/admin-domain.js.map +1 -1
- package/dist/adopt-existing.d.ts +6 -1
- package/dist/adopt-existing.d.ts.map +1 -1
- package/dist/adopt-existing.js +86 -0
- package/dist/adopt-existing.js.map +1 -1
- package/dist/agent/command.d.ts +20 -0
- package/dist/agent/command.d.ts.map +1 -1
- package/dist/agent/command.js +32 -9
- package/dist/agent/command.js.map +1 -1
- package/dist/agent/run.d.ts +2 -0
- package/dist/agent/run.d.ts.map +1 -1
- package/dist/agent/run.js +5 -1
- package/dist/agent/run.js.map +1 -1
- package/dist/agent-db.js +65 -3
- package/dist/aws-task-roles.d.ts +4 -2
- package/dist/aws-task-roles.d.ts.map +1 -1
- package/dist/aws-task-roles.js +4 -1
- package/dist/aws-task-roles.js.map +1 -1
- package/dist/caddy.d.ts +21 -4
- package/dist/caddy.d.ts.map +1 -1
- package/dist/caddy.js +27 -3
- package/dist/caddy.js.map +1 -1
- package/dist/cli.js +12 -3
- package/dist/cli.js.map +1 -1
- package/dist/cloudsql-tls.d.ts +63 -0
- package/dist/cloudsql-tls.d.ts.map +1 -0
- package/dist/cloudsql-tls.js +289 -0
- package/dist/cloudsql-tls.js.map +1 -0
- package/dist/compose.d.ts +29 -6
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +95 -23
- package/dist/compose.js.map +1 -1
- package/dist/converge-checkpoint.d.ts +1 -0
- package/dist/converge-checkpoint.d.ts.map +1 -1
- package/dist/converge-checkpoint.js +8 -1
- package/dist/converge-checkpoint.js.map +1 -1
- package/dist/converge-job-gcp.d.ts.map +1 -1
- package/dist/converge-job-gcp.js +2 -11
- package/dist/converge-job-gcp.js.map +1 -1
- package/dist/deployed-release.d.ts +4 -1
- package/dist/deployed-release.d.ts.map +1 -1
- package/dist/deployed-release.js +18 -8
- package/dist/deployed-release.js.map +1 -1
- package/dist/gcp-names.d.ts +21 -0
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +24 -0
- package/dist/gcp-names.js.map +1 -1
- package/dist/gcp-url-map.d.ts.map +1 -1
- package/dist/gcp-url-map.js +6 -2
- package/dist/gcp-url-map.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.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 +24 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-state.d.ts +6 -0
- package/dist/install-state.d.ts.map +1 -1
- package/dist/install-state.js +17 -4
- package/dist/install-state.js.map +1 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +17 -8
- package/dist/lifecycle.js.map +1 -1
- package/dist/mcp-auth-proxy-setup.d.ts +48 -0
- package/dist/mcp-auth-proxy-setup.d.ts.map +1 -0
- package/dist/mcp-auth-proxy-setup.js +280 -0
- package/dist/mcp-auth-proxy-setup.js.map +1 -0
- package/dist/release-manifest.d.ts +25 -1
- package/dist/release-manifest.d.ts.map +1 -1
- package/dist/release-manifest.js +36 -3
- package/dist/release-manifest.js.map +1 -1
- package/dist/remote-mcp.d.ts +55 -18
- package/dist/remote-mcp.d.ts.map +1 -1
- package/dist/remote-mcp.js +307 -77
- package/dist/remote-mcp.js.map +1 -1
- package/dist/runtime-secrets.d.ts +9 -0
- package/dist/runtime-secrets.d.ts.map +1 -1
- package/dist/runtime-secrets.js +21 -10
- package/dist/runtime-secrets.js.map +1 -1
- package/dist/seal-key-rotation.d.ts +7 -7
- package/dist/seal-key-rotation.d.ts.map +1 -1
- package/dist/seal-key-rotation.js +22 -22
- package/dist/seal-key-rotation.js.map +1 -1
- package/dist/self-hosted-db-tls.d.ts +37 -0
- package/dist/self-hosted-db-tls.d.ts.map +1 -0
- package/dist/self-hosted-db-tls.js +220 -0
- package/dist/self-hosted-db-tls.js.map +1 -0
- package/dist/self-hosted-release.d.ts +3 -1
- package/dist/self-hosted-release.d.ts.map +1 -1
- package/dist/self-hosted-release.js +33 -9
- package/dist/self-hosted-release.js.map +1 -1
- package/dist/self-hosted-roles.d.ts +34 -0
- package/dist/self-hosted-roles.d.ts.map +1 -1
- package/dist/self-hosted-roles.js +52 -0
- package/dist/self-hosted-roles.js.map +1 -1
- package/dist/self-hosted-start.d.ts +0 -21
- package/dist/self-hosted-start.d.ts.map +1 -1
- package/dist/self-hosted-start.js +39 -1
- package/dist/self-hosted-start.js.map +1 -1
- package/dist/self-hosted-upgrade.d.ts +15 -6
- package/dist/self-hosted-upgrade.d.ts.map +1 -1
- package/dist/self-hosted-upgrade.js +72 -23
- package/dist/self-hosted-upgrade.js.map +1 -1
- package/dist/stack-contract.d.ts +170 -25
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +314 -37
- package/dist/stack-contract.js.map +1 -1
- package/dist/stack-converge.d.ts +31 -0
- package/dist/stack-converge.d.ts.map +1 -1
- package/dist/stack-converge.js +71 -30
- package/dist/stack-converge.js.map +1 -1
- package/dist/upgrade/app-roll.d.ts +25 -4
- package/dist/upgrade/app-roll.d.ts.map +1 -1
- package/dist/upgrade/app-roll.js +63 -17
- package/dist/upgrade/app-roll.js.map +1 -1
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +99 -12
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/upgrade/health.d.ts +31 -7
- package/dist/upgrade/health.d.ts.map +1 -1
- package/dist/upgrade/health.js +41 -17
- package/dist/upgrade/health.js.map +1 -1
- package/dist/upgrade/image-forms.d.ts +1 -1
- package/dist/upgrade/image-forms.d.ts.map +1 -1
- package/dist/upgrade/image-forms.js +24 -7
- package/dist/upgrade/image-forms.js.map +1 -1
- package/dist/upgrade/release-images.d.ts +12 -3
- package/dist/upgrade/release-images.d.ts.map +1 -1
- package/dist/upgrade/release-images.js +13 -4
- package/dist/upgrade/release-images.js.map +1 -1
- package/dist/upgrade/upgrade.d.ts +13 -3
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +34 -2
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-gcp.d.ts +12 -0
- package/dist/upgrader-gcp.d.ts.map +1 -1
- package/dist/upgrader-gcp.js +49 -1
- package/dist/upgrader-gcp.js.map +1 -1
- package/dist/upgrader-iam-preflight.d.ts.map +1 -1
- package/dist/upgrader-iam-preflight.js +6 -1
- package/dist/upgrader-iam-preflight.js.map +1 -1
- package/dist/upgrader-job.d.ts +1 -1
- package/dist/upgrader-job.d.ts.map +1 -1
- package/dist/upgrader-job.js +20 -14
- package/dist/upgrader-job.js.map +1 -1
- package/dist/upgrader-records.d.ts +1 -1
- package/dist/upgrader-records.d.ts.map +1 -1
- package/dist/upgrader-records.js +26 -7
- package/dist/upgrader-records.js.map +1 -1
- package/dist/upgrader-setup.d.ts.map +1 -1
- package/dist/upgrader-setup.js +10 -5
- package/dist/upgrader-setup.js.map +1 -1
- package/dist/wizards/gcp-cost.d.ts.map +1 -1
- package/dist/wizards/gcp-cost.js +22 -7
- package/dist/wizards/gcp-cost.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +43 -17
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +2 -2
- package/src/admin-domain.test.ts +9 -8
- package/src/admin-domain.ts +13 -3
- package/src/admin-memory.test.ts +1 -0
- package/src/adopt-existing.test.ts +39 -0
- package/src/adopt-existing.ts +83 -1
- package/src/agent/command.test.ts +49 -1
- package/src/agent/command.ts +36 -9
- package/src/agent/run.test.ts +19 -0
- package/src/agent/run.ts +11 -2
- package/src/aws-task-roles.ts +5 -2
- package/src/caddy-redirects.docker.test.ts +2 -0
- package/src/caddy-redirects.test.ts +3 -0
- package/src/caddy-validate.test.ts +7 -1
- package/src/caddy.test.ts +39 -1
- package/src/caddy.ts +36 -4
- package/src/checkpoint-secrets.test.ts +14 -6
- package/src/cli.ts +12 -1
- package/src/cloudsql-tls.test.ts +225 -0
- package/src/cloudsql-tls.ts +352 -0
- package/src/compose.ts +125 -24
- package/src/converge-checkpoint.ts +8 -1
- package/src/converge-job-gcp.ts +2 -11
- package/src/database-tls-builders.test.ts +55 -0
- package/src/deployed-release.test.ts +104 -40
- package/src/deployed-release.ts +30 -9
- package/src/gateway-env-isolation.test.ts +10 -5
- package/src/gcp-names.ts +31 -0
- package/src/gcp-url-map.test.ts +6 -8
- package/src/gcp-url-map.ts +6 -2
- package/src/index.test.ts +7 -1
- package/src/index.ts +1 -0
- package/src/infra-contract.test.ts +5 -3
- package/src/infra-contract.ts +24 -1
- package/src/install-state.ts +26 -4
- package/src/lifecycle.ts +20 -11
- package/src/mcp-auth-proxy-setup.test.ts +305 -0
- package/src/mcp-auth-proxy-setup.ts +374 -0
- package/src/mcp-iap-stacks.test.ts +1 -1
- package/src/media-volume.test.ts +1 -1
- package/src/operator-access-stacks.test.ts +1 -1
- package/src/operator-access.test.ts +5 -4
- package/src/pool-budget-stacks.test.ts +1 -1
- package/src/release-manifest.test.ts +25 -0
- package/src/release-manifest.ts +59 -4
- package/src/remote-mcp-stack-config.test.ts +19 -9
- package/src/remote-mcp.test.ts +252 -57
- package/src/remote-mcp.ts +336 -77
- package/src/runtime-secrets.test.ts +20 -7
- package/src/runtime-secrets.ts +50 -9
- package/src/seal-key-rotation.test.ts +2 -2
- package/src/seal-key-rotation.ts +25 -25
- package/src/self-hosted-compose.test.ts +108 -5
- package/src/self-hosted-db-tls.test.ts +111 -0
- package/src/self-hosted-db-tls.ts +287 -0
- package/src/self-hosted-release-resolve.test.ts +71 -0
- package/src/self-hosted-release.ts +41 -11
- package/src/self-hosted-roles.test.ts +54 -0
- package/src/self-hosted-roles.ts +58 -0
- package/src/self-hosted-start.test.ts +34 -2
- package/src/self-hosted-start.ts +20 -2
- package/src/self-hosted-upgrade.test.ts +103 -14
- package/src/self-hosted-upgrade.ts +115 -29
- package/src/stack-contract-parity.test.ts +79 -12
- package/src/stack-contract.test.ts +169 -29
- package/src/stack-contract.ts +362 -42
- package/src/stack-converge-secrets.test.ts +26 -13
- package/src/stack-converge.test.ts +199 -26
- package/src/stack-converge.ts +92 -30
- package/src/state-store.test.ts +2 -1
- package/src/upgrade/app-roll.ts +97 -19
- package/src/upgrade/converge.ts +120 -11
- package/src/upgrade/health.test.ts +63 -25
- package/src/upgrade/health.ts +54 -22
- package/src/upgrade/image-forms.test.ts +34 -0
- package/src/upgrade/image-forms.ts +23 -8
- package/src/upgrade/release-images.test.ts +5 -1
- package/src/upgrade/release-images.ts +22 -6
- package/src/upgrade/upgrade.test.ts +353 -44
- package/src/upgrade/upgrade.ts +55 -6
- package/src/upgrader-gcp.ts +65 -3
- package/src/upgrader-iam-preflight.test.ts +19 -0
- package/src/upgrader-iam-preflight.ts +6 -0
- package/src/upgrader-job.test.ts +101 -27
- package/src/upgrader-job.ts +27 -14
- package/src/upgrader-records.test.ts +97 -12
- package/src/upgrader-records.ts +28 -11
- package/src/upgrader-setup.test.ts +2 -0
- package/src/upgrader-setup.ts +10 -4
- package/src/wizards/gcp-cost.test.ts +50 -0
- package/src/wizards/gcp-cost.ts +24 -7
- package/src/wizards/gcp.ts +55 -14
- package/stacks/aws/index.ts +32 -14
- package/stacks/azure/index.ts +57 -18
- package/stacks/gcp/index.ts +83 -23
- package/stacks/gcp-firebase/index.ts +162 -33
- package/stacks/self-hosted/README.md +2 -1
- package/stacks/self-hosted/index.ts +11 -1
|
@@ -16,6 +16,7 @@ import { readFileSync } from "node:fs";
|
|
|
16
16
|
import { resolve } from "node:path";
|
|
17
17
|
import {
|
|
18
18
|
CLOUD_RUN_IGNORE_CHANGES,
|
|
19
|
+
type CloudRunSlug,
|
|
19
20
|
type GcpProvider,
|
|
20
21
|
type IamInvariant,
|
|
21
22
|
runtimeSecretBindings,
|
|
@@ -61,11 +62,19 @@ const MEMBER_EXPR: Record<IamInvariant["member"], string> = {
|
|
|
61
62
|
"run-sa": "runSa.email",
|
|
62
63
|
// The secret loop resolves the SA per service through `serviceSa`.
|
|
63
64
|
"gateway-sa": "gatewaySa.email",
|
|
65
|
+
"mcp-auth-proxy-sa": "mcpAuthProxySa.email",
|
|
64
66
|
"static-publisher-sa": "staticPublisherSa.email",
|
|
65
67
|
"iap-service-agent": "iapServiceIdentity.email",
|
|
66
68
|
allUsers: 'member: "allUsers"',
|
|
67
69
|
};
|
|
68
70
|
|
|
71
|
+
/** The variable each stack holds a Cloud Run service in. */
|
|
72
|
+
const SERVICE_VAR: Record<CloudRunSlug, string> = {
|
|
73
|
+
admin: "adminSvc",
|
|
74
|
+
gateway: "gatewaySvc",
|
|
75
|
+
"mcp-auth-proxy": "mcpAuthProxySvc",
|
|
76
|
+
};
|
|
77
|
+
|
|
69
78
|
function targetExpr(inv: IamInvariant): { type: RegExp; ref?: string } {
|
|
70
79
|
switch (inv.target.kind) {
|
|
71
80
|
case "project":
|
|
@@ -77,7 +86,7 @@ function targetExpr(inv: IamInvariant): { type: RegExp; ref?: string } {
|
|
|
77
86
|
case "run-service":
|
|
78
87
|
return {
|
|
79
88
|
type: /^cloudrunv2\.ServiceIamMember$/,
|
|
80
|
-
ref: `name: ${inv.target.service}
|
|
89
|
+
ref: `name: ${SERVICE_VAR[inv.target.service]}.name`,
|
|
81
90
|
};
|
|
82
91
|
}
|
|
83
92
|
}
|
|
@@ -125,6 +134,19 @@ describe.each(["gcp", "gcp-firebase"] as const)("%s stack ↔ upgrade parity", (
|
|
|
125
134
|
expect(unhandled).toEqual([]);
|
|
126
135
|
});
|
|
127
136
|
|
|
137
|
+
it("#1328 — the MCP-Auth-Proxy's SA reads its two secrets and holds no other role", () => {
|
|
138
|
+
expect(src).toContain("accountId: mcpAuthProxyServiceAccountId(env)");
|
|
139
|
+
expect(src).toMatch(/"mcp-auth-proxy": mcpAuthProxySa,/);
|
|
140
|
+
// No binding but the secret loop names it: no project role, no bucket,
|
|
141
|
+
// no service — the loop gives it exactly serviceSecrets("mcp-auth-proxy").
|
|
142
|
+
expect(declared.filter((d) => d.body.includes("mcpAuthProxySa"))).toEqual([]);
|
|
143
|
+
expect(
|
|
144
|
+
runtimeSecretBindings()
|
|
145
|
+
.filter((b) => b.service === "mcp-auth-proxy")
|
|
146
|
+
.map((b) => b.secret),
|
|
147
|
+
).toEqual(["mcp-oauth-seal-key", "operator-oauth-client"]);
|
|
148
|
+
});
|
|
149
|
+
|
|
128
150
|
it("every invariant and exemption names a binding the stack still declares", () => {
|
|
129
151
|
const names = new Set(declared.flatMap(expand));
|
|
130
152
|
for (const inv of invariants) expect(names).toContain(inv.stackResource);
|
|
@@ -139,9 +161,20 @@ describe.each(["gcp", "gcp-firebase"] as const)("%s stack ↔ upgrade parity", (
|
|
|
139
161
|
expect(src).toMatch(
|
|
140
162
|
/serviceName: "admin",[\s\S]*?contractEnv: \[\s*\.\.\.adminEnvContract\([\s\S]*?\.\.\.remoteMcpEnv\.admin,\s*(\/\/[^\n]*\n\s*)?\.\.\.poolBudget\.env\.admin,?\s*\]/,
|
|
141
163
|
);
|
|
164
|
+
// #1328 — the gateway carries no remote-MCP env any more.
|
|
142
165
|
expect(src).toMatch(
|
|
143
|
-
/serviceName: "gateway",[\s\S]*?contractEnv: \[\s*\.\.\.gatewayEnvContract\(
|
|
166
|
+
/serviceName: "gateway",[\s\S]*?contractEnv: \[\s*\.\.\.gatewayEnvContract\(envContractInputs\),\s*\.\.\.poolBudget\.env\.gateway,?\s*\]/,
|
|
144
167
|
);
|
|
168
|
+
if (provider === "gcp-firebase") {
|
|
169
|
+
// The proxy: its contract, its identity env, and (until #1331) the
|
|
170
|
+
// data gateway it forwards to — a stack output no contract can name.
|
|
171
|
+
expect(src).toMatch(
|
|
172
|
+
/serviceName: "mcp-auth-proxy",[\s\S]*?contractEnv: \[\s*\.\.\.mcpAuthProxyEnvContract\(\{ provider: "gcp-firebase", env \}\),\s*\.\.\.remoteMcpEnv\["mcp-auth-proxy"\],[\s\S]*?\{ name: INTERIM_FORWARD_ORIGIN_ENV, value: gatewaySvc\.uri \},\s*\]/,
|
|
173
|
+
);
|
|
174
|
+
} else {
|
|
175
|
+
// gcp: the proxy is CLI-owned (mcp-auth-proxy-setup.ts), not a stack service.
|
|
176
|
+
expect(src).not.toContain('serviceName: "mcp-auth-proxy"');
|
|
177
|
+
}
|
|
145
178
|
expect(src).toContain("envs: [...args.contractEnv],");
|
|
146
179
|
});
|
|
147
180
|
|
|
@@ -151,10 +184,11 @@ describe.each(["gcp", "gcp-firebase"] as const)("%s stack ↔ upgrade parity", (
|
|
|
151
184
|
expect(src).toContain(
|
|
152
185
|
"const remoteMcpEnv = remoteMcpIdentityStackEnv(cfg.get(REMOTE_MCP_IDENTITY_CONFIG));",
|
|
153
186
|
);
|
|
154
|
-
expect(src.match(/remoteMcpEnv
|
|
155
|
-
"
|
|
156
|
-
|
|
157
|
-
|
|
187
|
+
expect(src.match(/remoteMcpEnv(\.\w+|\["[\w-]+"\])/g)?.sort()).toEqual(
|
|
188
|
+
provider === "gcp-firebase"
|
|
189
|
+
? ["remoteMcpEnv.admin", 'remoteMcpEnv["mcp-auth-proxy"]']
|
|
190
|
+
: ["remoteMcpEnv.admin"],
|
|
191
|
+
);
|
|
158
192
|
});
|
|
159
193
|
|
|
160
194
|
it("#1082 — leaves client / clientVersion (who deployed last) to gcloud on both services", () => {
|
|
@@ -165,6 +199,17 @@ describe.each(["gcp", "gcp-firebase"] as const)("%s stack ↔ upgrade parity", (
|
|
|
165
199
|
expect(CLOUD_RUN_IGNORE_CHANGES).toEqual(["client", "clientVersion"]);
|
|
166
200
|
});
|
|
167
201
|
|
|
202
|
+
it("#1328 E13 a — the proxy keeps the gateway's logical resource names, so its service and URL stay", () => {
|
|
203
|
+
if (!src.includes('serviceName: "mcp-auth-proxy"')) return; // the gcp stack: CLI-owned proxy
|
|
204
|
+
// Same URN as before → Pulumi updates the existing service (keeping its
|
|
205
|
+
// auto-generated name and run.app URL) instead of creating a new one.
|
|
206
|
+
expect(src).toMatch(
|
|
207
|
+
/serviceName: "mcp-auth-proxy",\s*resourceName: `\$\{namePrefix\}-gateway`,/,
|
|
208
|
+
);
|
|
209
|
+
expect(src).toMatch(/`\$\{namePrefix\}-gateway-public-invoker`/);
|
|
210
|
+
expect(src).not.toContain("aliases");
|
|
211
|
+
});
|
|
212
|
+
|
|
168
213
|
it("mounts the media bucket on the admin, and only there, from the contract upgrade applies", () => {
|
|
169
214
|
// The stack's admin gets adminMediaVolume() — the same volume
|
|
170
215
|
// stack-converge.ts planMediaVolume adds to installs that lack it.
|
|
@@ -193,10 +238,17 @@ describe.each(["gcp", "gcp-firebase"] as const)("%s stack ↔ upgrade parity", (
|
|
|
193
238
|
expect(src.match(/secretFileVolumesTemplate\(args\.serviceName, env\)/g)).toHaveLength(2);
|
|
194
239
|
});
|
|
195
240
|
|
|
196
|
-
it("#806 — gives the
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
)
|
|
241
|
+
it("#806 — gives the MCP-Auth-Proxy the contract's request timeout upgrade applies", () => {
|
|
242
|
+
// #1328 — the long remote-MCP streams are the proxy's; the gateway keeps
|
|
243
|
+
// Cloud Run's default. On gcp the proxy is deployed by the CLI with the
|
|
244
|
+
// same constant (mcp-auth-proxy-setup.ts gcpProxyDeployArgs).
|
|
245
|
+
expect(src).toMatch(/serviceName: "gateway",[\s\S]*?timeout: "300s",/);
|
|
246
|
+
expect(src).not.toContain("GATEWAY_TIMEOUT_SECONDS");
|
|
247
|
+
if (provider === "gcp-firebase") {
|
|
248
|
+
expect(src).toMatch(
|
|
249
|
+
/serviceName: "mcp-auth-proxy",[\s\S]*?timeout: `\$\{MCP_AUTH_PROXY_TIMEOUT_SECONDS\}s`,/,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
200
252
|
});
|
|
201
253
|
|
|
202
254
|
it("sets no env var outside the contract (C1: no secret value in a plain var)", () => {
|
|
@@ -207,7 +259,17 @@ describe.each(["gcp", "gcp-firebase"] as const)("%s stack ↔ upgrade parity", (
|
|
|
207
259
|
|
|
208
260
|
it("builds no database URL with a password in it", () => {
|
|
209
261
|
expect(src).not.toMatch(/:\$\{postgresPassword\}@|\$\{pw\}@/);
|
|
210
|
-
|
|
262
|
+
// #1338 — from the instance's DNS name, once its private zone record exists.
|
|
263
|
+
expect(src).toContain(".apply(([host]) => databaseUrls(host))");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("serves verify-full TLS: shared server CA, encrypted only, DNS name in the VPC (#1338)", () => {
|
|
267
|
+
expect(src).toContain("serverCaMode: CLOUD_SQL_SERVER_CA_MODE");
|
|
268
|
+
expect(src).toContain("sslMode: CLOUD_SQL_SSL_MODE");
|
|
269
|
+
// The names cloudsql-tls.ts converges and adopt-existing.ts adopts.
|
|
270
|
+
expect(src).toContain("name: cloudSqlDnsZoneName(env)");
|
|
271
|
+
expect(src).toContain("managedZone: cloudSqlDnsZoneName(env)");
|
|
272
|
+
expect(src).toContain('visibility: "private"');
|
|
211
273
|
});
|
|
212
274
|
|
|
213
275
|
it("references the CLI-generated secrets by id instead of creating them", () => {
|
|
@@ -225,11 +287,16 @@ describe.each(["gcp", "gcp-firebase"] as const)("%s stack ↔ upgrade parity", (
|
|
|
225
287
|
|
|
226
288
|
it("pins images by the config keys upgrade writes back", () => {
|
|
227
289
|
const keys = Object.keys(
|
|
228
|
-
imageDigestConfig(provider, {
|
|
290
|
+
imageDigestConfig(provider, {
|
|
291
|
+
admin: "sha256:a",
|
|
292
|
+
gateway: "sha256:b",
|
|
293
|
+
"mcp-auth-proxy": "sha256:c",
|
|
294
|
+
}),
|
|
229
295
|
);
|
|
230
296
|
expect(keys).toEqual([
|
|
231
297
|
`caelo-${provider}:image-digest-admin`,
|
|
232
298
|
`caelo-${provider}:image-digest-gateway`,
|
|
299
|
+
`caelo-${provider}:image-digest-mcp-auth-proxy`,
|
|
233
300
|
]);
|
|
234
301
|
// The stack reads `<ns>:image-digest-<service>` through pulumi.Config.
|
|
235
302
|
expect(src).toMatch(/cfg\.get\(`image-digest-\$\{service\}`\)/);
|
|
@@ -6,16 +6,26 @@ import { generateDockerCompose } from "./compose.js";
|
|
|
6
6
|
import {
|
|
7
7
|
adminEnvContract,
|
|
8
8
|
CLI_GENERATED_SECRETS,
|
|
9
|
+
CLOUD_RUN_SLUGS,
|
|
9
10
|
type CloudRunEnvVar,
|
|
11
|
+
cloudRunServicePrefix,
|
|
10
12
|
DATABASE_ROLE_SECRET,
|
|
11
13
|
databaseUrls,
|
|
12
14
|
defaultStagingHost,
|
|
15
|
+
fixedCloudRunServiceName,
|
|
13
16
|
gatewayEnvContract,
|
|
17
|
+
INTERIM_FORWARD_ORIGIN_ENV,
|
|
14
18
|
iamMember,
|
|
19
|
+
MCP_AUTH_PROXY_TRUSTED_PROXY_HOPS,
|
|
20
|
+
mcpAuthProxyEnvContract,
|
|
21
|
+
REMOTE_MCP_MODE,
|
|
15
22
|
RETIRED_IAM_BINDINGS,
|
|
23
|
+
RETIRED_SECRET_FILES,
|
|
16
24
|
RETIRED_SERVICE_ENV,
|
|
17
25
|
runtimeSecretBindings,
|
|
18
26
|
SERVICE_SECRET_ENV,
|
|
27
|
+
SERVICE_SECRET_FILES,
|
|
28
|
+
SERVICE_VPC_EGRESS,
|
|
19
29
|
STACK_IAM_NOT_ENSURED,
|
|
20
30
|
serviceSecrets,
|
|
21
31
|
stackIamInvariants,
|
|
@@ -27,7 +37,7 @@ const base = {
|
|
|
27
37
|
domain: "acme.com",
|
|
28
38
|
stagingHost: "staging.acme.com",
|
|
29
39
|
region: "europe-west1",
|
|
30
|
-
databaseUrls: databaseUrls("
|
|
40
|
+
databaseUrls: databaseUrls("db1.p-label.europe-west1.sql-psa.goog"),
|
|
31
41
|
};
|
|
32
42
|
const asRecord = (vars: CloudRunEnvVar[]) =>
|
|
33
43
|
Object.fromEntries(
|
|
@@ -50,10 +60,13 @@ const ADMIN_SECRETS = {
|
|
|
50
60
|
|
|
51
61
|
describe("databaseUrls", () => {
|
|
52
62
|
it("builds password-less URLs for the three role/database pairs", () => {
|
|
53
|
-
expect(databaseUrls("
|
|
54
|
-
admin:
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
expect(databaseUrls("db1.p-label.europe-west1.sql-psa.goog")).toEqual({
|
|
64
|
+
admin:
|
|
65
|
+
"postgresql://admin_role@db1.p-label.europe-west1.sql-psa.goog:5432/cms_admin?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem",
|
|
66
|
+
publicAdmin:
|
|
67
|
+
"postgresql://admin_role@db1.p-label.europe-west1.sql-psa.goog:5432/cms_public?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem",
|
|
68
|
+
public:
|
|
69
|
+
"postgresql://public_role@db1.p-label.europe-west1.sql-psa.goog:5432/cms_public?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem",
|
|
57
70
|
});
|
|
58
71
|
});
|
|
59
72
|
});
|
|
@@ -64,9 +77,10 @@ describe("adminEnvContract", () => {
|
|
|
64
77
|
CAELO_PROVIDER: "gcp",
|
|
65
78
|
CAELO_ENV: "production",
|
|
66
79
|
MEDIA_STORAGE_URL: "gs://acme-caelo-production-media",
|
|
67
|
-
ADMIN_DATABASE_URL:
|
|
80
|
+
ADMIN_DATABASE_URL:
|
|
81
|
+
"postgresql://admin_role@db1.p-label.europe-west1.sql-psa.goog:5432/cms_admin?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem",
|
|
68
82
|
PUBLIC_ADMIN_DATABASE_URL:
|
|
69
|
-
"postgresql://admin_role@
|
|
83
|
+
"postgresql://admin_role@db1.p-label.europe-west1.sql-psa.goog:5432/cms_public?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem",
|
|
70
84
|
CAELO_SITE_URL: "https://acme.com",
|
|
71
85
|
CAELO_GENERATOR_CLI: "/app/apps/static-generator/src/cli.ts",
|
|
72
86
|
MEDIA_ROOT_DIR: "/app/apps/admin/data/media",
|
|
@@ -103,9 +117,10 @@ describe("adminEnvContract", () => {
|
|
|
103
117
|
CAELO_PROVIDER: "gcp-firebase",
|
|
104
118
|
CAELO_ENV: "production",
|
|
105
119
|
MEDIA_STORAGE_URL: "gs://acme-caelo-production-media",
|
|
106
|
-
ADMIN_DATABASE_URL:
|
|
120
|
+
ADMIN_DATABASE_URL:
|
|
121
|
+
"postgresql://admin_role@db1.p-label.europe-west1.sql-psa.goog:5432/cms_admin?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem",
|
|
107
122
|
PUBLIC_ADMIN_DATABASE_URL:
|
|
108
|
-
"postgresql://admin_role@
|
|
123
|
+
"postgresql://admin_role@db1.p-label.europe-west1.sql-psa.goog:5432/cms_public?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem",
|
|
109
124
|
CAELO_SITE_URL: "https://acme.com",
|
|
110
125
|
CAELO_GENERATOR_CLI: "/app/apps/static-generator/src/cli.ts",
|
|
111
126
|
MEDIA_ROOT_DIR: "/app/apps/admin/data/media",
|
|
@@ -178,12 +193,11 @@ describe("gatewayEnvContract", () => {
|
|
|
178
193
|
MEDIA_STORAGE_URL: "gs://acme-caelo-production-media",
|
|
179
194
|
// #845 — gcp: the LB appends <client>,<LB IP>.
|
|
180
195
|
CAELO_TRUSTED_PROXY_HOPS: "2",
|
|
181
|
-
PUBLIC_DATABASE_URL:
|
|
196
|
+
PUBLIC_DATABASE_URL:
|
|
197
|
+
"postgresql://public_role@db1.p-label.europe-west1.sql-psa.goog:5432/cms_public?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem",
|
|
182
198
|
PUBLIC_DATABASE_PASSWORD: "secret:caelo-production-public-role-password:latest",
|
|
183
199
|
CAELO_STAGING_GATE_SECRET: "secret:caelo-production-staging-gate-secret:latest",
|
|
184
|
-
//
|
|
185
|
-
CAELO_OPERATOR_OAUTH_CLIENT_FILE: "/secrets/operator-oauth-client/client.json",
|
|
186
|
-
CAELO_MCP_OAUTH_SEAL_KEY: "secret:caelo-production-mcp-oauth-seal-key:latest",
|
|
200
|
+
// No remote-MCP env: the broker is the MCP-Auth-Proxy's (#1328).
|
|
187
201
|
});
|
|
188
202
|
});
|
|
189
203
|
|
|
@@ -215,13 +229,7 @@ describe("gatewayEnvContract", () => {
|
|
|
215
229
|
});
|
|
216
230
|
|
|
217
231
|
it("the gateway's run SA reads no admin-only secret (admin password, KEK, internal, tool approval)", () => {
|
|
218
|
-
expect(serviceSecrets("gateway")).toEqual([
|
|
219
|
-
"public-role-password",
|
|
220
|
-
"staging-gate-secret",
|
|
221
|
-
// #806 — the broker's seal key (env) and OAuth client (file).
|
|
222
|
-
"mcp-oauth-seal-key",
|
|
223
|
-
"operator-oauth-client",
|
|
224
|
-
]);
|
|
232
|
+
expect(serviceSecrets("gateway")).toEqual(["public-role-password", "staging-gate-secret"]);
|
|
225
233
|
for (const v of gateway) {
|
|
226
234
|
expect(v.name).not.toMatch(/KEK|INTERNAL|TOOL_APPROVAL|PUBLIC_ADMIN/);
|
|
227
235
|
}
|
|
@@ -318,23 +326,24 @@ describe("runtime secrets", () => {
|
|
|
318
326
|
secret: "staging-gate-secret",
|
|
319
327
|
stackResource: "gateway-staging-gate-secret-binding",
|
|
320
328
|
},
|
|
329
|
+
// #1328 — the broker's seal key (env) and Google sign-in client (a
|
|
330
|
+
// file, which counts like an env secret: the SA reads it).
|
|
321
331
|
{
|
|
322
|
-
service: "
|
|
332
|
+
service: "mcp-auth-proxy",
|
|
323
333
|
secret: "mcp-oauth-seal-key",
|
|
324
|
-
stackResource: "
|
|
334
|
+
stackResource: "mcp-auth-proxy-mcp-oauth-seal-key-binding",
|
|
325
335
|
},
|
|
326
|
-
// The secret file counts like an env secret: the SA reads it.
|
|
327
336
|
{
|
|
328
|
-
service: "
|
|
337
|
+
service: "mcp-auth-proxy",
|
|
329
338
|
secret: "operator-oauth-client",
|
|
330
|
-
stackResource: "
|
|
339
|
+
stackResource: "mcp-auth-proxy-operator-oauth-client-binding",
|
|
331
340
|
},
|
|
332
341
|
]);
|
|
333
342
|
});
|
|
334
343
|
|
|
335
344
|
it("every CLI-generated secret is read by some service", () => {
|
|
336
345
|
for (const s of CLI_GENERATED_SECRETS) {
|
|
337
|
-
expect(
|
|
346
|
+
expect(CLOUD_RUN_SLUGS.flatMap((slug) => serviceSecrets(slug))).toContain(s);
|
|
338
347
|
}
|
|
339
348
|
});
|
|
340
349
|
});
|
|
@@ -346,6 +355,7 @@ describe("self-hosted compose", () => {
|
|
|
346
355
|
postgresPassword: "p",
|
|
347
356
|
rolePasswords: { admin: "a", public: "b" },
|
|
348
357
|
caeloSecretKek: "k",
|
|
358
|
+
dbTlsServerCertSha256: "abababababababababababababababababababababababababababababababab",
|
|
349
359
|
internalSecret: "i".repeat(48),
|
|
350
360
|
diskSize: "10G",
|
|
351
361
|
ports: { http: 80, https: 443 },
|
|
@@ -386,14 +396,12 @@ describe("stackIamInvariants", () => {
|
|
|
386
396
|
}
|
|
387
397
|
});
|
|
388
398
|
|
|
389
|
-
it("grants the gateway SA only its own role password, the staging gate key
|
|
399
|
+
it("grants the gateway SA only its own role password, the staging gate key + telemetry roles", () => {
|
|
390
400
|
for (const provider of ["gcp", "gcp-firebase"] as const) {
|
|
391
401
|
const gateway = stackIamInvariants(provider).filter((i) => i.member === "gateway-sa");
|
|
392
402
|
expect(gateway.map((i) => `${i.role} ${JSON.stringify(i.target)}`).sort()).toEqual([
|
|
393
403
|
'roles/logging.logWriter {"kind":"project"}',
|
|
394
404
|
'roles/monitoring.metricWriter {"kind":"project"}',
|
|
395
|
-
'roles/secretmanager.secretAccessor {"kind":"secret","name":"mcp-oauth-seal-key"}',
|
|
396
|
-
'roles/secretmanager.secretAccessor {"kind":"secret","name":"operator-oauth-client"}',
|
|
397
405
|
'roles/secretmanager.secretAccessor {"kind":"secret","name":"public-role-password"}',
|
|
398
406
|
'roles/secretmanager.secretAccessor {"kind":"secret","name":"staging-gate-secret"}',
|
|
399
407
|
]);
|
|
@@ -407,6 +415,135 @@ describe("stackIamInvariants", () => {
|
|
|
407
415
|
});
|
|
408
416
|
});
|
|
409
417
|
|
|
418
|
+
describe("MCP-Auth-Proxy contract (#1328)", () => {
|
|
419
|
+
it("names one service per role; no prefix is a prefix of another", () => {
|
|
420
|
+
for (const provider of ["gcp", "gcp-firebase"] as const) {
|
|
421
|
+
const prefixes = CLOUD_RUN_SLUGS.map((s) => cloudRunServicePrefix(provider, "production", s));
|
|
422
|
+
expect(new Set(prefixes).size).toBe(prefixes.length);
|
|
423
|
+
for (const a of prefixes) {
|
|
424
|
+
for (const b of prefixes) if (a !== b) expect(b.startsWith(a)).toBe(false);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// E13 (a): on gcp-firebase the proxy keeps the old gateway service.
|
|
428
|
+
expect(cloudRunServicePrefix("gcp-firebase", "production", "mcp-auth-proxy")).toBe(
|
|
429
|
+
"caelo-production-gateway",
|
|
430
|
+
);
|
|
431
|
+
expect(cloudRunServicePrefix("gcp-firebase", "production", "gateway")).toBe(
|
|
432
|
+
"caelo-production-data-gateway",
|
|
433
|
+
);
|
|
434
|
+
expect(cloudRunServicePrefix("gcp", "production", "gateway")).toBe("caelo-production-gateway");
|
|
435
|
+
expect(cloudRunServicePrefix("gcp", "production", "mcp-auth-proxy")).toBe(
|
|
436
|
+
"caelo-production-mcp-auth-proxy",
|
|
437
|
+
);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it("fixes the names of exactly the services the converge creates", () => {
|
|
441
|
+
expect(fixedCloudRunServiceName("gcp-firebase", "production", "gateway")).toBe(
|
|
442
|
+
"caelo-production-data-gateway",
|
|
443
|
+
);
|
|
444
|
+
expect(fixedCloudRunServiceName("gcp", "production", "mcp-auth-proxy")).toBe(
|
|
445
|
+
"caelo-production-mcp-auth-proxy",
|
|
446
|
+
);
|
|
447
|
+
for (const [provider, slug] of [
|
|
448
|
+
["gcp-firebase", "admin"],
|
|
449
|
+
["gcp-firebase", "mcp-auth-proxy"],
|
|
450
|
+
["gcp", "admin"],
|
|
451
|
+
["gcp", "gateway"],
|
|
452
|
+
] as const) {
|
|
453
|
+
expect(fixedCloudRunServiceName(provider, "production", slug)).toBeNull();
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it("gives the proxy no database, site or pool env — only its mode, hops, file and seal key", () => {
|
|
458
|
+
for (const provider of ["gcp", "gcp-firebase"] as const) {
|
|
459
|
+
const env = asRecord(mcpAuthProxyEnvContract({ provider, env: "production" }));
|
|
460
|
+
expect(env).toEqual({
|
|
461
|
+
CAELO_PROVIDER: provider,
|
|
462
|
+
CAELO_REMOTE_MCP: "available",
|
|
463
|
+
CAELO_TRUSTED_PROXY_HOPS: provider === "gcp" ? "2" : "1",
|
|
464
|
+
CAELO_OPERATOR_OAUTH_CLIENT_FILE: "/secrets/operator-oauth-client/client.json",
|
|
465
|
+
CAELO_MCP_OAUTH_SEAL_KEY: "secret:caelo-production-mcp-oauth-seal-key:latest",
|
|
466
|
+
});
|
|
467
|
+
for (const name of Object.keys(env)) expect(name).not.toMatch(/DATABASE|POOL|KEK|STAGING/);
|
|
468
|
+
}
|
|
469
|
+
// Remote MCP needs Google IAP: everywhere else the proxy answers its 404.
|
|
470
|
+
expect(REMOTE_MCP_MODE).toEqual({
|
|
471
|
+
gcp: "available",
|
|
472
|
+
"gcp-firebase": "available",
|
|
473
|
+
"self-hosted": "unavailable",
|
|
474
|
+
aws: "unavailable",
|
|
475
|
+
azure: "unavailable",
|
|
476
|
+
});
|
|
477
|
+
expect(
|
|
478
|
+
asRecord(mcpAuthProxyEnvContract({ provider: "azure", env: "production" })),
|
|
479
|
+
).toMatchObject({
|
|
480
|
+
CAELO_REMOTE_MCP: "unavailable",
|
|
481
|
+
CAELO_TRUSTED_PROXY_HOPS: String(MCP_AUTH_PROXY_TRUSTED_PROXY_HOPS.azure),
|
|
482
|
+
});
|
|
483
|
+
// The interim forward origin is the stack's / converge's, never the contract's.
|
|
484
|
+
expect(
|
|
485
|
+
mcpAuthProxyEnvContract({ provider: "gcp-firebase", env: "production" }).map((v) => v.name),
|
|
486
|
+
).not.toContain(INTERIM_FORWARD_ORIGIN_ENV);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("the proxy's SA reads exactly its two secrets and holds no other invariant", () => {
|
|
490
|
+
expect(serviceSecrets("mcp-auth-proxy")).toEqual([
|
|
491
|
+
"mcp-oauth-seal-key",
|
|
492
|
+
"operator-oauth-client",
|
|
493
|
+
]);
|
|
494
|
+
for (const provider of ["gcp", "gcp-firebase"] as const) {
|
|
495
|
+
const proxy = stackIamInvariants(provider).filter((i) => i.member === "mcp-auth-proxy-sa");
|
|
496
|
+
expect(proxy.map((i) => `${i.role} ${JSON.stringify(i.target)}`).sort()).toEqual([
|
|
497
|
+
'roles/secretmanager.secretAccessor {"kind":"secret","name":"mcp-oauth-seal-key"}',
|
|
498
|
+
'roles/secretmanager.secretAccessor {"kind":"secret","name":"operator-oauth-client"}',
|
|
499
|
+
]);
|
|
500
|
+
}
|
|
501
|
+
expect(SERVICE_VPC_EGRESS["mcp-auth-proxy"]).toBe(false);
|
|
502
|
+
expect(SERVICE_SECRET_FILES["mcp-auth-proxy"]).toHaveLength(1);
|
|
503
|
+
expect(SERVICE_SECRET_FILES.gateway).toEqual([]);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it("retires the broker's env, file and secret access on the gateway", () => {
|
|
507
|
+
for (const name of [
|
|
508
|
+
"CAELO_OPERATOR_OAUTH_CLIENT_FILE",
|
|
509
|
+
"CAELO_MCP_OAUTH_SEAL_KEY",
|
|
510
|
+
"CAELO_MCP_OAUTH_SEAL_KEY_PREVIOUS",
|
|
511
|
+
"CAELO_MCP_PUBLIC_URL",
|
|
512
|
+
"CAELO_ADMIN_URL",
|
|
513
|
+
]) {
|
|
514
|
+
expect(RETIRED_SERVICE_ENV.gateway).toContain(name);
|
|
515
|
+
}
|
|
516
|
+
expect(RETIRED_SECRET_FILES.gateway.map((f) => f.secret)).toContain("operator-oauth-client");
|
|
517
|
+
for (const secret of ["mcp-oauth-seal-key", "operator-oauth-client"]) {
|
|
518
|
+
expect(RETIRED_IAM_BINDINGS).toContainEqual(
|
|
519
|
+
expect.objectContaining({ member: "gateway-sa", target: { kind: "secret", name: secret } }),
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
// On gcp-firebase the proxy is the old gateway service: its visitor env goes.
|
|
523
|
+
for (const name of [
|
|
524
|
+
"PUBLIC_DATABASE_URL",
|
|
525
|
+
"PUBLIC_DATABASE_PASSWORD",
|
|
526
|
+
"CAELO_STAGING_GATE_SECRET",
|
|
527
|
+
]) {
|
|
528
|
+
expect(RETIRED_SERVICE_ENV["mcp-auth-proxy"]).toContain(name);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("gcp-firebase: the data gateway is the rewrite target, the proxy public", () => {
|
|
533
|
+
const inv = stackIamInvariants("gcp-firebase");
|
|
534
|
+
const on = (service: string) =>
|
|
535
|
+
inv
|
|
536
|
+
.filter((i) => i.target.kind === "run-service" && i.target.service === service)
|
|
537
|
+
.map((i) => `${i.stackResource} ${i.role} ${i.member}`)
|
|
538
|
+
.sort();
|
|
539
|
+
expect(on("gateway")).toEqual([
|
|
540
|
+
"data-gateway-public-invoker roles/run.invoker allUsers",
|
|
541
|
+
"data-gateway-viewer-for-admin-sa roles/run.viewer run-sa",
|
|
542
|
+
]);
|
|
543
|
+
expect(on("mcp-auth-proxy")).toEqual(["gateway-public-invoker roles/run.invoker allUsers"]);
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
410
547
|
describe("iamMember", () => {
|
|
411
548
|
const install = { projectId: "acme", projectNumber: "123", env: "production" };
|
|
412
549
|
it("resolves every principal to the name the stack gives it", () => {
|
|
@@ -416,6 +553,9 @@ describe("iamMember", () => {
|
|
|
416
553
|
expect(iamMember("gateway-sa", install)).toBe(
|
|
417
554
|
"serviceAccount:caelo-production-gateway-sa@acme.iam.gserviceaccount.com",
|
|
418
555
|
);
|
|
556
|
+
expect(iamMember("mcp-auth-proxy-sa", install)).toBe(
|
|
557
|
+
"serviceAccount:caelo-production-mcp-proxy-sa@acme.iam.gserviceaccount.com",
|
|
558
|
+
);
|
|
419
559
|
expect(iamMember("static-publisher-sa", install)).toBe(
|
|
420
560
|
"serviceAccount:caelo-prod-publisher@acme.iam.gserviceaccount.com",
|
|
421
561
|
);
|