@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/stacks/aws/index.ts
CHANGED
|
@@ -27,7 +27,11 @@
|
|
|
27
27
|
* is to wire those env vars to AWS-native resources.
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
AWS_RDS_CA_BUNDLE,
|
|
32
|
+
cloudFrontRedirectFunctionCode,
|
|
33
|
+
verifyFullQuery,
|
|
34
|
+
} from "@caelo-cms/shared";
|
|
31
35
|
import * as aws from "@pulumi/aws";
|
|
32
36
|
import * as awsx from "@pulumi/awsx";
|
|
33
37
|
import * as pulumi from "@pulumi/pulumi";
|
|
@@ -171,9 +175,15 @@ const dbParameters = new aws.rds.ParameterGroup(`${namePrefix}-pg-params`, {
|
|
|
171
175
|
family: "postgres16",
|
|
172
176
|
parameters: [
|
|
173
177
|
{ name: "max_connections", value: String(maxConnections), applyMethod: "pending-reboot" },
|
|
178
|
+
// #1338 — refuse plaintext; every client connects with verify-full.
|
|
179
|
+
{ name: "rds.force_ssl", value: "1", applyMethod: "pending-reboot" },
|
|
174
180
|
],
|
|
175
181
|
});
|
|
176
182
|
|
|
183
|
+
// #1338 — verify-full against Amazon's RDS CA bundle (shipped in every image,
|
|
184
|
+
// shared database-tls.ts); the RDS certificate names the endpoint host.
|
|
185
|
+
const RDS_TLS = verifyFullQuery(AWS_RDS_CA_BUNDLE);
|
|
186
|
+
|
|
177
187
|
const db = new aws.rds.Instance(`${namePrefix}-pg`, {
|
|
178
188
|
engine: "postgres",
|
|
179
189
|
engineVersion: "16.4",
|
|
@@ -196,14 +206,12 @@ const db = new aws.rds.Instance(`${namePrefix}-pg`, {
|
|
|
196
206
|
const adminDatabaseUrl = pulumi
|
|
197
207
|
.all([db.address, db.port, postgresPassword])
|
|
198
208
|
.apply(
|
|
199
|
-
([host, port, pw]) =>
|
|
200
|
-
`postgresql://caelo_admin:${pw}@${host}:${port}/cms_admin?sslmode=require`,
|
|
209
|
+
([host, port, pw]) => `postgresql://caelo_admin:${pw}@${host}:${port}/cms_admin?${RDS_TLS}`,
|
|
201
210
|
);
|
|
202
211
|
const publicDatabaseUrl = pulumi
|
|
203
212
|
.all([db.address, db.port, publicRolePassword])
|
|
204
213
|
.apply(
|
|
205
|
-
([host, port, pw]) =>
|
|
206
|
-
`postgresql://public_role:${pw}@${host}:${port}/cms_public?sslmode=require`,
|
|
214
|
+
([host, port, pw]) => `postgresql://public_role:${pw}@${host}:${port}/cms_public?${RDS_TLS}`,
|
|
207
215
|
);
|
|
208
216
|
|
|
209
217
|
// === 4. S3 buckets ===
|
|
@@ -450,9 +458,12 @@ const cluster = new aws.ecs.Cluster(`${namePrefix}-ecs`, {
|
|
|
450
458
|
settings: [{ name: "containerInsights", value: "enabled" }],
|
|
451
459
|
});
|
|
452
460
|
|
|
453
|
-
// #
|
|
454
|
-
//
|
|
455
|
-
//
|
|
461
|
+
// #1328 — the MCP-Auth-Proxy (remote MCP's sign-in service) is a service of
|
|
462
|
+
// its own on every provider, with its own task role that may read NO secret.
|
|
463
|
+
// Remote MCP rides Google IAP (gcp stacks only), so on AWS it would run with
|
|
464
|
+
// CAELO_REMOTE_MCP=unavailable and answer a fixed 404. Like the admin and
|
|
465
|
+
// gateway it has no task definition yet: the service lands with #749, in the
|
|
466
|
+
// same shape (own origin, zero minimum tasks, no database env).
|
|
456
467
|
//
|
|
457
468
|
// One task role per service (#635): the gateway's may read only the
|
|
458
469
|
// public_role password, never a secret that unlocks a cms_admin login
|
|
@@ -482,18 +493,23 @@ function taskRole(service: AwsTaskService): aws.iam.Role {
|
|
|
482
493
|
],
|
|
483
494
|
}),
|
|
484
495
|
});
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
.
|
|
490
|
-
|
|
496
|
+
// A service that reads no secret gets no secrets policy at all (an empty
|
|
497
|
+
// policy is malformed, see secretsReadPolicy).
|
|
498
|
+
if (AWS_SERVICE_SECRETS[service].length > 0) {
|
|
499
|
+
new aws.iam.RolePolicy(`${prefix}-task-secrets`, {
|
|
500
|
+
role: role.id,
|
|
501
|
+
policy: pulumi
|
|
502
|
+
.all(AWS_SERVICE_SECRETS[service].map((name) => secretArns[name]))
|
|
503
|
+
.apply(secretsReadPolicy),
|
|
504
|
+
});
|
|
505
|
+
}
|
|
491
506
|
return role;
|
|
492
507
|
}
|
|
493
508
|
|
|
494
509
|
const taskRoles: Record<AwsTaskService, aws.iam.Role> = {
|
|
495
510
|
admin: taskRole("admin"),
|
|
496
511
|
gateway: taskRole("gateway"),
|
|
512
|
+
"mcp-auth-proxy": taskRole("mcp-auth-proxy"),
|
|
497
513
|
};
|
|
498
514
|
|
|
499
515
|
const execRole = new aws.iam.Role(`${namePrefix}-exec-role`, {
|
|
@@ -563,6 +579,8 @@ export const rdsAddressOut = db.address;
|
|
|
563
579
|
// The task roles the admin-side and gateway task definitions run as.
|
|
564
580
|
export const adminTaskRoleArnOut = taskRoles.admin.arn;
|
|
565
581
|
export const gatewayTaskRoleArnOut = taskRoles.gateway.arn;
|
|
582
|
+
// #1328 — the role the MCP-Auth-Proxy task definition will run as (#749).
|
|
583
|
+
export const mcpAuthProxyTaskRoleArnOut = taskRoles["mcp-auth-proxy"].arn;
|
|
566
584
|
// #1254 — the pool env the admin and gateway task definitions set.
|
|
567
585
|
export const poolBudgetEnvOut = poolBudget.env;
|
|
568
586
|
|
package/stacks/azure/index.ts
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* 4. Azure Front Door Standard serving the static website on the primary
|
|
14
14
|
* domain with a managed certificate — files served as files, no compute
|
|
15
15
|
* on the request path (CLAUDE.md §11.B Tier 1).
|
|
16
|
-
* 5. Azure Container Apps (admin/gateway/orchestrator/runner
|
|
16
|
+
* 5. Azure Container Apps (admin/gateway/orchestrator/runner, and the
|
|
17
|
+
* MCP-Auth-Proxy without any secret, #1328),
|
|
17
18
|
* mounted with Key Vault secrets via managed identity.
|
|
18
19
|
* 6. Key Vault entries (postgres-password, csrf-secret, cookie-secret,
|
|
19
20
|
* anthropic-api-key, resend-api-key).
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
* env vars wired by this stack.
|
|
35
36
|
*/
|
|
36
37
|
|
|
38
|
+
import { verifyFullQuery } from "@caelo-cms/shared";
|
|
37
39
|
import * as azure from "@pulumi/azure-native";
|
|
38
40
|
import * as pulumi from "@pulumi/pulumi";
|
|
39
41
|
import type { CloudAdapterOutputs, DnsRecord } from "../../dist/adapter.js";
|
|
@@ -44,6 +46,9 @@ import {
|
|
|
44
46
|
ADMIN_MEMORY_DEFAULT,
|
|
45
47
|
azureContainerAppResources,
|
|
46
48
|
GATEWAY_TRUSTED_PROXY_HOPS,
|
|
49
|
+
MCP_AUTH_PROXY_TRUSTED_PROXY_HOPS,
|
|
50
|
+
REMOTE_MCP_MODE,
|
|
51
|
+
REMOTE_MCP_MODE_ENV,
|
|
47
52
|
TRUSTED_PROXY_HOPS_ENV,
|
|
48
53
|
} from "../../dist/stack-contract.js";
|
|
49
54
|
import {
|
|
@@ -191,18 +196,23 @@ const _pgPublic = new azure.dbforpostgresql.Database(`${namePrefix}-cms-public-d
|
|
|
191
196
|
charset: "UTF8",
|
|
192
197
|
});
|
|
193
198
|
|
|
199
|
+
// #1338 — verify-full to the server's FQDN. Flexible Server's certificate chains
|
|
200
|
+
// to public roots (DigiCert Global Root G2, Microsoft RSA Root CA 2017) that
|
|
201
|
+
// Bun trusts out of the box, so no sslrootcert (shared database-tls.ts).
|
|
202
|
+
const AZURE_TLS = verifyFullQuery();
|
|
203
|
+
|
|
194
204
|
const adminDatabaseUrl = pulumi
|
|
195
205
|
.all([pgServer.fullyQualifiedDomainName, postgresPassword])
|
|
196
|
-
.apply(([host, pw]) => `postgresql://caelo_admin:${pw}@${host}:5432/cms_admin
|
|
206
|
+
.apply(([host, pw]) => `postgresql://caelo_admin:${pw}@${host}:5432/cms_admin?${AZURE_TLS}`);
|
|
197
207
|
// What the admin connects to cms_public with (DDL, migrations, moderation).
|
|
198
208
|
const publicAdminDatabaseUrl = pulumi
|
|
199
209
|
.all([pgServer.fullyQualifiedDomainName, postgresPassword])
|
|
200
|
-
.apply(([host, pw]) => `postgresql://caelo_admin:${pw}@${host}:5432/cms_public
|
|
210
|
+
.apply(([host, pw]) => `postgresql://caelo_admin:${pw}@${host}:5432/cms_public?${AZURE_TLS}`);
|
|
201
211
|
// The gateway's only login (#613, #635): public_role on cms_public. It has
|
|
202
212
|
// no cms_admin login at all (CLAUDE.md §2).
|
|
203
213
|
const publicDatabaseUrl = pulumi
|
|
204
214
|
.all([pgServer.fullyQualifiedDomainName, publicRolePassword])
|
|
205
|
-
.apply(([host, pw]) => `postgresql://public_role:${pw}@${host}:5432/cms_public
|
|
215
|
+
.apply(([host, pw]) => `postgresql://public_role:${pw}@${host}:5432/cms_public?${AZURE_TLS}`);
|
|
206
216
|
|
|
207
217
|
// === 4. Storage account + containers ===
|
|
208
218
|
const storage = new azure.storage.StorageAccount(`${namePrefix}-st`, {
|
|
@@ -437,6 +447,12 @@ interface ContainerAppArgs {
|
|
|
437
447
|
readonly extraEnv?: ReadonlyArray<{ name: string; value: pulumi.Input<string> }>;
|
|
438
448
|
/** Defaults to 0.5 vCPU / 1 GiB. */
|
|
439
449
|
readonly resources?: { readonly cpu: number; readonly memory: string };
|
|
450
|
+
/**
|
|
451
|
+
* #1328 — a service that reads nothing of the install: no CAELO_ENV, no
|
|
452
|
+
* media URL, only `CAELO_PROVIDER` plus {@link extraEnv}, and zero
|
|
453
|
+
* minimum replicas in every environment (E14 a of #763).
|
|
454
|
+
*/
|
|
455
|
+
readonly isolated?: true;
|
|
440
456
|
}
|
|
441
457
|
|
|
442
458
|
function containerApp(args: ContainerAppArgs): azure.app.ContainerApp {
|
|
@@ -454,7 +470,10 @@ function containerApp(args: ContainerAppArgs): azure.app.ContainerApp {
|
|
|
454
470
|
},
|
|
455
471
|
},
|
|
456
472
|
template: {
|
|
457
|
-
scale: {
|
|
473
|
+
scale: {
|
|
474
|
+
minReplicas: args.isolated || env !== "production" ? 0 : 1,
|
|
475
|
+
maxReplicas: MAX_REPLICAS,
|
|
476
|
+
},
|
|
458
477
|
containers: [
|
|
459
478
|
{
|
|
460
479
|
name: args.serviceName,
|
|
@@ -462,16 +481,18 @@ function containerApp(args: ContainerAppArgs): azure.app.ContainerApp {
|
|
|
462
481
|
image:
|
|
463
482
|
cfg.get(`image-${args.serviceName}`) ??
|
|
464
483
|
`${namePrefix}.azurecr.io/${args.serviceName}:latest`,
|
|
465
|
-
env:
|
|
466
|
-
{ name: "CAELO_PROVIDER", value: "azure" },
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
484
|
+
env: args.isolated
|
|
485
|
+
? [{ name: "CAELO_PROVIDER", value: "azure" }, ...(args.extraEnv ?? [])]
|
|
486
|
+
: [
|
|
487
|
+
{ name: "CAELO_PROVIDER", value: "azure" },
|
|
488
|
+
{ name: "CAELO_ENV", value: env },
|
|
489
|
+
...args.databaseEnv,
|
|
490
|
+
{
|
|
491
|
+
name: "MEDIA_STORAGE_URL",
|
|
492
|
+
value: pulumi.interpolate`https://${storage.name}.blob.core.windows.net/media`,
|
|
493
|
+
},
|
|
494
|
+
...(args.extraEnv ?? []),
|
|
495
|
+
],
|
|
475
496
|
resources: args.resources ?? { cpu: 0.5, memory: "1.0Gi" },
|
|
476
497
|
},
|
|
477
498
|
],
|
|
@@ -499,11 +520,29 @@ const _gatewayApp = containerApp({
|
|
|
499
520
|
...poolBudget.env.gateway,
|
|
500
521
|
],
|
|
501
522
|
// Which X-Forwarded-For entry is the visitor (api-gateway client-ip.ts).
|
|
502
|
-
// #806 — no remote-MCP env: remote MCP rides Google IAP (gcp stacks
|
|
503
|
-
// only); unset, the gateway answers /api/mcp/* with 503
|
|
504
|
-
// remote_mcp_not_configured and the admin reports it unavailable.
|
|
505
523
|
extraEnv: [{ name: TRUSTED_PROXY_HOPS_ENV, value: String(GATEWAY_TRUSTED_PROXY_HOPS.azure) }],
|
|
506
524
|
});
|
|
525
|
+
// #1328 — the MCP-Auth-Proxy, its own Container App and so its own FQDN:
|
|
526
|
+
// remote MCP's sign-in pages never share an origin with the site's module
|
|
527
|
+
// JS. Remote MCP needs the admin behind Google IAP, which Azure does not
|
|
528
|
+
// have, so it runs with CAELO_REMOTE_MCP=unavailable and answers everything
|
|
529
|
+
// but its health check (`/api/mcp/health`) with one fixed 404; the admin
|
|
530
|
+
// reports remote MCP as not available here. No database URL, no secret
|
|
531
|
+
// and no managed identity, because there is nothing it may read. Same
|
|
532
|
+
// shape as on every provider (E11 of #763), at zero replicas until a
|
|
533
|
+
// request comes.
|
|
534
|
+
const _mcpAuthProxyApp = containerApp({
|
|
535
|
+
serviceName: "mcp-auth-proxy",
|
|
536
|
+
databaseEnv: [],
|
|
537
|
+
isolated: true,
|
|
538
|
+
resources: { cpu: 0.25, memory: "0.5Gi" },
|
|
539
|
+
extraEnv: [
|
|
540
|
+
{ name: REMOTE_MCP_MODE_ENV, value: REMOTE_MCP_MODE.azure },
|
|
541
|
+
{ name: TRUSTED_PROXY_HOPS_ENV, value: String(MCP_AUTH_PROXY_TRUSTED_PROXY_HOPS.azure) },
|
|
542
|
+
// The app's ingress targets 8080 (containerApp above).
|
|
543
|
+
{ name: "PORT", value: "8080" },
|
|
544
|
+
],
|
|
545
|
+
});
|
|
507
546
|
const _orchestratorApp = containerApp({
|
|
508
547
|
serviceName: "orchestrator",
|
|
509
548
|
databaseEnv: adminDatabaseEnv,
|
package/stacks/gcp/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
gcpBucketName,
|
|
28
28
|
gcpSecretId,
|
|
29
29
|
MCP_IAP_SERVICE_ACCOUNT_ID,
|
|
30
|
+
mcpAuthProxyServiceAccountId,
|
|
30
31
|
runServiceAccountId,
|
|
31
32
|
staticPublisherServiceAccountId,
|
|
32
33
|
} from "../../dist/gcp-names.js";
|
|
@@ -36,18 +37,21 @@ import {
|
|
|
36
37
|
DEFAULT_MAX_CONNECTIONS,
|
|
37
38
|
stackPoolBudget,
|
|
38
39
|
} from "../../dist/pool-budget-stack.js";
|
|
39
|
-
import { mcpArmorRuleArgs } from "../../dist/remote-mcp-edge.js";
|
|
40
40
|
import {
|
|
41
41
|
ADMIN_MEMORY_DEFAULT,
|
|
42
42
|
adminEnvContract,
|
|
43
43
|
adminMediaVolume,
|
|
44
44
|
adminMediaVolumeTemplate,
|
|
45
45
|
CLOUD_RUN_IGNORE_CHANGES,
|
|
46
|
+
CLOUD_SQL_PSA_CONNECTION_TYPE,
|
|
47
|
+
CLOUD_SQL_SERVER_CA_MODE,
|
|
48
|
+
CLOUD_SQL_SSL_MODE,
|
|
46
49
|
type CloudRunEnvVar,
|
|
47
50
|
type CloudRunSlug,
|
|
51
|
+
cloudSqlDnsZoneName,
|
|
52
|
+
cloudSqlPrivateDnsName,
|
|
48
53
|
databaseUrls,
|
|
49
54
|
defaultStagingHost,
|
|
50
|
-
GATEWAY_TIMEOUT_SECONDS,
|
|
51
55
|
gatewayEnvContract,
|
|
52
56
|
REMOTE_MCP_IDENTITY_CONFIG,
|
|
53
57
|
type RuntimeSecret,
|
|
@@ -331,6 +335,11 @@ const sqlInstance = new gcp.sql.DatabaseInstance(
|
|
|
331
335
|
ipConfiguration: {
|
|
332
336
|
ipv4Enabled: false,
|
|
333
337
|
privateNetwork: network.id,
|
|
338
|
+
// #1338 — the shared CA names the instance's DNS name in its server
|
|
339
|
+
// certificate, so every client verifies it (sslmode=verify-full);
|
|
340
|
+
// unencrypted connections are refused outright.
|
|
341
|
+
serverCaMode: CLOUD_SQL_SERVER_CA_MODE,
|
|
342
|
+
sslMode: CLOUD_SQL_SSL_MODE,
|
|
334
343
|
},
|
|
335
344
|
deletionProtectionEnabled: deletionProtection,
|
|
336
345
|
},
|
|
@@ -374,10 +383,50 @@ const cmsPublicDb = new gcp.sql.Database(
|
|
|
374
383
|
opts,
|
|
375
384
|
);
|
|
376
385
|
|
|
386
|
+
// #1338 — the instance's DNS name, resolved to its private IP by a private
|
|
387
|
+
// zone in the VPC (Cloud SQL assigns the name, it does not resolve it).
|
|
388
|
+
// Every client dials this name with sslmode=verify-full; the certificate
|
|
389
|
+
// names no IP. `cms-provision upgrade` converges the same zone and record
|
|
390
|
+
// through gcloud (cloudsql-tls.ts); the installer adopts them (#1079).
|
|
391
|
+
const sqlDnsName = sqlInstance.dnsNames.apply((names) => {
|
|
392
|
+
const name = cloudSqlPrivateDnsName(names);
|
|
393
|
+
if (!name) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
`Cloud SQL instance has no ${CLOUD_SQL_PSA_CONNECTION_TYPE} DNS name; it needs the shared server CA (${CLOUD_SQL_SERVER_CA_MODE}) for verify-full connections (#1338)`,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
return name;
|
|
399
|
+
});
|
|
400
|
+
const sqlDnsZone = new gcp.dns.ManagedZone(
|
|
401
|
+
`${namePrefix}-pg-dns`,
|
|
402
|
+
{
|
|
403
|
+
name: cloudSqlDnsZoneName(env),
|
|
404
|
+
dnsName: pulumi.interpolate`${sqlDnsName}.`,
|
|
405
|
+
visibility: "private",
|
|
406
|
+
privateVisibilityConfig: { networks: [{ networkUrl: network.id }] },
|
|
407
|
+
description: `Caelo ${env}: the Cloud SQL instance's DNS name inside the VPC (#1338)`,
|
|
408
|
+
},
|
|
409
|
+
opts,
|
|
410
|
+
);
|
|
411
|
+
const sqlDnsRecord = new gcp.dns.RecordSet(
|
|
412
|
+
`${namePrefix}-pg-dns-a`,
|
|
413
|
+
{
|
|
414
|
+
// Literal names, not the zone's outputs: the installer can then adopt
|
|
415
|
+
// a record an upgrade created (adopt-existing.ts) from the preview.
|
|
416
|
+
managedZone: cloudSqlDnsZoneName(env),
|
|
417
|
+
name: pulumi.interpolate`${sqlDnsName}.`,
|
|
418
|
+
type: "A",
|
|
419
|
+
ttl: 300,
|
|
420
|
+
rrdatas: [sqlInstance.privateIpAddress],
|
|
421
|
+
},
|
|
422
|
+
{ ...opts, dependsOn: [sqlDnsZone] },
|
|
423
|
+
);
|
|
424
|
+
|
|
377
425
|
// Password-less connection URLs (stack-contract.ts databaseUrls); the
|
|
378
426
|
// services get the password as a Secret Manager reference
|
|
379
|
-
// (SERVICE_SECRET_ENV), so no revision ever carries it.
|
|
380
|
-
|
|
427
|
+
// (SERVICE_SECRET_ENV), so no revision ever carries it. Tied to the record
|
|
428
|
+
// so no revision boots before its database host resolves.
|
|
429
|
+
const dbUrls = pulumi.all([sqlDnsName, sqlDnsRecord.id]).apply(([host]) => databaseUrls(host));
|
|
381
430
|
|
|
382
431
|
// Reference pgPublicUser so Pulumi knows it's part of the dependency
|
|
383
432
|
// graph (Cloud Run env-var deps don't see this directly).
|
|
@@ -475,9 +524,24 @@ const gatewaySa = new gcp.serviceaccount.Account(
|
|
|
475
524
|
},
|
|
476
525
|
opts,
|
|
477
526
|
);
|
|
527
|
+
// #1328 — the MCP-Auth-Proxy's own SA and (below) its secretAccessor on its
|
|
528
|
+
// two secrets, nothing else. The proxy's service, NEG, backend and Cloud
|
|
529
|
+
// Armor policy are the CLI's (remote-mcp.ts ensureRemoteMcpEdge, after the
|
|
530
|
+
// first `up`), like the jobs: the converge creates the same on existing
|
|
531
|
+
// installs, and the URL map's path rules are the CLI's already.
|
|
532
|
+
const mcpAuthProxySa = new gcp.serviceaccount.Account(
|
|
533
|
+
`${namePrefix}-mcp-auth-proxy-sa`,
|
|
534
|
+
{
|
|
535
|
+
accountId: mcpAuthProxyServiceAccountId(env),
|
|
536
|
+
displayName: `Caelo ${env} MCP-Auth-Proxy`,
|
|
537
|
+
createIgnoreAlreadyExists: true,
|
|
538
|
+
},
|
|
539
|
+
opts,
|
|
540
|
+
);
|
|
478
541
|
const serviceSa: Record<CloudRunSlug, gcp.serviceaccount.Account> = {
|
|
479
542
|
admin: runSa,
|
|
480
543
|
gateway: gatewaySa,
|
|
544
|
+
"mcp-auth-proxy": mcpAuthProxySa,
|
|
481
545
|
};
|
|
482
546
|
|
|
483
547
|
// secretAccessor for each service's SA on exactly the secrets its env reads.
|
|
@@ -486,6 +550,7 @@ const serviceSa: Record<CloudRunSlug, gcp.serviceaccount.Account> = {
|
|
|
486
550
|
const secretBindings: Record<CloudRunSlug, gcp.secretmanager.SecretIamMember[]> = {
|
|
487
551
|
admin: [],
|
|
488
552
|
gateway: [],
|
|
553
|
+
"mcp-auth-proxy": [],
|
|
489
554
|
};
|
|
490
555
|
for (const binding of runtimeSecretBindings()) {
|
|
491
556
|
secretBindings[binding.service].push(
|
|
@@ -662,7 +727,8 @@ function imageTag(service: string): pulumi.Output<string> {
|
|
|
662
727
|
}
|
|
663
728
|
|
|
664
729
|
interface CloudRunArgs {
|
|
665
|
-
|
|
730
|
+
/** The MCP-Auth-Proxy is CLI-owned on this stack (see mcpAuthProxySa). */
|
|
731
|
+
readonly serviceName: Exclude<CloudRunSlug, "mcp-auth-proxy">;
|
|
666
732
|
readonly minInstances: number;
|
|
667
733
|
readonly maxInstances: number;
|
|
668
734
|
readonly memory: string;
|
|
@@ -670,8 +736,7 @@ interface CloudRunArgs {
|
|
|
670
736
|
* Per-request timeout. Cloud Run's default is 300s (5min), which is too
|
|
671
737
|
* short for the admin's SSE chat-stream — a multi-tool compose-page session
|
|
672
738
|
* regularly runs 5–15 min. Pass an explicit "<n>s" Pulumi-style duration
|
|
673
|
-
* string.
|
|
674
|
-
* streams to the admin.
|
|
739
|
+
* string.
|
|
675
740
|
*/
|
|
676
741
|
readonly timeout?: string;
|
|
677
742
|
/**
|
|
@@ -816,18 +881,12 @@ const gatewaySvc = cloudRunService({
|
|
|
816
881
|
minInstances: gatewayMinInstances,
|
|
817
882
|
maxInstances: gatewayMaxInstances,
|
|
818
883
|
memory: "512Mi",
|
|
819
|
-
// #
|
|
820
|
-
//
|
|
821
|
-
|
|
822
|
-
// shorter limit on serverless NEG backends.
|
|
823
|
-
timeout: `${GATEWAY_TIMEOUT_SECONDS}s`,
|
|
884
|
+
// Visitor requests only (#1328 moved remote MCP's long streams to the
|
|
885
|
+
// MCP-Auth-Proxy): Cloud Run's default.
|
|
886
|
+
timeout: "300s",
|
|
824
887
|
// public_role on cms_public only: no cms_admin login of any role
|
|
825
888
|
// (#613, #635, CLAUDE.md §2).
|
|
826
|
-
contractEnv: [
|
|
827
|
-
...gatewayEnvContract(envContractInputs),
|
|
828
|
-
...remoteMcpEnv.gateway,
|
|
829
|
-
...poolBudget.env.gateway,
|
|
830
|
-
],
|
|
889
|
+
contractEnv: [...gatewayEnvContract(envContractInputs), ...poolBudget.env.gateway],
|
|
831
890
|
});
|
|
832
891
|
|
|
833
892
|
// =========================================================================
|
|
@@ -838,6 +897,9 @@ const gatewaySvc = cloudRunService({
|
|
|
838
897
|
// admin.caelo-cms.com. Three backends routed by Host header / path:
|
|
839
898
|
// - host=caelo-cms.com, default → static bucket (Cloud CDN)
|
|
840
899
|
// - host=caelo-cms.com, path=/api/* → gateway Cloud Run (Cloud Armor)
|
|
900
|
+
// - host=caelo-cms.com, path=/api/mcp/*, /.well-known/oauth-*
|
|
901
|
+
// → MCP-Auth-Proxy Cloud Run (its own
|
|
902
|
+
// Cloud Armor; CLI-owned, #1328)
|
|
841
903
|
// - host=admin.caelo-cms.com, default → admin Cloud Run (IAP-gated)
|
|
842
904
|
//
|
|
843
905
|
// CLAUDE.md §11.B Tier 2: admin behind cloud-native identity proxy. IAP
|
|
@@ -860,12 +922,10 @@ const wafPolicy = new gcp.compute.SecurityPolicy(
|
|
|
860
922
|
{
|
|
861
923
|
description: `Caelo ${env} Cloud Armor — rate limit + OWASP`,
|
|
862
924
|
rules: [
|
|
863
|
-
// #
|
|
864
|
-
//
|
|
865
|
-
//
|
|
866
|
-
//
|
|
867
|
-
// why; `cms-provision upgrade` adds the same rule to existing installs.
|
|
868
|
-
mcpArmorRuleArgs(),
|
|
925
|
+
// #1328 — no remote-MCP rule here any more: the URL map sends
|
|
926
|
+
// /api/mcp/* to the MCP-Auth-Proxy's backend, whose own policy carries
|
|
927
|
+
// it (remote-mcp-edge.ts, created by the CLI; `upgrade` drops the old
|
|
928
|
+
// rule from this policy).
|
|
869
929
|
// Rate limit per source IP — 100 req / min sliding window.
|
|
870
930
|
{
|
|
871
931
|
action: "rate_based_ban",
|