@caelo-cms/provisioning 0.11.9 → 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 +44 -2
- 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/admin-domain.test.ts
CHANGED
|
@@ -170,9 +170,9 @@ describe("enableStagingDomain (#650)", () => {
|
|
|
170
170
|
},
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
-
it("
|
|
173
|
+
it("#1328 — maps staging.<domain> to the data gateway (it runs the gate), opening its ingress", async () => {
|
|
174
174
|
const { run, calls } = fakeGcloud({
|
|
175
|
-
"run services list": [ok("caelo-production-gateway
|
|
175
|
+
"run services list": [ok("caelo-production-data-gateway\n")],
|
|
176
176
|
"run services describe": [ok("internal-and-cloud-load-balancing\n")],
|
|
177
177
|
"beta run domain-mappings describe": [fail("NOT_FOUND"), ok(STAGING_MAPPING)],
|
|
178
178
|
"domains list-user-verified": [ok("example.com\n")],
|
|
@@ -189,21 +189,22 @@ describe("enableStagingDomain (#650)", () => {
|
|
|
189
189
|
"run",
|
|
190
190
|
"services",
|
|
191
191
|
"update",
|
|
192
|
-
"caelo-production-gateway
|
|
192
|
+
"caelo-production-data-gateway",
|
|
193
193
|
"--region=europe-west1",
|
|
194
194
|
"--project=p",
|
|
195
195
|
"--ingress=all",
|
|
196
196
|
"--quiet",
|
|
197
197
|
]);
|
|
198
198
|
expect(calls.find((c) => c.includes("create"))).toContain(
|
|
199
|
-
"--service=caelo-production-gateway
|
|
199
|
+
"--service=caelo-production-data-gateway",
|
|
200
200
|
);
|
|
201
|
-
|
|
201
|
+
// Never the old gateway service, which is the MCP-Auth-Proxy now.
|
|
202
|
+
expect(calls[0]).toContain("--filter=metadata.name~^caelo-production-data-gateway");
|
|
202
203
|
});
|
|
203
204
|
|
|
204
205
|
it("is idempotent: ingress already open and mapping present ⇒ no update, no create", async () => {
|
|
205
206
|
const { run, calls } = fakeGcloud({
|
|
206
|
-
"run services list": [ok("caelo-production-gateway
|
|
207
|
+
"run services list": [ok("caelo-production-data-gateway\n")],
|
|
207
208
|
"run services describe": [ok("all\n")],
|
|
208
209
|
"beta run domain-mappings describe": [ok(STAGING_MAPPING)],
|
|
209
210
|
});
|
|
@@ -215,7 +216,7 @@ describe("enableStagingDomain (#650)", () => {
|
|
|
215
216
|
|
|
216
217
|
it("maps a host the operator chose on another domain, verifying that host itself", async () => {
|
|
217
218
|
const { run, calls } = fakeGcloud({
|
|
218
|
-
"run services list": [ok("caelo-production-gateway
|
|
219
|
+
"run services list": [ok("caelo-production-data-gateway\n")],
|
|
219
220
|
"run services describe": [ok("all\n")],
|
|
220
221
|
"beta run domain-mappings describe": [fail("NOT_FOUND")],
|
|
221
222
|
"domains list-user-verified": [ok("example.com\n")],
|
|
@@ -240,7 +241,7 @@ describe("enableStagingDomain (#650)", () => {
|
|
|
240
241
|
it("installs the missing beta component before its first beta command", async () => {
|
|
241
242
|
const { run, calls } = fakeGcloud({
|
|
242
243
|
"components list": [ok("")],
|
|
243
|
-
"run services list": [ok("caelo-production-gateway
|
|
244
|
+
"run services list": [ok("caelo-production-data-gateway\n")],
|
|
244
245
|
"run services describe": [ok("all\n")],
|
|
245
246
|
"beta run domain-mappings describe": [ok(STAGING_MAPPING)],
|
|
246
247
|
});
|
package/src/admin-domain.ts
CHANGED
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
|
|
32
32
|
import { gcloud as defaultGcloud, type GcloudResult } from "./gcloud.js";
|
|
33
33
|
import { ensureGcloudComponent } from "./gcloud-components.js";
|
|
34
|
-
import {
|
|
34
|
+
import { GCP_STACK_ENV } from "./gcp-names.js";
|
|
35
|
+
import { cloudRunServicePrefix, STAGING_HOST_ENV } from "./stack-contract.js";
|
|
35
36
|
import { liveContainerEnv } from "./stack-converge.js";
|
|
36
37
|
|
|
37
38
|
type Run = (args: string[]) => Promise<GcloudResult>;
|
|
@@ -76,13 +77,17 @@ function logOf(opts: DomainOpts): (message: string) => void {
|
|
|
76
77
|
return opts.log ?? ((message: string) => console.log(message));
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* The one deployed Cloud Run service playing `slug` on a gcp-firebase
|
|
82
|
+
* install (stack-contract.ts cloudRunServicePrefix: since #1328 the visitor
|
|
83
|
+
* gateway is `caelo-production-data-gateway`); also used by staging-admin.ts.
|
|
84
|
+
*/
|
|
80
85
|
export async function findService(
|
|
81
86
|
run: Run,
|
|
82
87
|
scope: string[],
|
|
83
88
|
slug: "admin" | "gateway",
|
|
84
89
|
): Promise<{ ok: true; service: string } | { ok: false; error: string }> {
|
|
85
|
-
const prefix =
|
|
90
|
+
const prefix = cloudRunServicePrefix("gcp-firebase", GCP_STACK_ENV, slug);
|
|
86
91
|
const services = await run([
|
|
87
92
|
"run",
|
|
88
93
|
"services",
|
|
@@ -128,6 +133,11 @@ export async function enableAdminDomain(opts: DomainOpts): Promise<AdminDomainRe
|
|
|
128
133
|
* ingress is internal-only, so the gateway's ingress is opened first (the
|
|
129
134
|
* stack declares the same since #650; installs from before keep the old
|
|
130
135
|
* value until this runs). Idempotent like the admin mapping.
|
|
136
|
+
*
|
|
137
|
+
* #1328 — a new mapping goes to the data gateway, which runs the gate. A
|
|
138
|
+
* mapping made before stays on the service that was the gateway (now the
|
|
139
|
+
* MCP-Auth-Proxy, which forwards it there) until the staging cutover moves
|
|
140
|
+
* it (#1330, #1331); an existing mapping is only reported here.
|
|
131
141
|
*/
|
|
132
142
|
export async function enableStagingDomain(
|
|
133
143
|
opts: DomainOpts & { stagingHost: string },
|
package/src/admin-memory.test.ts
CHANGED
|
@@ -76,6 +76,7 @@ describe("self-hosted compose", () => {
|
|
|
76
76
|
postgresPassword: "pw",
|
|
77
77
|
rolePasswords: { admin: "a", public: "b" },
|
|
78
78
|
caeloSecretKek: "0".repeat(64),
|
|
79
|
+
dbTlsServerCertSha256: "abababababababababababababababababababababababababababababababab",
|
|
79
80
|
internalSecret: "i".repeat(48),
|
|
80
81
|
diskSize: "20Gi",
|
|
81
82
|
ports: { http: 80, https: 443 },
|
|
@@ -341,6 +341,45 @@ describe("adoptionCandidates — only declared creates of an adoptable kind", ()
|
|
|
341
341
|
);
|
|
342
342
|
expect(c?.label).toBe("service account");
|
|
343
343
|
});
|
|
344
|
+
|
|
345
|
+
it("#1328 — names an explicitly named Cloud Run service (the data gateway) and only such", () => {
|
|
346
|
+
const SERVICE = "gcp:cloudrunv2/service:Service";
|
|
347
|
+
const [c, ...rest] = adoptionCandidates(
|
|
348
|
+
[
|
|
349
|
+
step({
|
|
350
|
+
type: SERVICE,
|
|
351
|
+
name: "caelo-production-data-gateway",
|
|
352
|
+
inputs: { name: "caelo-production-data-gateway", location: "europe-west1" },
|
|
353
|
+
}),
|
|
354
|
+
// Auto-named services (the admin, the proxy) have no name to look up.
|
|
355
|
+
step({
|
|
356
|
+
type: SERVICE,
|
|
357
|
+
name: "caelo-production-admin",
|
|
358
|
+
inputs: { location: "europe-west1" },
|
|
359
|
+
}),
|
|
360
|
+
step({
|
|
361
|
+
type: SERVICE,
|
|
362
|
+
name: "elsewhere",
|
|
363
|
+
inputs: { name: "x-svc", location: "europe-west1", project: "other-project" },
|
|
364
|
+
}),
|
|
365
|
+
],
|
|
366
|
+
PROJECT,
|
|
367
|
+
);
|
|
368
|
+
expect(rest).toEqual([]);
|
|
369
|
+
expect(c?.label).toBe("Cloud Run service");
|
|
370
|
+
expect(c?.importId).toBe(
|
|
371
|
+
`projects/${PROJECT}/locations/europe-west1/services/caelo-production-data-gateway`,
|
|
372
|
+
);
|
|
373
|
+
expect(c?.describeArgs).toEqual([
|
|
374
|
+
"run",
|
|
375
|
+
"services",
|
|
376
|
+
"describe",
|
|
377
|
+
"caelo-production-data-gateway",
|
|
378
|
+
"--region=europe-west1",
|
|
379
|
+
`--project=${PROJECT}`,
|
|
380
|
+
"--format=value(metadata.name)",
|
|
381
|
+
]);
|
|
382
|
+
});
|
|
344
383
|
});
|
|
345
384
|
|
|
346
385
|
describe("preview steps carry a create's inputs for adoption", () => {
|
package/src/adopt-existing.ts
CHANGED
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
* 4. A second preview: what the review shows and `up` applies. An adopted
|
|
26
26
|
* resource that is still planned as a create stops the run.
|
|
27
27
|
*
|
|
28
|
-
* Why
|
|
28
|
+
* Why service accounts, since #1338 the Cloud SQL private DNS zone and its
|
|
29
|
+
* record (cloudsql-tls.ts creates both on upgrade), and since #1328
|
|
30
|
+
* explicitly named Cloud Run services — {@link ADOPTABLE_KINDS}. A kind is adopted
|
|
29
31
|
* when the stack declares it AND the CLI also creates it through gcloud, and
|
|
30
32
|
* its name is unique in the project. Checked per kind against both stacks:
|
|
31
33
|
*
|
|
@@ -34,6 +36,9 @@
|
|
|
34
36
|
* (stack-contract.ts CLI_GENERATED_SECRETS) the stack only references by
|
|
35
37
|
* id. Adopting a secret without its value resource would also make `up`
|
|
36
38
|
* add a version with a freshly generated value — a new KEK.
|
|
39
|
+
* - Cloud Run services: only the ones the stack names explicitly (#1328:
|
|
40
|
+
* the gcp-firebase data gateway, which the converge creates on older
|
|
41
|
+
* installs); the auto-named admin and proxy are Pulumi's alone.
|
|
37
42
|
* - Buckets, Cloud Run jobs, custom roles: the stack's buckets are made by
|
|
38
43
|
* Pulumi alone; the CLI's own (ops, audit results, the upgrader, converge,
|
|
39
44
|
* audit and operator-access jobs, their custom roles) are not declared.
|
|
@@ -102,6 +107,83 @@ export const ADOPTABLE_KINDS: Readonly<Record<string, AdoptableKind>> = {
|
|
|
102
107
|
};
|
|
103
108
|
},
|
|
104
109
|
},
|
|
110
|
+
// #1338 — the private zone + A record that resolve the Cloud SQL
|
|
111
|
+
// instance's DNS name in the VPC. `cms-provision upgrade` creates both
|
|
112
|
+
// through gcloud (cloudsql-tls.ts) on installs that predate them; the
|
|
113
|
+
// stacks declare them with literal names so the preview pins them down.
|
|
114
|
+
"gcp:dns/managedZone:ManagedZone": {
|
|
115
|
+
label: "private DNS zone",
|
|
116
|
+
target(inputs, projectId) {
|
|
117
|
+
const name = inputs.name;
|
|
118
|
+
if (typeof name !== "string" || !/^[a-z][a-z0-9-]{0,62}$/.test(name)) return null;
|
|
119
|
+
if ((inputs.project ?? projectId) !== projectId) return null;
|
|
120
|
+
return {
|
|
121
|
+
display: name,
|
|
122
|
+
describeArgs: [
|
|
123
|
+
"dns",
|
|
124
|
+
"managed-zones",
|
|
125
|
+
"describe",
|
|
126
|
+
name,
|
|
127
|
+
`--project=${projectId}`,
|
|
128
|
+
"--format=value(name)",
|
|
129
|
+
],
|
|
130
|
+
importId: `projects/${projectId}/managedZones/${name}`,
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
"gcp:dns/recordSet:RecordSet": {
|
|
135
|
+
label: "DNS record",
|
|
136
|
+
target(inputs, projectId) {
|
|
137
|
+
const { managedZone: zone, name, type } = inputs;
|
|
138
|
+
if (typeof zone !== "string" || !/^[a-z][a-z0-9-]{0,62}$/.test(zone)) return null;
|
|
139
|
+
if (typeof name !== "string" || !/^[a-z0-9.-]{1,253}\.$/.test(name)) return null;
|
|
140
|
+
if (typeof type !== "string" || !/^[A-Z]{1,10}$/.test(type)) return null;
|
|
141
|
+
if ((inputs.project ?? projectId) !== projectId) return null;
|
|
142
|
+
return {
|
|
143
|
+
display: `${name} ${type}`,
|
|
144
|
+
describeArgs: [
|
|
145
|
+
"dns",
|
|
146
|
+
"record-sets",
|
|
147
|
+
"describe",
|
|
148
|
+
name,
|
|
149
|
+
`--zone=${zone}`,
|
|
150
|
+
`--type=${type}`,
|
|
151
|
+
`--project=${projectId}`,
|
|
152
|
+
"--format=value(name)",
|
|
153
|
+
],
|
|
154
|
+
importId: `projects/${projectId}/managedZones/${zone}/rrsets/${name}/${type}`,
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
// #1328 — the gcp-firebase data gateway: the stack declares it under a
|
|
159
|
+
// fixed name (stack-contract.ts fixedCloudRunServiceName), and the converge
|
|
160
|
+
// creates the same service on installs whose stack predates it. Only a
|
|
161
|
+
// service the stack names explicitly is a candidate: an auto-named one has
|
|
162
|
+
// no name until Pulumi picks it.
|
|
163
|
+
"gcp:cloudrunv2/service:Service": {
|
|
164
|
+
label: "Cloud Run service",
|
|
165
|
+
target(inputs, projectId) {
|
|
166
|
+
const name = inputs.name;
|
|
167
|
+
const location = inputs.location;
|
|
168
|
+
if (typeof name !== "string" || !/^[a-z][a-z0-9-]{0,47}[a-z0-9]$/.test(name)) return null;
|
|
169
|
+
if (typeof location !== "string" || !/^[a-z]+-[a-z]+\d+$/.test(location)) return null;
|
|
170
|
+
const project = inputs.project ?? projectId;
|
|
171
|
+
if (project !== projectId) return null;
|
|
172
|
+
return {
|
|
173
|
+
display: `${name} (${location})`,
|
|
174
|
+
describeArgs: [
|
|
175
|
+
"run",
|
|
176
|
+
"services",
|
|
177
|
+
"describe",
|
|
178
|
+
name,
|
|
179
|
+
`--region=${location}`,
|
|
180
|
+
`--project=${projectId}`,
|
|
181
|
+
"--format=value(metadata.name)",
|
|
182
|
+
],
|
|
183
|
+
importId: `projects/${projectId}/locations/${location}/services/${name}`,
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
},
|
|
105
187
|
};
|
|
106
188
|
|
|
107
189
|
/** A planned create the installer may adopt instead. */
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
import { describe, expect, it } from "bun:test";
|
|
10
10
|
import type { CommandResult, HostRunner } from "../self-hosted-release.js";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
applyArgv,
|
|
13
|
+
applyDatabaseSide,
|
|
14
|
+
POSTGRES_RESTART_NOTICE,
|
|
15
|
+
parseApplyArgs,
|
|
16
|
+
} from "./command.js";
|
|
12
17
|
|
|
13
18
|
function runner(published: string | null): HostRunner & { calls: string[] } {
|
|
14
19
|
const calls: string[] = [];
|
|
@@ -75,3 +80,46 @@ describe("applyDatabaseSide", () => {
|
|
|
75
80
|
expect(r.calls).toEqual([]);
|
|
76
81
|
});
|
|
77
82
|
});
|
|
83
|
+
|
|
84
|
+
describe("agent apply argv (#1328)", () => {
|
|
85
|
+
const D = (c: string) => `sha256:${c.repeat(64)}`;
|
|
86
|
+
const request = {
|
|
87
|
+
requestId: "11111111-1111-4111-8111-111111111111",
|
|
88
|
+
targetVersion: "0.12.0",
|
|
89
|
+
trigger: "manual" as const,
|
|
90
|
+
cliPath: "/srv/.caelo/agent/cli/caelo-0.12.0/node_modules/@caelo-cms/provisioning/dist/cli.js",
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
it("passes the MCP-Auth-Proxy digest when the manifest names one, and the apply reads it back", () => {
|
|
94
|
+
const argv = applyArgv("/usr/bin/bun", {
|
|
95
|
+
...request,
|
|
96
|
+
digests: { admin: D("a"), gateway: D("b"), "mcp-auth-proxy": D("d") },
|
|
97
|
+
});
|
|
98
|
+
expect(argv.slice(argv.indexOf("--mcp-auth-proxy-digest"))).toEqual([
|
|
99
|
+
"--mcp-auth-proxy-digest",
|
|
100
|
+
D("d"),
|
|
101
|
+
]);
|
|
102
|
+
const parsed = parseApplyArgs(argv);
|
|
103
|
+
expect(parsed.success && parsed.data.mcpAuthProxyDigest).toBe(D("d"));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("an agent of a release from before the proxy starts the apply without it", () => {
|
|
107
|
+
const argv = applyArgv("/usr/bin/bun", {
|
|
108
|
+
...request,
|
|
109
|
+
digests: { admin: D("a"), gateway: D("b") },
|
|
110
|
+
});
|
|
111
|
+
expect(argv).not.toContain("--mcp-auth-proxy-digest");
|
|
112
|
+
const parsed = parseApplyArgs(argv);
|
|
113
|
+
expect(parsed.success).toBe(true);
|
|
114
|
+
expect(parsed.success && parsed.data.mcpAuthProxyDigest).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("refuses a malformed proxy digest", () => {
|
|
118
|
+
const argv = [
|
|
119
|
+
...applyArgv("/usr/bin/bun", { ...request, digests: { admin: D("a"), gateway: D("b") } }),
|
|
120
|
+
"--mcp-auth-proxy-digest",
|
|
121
|
+
"latest",
|
|
122
|
+
];
|
|
123
|
+
expect(parseApplyArgs(argv).success).toBe(false);
|
|
124
|
+
});
|
|
125
|
+
});
|
package/src/agent/command.ts
CHANGED
|
@@ -57,6 +57,8 @@ export interface AgentCliContext {
|
|
|
57
57
|
emitConfig(): void;
|
|
58
58
|
/** The role convergence as the superuser, including the agent's login. */
|
|
59
59
|
convergeRoles(): Promise<ConvergeOutcome>;
|
|
60
|
+
/** #1338 — public_role's limits as the superuser, before an upgrade's migrations. */
|
|
61
|
+
applyPublicRoleLimits(): Promise<ConvergeOutcome>;
|
|
60
62
|
/** The Compose file for `images` from config.json. */
|
|
61
63
|
renderCompose(images: ReleaseImages): string;
|
|
62
64
|
/** Re-write the Caddyfile (config + domains table) and reload Caddy (#891). */
|
|
@@ -417,12 +419,15 @@ function pruneVerifiedClis(caeloDir: string, keep: readonly (string | undefined)
|
|
|
417
419
|
}
|
|
418
420
|
}
|
|
419
421
|
|
|
420
|
-
/**
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
422
|
+
/**
|
|
423
|
+
* The argv that starts the target release's `agent apply` with `bun` at
|
|
424
|
+
* `execPath`: the verified install (agent/cli-provenance.ts) — never
|
|
425
|
+
* `bun x`, which would fetch an unverified copy — and the digests the signed
|
|
426
|
+
* manifest names, the MCP-Auth-Proxy's only when it names one (#1328).
|
|
427
|
+
*/
|
|
428
|
+
export function applyArgv(execPath: string, request: AgentApplyRequest): string[] {
|
|
429
|
+
return [
|
|
430
|
+
execPath,
|
|
426
431
|
request.cliPath,
|
|
427
432
|
"agent",
|
|
428
433
|
"apply",
|
|
@@ -436,7 +441,15 @@ async function spawnApply(request: AgentApplyRequest): Promise<AgentApplyOutcome
|
|
|
436
441
|
request.digests.admin,
|
|
437
442
|
"--gateway-digest",
|
|
438
443
|
request.digests.gateway,
|
|
444
|
+
...(request.digests["mcp-auth-proxy"]
|
|
445
|
+
? ["--mcp-auth-proxy-digest", request.digests["mcp-auth-proxy"]]
|
|
446
|
+
: []),
|
|
439
447
|
];
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Start the target release's `agent apply` and wait for it. */
|
|
451
|
+
async function spawnApply(request: AgentApplyRequest): Promise<AgentApplyOutcome> {
|
|
452
|
+
const argv = applyArgv(process.execPath, request);
|
|
440
453
|
try {
|
|
441
454
|
const proc = Bun.spawn(argv, {
|
|
442
455
|
stdout: "pipe",
|
|
@@ -504,16 +517,25 @@ const applyArgs = z.object({
|
|
|
504
517
|
trigger: z.enum(PLATFORM_UPDATE_TRIGGERS),
|
|
505
518
|
adminDigest: digest,
|
|
506
519
|
gatewayDigest: digest,
|
|
520
|
+
// #1328 — an agent of a release before the MCP-Auth-Proxy starts this
|
|
521
|
+
// without it; the signed manifest the apply reads names it anyway.
|
|
522
|
+
mcpAuthProxyDigest: digest.optional(),
|
|
507
523
|
});
|
|
508
524
|
|
|
509
|
-
|
|
510
|
-
|
|
525
|
+
/** The `agent apply` flags {@link applyArgv} writes, validated. */
|
|
526
|
+
export function parseApplyArgs(argv: readonly string[]) {
|
|
527
|
+
return applyArgs.safeParse({
|
|
511
528
|
request: flag(argv, "request"),
|
|
512
529
|
version: flag(argv, "version"),
|
|
513
530
|
trigger: flag(argv, "trigger"),
|
|
514
531
|
adminDigest: flag(argv, "admin-digest"),
|
|
515
532
|
gatewayDigest: flag(argv, "gateway-digest"),
|
|
533
|
+
mcpAuthProxyDigest: flag(argv, "mcp-auth-proxy-digest"),
|
|
516
534
|
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function agentApply(argv: readonly string[], ctx: AgentCliContext): Promise<number> {
|
|
538
|
+
const parsed = parseApplyArgs(argv);
|
|
517
539
|
if (!parsed.success) {
|
|
518
540
|
console.error(
|
|
519
541
|
`agent apply is started by the update agent, not by hand (${parsed.error.issues.map((i) => i.path.join(".")).join(", ")}).`,
|
|
@@ -539,13 +561,18 @@ async function agentApply(argv: readonly string[], ctx: AgentCliContext): Promis
|
|
|
539
561
|
upgradeSelfHosted(
|
|
540
562
|
{
|
|
541
563
|
version: args.version,
|
|
542
|
-
expectedDigests: {
|
|
564
|
+
expectedDigests: {
|
|
565
|
+
admin: args.adminDigest,
|
|
566
|
+
gateway: args.gatewayDigest,
|
|
567
|
+
...(args.mcpAuthProxyDigest ? { "mcp-auth-proxy": args.mcpAuthProxyDigest } : {}),
|
|
568
|
+
},
|
|
543
569
|
},
|
|
544
570
|
{
|
|
545
571
|
caeloDir: ctx.caeloDir,
|
|
546
572
|
runner: bunHostRunner,
|
|
547
573
|
renderCompose: (images) => ctx.renderCompose(images),
|
|
548
574
|
convergeRoles: () => ctx.convergeRoles(),
|
|
575
|
+
applyPublicRoleLimits: () => ctx.applyPublicRoleLimits(),
|
|
549
576
|
refreshCaddy: () => ctx.refreshCaddy(),
|
|
550
577
|
log,
|
|
551
578
|
},
|
package/src/agent/run.test.ts
CHANGED
|
@@ -198,6 +198,25 @@ describe("runAgentOnce", () => {
|
|
|
198
198
|
expect(locks).toEqual({ taken: 1, released: 1 });
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
+
it("#1328: hands over the MCP-Auth-Proxy digest the signed manifest names", async () => {
|
|
202
|
+
const withProxy: ReleaseManifest = {
|
|
203
|
+
...MANIFEST,
|
|
204
|
+
digests: { ...MANIFEST.digests, "mcp-auth-proxy": D("d") },
|
|
205
|
+
};
|
|
206
|
+
const { deps, applied } = setup({
|
|
207
|
+
fetchManifest: async (): Promise<ReleaseManifestResult> => ({
|
|
208
|
+
ok: true,
|
|
209
|
+
manifest: withProxy,
|
|
210
|
+
}),
|
|
211
|
+
});
|
|
212
|
+
await runAgentOnce(deps);
|
|
213
|
+
expect(applied[0]?.digests).toEqual({
|
|
214
|
+
admin: D("a"),
|
|
215
|
+
gateway: D("b"),
|
|
216
|
+
"mcp-auth-proxy": D("d"),
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
201
220
|
interface RefusalRow {
|
|
202
221
|
readonly name: string;
|
|
203
222
|
readonly over: Partial<AgentRunDeps>;
|
package/src/agent/run.ts
CHANGED
|
@@ -48,7 +48,12 @@ export interface AgentApplyRequest {
|
|
|
48
48
|
readonly targetVersion: string;
|
|
49
49
|
readonly trigger: PlatformUpdateTrigger;
|
|
50
50
|
/** From the verified manifest: the images the target must resolve to. */
|
|
51
|
-
readonly digests: {
|
|
51
|
+
readonly digests: {
|
|
52
|
+
readonly admin: string;
|
|
53
|
+
readonly gateway: string;
|
|
54
|
+
/** #1328 — absent in manifests of releases from before the MCP-Auth-Proxy. */
|
|
55
|
+
readonly "mcp-auth-proxy"?: string;
|
|
56
|
+
};
|
|
52
57
|
/** The target's CLI, installed and provenance-verified (agent/cli-provenance.ts). */
|
|
53
58
|
readonly cliPath: string;
|
|
54
59
|
}
|
|
@@ -254,7 +259,11 @@ export async function runAgentOnce(deps: AgentRunDeps): Promise<AgentRunResult>
|
|
|
254
259
|
requestId: request.id,
|
|
255
260
|
targetVersion: m.version,
|
|
256
261
|
trigger: request.trigger,
|
|
257
|
-
digests: {
|
|
262
|
+
digests: {
|
|
263
|
+
admin: m.digests.admin,
|
|
264
|
+
gateway: m.digests.gateway,
|
|
265
|
+
...(m.digests["mcp-auth-proxy"] ? { "mcp-auth-proxy": m.digests["mcp-auth-proxy"] } : {}),
|
|
266
|
+
},
|
|
258
267
|
cliPath: cli.cliPath,
|
|
259
268
|
});
|
|
260
269
|
} finally {
|
package/src/aws-task-roles.ts
CHANGED
|
@@ -20,12 +20,14 @@ export type AwsSecret =
|
|
|
20
20
|
| "resend-api-key";
|
|
21
21
|
|
|
22
22
|
/** A service with its own ECS task role. */
|
|
23
|
-
export type AwsTaskService = "admin" | "gateway";
|
|
23
|
+
export type AwsTaskService = "admin" | "gateway" | "mcp-auth-proxy";
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* The secrets each service's task role may read. The admin side (admin and
|
|
27
27
|
* its in-process workers) reads all of them; the gateway reads only
|
|
28
|
-
* public_role's password — its one database login
|
|
28
|
+
* public_role's password — its one database login; the MCP-Auth-Proxy
|
|
29
|
+
* (#1328) reads none: remote MCP is not available on AWS, so it holds
|
|
30
|
+
* neither a database login nor the broker's keys.
|
|
29
31
|
*/
|
|
30
32
|
export const AWS_SERVICE_SECRETS: Readonly<Record<AwsTaskService, readonly AwsSecret[]>> = {
|
|
31
33
|
admin: [
|
|
@@ -37,6 +39,7 @@ export const AWS_SERVICE_SECRETS: Readonly<Record<AwsTaskService, readonly AwsSe
|
|
|
37
39
|
"resend-api-key",
|
|
38
40
|
],
|
|
39
41
|
gateway: ["public-role-password"],
|
|
42
|
+
"mcp-auth-proxy": [],
|
|
40
43
|
};
|
|
41
44
|
|
|
42
45
|
/**
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
CADDY_IMAGE,
|
|
32
32
|
COMPOSE_ADMIN_UPSTREAM,
|
|
33
33
|
COMPOSE_GATEWAY_UPSTREAM,
|
|
34
|
+
COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
|
|
34
35
|
COMPOSE_REDIRECT_SNIPPETS,
|
|
35
36
|
SITE_OUTPUT_ROOT,
|
|
36
37
|
} from "./compose.js";
|
|
@@ -149,6 +150,7 @@ describe.skipIf(!dockerUp)(`self-hosted redirects through ${CADDY_IMAGE}`, () =>
|
|
|
149
150
|
stagingSiteRoot: `${SITE_OUTPUT_ROOT}/output/staging/current`,
|
|
150
151
|
adminUpstream: COMPOSE_ADMIN_UPSTREAM,
|
|
151
152
|
gatewayUpstream: COMPOSE_GATEWAY_UPSTREAM,
|
|
153
|
+
mcpAuthProxyUpstream: COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
|
|
152
154
|
domains: selfHostedSeedDomains(LIVE, STAGING),
|
|
153
155
|
}),
|
|
154
156
|
);
|
|
@@ -15,6 +15,7 @@ import { caddyRedirectReloadCommand, caddyRedirectReloadScript } from "./caddy-r
|
|
|
15
15
|
import {
|
|
16
16
|
COMPOSE_ADMIN_UPSTREAM,
|
|
17
17
|
COMPOSE_GATEWAY_UPSTREAM,
|
|
18
|
+
COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
|
|
18
19
|
COMPOSE_REDIRECT_SNIPPETS,
|
|
19
20
|
generateDockerCompose,
|
|
20
21
|
} from "./compose.js";
|
|
@@ -26,6 +27,7 @@ const caddyfile = generateCaddyfile({
|
|
|
26
27
|
stagingSiteRoot: "/srv/caelo/output/staging/current",
|
|
27
28
|
adminUpstream: COMPOSE_ADMIN_UPSTREAM,
|
|
28
29
|
gatewayUpstream: COMPOSE_GATEWAY_UPSTREAM,
|
|
30
|
+
mcpAuthProxyUpstream: COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
|
|
29
31
|
domains: selfHostedSeedDomains("example.com", "staging.example.com"),
|
|
30
32
|
});
|
|
31
33
|
|
|
@@ -63,6 +65,7 @@ describe("Compose — Caddy picks up new redirects (#587)", () => {
|
|
|
63
65
|
postgresPassword: "pgpw",
|
|
64
66
|
rolePasswords: { admin: "a", public: "p" },
|
|
65
67
|
caeloSecretKek: "0".repeat(64),
|
|
68
|
+
dbTlsServerCertSha256: "abababababababababababababababababababababababababababababababab",
|
|
66
69
|
internalSecret: "i".repeat(48),
|
|
67
70
|
diskSize: "20Gi",
|
|
68
71
|
ports: { http: 80, https: 443 },
|
|
@@ -16,7 +16,12 @@ import { describe, expect, it } from "bun:test";
|
|
|
16
16
|
import { readFileSync } from "node:fs";
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { type CaddyfileSpec, generateCaddyfile, selfHostedSeedDomains } from "./caddy.js";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
CADDY_IMAGE,
|
|
21
|
+
COMPOSE_ADMIN_UPSTREAM,
|
|
22
|
+
COMPOSE_GATEWAY_UPSTREAM,
|
|
23
|
+
COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
|
|
24
|
+
} from "./compose.js";
|
|
20
25
|
|
|
21
26
|
const REQUIRED = process.env.CAELO_CADDY_VALIDATE === "required";
|
|
22
27
|
|
|
@@ -62,6 +67,7 @@ const base: CaddyfileSpec = {
|
|
|
62
67
|
stagingSiteRoot: "/srv/caelo/output/staging/current",
|
|
63
68
|
adminUpstream: COMPOSE_ADMIN_UPSTREAM,
|
|
64
69
|
gatewayUpstream: COMPOSE_GATEWAY_UPSTREAM,
|
|
70
|
+
mcpAuthProxyUpstream: COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
|
|
65
71
|
domains: selfHostedSeedDomains("example.com", "staging.example.com"),
|
|
66
72
|
};
|
|
67
73
|
|
package/src/caddy.test.ts
CHANGED
|
@@ -6,9 +6,14 @@ import {
|
|
|
6
6
|
caddyTokenProblem,
|
|
7
7
|
generateCaddyfile,
|
|
8
8
|
selfHostedAdminHost,
|
|
9
|
+
selfHostedMcpHost,
|
|
9
10
|
selfHostedSeedDomains,
|
|
10
11
|
} from "./caddy.js";
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
COMPOSE_ADMIN_UPSTREAM,
|
|
14
|
+
COMPOSE_GATEWAY_UPSTREAM,
|
|
15
|
+
COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
|
|
16
|
+
} from "./compose.js";
|
|
12
17
|
|
|
13
18
|
/** The inputs `cms-provision init` passes for example.com. */
|
|
14
19
|
const spec: CaddyfileSpec = {
|
|
@@ -18,6 +23,7 @@ const spec: CaddyfileSpec = {
|
|
|
18
23
|
stagingSiteRoot: "/srv/caelo/output/staging/current",
|
|
19
24
|
adminUpstream: COMPOSE_ADMIN_UPSTREAM,
|
|
20
25
|
gatewayUpstream: COMPOSE_GATEWAY_UPSTREAM,
|
|
26
|
+
mcpAuthProxyUpstream: COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
|
|
21
27
|
domains: selfHostedSeedDomains("example.com", "staging.example.com"),
|
|
22
28
|
};
|
|
23
29
|
|
|
@@ -35,7 +41,39 @@ describe("self-hosted seed vhosts", () => {
|
|
|
35
41
|
{ hostname: "example.com", kind: "public", env: "production" },
|
|
36
42
|
{ hostname: "staging.example.com", kind: "public", env: "staging" },
|
|
37
43
|
]);
|
|
44
|
+
// #1328 — no MCP vhost until remote MCP exists on self-hosted: Caddy would
|
|
45
|
+
// otherwise chase an ACME certificate for a host without a DNS record.
|
|
46
|
+
expect(
|
|
47
|
+
selfHostedSeedDomains("example.com", "staging.example.com").some((d) => d.kind === "mcp"),
|
|
48
|
+
).toBe(false);
|
|
38
49
|
expect(selfHostedAdminHost("example.com")).toBe("admin.example.com");
|
|
50
|
+
expect(selfHostedMcpHost("example.com")).toBe("mcp.example.com");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// #1328 — the MCP-Auth-Proxy never shares an origin with the site's module JS.
|
|
54
|
+
it("serves the MCP-Auth-Proxy on its own vhost, nothing else of the site", () => {
|
|
55
|
+
const out = generateCaddyfile({
|
|
56
|
+
...spec,
|
|
57
|
+
domains: [
|
|
58
|
+
...selfHostedSeedDomains("example.com", "staging.example.com"),
|
|
59
|
+
{ hostname: selfHostedMcpHost("example.com"), kind: "mcp", env: "production" },
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
const block = out.slice(out.indexOf("mcp.example.com {"));
|
|
63
|
+
const mcp = block.slice(0, block.indexOf("\n}\n") + 3);
|
|
64
|
+
expect(mcp).toContain(`reverse_proxy ${COMPOSE_MCP_AUTH_PROXY_UPSTREAM}`);
|
|
65
|
+
expect(mcp).toContain('X-Robots-Tag "noindex"');
|
|
66
|
+
expect(mcp).not.toContain("file_server");
|
|
67
|
+
expect(mcp).not.toContain(COMPOSE_GATEWAY_UPSTREAM);
|
|
68
|
+
// ... and the site's vhost does not route the proxy.
|
|
69
|
+
const site = out.slice(out.indexOf("\nexample.com {"));
|
|
70
|
+
expect(site.slice(0, site.indexOf("\n}\n"))).not.toContain(COMPOSE_MCP_AUTH_PROXY_UPSTREAM);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("refuses an MCP-Auth-Proxy upstream that would break out of its token", () => {
|
|
74
|
+
expect(() => generateCaddyfile({ ...spec, mcpAuthProxyUpstream: "x }\nevil {" })).toThrow(
|
|
75
|
+
/mcpAuthProxyUpstream/,
|
|
76
|
+
);
|
|
39
77
|
});
|
|
40
78
|
|
|
41
79
|
it("refuses two vhosts for one host — Caddy would reject the whole file", () => {
|