@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
package/src/remote-mcp.test.ts
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { describe, expect, it } from "bun:test";
|
|
10
|
-
import {
|
|
10
|
+
import { GCP_GATEWAY_PATHS, GCP_MCP_AUTH_PROXY_PATHS } from "@caelo-cms/shared";
|
|
11
11
|
import type { GcloudResult } from "./gcloud.js";
|
|
12
12
|
import type { GcloudRunner } from "./gcloud-retry.js";
|
|
13
13
|
import {
|
|
14
14
|
checkRemoteMcpIdentityEnv,
|
|
15
15
|
ensureMcpArmorRule,
|
|
16
|
+
ensureMcpAuthProxyBackend,
|
|
17
|
+
ensureRemoteMcpEdge,
|
|
16
18
|
findRemoteMcpTarget,
|
|
17
19
|
type LiveUrlMap,
|
|
18
20
|
planUrlMapPaths,
|
|
@@ -20,6 +22,7 @@ import {
|
|
|
20
22
|
resolveGoogleOrgDomain,
|
|
21
23
|
resolveRemoteMcpIdentity,
|
|
22
24
|
resolveStackRemoteMcpIdentity,
|
|
25
|
+
retireGatewayMcpArmorRule,
|
|
23
26
|
} from "./remote-mcp.js";
|
|
24
27
|
import {
|
|
25
28
|
GENERAL_RATE_LIMIT_PRIORITY,
|
|
@@ -33,9 +36,10 @@ import {
|
|
|
33
36
|
import { initialSecretValue } from "./runtime-secrets.js";
|
|
34
37
|
import {
|
|
35
38
|
CLI_GENERATED_SECRETS,
|
|
36
|
-
GATEWAY_TIMEOUT_SECONDS,
|
|
37
39
|
GATEWAY_TRUSTED_PROXY_HOPS,
|
|
38
40
|
GENERATED_SECRET_FORMAT,
|
|
41
|
+
MCP_AUTH_PROXY_TIMEOUT_SECONDS,
|
|
42
|
+
MCP_AUTH_PROXY_TRUSTED_PROXY_HOPS,
|
|
39
43
|
OPERATOR_OAUTH_CLIENT_FILE,
|
|
40
44
|
REMOTE_MCP_ENV,
|
|
41
45
|
RETIRED_RUNTIME_SECRETS,
|
|
@@ -82,7 +86,7 @@ describe("remote-MCP identity (contract)", () => {
|
|
|
82
86
|
});
|
|
83
87
|
});
|
|
84
88
|
|
|
85
|
-
it("gcp-firebase: the
|
|
89
|
+
it("gcp-firebase: the MCP-Auth-Proxy's (the old gateway service, E13 a) and admin's Cloud Run URLs, the service's audience", () => {
|
|
86
90
|
expect(
|
|
87
91
|
remoteMcpIdentity({
|
|
88
92
|
provider: "gcp-firebase",
|
|
@@ -90,7 +94,7 @@ describe("remote-MCP identity (contract)", () => {
|
|
|
90
94
|
region: "europe-west1",
|
|
91
95
|
adminServiceName: "caelo-production-admin-aaa",
|
|
92
96
|
adminServiceUrl: "https://caelo-production-admin-aaa-xyz.a.run.app/",
|
|
93
|
-
|
|
97
|
+
mcpAuthProxyServiceUrl: "https://caelo-production-gateway-bbb-xyz.a.run.app",
|
|
94
98
|
}),
|
|
95
99
|
).toEqual({
|
|
96
100
|
adminUrl: "https://caelo-production-admin-aaa-xyz.a.run.app",
|
|
@@ -105,8 +109,10 @@ describe("remote-MCP identity (contract)", () => {
|
|
|
105
109
|
// broker answers on the site host, through the LB (client + LB = 2).
|
|
106
110
|
// gcp-firebase: on the gateway's own run.app URL, never through the
|
|
107
111
|
// Firebase Hosting rewrite, so Cloud Run's front end appends the real
|
|
108
|
-
// client (1). A broker URL on the Hosting path would
|
|
109
|
-
//
|
|
112
|
+
// client (1). A broker URL on the Hosting path would key every client by
|
|
113
|
+
// a Google proxy address that changes per request
|
|
114
|
+
// (docs/dev/firebase-hosting-behaviour.md).
|
|
115
|
+
expect(MCP_AUTH_PROXY_TRUSTED_PROXY_HOPS.gcp).toBe(2);
|
|
110
116
|
expect(GATEWAY_TRUSTED_PROXY_HOPS.gcp).toBe(2);
|
|
111
117
|
expect(
|
|
112
118
|
remoteMcpIdentity({
|
|
@@ -116,19 +122,19 @@ describe("remote-MCP identity (contract)", () => {
|
|
|
116
122
|
adminBackendServiceId: "1",
|
|
117
123
|
}).mcpPublicUrl,
|
|
118
124
|
).toBe("https://acme.com");
|
|
119
|
-
expect(
|
|
125
|
+
expect(MCP_AUTH_PROXY_TRUSTED_PROXY_HOPS["gcp-firebase"]).toBe(1);
|
|
120
126
|
const firebase = remoteMcpIdentity({
|
|
121
127
|
provider: "gcp-firebase",
|
|
122
128
|
projectNumber: "42",
|
|
123
129
|
region: "europe-west1",
|
|
124
130
|
adminServiceName: "a",
|
|
125
131
|
adminServiceUrl: "https://a-xyz.a.run.app",
|
|
126
|
-
|
|
132
|
+
mcpAuthProxyServiceUrl: "https://g-xyz.a.run.app",
|
|
127
133
|
});
|
|
128
134
|
expect(new URL(firebase.mcpPublicUrl).hostname.endsWith(".run.app")).toBe(true);
|
|
129
135
|
});
|
|
130
136
|
|
|
131
|
-
it("gives the admin the audience, public URL and its own URL,
|
|
137
|
+
it("gives the admin the audience, public URL and its own URL, the proxy the admin + public URL, the gateway nothing", () => {
|
|
132
138
|
const env = remoteMcpIdentityEnv({ adminUrl: "A", mcpPublicUrl: "P", iapAudience: "AUD" });
|
|
133
139
|
expect(env.admin).toEqual([
|
|
134
140
|
{ name: REMOTE_MCP_ENV.iapAudience, value: "AUD" },
|
|
@@ -136,10 +142,12 @@ describe("remote-MCP identity (contract)", () => {
|
|
|
136
142
|
// #914 — Google sign-in's self-check opens the admin signed out.
|
|
137
143
|
{ name: REMOTE_MCP_ENV.adminUrl, value: "A" },
|
|
138
144
|
]);
|
|
139
|
-
expect(env
|
|
145
|
+
expect(env["mcp-auth-proxy"]).toEqual([
|
|
140
146
|
{ name: REMOTE_MCP_ENV.adminUrl, value: "A" },
|
|
141
147
|
{ name: REMOTE_MCP_ENV.mcpPublicUrl, value: "P" },
|
|
142
148
|
]);
|
|
149
|
+
// #1328 — the visitor gateway holds none of remote MCP.
|
|
150
|
+
expect(env.gateway).toEqual([]);
|
|
143
151
|
expect(REMOTE_MCP_ENV).toEqual({
|
|
144
152
|
adminUrl: "CAELO_ADMIN_URL",
|
|
145
153
|
mcpPublicUrl: "CAELO_MCP_PUBLIC_URL",
|
|
@@ -156,7 +164,7 @@ describe("remote-MCP identity (contract)", () => {
|
|
|
156
164
|
googleOrgDomain: "acme.com",
|
|
157
165
|
});
|
|
158
166
|
expect(known.admin).toContainEqual({ name: "CAELO_GOOGLE_ORG_DOMAIN", value: "acme.com" });
|
|
159
|
-
expect(known.
|
|
167
|
+
expect(known["mcp-auth-proxy"].map((e) => e.name)).not.toContain("CAELO_GOOGLE_ORG_DOMAIN");
|
|
160
168
|
const unknown = remoteMcpIdentityEnv({ adminUrl: "A", mcpPublicUrl: "P", iapAudience: "AUD" });
|
|
161
169
|
expect(unknown.admin.map((e) => e.name)).not.toContain("CAELO_GOOGLE_ORG_DOMAIN");
|
|
162
170
|
});
|
|
@@ -199,10 +207,11 @@ describe("remote-MCP secrets + IAM (contract)", () => {
|
|
|
199
207
|
}
|
|
200
208
|
});
|
|
201
209
|
|
|
202
|
-
it("mounts the OAuth client on the
|
|
210
|
+
it("mounts the OAuth client on the MCP-Auth-Proxy only, at latest, as the file its env names", () => {
|
|
203
211
|
expect(SERVICE_SECRET_FILES.admin).toEqual([]);
|
|
204
|
-
expect(SERVICE_SECRET_FILES.gateway).toEqual([
|
|
205
|
-
expect(
|
|
212
|
+
expect(SERVICE_SECRET_FILES.gateway).toEqual([]);
|
|
213
|
+
expect(SERVICE_SECRET_FILES["mcp-auth-proxy"]).toEqual([OPERATOR_OAUTH_CLIENT_FILE]);
|
|
214
|
+
expect(secretFileVolumesTemplate("mcp-auth-proxy", "production")).toEqual({
|
|
206
215
|
volumes: [
|
|
207
216
|
{
|
|
208
217
|
name: "operator-oauth-client",
|
|
@@ -261,7 +270,7 @@ describe("planSecretFiles / planRequestTimeout (upgrade)", () => {
|
|
|
261
270
|
}
|
|
262
271
|
});
|
|
263
272
|
|
|
264
|
-
it("#914 — moves the gateway from the retired mcp-oauth-client mount to the renamed secret in one roll", () => {
|
|
273
|
+
it("#914 — moves the MCP-Auth-Proxy (the old gateway service) from the retired mcp-oauth-client mount to the renamed secret in one roll", () => {
|
|
265
274
|
const live = {
|
|
266
275
|
executionEnvironment: null,
|
|
267
276
|
volumes: [
|
|
@@ -275,14 +284,19 @@ describe("planSecretFiles / planRequestTimeout (upgrade)", () => {
|
|
|
275
284
|
],
|
|
276
285
|
mounts: [{ name: "mcp-oauth-client", mountPath: "/secrets/mcp-oauth-client" }],
|
|
277
286
|
};
|
|
287
|
+
expect(RETIRED_SECRET_FILES["mcp-auth-proxy"].map((f) => f.mountDir)).toEqual([
|
|
288
|
+
"/secrets/mcp-oauth-client",
|
|
289
|
+
]);
|
|
290
|
+
// #1328 — and the gateway gives the client file up altogether.
|
|
278
291
|
expect(RETIRED_SECRET_FILES.gateway.map((f) => f.mountDir)).toEqual([
|
|
279
292
|
"/secrets/mcp-oauth-client",
|
|
293
|
+
"/secrets/operator-oauth-client",
|
|
280
294
|
]);
|
|
281
295
|
const plan = planSecretFiles(
|
|
282
296
|
live,
|
|
283
297
|
[OPERATOR_OAUTH_CLIENT_FILE],
|
|
284
298
|
"production",
|
|
285
|
-
RETIRED_SECRET_FILES
|
|
299
|
+
RETIRED_SECRET_FILES["mcp-auth-proxy"],
|
|
286
300
|
);
|
|
287
301
|
expect(plan.ok && plan.flags).toEqual([
|
|
288
302
|
"--remove-secrets=/secrets/mcp-oauth-client/client.json",
|
|
@@ -309,7 +323,7 @@ describe("planSecretFiles / planRequestTimeout (upgrade)", () => {
|
|
|
309
323
|
after,
|
|
310
324
|
[OPERATOR_OAUTH_CLIENT_FILE],
|
|
311
325
|
"production",
|
|
312
|
-
RETIRED_SECRET_FILES
|
|
326
|
+
RETIRED_SECRET_FILES["mcp-auth-proxy"],
|
|
313
327
|
),
|
|
314
328
|
).toEqual({ ok: true, flags: [], changes: [] });
|
|
315
329
|
});
|
|
@@ -378,8 +392,8 @@ describe("planSecretFiles / planRequestTimeout (upgrade)", () => {
|
|
|
378
392
|
expect(plan.error).toContain("--remove-volume-mount=/secrets/operator-oauth-client");
|
|
379
393
|
});
|
|
380
394
|
|
|
381
|
-
it("raises the
|
|
382
|
-
expect(
|
|
395
|
+
it("raises the MCP-Auth-Proxy's request timeout to the contract's", () => {
|
|
396
|
+
expect(MCP_AUTH_PROXY_TIMEOUT_SECONDS).toBe(3600);
|
|
383
397
|
expect(planRequestTimeout(null, 3600).ok && planRequestTimeout(null, 3600)).toEqual({
|
|
384
398
|
ok: true,
|
|
385
399
|
flags: ["--timeout=3600"],
|
|
@@ -396,7 +410,11 @@ const firebase: RemoteMcpTarget = {
|
|
|
396
410
|
region: "europe-west1",
|
|
397
411
|
env: "production",
|
|
398
412
|
domain: "acme.com",
|
|
399
|
-
services: {
|
|
413
|
+
services: {
|
|
414
|
+
admin: "caelo-production-admin-aaa",
|
|
415
|
+
gateway: "caelo-production-data-gateway",
|
|
416
|
+
"mcp-auth-proxy": "caelo-production-gateway-bbb",
|
|
417
|
+
},
|
|
400
418
|
};
|
|
401
419
|
|
|
402
420
|
const ANCESTORS_IN_ORG = ok(
|
|
@@ -408,7 +426,7 @@ const ANCESTORS_IN_ORG = ok(
|
|
|
408
426
|
);
|
|
409
427
|
|
|
410
428
|
describe("resolveRemoteMcpIdentity", () => {
|
|
411
|
-
it("gcp-firebase: reads the project number,
|
|
429
|
+
it("gcp-firebase: reads the project number, the admin's and the MCP-Auth-Proxy's URLs and the organisation", async () => {
|
|
412
430
|
const { run } = fakeGcloud({
|
|
413
431
|
"projects describe acme": ok("42\n"),
|
|
414
432
|
"projects get-ancestors acme": ANCESTORS_IN_ORG,
|
|
@@ -504,7 +522,7 @@ const serviceJson = (env: { name: string; value: string }[]) =>
|
|
|
504
522
|
const DESCRIBE_JSON = "--region=europe-west1 --project=acme --format=json";
|
|
505
523
|
|
|
506
524
|
describe("findRemoteMcpTarget (#1082)", () => {
|
|
507
|
-
it("finds the Pulumi-suffixed services by prefix", async () => {
|
|
525
|
+
it("finds the Pulumi-suffixed services by prefix and names the fixed one (#1328)", async () => {
|
|
508
526
|
const { run } = fakeGcloud({
|
|
509
527
|
"run services list --region europe-west1 --project acme --filter metadata.name~^caelo-production-admin":
|
|
510
528
|
ok("caelo-production-admin-aaa\n"),
|
|
@@ -582,12 +600,13 @@ describe("checkRemoteMcpIdentityEnv (#1082)", () => {
|
|
|
582
600
|
[`run services describe caelo-production-admin-aaa ${DESCRIBE_JSON}`]: serviceJson(
|
|
583
601
|
plain(full.admin),
|
|
584
602
|
),
|
|
603
|
+
[`run services describe caelo-production-data-gateway ${DESCRIBE_JSON}`]: serviceJson([]),
|
|
585
604
|
[`run services describe caelo-production-gateway-bbb ${DESCRIBE_JSON}`]: serviceJson(
|
|
586
|
-
plain(full
|
|
605
|
+
plain(full["mcp-auth-proxy"]),
|
|
587
606
|
),
|
|
588
607
|
});
|
|
589
608
|
const r = await checkRemoteMcpIdentityEnv(firebase, identity, { run });
|
|
590
|
-
expect(r).toEqual({ ok: true, drift: { admin: [], gateway: [] } });
|
|
609
|
+
expect(r).toEqual({ ok: true, drift: { admin: [], gateway: [], "mcp-auth-proxy": [] } });
|
|
591
610
|
expect(calls.some((c) => c.startsWith("run services update"))).toBe(false);
|
|
592
611
|
});
|
|
593
612
|
|
|
@@ -596,10 +615,12 @@ describe("checkRemoteMcpIdentityEnv (#1082)", () => {
|
|
|
596
615
|
[`run services describe caelo-production-admin-aaa ${DESCRIBE_JSON}`]: serviceJson(
|
|
597
616
|
plain(full.admin),
|
|
598
617
|
),
|
|
618
|
+
[`run services describe caelo-production-data-gateway ${DESCRIBE_JSON}`]: serviceJson([]),
|
|
599
619
|
[`run services describe caelo-production-gateway-bbb ${DESCRIBE_JSON}`]: serviceJson([]),
|
|
600
620
|
});
|
|
601
621
|
const r = await checkRemoteMcpIdentityEnv(firebase, identity, { run });
|
|
602
|
-
expect(r.ok && r.drift.gateway
|
|
622
|
+
expect(r.ok && r.drift.gateway).toEqual([]);
|
|
623
|
+
expect(r.ok && r.drift["mcp-auth-proxy"].map((c) => c.name)).toEqual([
|
|
603
624
|
"CAELO_ADMIN_URL",
|
|
604
625
|
"CAELO_MCP_PUBLIC_URL",
|
|
605
626
|
]);
|
|
@@ -646,7 +667,7 @@ describe("gcp load balancer (remote-mcp-edge)", () => {
|
|
|
646
667
|
// Every /api/mcp/ route the gateway documents — the consent page's
|
|
647
668
|
// POST /api/mcp/oauth/authorize included — is covered.
|
|
648
669
|
const routes = await Bun.file(
|
|
649
|
-
new URL("../../../apps/
|
|
670
|
+
new URL("../../../apps/mcp-auth-proxy/src/routes.ts", import.meta.url),
|
|
650
671
|
).text();
|
|
651
672
|
const documented = [...routes.matchAll(/^ \* {3}[A-Z|]+ +(\/api\/mcp\/[^\s]+)/gm)]
|
|
652
673
|
.map((m) => m[1] as string)
|
|
@@ -665,7 +686,7 @@ describe("gcp load balancer (remote-mcp-edge)", () => {
|
|
|
665
686
|
// gateway: CAELO_TRUSTED_PROXY_HOPS). Neither may be the tighter one by
|
|
666
687
|
// accident — a lower edge limit would 429 traffic the broker allows.
|
|
667
688
|
const src = await Bun.file(
|
|
668
|
-
new URL("../../../apps/
|
|
689
|
+
new URL("../../../apps/mcp-auth-proxy/src/rate-limit.ts", import.meta.url),
|
|
669
690
|
).text();
|
|
670
691
|
const proxy = /proxy: \{ capacity: (\d+), windowSeconds: (\d+) \}/.exec(src);
|
|
671
692
|
expect(proxy).not.toBeNull();
|
|
@@ -692,14 +713,13 @@ describe("gcp load balancer (remote-mcp-edge)", () => {
|
|
|
692
713
|
]);
|
|
693
714
|
});
|
|
694
715
|
|
|
695
|
-
it("the gcp stack
|
|
716
|
+
it("#1328 — the gcp stack's own WAF policy no longer carries the rule (the proxy's policy does)", async () => {
|
|
696
717
|
const src = await Bun.file(new URL("../stacks/gcp/index.ts", import.meta.url)).text();
|
|
697
|
-
expect(src).toContain("mcpArmorRuleArgs
|
|
698
|
-
expect(src.indexOf("mcpArmorRuleArgs(),")).toBeLessThan(
|
|
699
|
-
src.indexOf("evaluatePreconfiguredWaf"),
|
|
700
|
-
);
|
|
718
|
+
expect(src).not.toContain("mcpArmorRuleArgs");
|
|
701
719
|
});
|
|
702
720
|
|
|
721
|
+
const PROXY_BACKEND =
|
|
722
|
+
"https://www.googleapis.com/compute/v1/projects/acme/global/backendServices/caelo-production-mcp-auth-proxy-backend";
|
|
703
723
|
const map = (paths: string[], extra: object[] = []): LiveUrlMap => ({
|
|
704
724
|
name: "caelo-production-url-map-abc",
|
|
705
725
|
fingerprint: "fp",
|
|
@@ -718,46 +738,87 @@ describe("gcp load balancer (remote-mcp-edge)", () => {
|
|
|
718
738
|
{ name: "admin", pathRules: [] },
|
|
719
739
|
],
|
|
720
740
|
});
|
|
741
|
+
const redirect = { paths: ["/old"], urlRedirect: { pathRedirect: "/new" } };
|
|
721
742
|
|
|
722
|
-
it("
|
|
723
|
-
|
|
724
|
-
const plan = planUrlMapPaths(
|
|
743
|
+
it("#1328 — moves the remote-MCP paths off the gateway's rule onto the proxy's, keeping redirects", () => {
|
|
744
|
+
// What #806 left: the discovery paths on the gateway's /api/* rule.
|
|
745
|
+
const plan = planUrlMapPaths(
|
|
746
|
+
[map(["/api/*", ...GCP_MCP_AUTH_PROXY_PATHS.slice(1)], [redirect])],
|
|
747
|
+
"acme.com",
|
|
748
|
+
PROXY_BACKEND,
|
|
749
|
+
);
|
|
725
750
|
if (!plan.ok) throw new Error(plan.error);
|
|
726
|
-
expect(plan.added).toEqual(GCP_PUBLIC_BASE_PATHS.filter((p) => p !== "/api/*"));
|
|
727
751
|
const rules = plan.map.pathMatchers?.[0]?.pathRules ?? [];
|
|
728
|
-
expect(rules
|
|
729
|
-
|
|
752
|
+
expect(rules).toEqual([
|
|
753
|
+
{ paths: [...GCP_GATEWAY_PATHS], service: "projects/acme/global/backendServices/gw" },
|
|
754
|
+
{ paths: [...GCP_MCP_AUTH_PROXY_PATHS], service: PROXY_BACKEND },
|
|
755
|
+
redirect,
|
|
756
|
+
]);
|
|
757
|
+
expect(plan.changes).toHaveLength(2);
|
|
730
758
|
expect(plan.map.fingerprint).toBe("fp");
|
|
759
|
+
// The admin host's matcher is not touched.
|
|
760
|
+
expect(plan.map.pathMatchers?.[1]).toEqual({ name: "admin", pathRules: [] });
|
|
731
761
|
});
|
|
732
762
|
|
|
733
|
-
it("
|
|
734
|
-
const
|
|
735
|
-
|
|
763
|
+
it("#1328 — is idempotent, and brings a drifted proxy rule back to its paths", () => {
|
|
764
|
+
const converged = map(
|
|
765
|
+
[...GCP_GATEWAY_PATHS],
|
|
766
|
+
[{ paths: [...GCP_MCP_AUTH_PROXY_PATHS], service: PROXY_BACKEND }, redirect],
|
|
767
|
+
);
|
|
768
|
+
const again = planUrlMapPaths([converged], "acme.com", PROXY_BACKEND);
|
|
769
|
+
expect(again.ok && again.changes).toEqual([]);
|
|
770
|
+
expect(again.ok && again.map).toBe(converged);
|
|
771
|
+
|
|
772
|
+
const drifted = map(
|
|
773
|
+
[...GCP_GATEWAY_PATHS],
|
|
774
|
+
[{ paths: ["/api/mcp/*"], service: PROXY_BACKEND }],
|
|
775
|
+
);
|
|
776
|
+
const fixed = planUrlMapPaths([drifted], "acme.com", PROXY_BACKEND);
|
|
777
|
+
if (!fixed.ok) throw new Error(fixed.error);
|
|
778
|
+
expect(fixed.map.pathMatchers?.[0]?.pathRules?.[1]).toEqual({
|
|
779
|
+
paths: [...GCP_MCP_AUTH_PROXY_PATHS],
|
|
780
|
+
service: PROXY_BACKEND,
|
|
781
|
+
});
|
|
736
782
|
});
|
|
737
783
|
|
|
738
784
|
it("refuses a map without the gateway rule instead of inventing one", () => {
|
|
739
|
-
const plan = planUrlMapPaths([map(["/other/*"])], "acme.com");
|
|
785
|
+
const plan = planUrlMapPaths([map(["/other/*"])], "acme.com", PROXY_BACKEND);
|
|
740
786
|
expect(plan.ok).toBe(false);
|
|
741
|
-
expect(planUrlMapPaths([], "acme.com").ok).toBe(false);
|
|
742
|
-
});
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
787
|
+
expect(planUrlMapPaths([], "acme.com", PROXY_BACKEND).ok).toBe(false);
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
const policyJson = (rules: object[]) => ok(JSON.stringify({ rules }));
|
|
791
|
+
const PROXY_POLICY = "caelo-production-mcp-auth-proxy-armor";
|
|
792
|
+
|
|
793
|
+
it("#1328 — creates the proxy's own policy and its rule when missing", async () => {
|
|
794
|
+
let created = false;
|
|
795
|
+
const calls: string[] = [];
|
|
796
|
+
const run: GcloudRunner = async (args) => {
|
|
797
|
+
const line = args.join(" ");
|
|
798
|
+
calls.push(line);
|
|
799
|
+
if (line.startsWith(`compute security-policies describe ${PROXY_POLICY}`)) {
|
|
800
|
+
return created ? policyJson([{ priority: 2147483647 }]) : fail("NOT_FOUND: not found");
|
|
801
|
+
}
|
|
802
|
+
if (line.startsWith(`compute security-policies create ${PROXY_POLICY}`)) {
|
|
803
|
+
created = true;
|
|
804
|
+
return ok();
|
|
805
|
+
}
|
|
806
|
+
if (line.startsWith("compute security-policies rules create 900")) return ok();
|
|
807
|
+
return fail(`unexpected: ${line}`);
|
|
808
|
+
};
|
|
809
|
+
expect(await ensureMcpArmorRule({ projectId: "acme", env: "production" }, { run })).toEqual({
|
|
752
810
|
ok: true,
|
|
753
|
-
policy:
|
|
811
|
+
policy: PROXY_POLICY,
|
|
754
812
|
action: "created",
|
|
755
813
|
});
|
|
756
|
-
expect(
|
|
814
|
+
expect(calls.at(-1)).toContain(`--security-policy=${PROXY_POLICY}`);
|
|
815
|
+
// Never the gateway's policy.
|
|
816
|
+
expect(calls.some((c) => c.includes("-waf"))).toBe(false);
|
|
817
|
+
});
|
|
757
818
|
|
|
819
|
+
it("leaves a rule in shape alone and brings a drifted one back", async () => {
|
|
758
820
|
const args = mcpArmorRuleArgs();
|
|
759
821
|
const present = fakeGcloud({
|
|
760
|
-
"compute security-policies list": ok("caelo-production-waf-1a2b\n"),
|
|
761
822
|
"compute security-policies describe": policyJson([{ ...args }]),
|
|
762
823
|
});
|
|
763
824
|
const r = await ensureMcpArmorRule({ projectId: "acme", env: "production" }, present);
|
|
@@ -765,11 +826,145 @@ describe("gcp load balancer (remote-mcp-edge)", () => {
|
|
|
765
826
|
expect(present.calls.some((c) => c.includes("rules "))).toBe(false);
|
|
766
827
|
|
|
767
828
|
const drifted = fakeGcloud({
|
|
768
|
-
"compute security-policies list": ok("caelo-production-waf-1a2b\n"),
|
|
769
829
|
"compute security-policies describe": policyJson([{ ...args, action: "allow" }]),
|
|
770
830
|
"compute security-policies rules update 900": ok(),
|
|
771
831
|
});
|
|
772
832
|
const d = await ensureMcpArmorRule({ projectId: "acme", env: "production" }, drifted);
|
|
773
833
|
expect(d.ok && d.action).toBe("updated");
|
|
774
834
|
});
|
|
835
|
+
|
|
836
|
+
it("#1328 — takes the MCP rule off the gateway's policy, and only exactly that rule", async () => {
|
|
837
|
+
const args = mcpArmorRuleArgs();
|
|
838
|
+
const withRule = fakeGcloud({
|
|
839
|
+
"compute security-policies list": ok("caelo-production-waf-1a2b\n"),
|
|
840
|
+
"compute security-policies describe caelo-production-waf-1a2b": policyJson([{ ...args }]),
|
|
841
|
+
"compute security-policies rules delete 900": ok(),
|
|
842
|
+
});
|
|
843
|
+
expect(
|
|
844
|
+
await retireGatewayMcpArmorRule({ projectId: "acme", env: "production" }, withRule),
|
|
845
|
+
).toEqual({ ok: true, removed: "caelo-production-waf-1a2b" });
|
|
846
|
+
expect(withRule.calls.at(-1)).toContain("--security-policy=caelo-production-waf-1a2b");
|
|
847
|
+
|
|
848
|
+
// Someone's own rule at the priority stays.
|
|
849
|
+
const own = fakeGcloud({
|
|
850
|
+
"compute security-policies list": ok("caelo-production-waf-1a2b\n"),
|
|
851
|
+
"compute security-policies describe caelo-production-waf-1a2b": policyJson([
|
|
852
|
+
{ priority: 900, action: "deny(403)" },
|
|
853
|
+
]),
|
|
854
|
+
});
|
|
855
|
+
expect(await retireGatewayMcpArmorRule({ projectId: "acme", env: "production" }, own)).toEqual({
|
|
856
|
+
ok: true,
|
|
857
|
+
removed: null,
|
|
858
|
+
});
|
|
859
|
+
expect(own.calls.some((c) => c.includes("rules delete"))).toBe(false);
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
it("#1328 — creates the proxy's NEG and backend with its policy, then is a no-op", async () => {
|
|
863
|
+
const target = { projectId: "acme", region: "europe-west1", env: "production" };
|
|
864
|
+
const missing = fakeGcloud({
|
|
865
|
+
"compute network-endpoint-groups describe": fail("NOT_FOUND: was not found"),
|
|
866
|
+
"compute network-endpoint-groups create": ok(),
|
|
867
|
+
"compute backend-services describe": fail("NOT_FOUND: was not found"),
|
|
868
|
+
"compute backend-services create": ok(JSON.stringify([{ selfLink: PROXY_BACKEND }])),
|
|
869
|
+
"compute backend-services add-backend": ok(),
|
|
870
|
+
});
|
|
871
|
+
const r = await ensureMcpAuthProxyBackend(target, missing);
|
|
872
|
+
expect(r.ok && r.backend).toBe(PROXY_BACKEND);
|
|
873
|
+
expect(missing.calls).toContain(
|
|
874
|
+
"compute network-endpoint-groups create caelo-production-mcp-auth-proxy-neg --region=europe-west1 --project=acme --network-endpoint-type=serverless --cloud-run-service=caelo-production-mcp-auth-proxy --quiet",
|
|
875
|
+
);
|
|
876
|
+
expect(missing.calls.find((c) => c.startsWith("compute backend-services create"))).toContain(
|
|
877
|
+
`--security-policy=${PROXY_POLICY}`,
|
|
878
|
+
);
|
|
879
|
+
expect(
|
|
880
|
+
missing.calls.find((c) => c.startsWith("compute backend-services add-backend")),
|
|
881
|
+
).toContain("--network-endpoint-group=caelo-production-mcp-auth-proxy-neg");
|
|
882
|
+
|
|
883
|
+
const present = fakeGcloud({
|
|
884
|
+
"compute network-endpoint-groups describe": ok("caelo-production-mcp-auth-proxy-neg\n"),
|
|
885
|
+
"compute backend-services describe": ok(
|
|
886
|
+
JSON.stringify({
|
|
887
|
+
selfLink: PROXY_BACKEND,
|
|
888
|
+
securityPolicy: `https://www.googleapis.com/compute/v1/projects/acme/global/securityPolicies/${PROXY_POLICY}`,
|
|
889
|
+
backends: [
|
|
890
|
+
{
|
|
891
|
+
group:
|
|
892
|
+
"https://www.googleapis.com/compute/v1/projects/acme/regions/europe-west1/networkEndpointGroups/caelo-production-mcp-auth-proxy-neg",
|
|
893
|
+
},
|
|
894
|
+
],
|
|
895
|
+
}),
|
|
896
|
+
),
|
|
897
|
+
});
|
|
898
|
+
const again = await ensureMcpAuthProxyBackend(target, present);
|
|
899
|
+
expect(again).toEqual({ ok: true, backend: PROXY_BACKEND, done: [] });
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
it("#1328 — ensureRemoteMcpEdge: service, policy, backend, URL map, then the gateway rule; no-op on gcp-firebase", async () => {
|
|
903
|
+
expect(
|
|
904
|
+
await ensureRemoteMcpEdge({
|
|
905
|
+
provider: "gcp-firebase",
|
|
906
|
+
projectId: "acme",
|
|
907
|
+
region: "europe-west1",
|
|
908
|
+
env: "production",
|
|
909
|
+
domain: "acme.com",
|
|
910
|
+
mcpAuthProxyImageRef: "img",
|
|
911
|
+
}),
|
|
912
|
+
).toEqual({ ok: true, done: [] });
|
|
913
|
+
|
|
914
|
+
const order: string[] = [];
|
|
915
|
+
const run: GcloudRunner = async (args) => {
|
|
916
|
+
const line = args.join(" ");
|
|
917
|
+
const step = [
|
|
918
|
+
["run services describe caelo-production-mcp-auth-proxy", "service"],
|
|
919
|
+
[`compute security-policies describe ${PROXY_POLICY}`, "policy"],
|
|
920
|
+
["compute network-endpoint-groups describe", "neg"],
|
|
921
|
+
["compute backend-services describe", "backend"],
|
|
922
|
+
["compute url-maps list", "url-map"],
|
|
923
|
+
["compute security-policies list", "gateway-policy"],
|
|
924
|
+
].find(([prefix]) => line.startsWith(prefix as string))?.[1];
|
|
925
|
+
if (step) order.push(step);
|
|
926
|
+
if (line.startsWith("run services describe")) return ok("caelo-production-mcp-auth-proxy\n");
|
|
927
|
+
if (line.startsWith(`compute security-policies describe ${PROXY_POLICY}`)) {
|
|
928
|
+
return policyJson([{ ...mcpArmorRuleArgs() }]);
|
|
929
|
+
}
|
|
930
|
+
if (line.startsWith("compute network-endpoint-groups describe")) return ok("neg\n");
|
|
931
|
+
if (line.startsWith("compute backend-services describe")) {
|
|
932
|
+
return ok(
|
|
933
|
+
JSON.stringify({
|
|
934
|
+
selfLink: PROXY_BACKEND,
|
|
935
|
+
securityPolicy: `x/securityPolicies/${PROXY_POLICY}`,
|
|
936
|
+
backends: [{ group: "x/networkEndpointGroups/caelo-production-mcp-auth-proxy-neg" }],
|
|
937
|
+
}),
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
if (line.startsWith("compute url-maps list")) {
|
|
941
|
+
return ok(
|
|
942
|
+
JSON.stringify([
|
|
943
|
+
map(
|
|
944
|
+
[...GCP_GATEWAY_PATHS],
|
|
945
|
+
[{ paths: [...GCP_MCP_AUTH_PROXY_PATHS], service: PROXY_BACKEND }],
|
|
946
|
+
),
|
|
947
|
+
]),
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
if (line.startsWith("compute security-policies list")) return ok("caelo-production-waf-1\n");
|
|
951
|
+
if (line.startsWith("compute security-policies describe caelo-production-waf-1")) {
|
|
952
|
+
return policyJson([]);
|
|
953
|
+
}
|
|
954
|
+
return fail(`unexpected: ${line}`);
|
|
955
|
+
};
|
|
956
|
+
const r = await ensureRemoteMcpEdge(
|
|
957
|
+
{
|
|
958
|
+
provider: "gcp",
|
|
959
|
+
projectId: "acme",
|
|
960
|
+
region: "europe-west1",
|
|
961
|
+
env: "production",
|
|
962
|
+
domain: "acme.com",
|
|
963
|
+
mcpAuthProxyImageRef: "img",
|
|
964
|
+
},
|
|
965
|
+
{ run },
|
|
966
|
+
);
|
|
967
|
+
expect(r.ok).toBe(true);
|
|
968
|
+
expect(order).toEqual(["service", "policy", "neg", "backend", "url-map", "gateway-policy"]);
|
|
969
|
+
});
|
|
775
970
|
});
|