@caelo-cms/provisioning 0.11.9 → 0.11.11
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/adapter.d.ts +6 -0
- package/dist/adapter.d.ts.map +1 -1
- 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 +12 -3
- package/dist/adopt-existing.d.ts.map +1 -1
- package/dist/adopt-existing.js +143 -0
- package/dist/adopt-existing.js.map +1 -1
- package/dist/agent/command.d.ts +23 -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 +69 -10
- 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-routing.js +1 -1
- package/dist/cli-routing.js.map +1 -1
- package/dist/cli.js +68 -7
- package/dist/cli.js.map +1 -1
- package/dist/cloudsql-tls.d.ts +93 -0
- package/dist/cloudsql-tls.d.ts.map +1 -0
- package/dist/cloudsql-tls.js +295 -0
- package/dist/cloudsql-tls.js.map +1 -0
- package/dist/compose.d.ts +43 -6
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +148 -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-db-trust.d.ts +44 -0
- package/dist/converge-db-trust.d.ts.map +1 -0
- package/dist/converge-db-trust.js +158 -0
- package/dist/converge-db-trust.js.map +1 -0
- package/dist/converge-job-gcp.d.ts +5 -2
- package/dist/converge-job-gcp.d.ts.map +1 -1
- package/dist/converge-job-gcp.js +11 -13
- package/dist/converge-job-gcp.js.map +1 -1
- package/dist/converge-job-preflight.d.ts.map +1 -1
- package/dist/converge-job-preflight.js +8 -0
- package/dist/converge-job-preflight.js.map +1 -1
- package/dist/converge-job.d.ts +17 -0
- package/dist/converge-job.d.ts.map +1 -1
- package/dist/converge-job.js +12 -0
- package/dist/converge-job.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 +34 -0
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +45 -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 +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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 +45 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-job.d.ts +4 -2
- package/dist/install-job.d.ts.map +1 -1
- package/dist/install-job.js +4 -1
- package/dist/install-job.js.map +1 -1
- package/dist/install-state.d.ts +13 -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 +20 -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/migration-runner.d.ts.map +1 -1
- package/dist/migration-runner.js +8 -3
- package/dist/migration-runner.js.map +1 -1
- package/dist/migration-targets.d.ts +11 -0
- package/dist/migration-targets.d.ts.map +1 -0
- package/dist/migration-targets.js +12 -0
- package/dist/migration-targets.js.map +1 -0
- package/dist/operator-access.d.ts +10 -0
- package/dist/operator-access.d.ts.map +1 -1
- package/dist/operator-access.js +5 -0
- package/dist/operator-access.js.map +1 -1
- package/dist/pool-budget-stack.d.ts +13 -1
- package/dist/pool-budget-stack.d.ts.map +1 -1
- package/dist/pool-budget-stack.js +11 -1
- package/dist/pool-budget-stack.js.map +1 -1
- package/dist/public-role-isolation.d.ts +7 -3
- package/dist/public-role-isolation.d.ts.map +1 -1
- package/dist/public-role-isolation.js +25 -13
- package/dist/public-role-isolation.js.map +1 -1
- package/dist/release-contract.d.ts.map +1 -1
- package/dist/release-contract.js +3 -0
- package/dist/release-contract.js.map +1 -1
- 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 +11 -0
- package/dist/runtime-secrets.d.ts.map +1 -1
- package/dist/runtime-secrets.js +40 -25
- package/dist/runtime-secrets.js.map +1 -1
- package/dist/scheduler-setup.d.ts +75 -0
- package/dist/scheduler-setup.d.ts.map +1 -0
- package/dist/scheduler-setup.js +331 -0
- package/dist/scheduler-setup.js.map +1 -0
- 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 +43 -2
- package/dist/self-hosted-roles.d.ts.map +1 -1
- package/dist/self-hosted-roles.js +87 -1
- package/dist/self-hosted-roles.js.map +1 -1
- package/dist/self-hosted-scheduler-interval.d.ts +21 -0
- package/dist/self-hosted-scheduler-interval.d.ts.map +1 -0
- package/dist/self-hosted-scheduler-interval.js +68 -0
- package/dist/self-hosted-scheduler-interval.js.map +1 -0
- 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 +19 -7
- package/dist/self-hosted-upgrade.d.ts.map +1 -1
- package/dist/self-hosted-upgrade.js +78 -25
- package/dist/self-hosted-upgrade.js.map +1 -1
- package/dist/stack-contract.d.ts +234 -28
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +397 -39
- 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/staging-database.d.ts +22 -0
- package/dist/staging-database.d.ts.map +1 -0
- package/dist/staging-database.js +117 -0
- package/dist/staging-database.js.map +1 -0
- 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 +5 -0
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +207 -20
- 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 +19 -3
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +37 -2
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-gcp.d.ts +13 -0
- package/dist/upgrader-gcp.d.ts.map +1 -1
- package/dist/upgrader-gcp.js +50 -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/wizard.d.ts +7 -0
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +11 -0
- package/dist/wizard.js.map +1 -1
- package/dist/wizards/gcp-cost.d.ts +31 -0
- package/dist/wizards/gcp-cost.d.ts.map +1 -1
- package/dist/wizards/gcp-cost.js +103 -18
- package/dist/wizards/gcp-cost.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +129 -27
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +2 -2
- package/src/adapter.ts +6 -0
- package/src/admin-domain.test.ts +9 -8
- package/src/admin-domain.ts +13 -3
- package/src/admin-memory.test.ts +2 -1
- package/src/adopt-existing.test.ts +85 -0
- package/src/adopt-existing.ts +141 -3
- package/src/agent/command.test.ts +49 -1
- package/src/agent/command.ts +39 -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 +4 -1
- 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-routing.test.ts +1 -1
- package/src/cli-routing.ts +1 -1
- package/src/cli.ts +71 -5
- package/src/cloudsql-tls.test.ts +255 -0
- package/src/cloudsql-tls.ts +359 -0
- package/src/compose.ts +193 -24
- package/src/converge-checkpoint.ts +8 -1
- package/src/converge-db-trust.test.ts +237 -0
- package/src/converge-db-trust.ts +212 -0
- package/src/converge-job-gcp.ts +11 -12
- package/src/converge-job-preflight.test.ts +5 -0
- package/src/converge-job-preflight.ts +8 -0
- package/src/converge-job.test.ts +70 -0
- package/src/converge-job.ts +32 -0
- package/src/database-tls-builders.test.ts +56 -0
- package/src/deployed-release.test.ts +107 -40
- package/src/deployed-release.ts +30 -9
- package/src/gateway-env-isolation.test.ts +50 -12
- package/src/gcp-names.ts +56 -0
- package/src/gcp-url-map.test.ts +6 -8
- package/src/gcp-url-map.ts +6 -2
- package/src/index.test.ts +10 -4
- package/src/index.ts +7 -0
- package/src/infra-contract.test.ts +7 -3
- package/src/infra-contract.ts +45 -1
- package/src/install-job.ts +4 -1
- package/src/install-state.ts +33 -4
- package/src/lifecycle.ts +23 -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/migration-runner.ts +14 -9
- package/src/migration-targets.ts +12 -0
- package/src/operator-access-stacks.test.ts +1 -1
- package/src/operator-access.test.ts +20 -4
- package/src/operator-access.ts +14 -0
- package/src/pool-budget-stack.ts +15 -0
- package/src/pool-budget-stacks.test.ts +39 -14
- package/src/public-role-isolation.test.ts +18 -8
- package/src/public-role-isolation.ts +31 -17
- package/src/release-contract.ts +3 -0
- 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 +37 -14
- package/src/runtime-secrets.ts +87 -27
- package/src/scheduler-setup.test.ts +201 -0
- package/src/scheduler-setup.ts +375 -0
- package/src/seal-key-rotation.test.ts +2 -2
- package/src/seal-key-rotation.ts +25 -25
- package/src/self-hosted-backup.test.ts +1 -0
- package/src/self-hosted-compose.test.ts +180 -6
- 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.integration.test.ts +2 -0
- package/src/self-hosted-roles.test.ts +87 -3
- package/src/self-hosted-roles.ts +101 -3
- package/src/self-hosted-scheduler-interval.test.ts +66 -0
- package/src/self-hosted-scheduler-interval.ts +79 -0
- package/src/self-hosted-start.test.ts +35 -2
- package/src/self-hosted-start.ts +20 -2
- package/src/self-hosted-upgrade.test.ts +104 -14
- package/src/self-hosted-upgrade.ts +123 -31
- package/src/stack-contract-parity.test.ts +79 -12
- package/src/stack-contract.test.ts +199 -31
- package/src/stack-contract.ts +472 -45
- package/src/stack-converge-secrets.test.ts +28 -13
- package/src/stack-converge.test.ts +203 -28
- package/src/stack-converge.ts +92 -30
- package/src/staging-database.test.ts +114 -0
- package/src/staging-database.ts +148 -0
- package/src/state-store.test.ts +2 -1
- package/src/upgrade/app-roll.ts +97 -19
- package/src/upgrade/converge.ts +249 -19
- 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 +438 -46
- package/src/upgrade/upgrade.ts +64 -6
- package/src/upgrader-gcp.ts +66 -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/wizard.ts +19 -0
- package/src/wizards/gcp-cost.test.ts +103 -0
- package/src/wizards/gcp-cost.ts +125 -18
- package/src/wizards/gcp.ts +164 -22
- package/stacks/aws/Pulumi.yaml +1 -1
- package/stacks/aws/index.ts +72 -14
- package/stacks/azure/index.ts +108 -21
- package/stacks/gcp/Pulumi.yaml +1 -1
- package/stacks/gcp/README.md +1 -0
- package/stacks/gcp/index.ts +133 -24
- package/stacks/gcp-firebase/Pulumi.yaml +1 -1
- package/stacks/gcp-firebase/index.ts +212 -34
- package/stacks/self-hosted/Pulumi.yaml +3 -0
- package/stacks/self-hosted/README.md +6 -1
- package/stacks/self-hosted/index.ts +40 -5
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.
|
|
@@ -44,12 +49,17 @@
|
|
|
44
49
|
* Their create is a read-modify-write that merges the member into the
|
|
45
50
|
* policy and succeeds when it is there already, so a planned create of
|
|
46
51
|
* one is an adoption already.
|
|
47
|
-
* - Cloud SQL
|
|
48
|
-
*
|
|
52
|
+
* - Cloud SQL databases and users: only the staging database
|
|
53
|
+
* `cms_public_staging` and its login `public_staging_role` (#1329), which
|
|
54
|
+
* the converge creates on installs from before them (staging-database.ts).
|
|
55
|
+
* Every other one is Pulumi's alone: the CLI's other users are IAM users
|
|
56
|
+
* named after service accounts, the stack's the built-in `admin_role` /
|
|
57
|
+
* `public_role`.
|
|
49
58
|
*/
|
|
50
59
|
|
|
51
60
|
import type { GcloudRunner } from "./gcloud-retry.js";
|
|
52
61
|
import type { PlannedStep } from "./preview-plan.js";
|
|
62
|
+
import { STAGING_DATABASE, STAGING_DATABASE_ROLE } from "./staging-database.js";
|
|
53
63
|
|
|
54
64
|
/** The cloud identity of a resource a planned create would make. */
|
|
55
65
|
export interface AdoptionTarget {
|
|
@@ -72,6 +82,9 @@ export interface AdoptableKind {
|
|
|
72
82
|
target(inputs: Readonly<Record<string, unknown>>, projectId: string): AdoptionTarget | null;
|
|
73
83
|
}
|
|
74
84
|
|
|
85
|
+
/** The Cloud SQL objects the converge creates that the stack declares too (#1329). */
|
|
86
|
+
const CONVERGE_CREATED_SQL = { database: STAGING_DATABASE, user: STAGING_DATABASE_ROLE } as const;
|
|
87
|
+
|
|
75
88
|
/** Kinds adopted before `up`, by Pulumi type token. See the file comment for the rest. */
|
|
76
89
|
export const ADOPTABLE_KINDS: Readonly<Record<string, AdoptableKind>> = {
|
|
77
90
|
"gcp:serviceaccount/account:Account": {
|
|
@@ -102,6 +115,131 @@ export const ADOPTABLE_KINDS: Readonly<Record<string, AdoptableKind>> = {
|
|
|
102
115
|
};
|
|
103
116
|
},
|
|
104
117
|
},
|
|
118
|
+
// #1338 — the private zone + A record that resolve the Cloud SQL
|
|
119
|
+
// instance's DNS name in the VPC. `cms-provision upgrade` creates both
|
|
120
|
+
// through gcloud (cloudsql-tls.ts) on installs that predate them; the
|
|
121
|
+
// stacks declare them with literal names so the preview pins them down.
|
|
122
|
+
"gcp:dns/managedZone:ManagedZone": {
|
|
123
|
+
label: "private DNS zone",
|
|
124
|
+
target(inputs, projectId) {
|
|
125
|
+
const name = inputs.name;
|
|
126
|
+
if (typeof name !== "string" || !/^[a-z][a-z0-9-]{0,62}$/.test(name)) return null;
|
|
127
|
+
if ((inputs.project ?? projectId) !== projectId) return null;
|
|
128
|
+
return {
|
|
129
|
+
display: name,
|
|
130
|
+
describeArgs: [
|
|
131
|
+
"dns",
|
|
132
|
+
"managed-zones",
|
|
133
|
+
"describe",
|
|
134
|
+
name,
|
|
135
|
+
`--project=${projectId}`,
|
|
136
|
+
"--format=value(name)",
|
|
137
|
+
],
|
|
138
|
+
importId: `projects/${projectId}/managedZones/${name}`,
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
"gcp:dns/recordSet:RecordSet": {
|
|
143
|
+
label: "DNS record",
|
|
144
|
+
target(inputs, projectId) {
|
|
145
|
+
const { managedZone: zone, name, type } = inputs;
|
|
146
|
+
if (typeof zone !== "string" || !/^[a-z][a-z0-9-]{0,62}$/.test(zone)) return null;
|
|
147
|
+
if (typeof name !== "string" || !/^[a-z0-9.-]{1,253}\.$/.test(name)) return null;
|
|
148
|
+
if (typeof type !== "string" || !/^[A-Z]{1,10}$/.test(type)) return null;
|
|
149
|
+
if ((inputs.project ?? projectId) !== projectId) return null;
|
|
150
|
+
return {
|
|
151
|
+
display: `${name} ${type}`,
|
|
152
|
+
describeArgs: [
|
|
153
|
+
"dns",
|
|
154
|
+
"record-sets",
|
|
155
|
+
"describe",
|
|
156
|
+
name,
|
|
157
|
+
`--zone=${zone}`,
|
|
158
|
+
`--type=${type}`,
|
|
159
|
+
`--project=${projectId}`,
|
|
160
|
+
"--format=value(name)",
|
|
161
|
+
],
|
|
162
|
+
importId: `projects/${projectId}/managedZones/${zone}/rrsets/${name}/${type}`,
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
// #1328 — the gcp-firebase data gateway: the stack declares it under a
|
|
167
|
+
// fixed name (stack-contract.ts fixedCloudRunServiceName), and the converge
|
|
168
|
+
// creates the same service on installs whose stack predates it. Only a
|
|
169
|
+
// service the stack names explicitly is a candidate: an auto-named one has
|
|
170
|
+
// no name until Pulumi picks it.
|
|
171
|
+
"gcp:cloudrunv2/service:Service": {
|
|
172
|
+
label: "Cloud Run service",
|
|
173
|
+
target(inputs, projectId) {
|
|
174
|
+
const name = inputs.name;
|
|
175
|
+
const location = inputs.location;
|
|
176
|
+
if (typeof name !== "string" || !/^[a-z][a-z0-9-]{0,47}[a-z0-9]$/.test(name)) return null;
|
|
177
|
+
if (typeof location !== "string" || !/^[a-z]+-[a-z]+\d+$/.test(location)) return null;
|
|
178
|
+
const project = inputs.project ?? projectId;
|
|
179
|
+
if (project !== projectId) return null;
|
|
180
|
+
return {
|
|
181
|
+
display: `${name} (${location})`,
|
|
182
|
+
describeArgs: [
|
|
183
|
+
"run",
|
|
184
|
+
"services",
|
|
185
|
+
"describe",
|
|
186
|
+
name,
|
|
187
|
+
`--region=${location}`,
|
|
188
|
+
`--project=${projectId}`,
|
|
189
|
+
"--format=value(metadata.name)",
|
|
190
|
+
],
|
|
191
|
+
importId: `projects/${projectId}/locations/${location}/services/${name}`,
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
// #1329 — the staging database and its login: created by the converge on
|
|
196
|
+
// an install whose stack predates them (staging-database.ts), declared by
|
|
197
|
+
// the stack with literal names. Only those two names are candidates.
|
|
198
|
+
"gcp:sql/database:Database": {
|
|
199
|
+
label: "Cloud SQL database",
|
|
200
|
+
target(inputs, projectId) {
|
|
201
|
+
const { instance, name } = inputs;
|
|
202
|
+
if (name !== CONVERGE_CREATED_SQL.database) return null;
|
|
203
|
+
if (typeof instance !== "string" || !/^[a-z][a-z0-9-]{0,97}$/.test(instance)) return null;
|
|
204
|
+
if ((inputs.project ?? projectId) !== projectId) return null;
|
|
205
|
+
return {
|
|
206
|
+
display: `${name} (${instance})`,
|
|
207
|
+
describeArgs: [
|
|
208
|
+
"sql",
|
|
209
|
+
"databases",
|
|
210
|
+
"describe",
|
|
211
|
+
name,
|
|
212
|
+
`--instance=${instance}`,
|
|
213
|
+
`--project=${projectId}`,
|
|
214
|
+
"--format=value(name)",
|
|
215
|
+
],
|
|
216
|
+
importId: `projects/${projectId}/instances/${instance}/databases/${name}`,
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
"gcp:sql/user:User": {
|
|
221
|
+
label: "Cloud SQL user",
|
|
222
|
+
target(inputs, projectId) {
|
|
223
|
+
const { instance, name } = inputs;
|
|
224
|
+
if (name !== CONVERGE_CREATED_SQL.user) return null;
|
|
225
|
+
if (typeof instance !== "string" || !/^[a-z][a-z0-9-]{0,97}$/.test(instance)) return null;
|
|
226
|
+
if ((inputs.project ?? projectId) !== projectId) return null;
|
|
227
|
+
return {
|
|
228
|
+
display: `${name} (${instance})`,
|
|
229
|
+
describeArgs: [
|
|
230
|
+
"sql",
|
|
231
|
+
"users",
|
|
232
|
+
"describe",
|
|
233
|
+
name,
|
|
234
|
+
`--instance=${instance}`,
|
|
235
|
+
`--project=${projectId}`,
|
|
236
|
+
"--format=value(name)",
|
|
237
|
+
],
|
|
238
|
+
// The provider's import id for a Postgres built-in user.
|
|
239
|
+
importId: `${projectId}/${instance}/${name}`,
|
|
240
|
+
};
|
|
241
|
+
},
|
|
242
|
+
},
|
|
105
243
|
};
|
|
106
244
|
|
|
107
245
|
/** 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,11 @@ 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
|
+
/**
|
|
61
|
+
* #1338 / #1329 — as the superuser, before an upgrade's migrations: the
|
|
62
|
+
* staging database with its gateway login, and both gateway logins' limits.
|
|
63
|
+
*/
|
|
64
|
+
applyPreMigrationRoles(): Promise<ConvergeOutcome>;
|
|
60
65
|
/** The Compose file for `images` from config.json. */
|
|
61
66
|
renderCompose(images: ReleaseImages): string;
|
|
62
67
|
/** Re-write the Caddyfile (config + domains table) and reload Caddy (#891). */
|
|
@@ -417,12 +422,15 @@ function pruneVerifiedClis(caeloDir: string, keep: readonly (string | undefined)
|
|
|
417
422
|
}
|
|
418
423
|
}
|
|
419
424
|
|
|
420
|
-
/**
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
425
|
+
/**
|
|
426
|
+
* The argv that starts the target release's `agent apply` with `bun` at
|
|
427
|
+
* `execPath`: the verified install (agent/cli-provenance.ts) — never
|
|
428
|
+
* `bun x`, which would fetch an unverified copy — and the digests the signed
|
|
429
|
+
* manifest names, the MCP-Auth-Proxy's only when it names one (#1328).
|
|
430
|
+
*/
|
|
431
|
+
export function applyArgv(execPath: string, request: AgentApplyRequest): string[] {
|
|
432
|
+
return [
|
|
433
|
+
execPath,
|
|
426
434
|
request.cliPath,
|
|
427
435
|
"agent",
|
|
428
436
|
"apply",
|
|
@@ -436,7 +444,15 @@ async function spawnApply(request: AgentApplyRequest): Promise<AgentApplyOutcome
|
|
|
436
444
|
request.digests.admin,
|
|
437
445
|
"--gateway-digest",
|
|
438
446
|
request.digests.gateway,
|
|
447
|
+
...(request.digests["mcp-auth-proxy"]
|
|
448
|
+
? ["--mcp-auth-proxy-digest", request.digests["mcp-auth-proxy"]]
|
|
449
|
+
: []),
|
|
439
450
|
];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** Start the target release's `agent apply` and wait for it. */
|
|
454
|
+
async function spawnApply(request: AgentApplyRequest): Promise<AgentApplyOutcome> {
|
|
455
|
+
const argv = applyArgv(process.execPath, request);
|
|
440
456
|
try {
|
|
441
457
|
const proc = Bun.spawn(argv, {
|
|
442
458
|
stdout: "pipe",
|
|
@@ -504,16 +520,25 @@ const applyArgs = z.object({
|
|
|
504
520
|
trigger: z.enum(PLATFORM_UPDATE_TRIGGERS),
|
|
505
521
|
adminDigest: digest,
|
|
506
522
|
gatewayDigest: digest,
|
|
523
|
+
// #1328 — an agent of a release before the MCP-Auth-Proxy starts this
|
|
524
|
+
// without it; the signed manifest the apply reads names it anyway.
|
|
525
|
+
mcpAuthProxyDigest: digest.optional(),
|
|
507
526
|
});
|
|
508
527
|
|
|
509
|
-
|
|
510
|
-
|
|
528
|
+
/** The `agent apply` flags {@link applyArgv} writes, validated. */
|
|
529
|
+
export function parseApplyArgs(argv: readonly string[]) {
|
|
530
|
+
return applyArgs.safeParse({
|
|
511
531
|
request: flag(argv, "request"),
|
|
512
532
|
version: flag(argv, "version"),
|
|
513
533
|
trigger: flag(argv, "trigger"),
|
|
514
534
|
adminDigest: flag(argv, "admin-digest"),
|
|
515
535
|
gatewayDigest: flag(argv, "gateway-digest"),
|
|
536
|
+
mcpAuthProxyDigest: flag(argv, "mcp-auth-proxy-digest"),
|
|
516
537
|
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async function agentApply(argv: readonly string[], ctx: AgentCliContext): Promise<number> {
|
|
541
|
+
const parsed = parseApplyArgs(argv);
|
|
517
542
|
if (!parsed.success) {
|
|
518
543
|
console.error(
|
|
519
544
|
`agent apply is started by the update agent, not by hand (${parsed.error.issues.map((i) => i.path.join(".")).join(", ")}).`,
|
|
@@ -539,13 +564,18 @@ async function agentApply(argv: readonly string[], ctx: AgentCliContext): Promis
|
|
|
539
564
|
upgradeSelfHosted(
|
|
540
565
|
{
|
|
541
566
|
version: args.version,
|
|
542
|
-
expectedDigests: {
|
|
567
|
+
expectedDigests: {
|
|
568
|
+
admin: args.adminDigest,
|
|
569
|
+
gateway: args.gatewayDigest,
|
|
570
|
+
...(args.mcpAuthProxyDigest ? { "mcp-auth-proxy": args.mcpAuthProxyDigest } : {}),
|
|
571
|
+
},
|
|
543
572
|
},
|
|
544
573
|
{
|
|
545
574
|
caeloDir: ctx.caeloDir,
|
|
546
575
|
runner: bunHostRunner,
|
|
547
576
|
renderCompose: (images) => ctx.renderCompose(images),
|
|
548
577
|
convergeRoles: () => ctx.convergeRoles(),
|
|
578
|
+
applyPreMigrationRoles: () => ctx.applyPreMigrationRoles(),
|
|
549
579
|
refreshCaddy: () => ctx.refreshCaddy(),
|
|
550
580
|
log,
|
|
551
581
|
},
|
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
|
|
|
@@ -61,8 +63,9 @@ describe("Compose — Caddy picks up new redirects (#587)", () => {
|
|
|
61
63
|
const yaml = generateDockerCompose({
|
|
62
64
|
domain: "example.com",
|
|
63
65
|
postgresPassword: "pgpw",
|
|
64
|
-
rolePasswords: { admin: "a", public: "p" },
|
|
66
|
+
rolePasswords: { admin: "a", public: "p", publicStaging: "ps" },
|
|
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", () => {
|
package/src/caddy.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* Per-vhost shape:
|
|
12
12
|
* <hostname> {
|
|
13
13
|
* # admin → reverse_proxy <adminUpstream>
|
|
14
|
+
* # mcp → reverse_proxy <mcpAuthProxyUpstream> (#1328: the
|
|
15
|
+
* # MCP-Auth-Proxy on its own host, never the site's origin)
|
|
14
16
|
* # public → /api/* → <gatewayUpstream>; everything else is the
|
|
15
17
|
* # static site (try_files + file_server, 404 → /404.html)
|
|
16
18
|
* }
|
|
@@ -68,7 +70,12 @@ import {
|
|
|
68
70
|
|
|
69
71
|
export interface CaddyDomainSpec {
|
|
70
72
|
readonly hostname: string;
|
|
71
|
-
|
|
73
|
+
/**
|
|
74
|
+
* `mcp` (#1328) is the MCP-Auth-Proxy's own host: remote MCP's sign-in
|
|
75
|
+
* pages and its forwarder must never share an origin with the site, whose
|
|
76
|
+
* module JS could otherwise read them same-origin or toss their cookies.
|
|
77
|
+
*/
|
|
78
|
+
readonly kind: "admin" | "public" | "mcp";
|
|
72
79
|
readonly env: "production" | "staging";
|
|
73
80
|
}
|
|
74
81
|
|
|
@@ -89,6 +96,8 @@ export interface CaddyfileSpec {
|
|
|
89
96
|
readonly adminUpstream: string;
|
|
90
97
|
/** `host:port` of the API gateway as Caddy reaches it (Compose: `caelo-gateway:8090`). */
|
|
91
98
|
readonly gatewayUpstream: string;
|
|
99
|
+
/** `host:port` of the MCP-Auth-Proxy as Caddy reaches it (Compose: `caelo-mcp-auth-proxy:8095`). */
|
|
100
|
+
readonly mcpAuthProxyUpstream: string;
|
|
92
101
|
readonly domains: ReadonlyArray<CaddyDomainSpec>;
|
|
93
102
|
}
|
|
94
103
|
|
|
@@ -97,12 +106,25 @@ export function selfHostedAdminHost(domain: string): string {
|
|
|
97
106
|
return `admin.${domain}`;
|
|
98
107
|
}
|
|
99
108
|
|
|
109
|
+
/** #1328 — the host the MCP-Auth-Proxy of a self-hosted install answers on. */
|
|
110
|
+
export function selfHostedMcpHost(domain: string): string {
|
|
111
|
+
return `mcp.${domain}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
100
114
|
/**
|
|
101
115
|
* The vhosts every self-hosted install serves before an Owner adds domains
|
|
102
116
|
* in the admin: the admin on `admin.<domain>`, the live site on `<domain>`,
|
|
103
|
-
* the staging site on its own host. The admin needs its own host — it
|
|
104
|
-
*
|
|
105
|
-
*
|
|
117
|
+
* the staging site on its own host. The admin needs its own host — it routes
|
|
118
|
+
* from `/` (`/setup`, `/login`, …), so it cannot share one with the public
|
|
119
|
+
* site, and Caddy refuses two site blocks for the same address.
|
|
120
|
+
*
|
|
121
|
+
* #1328 — the MCP-Auth-Proxy's vhost (`mcp.<domain>`, kind `mcp`) is not
|
|
122
|
+
* seeded: remote MCP needs an identity proxy in front of the admin (§11.B),
|
|
123
|
+
* which a self-hosted install does not have yet, so the proxy runs
|
|
124
|
+
* unexposed on the Compose network (E14 a: provisioned, fixed 404) — the
|
|
125
|
+
* same honest refusal as on aws and azure. Seeding it would make Caddy
|
|
126
|
+
* fetch and retry an ACME certificate for a host nobody has a DNS record
|
|
127
|
+
* for. The vhost comes with remote MCP on self-hosted.
|
|
106
128
|
*/
|
|
107
129
|
export function selfHostedSeedDomains(domain: string, stagingDomain: string): CaddyDomainSpec[] {
|
|
108
130
|
return [
|
|
@@ -154,6 +176,7 @@ export function generateCaddyfile(spec: CaddyfileSpec): string {
|
|
|
154
176
|
assertSafeCaddyToken("stagingSiteRoot", spec.stagingSiteRoot, "token");
|
|
155
177
|
assertSafeCaddyToken("adminUpstream", spec.adminUpstream, "token");
|
|
156
178
|
assertSafeCaddyToken("gatewayUpstream", spec.gatewayUpstream, "token");
|
|
179
|
+
assertSafeCaddyToken("mcpAuthProxyUpstream", spec.mcpAuthProxyUpstream, "token");
|
|
157
180
|
|
|
158
181
|
const hostnames = new Set<string>();
|
|
159
182
|
for (const d of spec.domains) {
|
|
@@ -200,6 +223,15 @@ function vhost(d: CaddyDomainSpec, spec: CaddyfileSpec): string {
|
|
|
200
223
|
return `${d.hostname} {${noindex}
|
|
201
224
|
reverse_proxy ${spec.adminUpstream}
|
|
202
225
|
}
|
|
226
|
+
`;
|
|
227
|
+
}
|
|
228
|
+
if (d.kind === "mcp") {
|
|
229
|
+
// Everything on this host goes to the proxy: it answers its own 404s.
|
|
230
|
+
return `${d.hostname} {
|
|
231
|
+
header X-Robots-Tag "noindex"
|
|
232
|
+
header X-Content-Type-Options "${NOSNIFF}"
|
|
233
|
+
reverse_proxy ${spec.mcpAuthProxyUpstream}
|
|
234
|
+
}
|
|
203
235
|
`;
|
|
204
236
|
}
|
|
205
237
|
// public — API routes go to the gateway, the rest is the static site.
|