@caelo-cms/provisioning 0.10.61 → 0.10.64
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/caddy/Caddyfile.production +7 -0
- package/caddy/Caddyfile.staging +7 -0
- package/dist/admin-internal-answer.d.ts +12 -0
- package/dist/admin-internal-answer.d.ts.map +1 -0
- package/dist/admin-internal-answer.js +32 -0
- package/dist/admin-internal-answer.js.map +1 -0
- package/dist/admin-internal-google.d.ts +4 -0
- package/dist/admin-internal-google.d.ts.map +1 -0
- package/dist/admin-internal-google.js +161 -0
- package/dist/admin-internal-google.js.map +1 -0
- package/dist/admin-internal-self-hosted.d.ts +30 -0
- package/dist/admin-internal-self-hosted.d.ts.map +1 -0
- package/dist/admin-internal-self-hosted.js +92 -0
- package/dist/admin-internal-self-hosted.js.map +1 -0
- package/dist/admin-internal.d.ts +33 -12
- package/dist/admin-internal.d.ts.map +1 -1
- package/dist/admin-internal.js +27 -159
- package/dist/admin-internal.js.map +1 -1
- package/dist/agent/command.d.ts +7 -0
- package/dist/agent/command.d.ts.map +1 -1
- package/dist/agent/command.js +1 -0
- package/dist/agent/command.js.map +1 -1
- package/dist/agent/lock.d.ts +13 -1
- package/dist/agent/lock.d.ts.map +1 -1
- package/dist/agent/lock.js +62 -5
- package/dist/agent/lock.js.map +1 -1
- package/dist/agent/run.d.ts +5 -1
- package/dist/agent/run.d.ts.map +1 -1
- package/dist/agent/run.js +8 -1
- package/dist/agent/run.js.map +1 -1
- package/dist/agent-db.js +192 -90
- package/dist/caddy.d.ts.map +1 -1
- package/dist/caddy.js +11 -1
- 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 +95 -38
- package/dist/cli.js.map +1 -1
- package/dist/compose.d.ts +7 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +5 -0
- package/dist/compose.js.map +1 -1
- package/dist/converge-job-gcp.d.ts.map +1 -1
- package/dist/converge-job-gcp.js +10 -7
- package/dist/converge-job-gcp.js.map +1 -1
- package/dist/converge-job-preflight.d.ts +21 -0
- package/dist/converge-job-preflight.d.ts.map +1 -0
- package/dist/converge-job-preflight.js +111 -0
- package/dist/converge-job-preflight.js.map +1 -0
- package/dist/converge-job.d.ts +8 -2
- package/dist/converge-job.d.ts.map +1 -1
- package/dist/converge-job.js +14 -0
- package/dist/converge-job.js.map +1 -1
- package/dist/gateway-credentials.d.ts +5 -3
- package/dist/gateway-credentials.d.ts.map +1 -1
- package/dist/gateway-credentials.js +5 -8
- package/dist/gateway-credentials.js.map +1 -1
- package/dist/gcloud.d.ts +29 -0
- package/dist/gcloud.d.ts.map +1 -1
- package/dist/gcloud.js +66 -0
- package/dist/gcloud.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 +10 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-job.d.ts +3 -1
- package/dist/install-job.d.ts.map +1 -1
- package/dist/install-job.js.map +1 -1
- package/dist/install-state.d.ts +10 -0
- package/dist/install-state.d.ts.map +1 -1
- package/dist/install-state.js +12 -0
- package/dist/install-state.js.map +1 -1
- package/dist/lifecycle.d.ts +12 -0
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +113 -28
- package/dist/lifecycle.js.map +1 -1
- package/dist/mcp-iap.d.ts +2 -0
- package/dist/mcp-iap.d.ts.map +1 -1
- package/dist/mcp-iap.js +50 -17
- package/dist/mcp-iap.js.map +1 -1
- package/dist/owner-reset.d.ts +16 -19
- package/dist/owner-reset.d.ts.map +1 -1
- package/dist/owner-reset.js +92 -19
- package/dist/owner-reset.js.map +1 -1
- package/dist/preview-plan.d.ts +71 -0
- package/dist/preview-plan.d.ts.map +1 -0
- package/dist/preview-plan.js +168 -0
- package/dist/preview-plan.js.map +1 -0
- package/dist/public-role-isolation.d.ts +2 -0
- package/dist/public-role-isolation.d.ts.map +1 -1
- package/dist/public-role-isolation.js +9 -3
- package/dist/public-role-isolation.js.map +1 -1
- package/dist/release-contract.d.ts.map +1 -1
- package/dist/release-contract.js +4 -0
- package/dist/release-contract.js.map +1 -1
- package/dist/self-hosted-internal-secret.d.ts +27 -0
- package/dist/self-hosted-internal-secret.d.ts.map +1 -0
- package/dist/self-hosted-internal-secret.js +69 -0
- package/dist/self-hosted-internal-secret.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 +6 -2
- package/dist/self-hosted-release.js.map +1 -1
- package/dist/self-hosted-upgrade.d.ts +13 -1
- package/dist/self-hosted-upgrade.d.ts.map +1 -1
- package/dist/self-hosted-upgrade.js +26 -2
- package/dist/self-hosted-upgrade.js.map +1 -1
- package/dist/stack-converge.d.ts +5 -2
- package/dist/stack-converge.d.ts.map +1 -1
- package/dist/stack-converge.js +73 -8
- package/dist/stack-converge.js.map +1 -1
- package/dist/staging-admin.d.ts +4 -1
- package/dist/staging-admin.d.ts.map +1 -1
- package/dist/staging-admin.js.map +1 -1
- package/dist/state-backend.d.ts +3 -2
- package/dist/state-backend.d.ts.map +1 -1
- package/dist/state-backend.js +3 -2
- package/dist/state-backend.js.map +1 -1
- package/dist/state-command.d.ts +17 -0
- package/dist/state-command.d.ts.map +1 -1
- package/dist/state-command.js +34 -5
- package/dist/state-command.js.map +1 -1
- package/dist/state-migrate.d.ts +14 -3
- package/dist/state-migrate.d.ts.map +1 -1
- package/dist/state-migrate.js +31 -7
- package/dist/state-migrate.js.map +1 -1
- package/dist/state-pulumi.d.ts +0 -2
- package/dist/state-pulumi.d.ts.map +1 -1
- package/dist/state-pulumi.js +19 -20
- package/dist/state-pulumi.js.map +1 -1
- package/dist/static-security-headers.d.ts +86 -0
- package/dist/static-security-headers.d.ts.map +1 -0
- package/dist/static-security-headers.js +126 -0
- package/dist/static-security-headers.js.map +1 -0
- package/dist/static-svg-gcp.d.ts +28 -0
- package/dist/static-svg-gcp.d.ts.map +1 -0
- package/dist/static-svg-gcp.js +106 -0
- package/dist/static-svg-gcp.js.map +1 -0
- package/dist/upgrade/app-roll.d.ts +3 -0
- package/dist/upgrade/app-roll.d.ts.map +1 -1
- package/dist/upgrade/app-roll.js +12 -5
- package/dist/upgrade/app-roll.js.map +1 -1
- package/dist/upgrade/backup.d.ts +2 -0
- package/dist/upgrade/backup.d.ts.map +1 -1
- package/dist/upgrade/backup.js +5 -4
- package/dist/upgrade/backup.js.map +1 -1
- package/dist/upgrade/converge.d.ts +7 -0
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +19 -7
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/upgrade/gcp-lookup.d.ts +38 -3
- package/dist/upgrade/gcp-lookup.d.ts.map +1 -1
- package/dist/upgrade/gcp-lookup.js +31 -9
- package/dist/upgrade/gcp-lookup.js.map +1 -1
- package/dist/upgrade/health.d.ts +31 -5
- package/dist/upgrade/health.d.ts.map +1 -1
- package/dist/upgrade/health.js +114 -25
- package/dist/upgrade/health.js.map +1 -1
- package/dist/upgrade/release-images.d.ts +17 -0
- package/dist/upgrade/release-images.d.ts.map +1 -1
- package/dist/upgrade/release-images.js +23 -12
- package/dist/upgrade/release-images.js.map +1 -1
- package/dist/upgrade/reporter.d.ts +2 -0
- package/dist/upgrade/reporter.d.ts.map +1 -1
- package/dist/upgrade/reporter.js +1 -0
- package/dist/upgrade/reporter.js.map +1 -1
- package/dist/upgrade/upgrade.d.ts +11 -1
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +5 -2
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-gcp.d.ts +12 -2
- package/dist/upgrader-gcp.d.ts.map +1 -1
- package/dist/upgrader-gcp.js +22 -13
- package/dist/upgrader-gcp.js.map +1 -1
- package/dist/upgrader-iam-preflight.d.ts +16 -3
- package/dist/upgrader-iam-preflight.d.ts.map +1 -1
- package/dist/upgrader-iam-preflight.js +9 -4
- package/dist/upgrader-iam-preflight.js.map +1 -1
- package/dist/upgrader-job.d.ts.map +1 -1
- package/dist/upgrader-job.js +10 -8
- package/dist/upgrader-job.js.map +1 -1
- package/dist/upgrader-reporter.d.ts.map +1 -1
- package/dist/upgrader-reporter.js +1 -0
- package/dist/upgrader-reporter.js.map +1 -1
- package/dist/wizards/gcp-pulumi.d.ts +70 -23
- package/dist/wizards/gcp-pulumi.d.ts.map +1 -1
- package/dist/wizards/gcp-pulumi.js +118 -39
- package/dist/wizards/gcp-pulumi.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +67 -11
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +2 -2
- package/src/admin-internal-answer.ts +43 -0
- package/src/admin-internal-google.ts +187 -0
- package/src/admin-internal-self-hosted.test.ts +70 -0
- package/src/admin-internal-self-hosted.ts +128 -0
- package/src/admin-internal.ts +59 -174
- package/src/admin-memory.test.ts +1 -0
- package/src/agent/command.ts +3 -0
- package/src/agent/lock-config.test.ts +57 -2
- package/src/agent/lock.ts +68 -5
- package/src/agent/run.test.ts +18 -1
- package/src/agent/run.ts +17 -6
- package/src/caddy-redirects.test.ts +1 -0
- package/src/caddy.ts +12 -0
- package/src/cli-routing.ts +1 -1
- package/src/cli.ts +102 -44
- package/src/compose.ts +12 -0
- package/src/converge-job-gcp.ts +16 -11
- package/src/converge-job-preflight.test.ts +108 -0
- package/src/converge-job-preflight.ts +124 -0
- package/src/converge-job.test.ts +50 -0
- package/src/converge-job.ts +38 -2
- package/src/gateway-credentials.test.ts +4 -3
- package/src/gateway-credentials.ts +6 -10
- package/src/gateway-env-isolation.test.ts +1 -0
- package/src/gcloud.test.ts +93 -1
- package/src/gcloud.ts +82 -0
- package/src/index.test.ts +38 -1
- package/src/infra-contract.test.ts +4 -3
- package/src/infra-contract.ts +10 -1
- package/src/install-job.ts +5 -2
- package/src/install-state.ts +13 -0
- package/src/lifecycle.ts +134 -28
- package/src/mcp-iap.test.ts +66 -5
- package/src/mcp-iap.ts +55 -17
- package/src/owner-reset-providers.test.ts +370 -0
- package/src/owner-reset.test.ts +2 -2
- package/src/owner-reset.ts +108 -19
- package/src/preview-plan.test.ts +233 -0
- package/src/preview-plan.ts +226 -0
- package/src/public-role-isolation.test.ts +13 -2
- package/src/public-role-isolation.ts +14 -4
- package/src/release-contract.ts +4 -0
- package/src/self-hosted-compose.test.ts +25 -0
- package/src/self-hosted-internal-secret.test.ts +61 -0
- package/src/self-hosted-internal-secret.ts +80 -0
- package/src/self-hosted-release.ts +10 -3
- package/src/self-hosted-upgrade.test.ts +35 -1
- package/src/self-hosted-upgrade.ts +40 -3
- package/src/stack-contract.test.ts +1 -0
- package/src/stack-converge.test.ts +89 -5
- package/src/stack-converge.ts +81 -8
- package/src/staging-admin.test.ts +1 -1
- package/src/staging-admin.ts +2 -1
- package/src/state-backend.ts +3 -2
- package/src/state-command.test.ts +31 -0
- package/src/state-command.ts +35 -8
- package/src/state-migrate.test.ts +58 -7
- package/src/state-migrate.ts +48 -10
- package/src/state-pulumi.ts +20 -21
- package/src/static-security-headers.test.ts +124 -0
- package/src/static-security-headers.ts +154 -0
- package/src/static-svg-gcp.test.ts +133 -0
- package/src/static-svg-gcp.ts +140 -0
- package/src/upgrade/app-roll.ts +18 -7
- package/src/upgrade/backup.ts +14 -5
- package/src/upgrade/converge.ts +25 -7
- package/src/upgrade/gcp-lookup.test.ts +80 -1
- package/src/upgrade/gcp-lookup.ts +61 -11
- package/src/upgrade/health.test.ts +132 -8
- package/src/upgrade/health.ts +169 -31
- package/src/upgrade/release-images.test.ts +17 -0
- package/src/upgrade/release-images.ts +26 -14
- package/src/upgrade/reporter.ts +3 -0
- package/src/upgrade/upgrade.test.ts +137 -8
- package/src/upgrade/upgrade.ts +14 -2
- package/src/upgrader-gcp.ts +28 -13
- package/src/upgrader-iam-preflight.test.ts +6 -3
- package/src/upgrader-iam-preflight.ts +26 -5
- package/src/upgrader-job.ts +14 -6
- package/src/upgrader-reporter.ts +1 -0
- package/src/wizards/gcp-pulumi.test.ts +1 -1
- package/src/wizards/gcp-pulumi.ts +147 -38
- package/src/wizards/gcp.ts +89 -9
- package/src/wizards/stack-work-dir.test.ts +175 -0
- package/stacks/aws/index.ts +40 -0
- package/stacks/azure/index.ts +22 -1
- package/stacks/gcp/README.md +4 -0
- package/stacks/gcp/index.ts +6 -2
- package/stacks/gcp/Pulumi.production.yaml +0 -16
package/src/wizards/gcp.ts
CHANGED
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
linkBilling,
|
|
60
60
|
listBillingAccounts,
|
|
61
61
|
PROVISIONER_ROLE_LIST,
|
|
62
|
+
projectDisplayName,
|
|
62
63
|
projectExists,
|
|
63
64
|
REQUIRED_API_LIST,
|
|
64
65
|
serviceAccountExists,
|
|
@@ -95,6 +96,7 @@ import {
|
|
|
95
96
|
resolveOperatorAccessTarget,
|
|
96
97
|
} from "../operator-access.js";
|
|
97
98
|
import { ensureOpsBackend } from "../ops-backend.js";
|
|
99
|
+
import { formatPlan, type PlannedStep, planGate, writePlanFile } from "../preview-plan.js";
|
|
98
100
|
import {
|
|
99
101
|
convergeKeylessProvisioner,
|
|
100
102
|
PROVISIONER_ACCOUNT_ID,
|
|
@@ -117,7 +119,7 @@ import {
|
|
|
117
119
|
resolveBackend,
|
|
118
120
|
} from "../state-backend.js";
|
|
119
121
|
import { acquireStateLock, GcsStateStore } from "../state-store.js";
|
|
120
|
-
import { resolveGcpResourceName } from "../upgrade/gcp-lookup.js";
|
|
122
|
+
import { lookupFailure, resolveGcpResourceName } from "../upgrade/gcp-lookup.js";
|
|
121
123
|
import {
|
|
122
124
|
releaseVersionForDigest,
|
|
123
125
|
resolveOpsImageRef,
|
|
@@ -127,7 +129,12 @@ import { ensureUpgraderJob, resolveUpgraderTarget } from "../upgrader-setup.js";
|
|
|
127
129
|
import { ensureUrlMapRedirectsAccess } from "../url-map-redirects-access.js";
|
|
128
130
|
import { estimateGcpCost } from "./gcp-cost.js";
|
|
129
131
|
import { promptProjectId } from "./gcp-prompts.js";
|
|
130
|
-
import {
|
|
132
|
+
import {
|
|
133
|
+
GCP_STACK_KNOB_DEFAULTS,
|
|
134
|
+
PulumiUpDeclinedError,
|
|
135
|
+
pulumiUpGcp,
|
|
136
|
+
readStackOutput,
|
|
137
|
+
} from "./gcp-pulumi.js";
|
|
131
138
|
|
|
132
139
|
export interface GcpWizardOpts {
|
|
133
140
|
installId: string;
|
|
@@ -177,7 +184,7 @@ export async function runGcpWizard(opts: GcpWizardOpts): Promise<void> {
|
|
|
177
184
|
if (meta) writeMetadata(installId, { ...meta, projectId, region });
|
|
178
185
|
|
|
179
186
|
// === 3. Project create ===
|
|
180
|
-
await stepProjectCreate(installId, projectId);
|
|
187
|
+
await stepProjectCreate(installId, projectId, opts.domain);
|
|
181
188
|
|
|
182
189
|
// === 4. Billing link ===
|
|
183
190
|
await stepBillingLink(installId, projectId, opts.nonInteractive);
|
|
@@ -396,6 +403,7 @@ export async function runGcpConverge(opts: GcpConvergeOpts): Promise<void> {
|
|
|
396
403
|
// v0.3.1 — route pulumi up at the right stack folder + use the
|
|
397
404
|
// matching config namespace.
|
|
398
405
|
provider,
|
|
406
|
+
nonInteractive: opts.nonInteractive,
|
|
399
407
|
});
|
|
400
408
|
recordImageDigests(installId, imageDigests);
|
|
401
409
|
|
|
@@ -692,7 +700,11 @@ async function stepRegion(
|
|
|
692
700
|
return choice as string;
|
|
693
701
|
}
|
|
694
702
|
|
|
695
|
-
async function stepProjectCreate(
|
|
703
|
+
async function stepProjectCreate(
|
|
704
|
+
installId: string,
|
|
705
|
+
projectId: string,
|
|
706
|
+
domain: string,
|
|
707
|
+
): Promise<void> {
|
|
696
708
|
const stepName = `project-create-${projectId}`;
|
|
697
709
|
if (isStepDone(installId, stepName)) {
|
|
698
710
|
log.success(`Project ${bold(projectId)} ${dim("(already created)")}`);
|
|
@@ -705,7 +717,7 @@ async function stepProjectCreate(installId: string, projectId: string): Promise<
|
|
|
705
717
|
}
|
|
706
718
|
const s = spinner();
|
|
707
719
|
s.start(`Creating GCP project ${projectId}...`);
|
|
708
|
-
const r = await createProject(projectId,
|
|
720
|
+
const r = await createProject(projectId, projectDisplayName(domain));
|
|
709
721
|
if (!r.ok) {
|
|
710
722
|
s.stop(red(`Failed: ${r.stderr.trim()}`));
|
|
711
723
|
log.error(
|
|
@@ -917,7 +929,11 @@ async function stepGrantBaseRoles(
|
|
|
917
929
|
async function stepPublicRoleIsolation(projectId: string): Promise<void> {
|
|
918
930
|
const s = spinner();
|
|
919
931
|
s.start("Removing public_role from the Cloud SQL superuser group...");
|
|
920
|
-
const r = await revokePublicRoleMemberships({
|
|
932
|
+
const r = await revokePublicRoleMemberships({
|
|
933
|
+
projectId,
|
|
934
|
+
env: GCP_STACK_ENV,
|
|
935
|
+
credentials: "operator",
|
|
936
|
+
});
|
|
921
937
|
if (r.status === "failed") {
|
|
922
938
|
s.stop(red(`Failed: ${r.error ?? ""}`));
|
|
923
939
|
log.error("Re-run the wizard once the cause is fixed (this step is idempotent).");
|
|
@@ -1161,7 +1177,7 @@ async function stepRemoteMcp(
|
|
|
1161
1177
|
projectId,
|
|
1162
1178
|
region,
|
|
1163
1179
|
);
|
|
1164
|
-
const [
|
|
1180
|
+
const [adminLookup, gatewayLookup] = [await name("admin"), await name("gateway")];
|
|
1165
1181
|
const failed = (error: string) => {
|
|
1166
1182
|
s.stop(yellow(`Remote MCP not prepared: ${error}`));
|
|
1167
1183
|
log.warn(
|
|
@@ -1169,7 +1185,17 @@ async function stepRemoteMcp(
|
|
|
1169
1185
|
);
|
|
1170
1186
|
return false;
|
|
1171
1187
|
};
|
|
1172
|
-
if (
|
|
1188
|
+
if (adminLookup.status !== "found") {
|
|
1189
|
+
return failed(
|
|
1190
|
+
lookupFailure(adminLookup, `caelo-${GCP_STACK_ENV}-admin* Cloud Run service`, "operator"),
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
if (gatewayLookup.status !== "found") {
|
|
1194
|
+
return failed(
|
|
1195
|
+
lookupFailure(gatewayLookup, `caelo-${GCP_STACK_ENV}-gateway* Cloud Run service`, "operator"),
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
const [admin, gateway] = [adminLookup.name, gatewayLookup.name];
|
|
1173
1199
|
const edge = await ensureRemoteMcpEdge({ provider, projectId, env: GCP_STACK_ENV, domain });
|
|
1174
1200
|
if (!edge.ok) return failed(edge.error);
|
|
1175
1201
|
const env = await ensureRemoteMcpIdentityEnv({
|
|
@@ -1436,6 +1462,50 @@ interface PulumiUpOpts {
|
|
|
1436
1462
|
imageDigests: ImageDigests;
|
|
1437
1463
|
/** v0.3.1 — provider variant; routes pulumi up at the right stack. */
|
|
1438
1464
|
provider?: "gcp" | "gcp-firebase";
|
|
1465
|
+
/** No terminal to ask (the install job, `--non-interactive`): see planGate. */
|
|
1466
|
+
nonInteractive: boolean;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
/**
|
|
1470
|
+
* The installer's review of a deployed stack's plan before `pulumi up`
|
|
1471
|
+
* (#737): the grouped list, the full diff in the install dir, and a y/N
|
|
1472
|
+
* for any replace or delete. A data-bearing one is asked about by name and
|
|
1473
|
+
* defaults to No; without a terminal it is refused (planGate).
|
|
1474
|
+
*
|
|
1475
|
+
* @returns whether `pulumi up` may apply the plan
|
|
1476
|
+
*/
|
|
1477
|
+
async function reviewPulumiPlan(
|
|
1478
|
+
installId: string,
|
|
1479
|
+
steps: readonly PlannedStep[],
|
|
1480
|
+
nonInteractive: boolean,
|
|
1481
|
+
): Promise<boolean> {
|
|
1482
|
+
const gate = planGate(steps, { interactive: !nonInteractive && process.stdin.isTTY === true });
|
|
1483
|
+
if (steps.length === 0) {
|
|
1484
|
+
log.info(dim("Preview: no changes planned."));
|
|
1485
|
+
return true;
|
|
1486
|
+
}
|
|
1487
|
+
const file = writePlanFile(installRoot(installId), "installer", steps);
|
|
1488
|
+
note([...formatPlan(steps), "", `Full diff: ${file}`].join("\n"), "pulumi up will change");
|
|
1489
|
+
if (gate.kind === "proceed") return true;
|
|
1490
|
+
if (gate.kind === "refuse") {
|
|
1491
|
+
log.error(
|
|
1492
|
+
red(
|
|
1493
|
+
`Nothing was applied: this plan replaces or deletes resources that hold the site's data, and an unattended run cannot confirm that. Read ${file}; if the change is intended, re-run the installer in a terminal and confirm it there.`,
|
|
1494
|
+
),
|
|
1495
|
+
);
|
|
1496
|
+
return false;
|
|
1497
|
+
}
|
|
1498
|
+
const answer = await confirm({
|
|
1499
|
+
message: gate.losesData
|
|
1500
|
+
? red("This plan DESTROYS the data named above. Apply it anyway?")
|
|
1501
|
+
: "This plan replaces or deletes the resources listed above. Apply it?",
|
|
1502
|
+
initialValue: false,
|
|
1503
|
+
});
|
|
1504
|
+
if (isCancel(answer) || answer !== true) {
|
|
1505
|
+
log.warn(`Nothing was applied. The plan is in ${file}.`);
|
|
1506
|
+
return false;
|
|
1507
|
+
}
|
|
1508
|
+
return true;
|
|
1439
1509
|
}
|
|
1440
1510
|
|
|
1441
1511
|
async function stepPulumiUp(installId: string, opts: PulumiUpOpts): Promise<void> {
|
|
@@ -1488,7 +1558,8 @@ async function stepPulumiUp(installId: string, opts: PulumiUpOpts): Promise<void
|
|
|
1488
1558
|
log.info(`Pulumi up — wall-clock 8–15 min (Cloud SQL is the long pole). Streaming progress...`);
|
|
1489
1559
|
|
|
1490
1560
|
let resourceCount = 0;
|
|
1491
|
-
let result: Awaited<ReturnType<typeof pulumiUpGcp
|
|
1561
|
+
let result: Awaited<ReturnType<typeof pulumiUpGcp>> | undefined;
|
|
1562
|
+
let declined = "";
|
|
1492
1563
|
try {
|
|
1493
1564
|
result = await pulumiUpGcp(
|
|
1494
1565
|
{
|
|
@@ -1514,10 +1585,18 @@ async function stepPulumiUp(installId: string, opts: PulumiUpOpts): Promise<void
|
|
|
1514
1585
|
// logs: silent — too noisy for a live progress UI; pulumi
|
|
1515
1586
|
// diagnostics will print on failure via the SDK's onOutput.
|
|
1516
1587
|
},
|
|
1588
|
+
(steps) => reviewPulumiPlan(installId, steps, opts.nonInteractive),
|
|
1517
1589
|
);
|
|
1590
|
+
} catch (e) {
|
|
1591
|
+
if (!(e instanceof PulumiUpDeclinedError)) throw e;
|
|
1592
|
+
declined = e.message;
|
|
1518
1593
|
} finally {
|
|
1519
1594
|
releaseLock();
|
|
1520
1595
|
}
|
|
1596
|
+
if (result === undefined) {
|
|
1597
|
+
cancel(declined);
|
|
1598
|
+
process.exit(1);
|
|
1599
|
+
}
|
|
1521
1600
|
process.stdout.write("\n");
|
|
1522
1601
|
log.success(
|
|
1523
1602
|
green(
|
|
@@ -1915,6 +1994,7 @@ async function readBootstrapUrl(installId: string, provider: string): Promise<st
|
|
|
1915
1994
|
const meta = readMetadata(installId);
|
|
1916
1995
|
if (!meta) throw new Error(`No install.json for install '${installId}'.`);
|
|
1917
1996
|
const value = await readStackOutput({
|
|
1997
|
+
installRoot: installRoot(installId),
|
|
1918
1998
|
backend: resolveBackend(meta, {
|
|
1919
1999
|
installRoot: installRoot(installId),
|
|
1920
2000
|
readPassphrase: () => readSecret(installId, "pulumi-passphrase"),
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* #736 — every install runs Pulumi in its own work dir, so two CLI runs for
|
|
5
|
+
* different installs of one provider never read each other's stack config.
|
|
6
|
+
* The last test drives the real `pulumi` CLI (skipped where it is not
|
|
7
|
+
* installed, as in CI): two installs set their config and run `up` at the
|
|
8
|
+
* same time, and each sees only its own.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { afterAll, describe, expect, it } from "bun:test";
|
|
12
|
+
import {
|
|
13
|
+
mkdirSync,
|
|
14
|
+
mkdtempSync,
|
|
15
|
+
readdirSync,
|
|
16
|
+
readFileSync,
|
|
17
|
+
realpathSync,
|
|
18
|
+
rmSync,
|
|
19
|
+
statSync,
|
|
20
|
+
writeFileSync,
|
|
21
|
+
} from "node:fs";
|
|
22
|
+
import { tmpdir } from "node:os";
|
|
23
|
+
import { join, resolve } from "node:path";
|
|
24
|
+
import * as pulumi from "@pulumi/pulumi/automation";
|
|
25
|
+
import { stackWorkDir } from "../install-state.js";
|
|
26
|
+
import {
|
|
27
|
+
assertStackProject,
|
|
28
|
+
packageStackDir,
|
|
29
|
+
prepareStackWorkDir,
|
|
30
|
+
workDirProjectFile,
|
|
31
|
+
} from "./gcp-pulumi.js";
|
|
32
|
+
|
|
33
|
+
const STACKS = resolve(import.meta.dir, "../../stacks");
|
|
34
|
+
const scratch = mkdtempSync(join(tmpdir(), "caelo-stack-work-dir-"));
|
|
35
|
+
afterAll(() => rmSync(scratch, { recursive: true, force: true }));
|
|
36
|
+
|
|
37
|
+
/** Every file under the package's stacks/ dir with its mtime. */
|
|
38
|
+
function packageStacksSnapshot(): Record<string, number> {
|
|
39
|
+
return Object.fromEntries(
|
|
40
|
+
readdirSync(STACKS, { recursive: true, withFileTypes: true })
|
|
41
|
+
.filter((e) => e.isFile())
|
|
42
|
+
.map((e) => {
|
|
43
|
+
const path = join(e.parentPath, e.name);
|
|
44
|
+
return [path, statSync(path).mtimeMs];
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe("workDirProjectFile", () => {
|
|
50
|
+
it("points main from the work dir at the package's program, keeping the rest", () => {
|
|
51
|
+
const yaml = "name: caelo-gcp\nruntime:\n name: nodejs\nmain: ./index.ts\ndescription: x\n";
|
|
52
|
+
expect(workDirProjectFile(yaml, "/pkg/stacks/gcp", "/home/o/.caelo-a/stack/gcp")).toBe(
|
|
53
|
+
"name: caelo-gcp\nruntime:\n name: nodejs\nmain: ../../../../../pkg/stacks/gcp/index.ts\ndescription: x\n",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("refuses a project file without exactly one main line", () => {
|
|
58
|
+
expect(() => workDirProjectFile("name: x\n", "/pkg", "/w")).toThrow(/exactly one/);
|
|
59
|
+
expect(() => workDirProjectFile("main: a.ts\nmain: b.ts\n", "/pkg", "/w")).toThrow(/found 2/);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("prepareStackWorkDir", () => {
|
|
64
|
+
for (const provider of ["gcp", "gcp-firebase"] as const) {
|
|
65
|
+
it(`gives two ${provider} installs separate work dirs and never writes into the package`, () => {
|
|
66
|
+
const before = packageStacksSnapshot();
|
|
67
|
+
const a = prepareStackWorkDir(join(scratch, "a"), provider);
|
|
68
|
+
const b = prepareStackWorkDir(join(scratch, "b"), provider);
|
|
69
|
+
expect(a).toBe(stackWorkDir(join(scratch, "a"), provider));
|
|
70
|
+
expect(a).not.toBe(b);
|
|
71
|
+
|
|
72
|
+
// Each install keeps its own settings file; preparing again (a new
|
|
73
|
+
// CLI version) leaves it alone.
|
|
74
|
+
writeFileSync(join(a, "Pulumi.production.yaml"), "config:\n x:project: a\n");
|
|
75
|
+
writeFileSync(join(b, "Pulumi.production.yaml"), "config:\n x:project: b\n");
|
|
76
|
+
prepareStackWorkDir(join(scratch, "a"), provider);
|
|
77
|
+
expect(readFileSync(join(a, "Pulumi.production.yaml"), "utf8")).toContain("project: a");
|
|
78
|
+
expect(readFileSync(join(b, "Pulumi.production.yaml"), "utf8")).toContain("project: b");
|
|
79
|
+
|
|
80
|
+
const project = readFileSync(join(a, "Pulumi.yaml"), "utf8");
|
|
81
|
+
const main = /^main: (\S+)$/m.exec(project)?.[1] ?? "";
|
|
82
|
+
expect(resolve(realpathSync(a), main)).toBe(
|
|
83
|
+
join(realpathSync(packageStackDir(provider)), "index.ts"),
|
|
84
|
+
);
|
|
85
|
+
expect(packageStacksSnapshot()).toEqual(before);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
it("the package ships no stack settings file (nothing for installs to share)", () => {
|
|
90
|
+
const settings = Object.keys(packageStacksSnapshot()).filter((p) =>
|
|
91
|
+
/Pulumi\.(?!yaml$)[^/]+\.ya?ml$/.test(p),
|
|
92
|
+
);
|
|
93
|
+
expect(settings).toEqual([]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
/** A stack whose config holds `project` (or nothing), for the guard. */
|
|
98
|
+
function stackWithProject(project: string | undefined): pulumi.Stack {
|
|
99
|
+
const fake: Pick<pulumi.Stack, "getConfig"> & { workspace: Pick<pulumi.Workspace, "workDir"> } = {
|
|
100
|
+
workspace: { workDir: "/home/o/.caelo-a/stack/gcp-firebase" },
|
|
101
|
+
getConfig: async (key) => {
|
|
102
|
+
if (project === undefined) throw new Error(`configuration key '${key}' not found`);
|
|
103
|
+
return { value: project, secret: false };
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
return fake as unknown as pulumi.Stack;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
describe("assertStackProject", () => {
|
|
110
|
+
it("passes when the stack config names the install's project", async () => {
|
|
111
|
+
await assertStackProject(stackWithProject("acme"), "gcp-firebase", "acme");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("refuses another install's project, naming both", async () => {
|
|
115
|
+
await expect(
|
|
116
|
+
assertStackProject(stackWithProject("searchviu-website"), "gcp-firebase", "caelo-site"),
|
|
117
|
+
).rejects.toThrow(/names project searchviu-website, but this install is caelo-site/);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("refuses a stack config without a project", async () => {
|
|
121
|
+
await expect(assertStackProject(stackWithProject(undefined), "gcp", "acme")).rejects.toThrow(
|
|
122
|
+
/names project \(none\)/,
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe.skipIf(!Bun.which("pulumi"))("two installs at once, real pulumi CLI", () => {
|
|
128
|
+
it("each install's up reads only its own config", async () => {
|
|
129
|
+
// A stand-in for the package's stack program; under the package so node
|
|
130
|
+
// resolves @pulumi/pulumi from it, as the real stacks do.
|
|
131
|
+
const pkg = mkdtempSync(join(import.meta.dir, "../../.stack-work-dir-test-"));
|
|
132
|
+
try {
|
|
133
|
+
const programDir = join(pkg, "stacks", "gcp-firebase");
|
|
134
|
+
mkdirSync(programDir, { recursive: true });
|
|
135
|
+
writeFileSync(
|
|
136
|
+
join(programDir, "Pulumi.yaml"),
|
|
137
|
+
"name: caelo-gcp-firebase\nruntime:\n name: nodejs\n options:\n typescript: true\nmain: ./index.ts\n",
|
|
138
|
+
);
|
|
139
|
+
writeFileSync(
|
|
140
|
+
join(programDir, "index.ts"),
|
|
141
|
+
'import * as pulumi from "@pulumi/pulumi";\nexport const project = new pulumi.Config("caelo-gcp-firebase").require("project");\n',
|
|
142
|
+
);
|
|
143
|
+
writeFileSync(
|
|
144
|
+
join(programDir, "tsconfig.json"),
|
|
145
|
+
readFileSync(join(STACKS, "gcp-firebase", "tsconfig.json")),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const run = async (install: string, project: string) => {
|
|
149
|
+
const workDir = prepareStackWorkDir(join(scratch, install), "gcp-firebase", programDir);
|
|
150
|
+
const state = join(scratch, install, "state");
|
|
151
|
+
mkdirSync(state, { recursive: true });
|
|
152
|
+
const stack = await pulumi.LocalWorkspace.createOrSelectStack(
|
|
153
|
+
{ stackName: "production", workDir },
|
|
154
|
+
{
|
|
155
|
+
envVars: {
|
|
156
|
+
PULUMI_BACKEND_URL: `file://${state}`,
|
|
157
|
+
PULUMI_CONFIG_PASSPHRASE: "test",
|
|
158
|
+
PULUMI_SKIP_UPDATE_CHECK: "true",
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
await stack.setConfig("caelo-gcp-firebase:project", { value: project });
|
|
163
|
+
await assertStackProject(stack, "gcp-firebase", project);
|
|
164
|
+
const up = await stack.up();
|
|
165
|
+
return up.outputs.project?.value;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const [a, b] = await Promise.all([run("real-a", "project-a"), run("real-b", "project-b")]);
|
|
169
|
+
expect([a, b]).toEqual(["project-a", "project-b"]);
|
|
170
|
+
expect(readdirSync(programDir).sort()).toEqual(["Pulumi.yaml", "index.ts", "tsconfig.json"]);
|
|
171
|
+
} finally {
|
|
172
|
+
rmSync(pkg, { recursive: true, force: true });
|
|
173
|
+
}
|
|
174
|
+
}, 180_000);
|
|
175
|
+
});
|
package/stacks/aws/index.ts
CHANGED
|
@@ -40,6 +40,10 @@ import {
|
|
|
40
40
|
} from "../../dist/aws-task-roles.js";
|
|
41
41
|
import { generateBootstrapToken } from "../../dist/bootstrap-token.js";
|
|
42
42
|
import { ADMIN_MEMORY_DEFAULT, memoryQuantityMiB } from "../../dist/stack-contract.js";
|
|
43
|
+
import {
|
|
44
|
+
CLOUDFRONT_SVG_BEHAVIORS,
|
|
45
|
+
cloudFrontHeadersPolicy,
|
|
46
|
+
} from "../../dist/static-security-headers.js";
|
|
43
47
|
|
|
44
48
|
const cfg = new pulumi.Config();
|
|
45
49
|
const domain = cfg.require("domain");
|
|
@@ -245,6 +249,19 @@ const redirectFunction = new aws.cloudfront.Function(`${namePrefix}-redirects`,
|
|
|
245
249
|
keyValueStoreAssociations: [redirectKvs.arn],
|
|
246
250
|
});
|
|
247
251
|
|
|
252
|
+
// === 6c. Security response headers (#891) ===
|
|
253
|
+
// nosniff on every response; the sandboxing CSP on every SVG, so an
|
|
254
|
+
// uploaded SVG opened directly runs no script on the site's origin
|
|
255
|
+
// (src/static-security-headers.ts).
|
|
256
|
+
const siteHeadersPolicy = new aws.cloudfront.ResponseHeadersPolicy(`${namePrefix}-site-headers`, {
|
|
257
|
+
name: `${namePrefix}-site-headers`,
|
|
258
|
+
...cloudFrontHeadersPolicy("site"),
|
|
259
|
+
});
|
|
260
|
+
const svgHeadersPolicy = new aws.cloudfront.ResponseHeadersPolicy(`${namePrefix}-svg-headers`, {
|
|
261
|
+
name: `${namePrefix}-svg-headers`,
|
|
262
|
+
...cloudFrontHeadersPolicy("svg"),
|
|
263
|
+
});
|
|
264
|
+
|
|
248
265
|
// === 7. CloudFront distribution ===
|
|
249
266
|
const distribution = new aws.cloudfront.Distribution(`${namePrefix}-cdn`, {
|
|
250
267
|
enabled: true,
|
|
@@ -273,8 +290,30 @@ const distribution = new aws.cloudfront.Distribution(`${namePrefix}-cdn`, {
|
|
|
273
290
|
defaultTtl: 60,
|
|
274
291
|
maxTtl: 86400,
|
|
275
292
|
functionAssociations: [{ eventType: "viewer-request", functionArn: redirectFunction.arn }],
|
|
293
|
+
responseHeadersPolicyId: siteHeadersPolicy.id,
|
|
276
294
|
},
|
|
277
295
|
orderedCacheBehaviors: [
|
|
296
|
+
// #891 — SVGs first (first match wins), each like the behavior it
|
|
297
|
+
// shadows except for the CSP policy.
|
|
298
|
+
...CLOUDFRONT_SVG_BEHAVIORS.map(({ pathPattern, targetOriginId }) => ({
|
|
299
|
+
pathPattern,
|
|
300
|
+
targetOriginId,
|
|
301
|
+
viewerProtocolPolicy: "redirect-to-https",
|
|
302
|
+
allowedMethods: ["GET", "HEAD"],
|
|
303
|
+
cachedMethods: ["GET", "HEAD"],
|
|
304
|
+
forwardedValues: { queryString: false, cookies: { forward: "none" } },
|
|
305
|
+
minTtl: 0,
|
|
306
|
+
...(targetOriginId === "media"
|
|
307
|
+
? { defaultTtl: 31536000, maxTtl: 31536000 }
|
|
308
|
+
: {
|
|
309
|
+
defaultTtl: 60,
|
|
310
|
+
maxTtl: 86400,
|
|
311
|
+
functionAssociations: [
|
|
312
|
+
{ eventType: "viewer-request", functionArn: redirectFunction.arn },
|
|
313
|
+
],
|
|
314
|
+
}),
|
|
315
|
+
responseHeadersPolicyId: svgHeadersPolicy.id,
|
|
316
|
+
})),
|
|
278
317
|
{
|
|
279
318
|
pathPattern: "/media/*",
|
|
280
319
|
targetOriginId: "media",
|
|
@@ -285,6 +324,7 @@ const distribution = new aws.cloudfront.Distribution(`${namePrefix}-cdn`, {
|
|
|
285
324
|
minTtl: 0,
|
|
286
325
|
defaultTtl: 31536000,
|
|
287
326
|
maxTtl: 31536000,
|
|
327
|
+
responseHeadersPolicyId: siteHeadersPolicy.id,
|
|
288
328
|
},
|
|
289
329
|
],
|
|
290
330
|
viewerCertificate: {
|
package/stacks/azure/index.ts
CHANGED
|
@@ -45,6 +45,10 @@ import {
|
|
|
45
45
|
GATEWAY_TRUSTED_PROXY_HOPS,
|
|
46
46
|
TRUSTED_PROXY_HOPS_ENV,
|
|
47
47
|
} from "../../dist/stack-contract.js";
|
|
48
|
+
import {
|
|
49
|
+
FRONT_DOOR_SECURITY_RULE_SET,
|
|
50
|
+
frontDoorSecurityRules,
|
|
51
|
+
} from "../../dist/static-security-headers.js";
|
|
48
52
|
|
|
49
53
|
const cfg = new pulumi.Config();
|
|
50
54
|
const domain = cfg.require("domain");
|
|
@@ -281,6 +285,23 @@ const redirectRuleSet = new azure.cdn.RuleSet(`${namePrefix}-fd-redirects`, {
|
|
|
281
285
|
ruleSetName: "caeloRedirects",
|
|
282
286
|
});
|
|
283
287
|
|
|
288
|
+
// #891 — the security response headers: nosniff on every response, the
|
|
289
|
+
// sandboxing CSP on every SVG (src/static-security-headers.ts). A set of
|
|
290
|
+
// its own: the deploy replaces the redirects set's rules wholesale.
|
|
291
|
+
const securityRuleSet = new azure.cdn.RuleSet(`${namePrefix}-fd-security`, {
|
|
292
|
+
resourceGroupName: rg.name,
|
|
293
|
+
profileName: frontDoor.name,
|
|
294
|
+
ruleSetName: FRONT_DOOR_SECURITY_RULE_SET,
|
|
295
|
+
});
|
|
296
|
+
for (const rule of frontDoorSecurityRules()) {
|
|
297
|
+
new azure.cdn.Rule(`${namePrefix}-fd-security-${rule.ruleName}`, {
|
|
298
|
+
resourceGroupName: rg.name,
|
|
299
|
+
profileName: frontDoor.name,
|
|
300
|
+
ruleSetName: securityRuleSet.name,
|
|
301
|
+
...rule,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
284
305
|
new azure.cdn.Route(
|
|
285
306
|
`${namePrefix}-fd-route`,
|
|
286
307
|
{
|
|
@@ -290,7 +311,7 @@ new azure.cdn.Route(
|
|
|
290
311
|
routeName: "static",
|
|
291
312
|
originGroup: { id: staticOriginGroup.id },
|
|
292
313
|
customDomains: [{ id: siteDomain.id }],
|
|
293
|
-
ruleSets: [{ id: redirectRuleSet.id }],
|
|
314
|
+
ruleSets: [{ id: redirectRuleSet.id }, { id: securityRuleSet.id }],
|
|
294
315
|
patternsToMatch: ["/*"],
|
|
295
316
|
// Without a cache configuration Front Door does not cache the route at
|
|
296
317
|
// all; static files are edge-cached per their Cache-Control (§11.B
|
package/stacks/gcp/README.md
CHANGED
|
@@ -66,6 +66,10 @@ pulumi stack output bootstrapUrlOut
|
|
|
66
66
|
|
|
67
67
|
Open after the domain's and the admin's DNS A records point at the load balancer IP and the managed certificate is active.
|
|
68
68
|
|
|
69
|
+
## Uploaded SVGs download instead of opening
|
|
70
|
+
|
|
71
|
+
Opening an SVG's URL on the live site (for example `https://<domain>/_assets/logo.<hash>.svg`) downloads the file. It still renders normally in pages. The other providers send a sandboxing CSP on SVG responses. This stack cannot: GCS serves only a fixed set of metadata headers, the backend bucket's custom headers apply to every file including pages, and the URL map's site path matcher holds the redirect rules, which cannot coexist with per-extension route rules. So the publisher stores SVGs with `Content-Disposition: attachment`, and the backend bucket adds `X-Content-Type-Options: nosniff` to every response (#891). `cms-provision upgrade` patches the SVGs already live and purges Cloud CDN once. If the upgrade warns that this step failed, the next Publish live rewrites every SVG. After that, purge the CDN with `gcloud compute url-maps invalidate-cdn-cache <url-map> --path="/*" --global`.
|
|
72
|
+
|
|
69
73
|
## v1 limitations (P15 review-pass items)
|
|
70
74
|
|
|
71
75
|
- **Container image build pipeline** — operators push via `gcloud builds submit` for v1. A unified `cms-provision build-images --provider gcp` lands later.
|
package/stacks/gcp/index.ts
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
STATIC_CDN_POLICY,
|
|
49
49
|
secretFileVolumesTemplate,
|
|
50
50
|
} from "../../dist/stack-contract.js";
|
|
51
|
+
import { GCP_STATIC_RESPONSE_HEADERS } from "../../dist/static-security-headers.js";
|
|
51
52
|
|
|
52
53
|
const cfg = new pulumi.Config();
|
|
53
54
|
const domain = cfg.require("domain");
|
|
@@ -870,14 +871,17 @@ const wafPolicy = new gcp.compute.SecurityPolicy(
|
|
|
870
871
|
);
|
|
871
872
|
|
|
872
873
|
// Tier 1 backend — static GCS bucket via Cloud CDN. The CDN policy (one-year
|
|
873
|
-
// client/edge TTL so immutable assets stay immutable, #555)
|
|
874
|
-
//
|
|
874
|
+
// client/edge TTL so immutable assets stay immutable, #555) and the response
|
|
875
|
+
// headers (#891: nosniff on every file; SVGs are stored as downloads by the
|
|
876
|
+
// publisher, see static-security-headers.ts) are shared with
|
|
877
|
+
// `cms-provision upgrade`, which applies them to existing installs.
|
|
875
878
|
const staticBackendBucket = new gcp.compute.BackendBucket(
|
|
876
879
|
`${namePrefix}-static-backend`,
|
|
877
880
|
{
|
|
878
881
|
bucketName: staticBucket.name,
|
|
879
882
|
enableCdn: true,
|
|
880
883
|
cdnPolicy: { ...STATIC_CDN_POLICY },
|
|
884
|
+
customResponseHeaders: [...GCP_STATIC_RESPONSE_HEADERS],
|
|
881
885
|
},
|
|
882
886
|
opts,
|
|
883
887
|
);
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
encryptionsalt: v1:mEv+SGjVDdU=:v1:1Tmd1g2Yh0KXxhp5:fGsA3LiAPntjAGUMORKO+B1gxQgIEg==
|
|
2
|
-
config:
|
|
3
|
-
caelo-gcp:adminMinInstances: "0"
|
|
4
|
-
caelo-gcp:anthropicApiKey:
|
|
5
|
-
secure: v1:/RCE2zS+Y8ZrTdcj:gIXucRKkT1vX+qg0yE8Uq3oIL+lHa23wLOheAKeR762d97ydAo332XwfteqJOgDg/scAtD2dict1NoNxNgxXpJnw1jltUi8fsF9vMt9elh2aV71eFB2oRGBlxIWbAgYdXqs/gfMxRtZ8r0dhYADuSCrtTQcI7GAFWg5EhA==
|
|
6
|
-
caelo-gcp:cloudSqlHa: "false"
|
|
7
|
-
caelo-gcp:cloudSqlTier: db-f1-micro
|
|
8
|
-
caelo-gcp:domain: caelo-cms.com
|
|
9
|
-
caelo-gcp:gatewayMinInstances: "0"
|
|
10
|
-
caelo-gcp:iapAllowlist: user:michael@searchviu.com
|
|
11
|
-
caelo-gcp:ownerEmail: michael@searchviu.com
|
|
12
|
-
caelo-gcp:project: caelo-website
|
|
13
|
-
caelo-gcp:region: europe-west1
|
|
14
|
-
caelo-gcp:wafAdaptiveProtection: "false"
|
|
15
|
-
caelo-gcp:image-digest-admin: sha256:92fb8ee1267de781f37c664e34d299b2f29c6051a113fa1bdbd6fddb753ef81e
|
|
16
|
-
caelo-gcp:image-digest-gateway: sha256:b4ceac2fd371be2850f7fc9549700fcd9e595eae6144f4a87b27192adf0cd9eb
|