@caelo-cms/provisioning 0.10.46 → 0.10.48
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/adc-impersonation.d.ts.map +1 -1
- package/dist/adc-impersonation.js +23 -1
- package/dist/adc-impersonation.js.map +1 -1
- package/dist/agent/apply.d.ts +45 -0
- package/dist/agent/apply.d.ts.map +1 -0
- package/dist/agent/apply.js +83 -0
- package/dist/agent/apply.js.map +1 -0
- package/dist/agent/cli-provenance.d.ts +42 -0
- package/dist/agent/cli-provenance.d.ts.map +1 -0
- package/dist/agent/cli-provenance.js +138 -0
- package/dist/agent/cli-provenance.js.map +1 -0
- package/dist/agent/command.d.ts +79 -0
- package/dist/agent/command.d.ts.map +1 -0
- package/dist/agent/command.js +454 -0
- package/dist/agent/command.js.map +1 -0
- package/dist/agent/config.d.ts +30 -0
- package/dist/agent/config.d.ts.map +1 -0
- package/dist/agent/config.js +60 -0
- package/dist/agent/config.js.map +1 -0
- package/dist/agent/lock.d.ts +13 -0
- package/dist/agent/lock.d.ts.map +1 -0
- package/dist/agent/lock.js +100 -0
- package/dist/agent/lock.js.map +1 -0
- package/dist/agent/queue.d.ts +35 -0
- package/dist/agent/queue.d.ts.map +1 -0
- package/dist/agent/queue.js +50 -0
- package/dist/agent/queue.js.map +1 -0
- package/dist/agent/run.d.ts +97 -0
- package/dist/agent/run.d.ts.map +1 -0
- package/dist/agent/run.js +198 -0
- package/dist/agent/run.js.map +1 -0
- package/dist/agent/systemd.d.ts +55 -0
- package/dist/agent/systemd.d.ts.map +1 -0
- package/dist/agent/systemd.js +171 -0
- package/dist/agent/systemd.js.map +1 -0
- package/dist/agent/units.d.ts +44 -0
- package/dist/agent/units.d.ts.map +1 -0
- package/dist/agent/units.js +127 -0
- package/dist/agent/units.js.map +1 -0
- package/dist/agent-db.js +18362 -0
- package/dist/cli.js +122 -16
- package/dist/cli.js.map +1 -1
- package/dist/cloudsql-iam-user.d.ts +39 -0
- package/dist/cloudsql-iam-user.d.ts.map +1 -0
- package/dist/cloudsql-iam-user.js +119 -0
- package/dist/cloudsql-iam-user.js.map +1 -0
- package/dist/compose.d.ts +16 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +26 -1
- package/dist/compose.js.map +1 -1
- package/dist/dns/index.d.ts +2 -0
- package/dist/dns/index.d.ts.map +1 -1
- package/dist/dns/index.js +3 -2
- package/dist/dns/index.js.map +1 -1
- package/dist/dns/manual.d.ts +5 -0
- package/dist/dns/manual.d.ts.map +1 -1
- package/dist/dns/manual.js +28 -12
- package/dist/dns/manual.js.map +1 -1
- package/dist/gcloud.d.ts +2 -0
- package/dist/gcloud.d.ts.map +1 -1
- package/dist/gcloud.js +1 -1
- package/dist/gcloud.js.map +1 -1
- package/dist/gcp-names.d.ts +15 -0
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +23 -0
- package/dist/gcp-names.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/infra-contract.d.ts +1 -1
- package/dist/infra-contract.js +7 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-job-runner.d.ts +13 -0
- package/dist/install-job-runner.d.ts.map +1 -0
- package/dist/install-job-runner.js +155 -0
- package/dist/install-job-runner.js.map +1 -0
- package/dist/install-job.d.ts +210 -0
- package/dist/install-job.d.ts.map +1 -0
- package/dist/install-job.js +449 -0
- package/dist/install-job.js.map +1 -0
- package/dist/install-state.d.ts +19 -1
- package/dist/install-state.d.ts.map +1 -1
- package/dist/install-state.js +31 -3
- package/dist/install-state.js.map +1 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +24 -5
- package/dist/lifecycle.js.map +1 -1
- package/dist/migration-runner.d.ts +5 -0
- package/dist/migration-runner.d.ts.map +1 -1
- package/dist/migration-runner.js +1 -1
- package/dist/migration-runner.js.map +1 -1
- package/dist/operator-access.d.ts +14 -8
- package/dist/operator-access.d.ts.map +1 -1
- package/dist/operator-access.js +16 -102
- package/dist/operator-access.js.map +1 -1
- package/dist/run-mode.d.ts +27 -0
- package/dist/run-mode.d.ts.map +1 -0
- package/dist/run-mode.js +22 -0
- package/dist/run-mode.js.map +1 -0
- package/dist/runtime-secrets.d.ts +19 -0
- package/dist/runtime-secrets.d.ts.map +1 -1
- package/dist/runtime-secrets.js +44 -0
- package/dist/runtime-secrets.js.map +1 -1
- package/dist/self-hosted-roles.d.ts +22 -1
- package/dist/self-hosted-roles.d.ts.map +1 -1
- package/dist/self-hosted-roles.js +81 -12
- package/dist/self-hosted-roles.js.map +1 -1
- package/dist/self-hosted-upgrade.d.ts +17 -0
- package/dist/self-hosted-upgrade.d.ts.map +1 -1
- package/dist/self-hosted-upgrade.js +16 -1
- package/dist/self-hosted-upgrade.js.map +1 -1
- package/dist/stack-contract.d.ts +6 -0
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +10 -1
- package/dist/stack-contract.js.map +1 -1
- package/dist/state-attach.d.ts +23 -0
- package/dist/state-attach.d.ts.map +1 -0
- package/dist/state-attach.js +112 -0
- package/dist/state-attach.js.map +1 -0
- package/dist/state-backend.d.ts +11 -0
- package/dist/state-backend.d.ts.map +1 -1
- package/dist/state-backend.js +20 -0
- package/dist/state-backend.js.map +1 -1
- package/dist/state-command.d.ts +20 -0
- package/dist/state-command.d.ts.map +1 -0
- package/dist/state-command.js +237 -0
- package/dist/state-command.js.map +1 -0
- package/dist/state-migrate.d.ts +121 -0
- package/dist/state-migrate.d.ts.map +1 -0
- package/dist/state-migrate.js +290 -0
- package/dist/state-migrate.js.map +1 -0
- package/dist/state-pulumi.d.ts +17 -0
- package/dist/state-pulumi.d.ts.map +1 -0
- package/dist/state-pulumi.js +218 -0
- package/dist/state-pulumi.js.map +1 -0
- package/dist/state-store.d.ts +27 -0
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +65 -0
- package/dist/state-store.js.map +1 -1
- package/dist/upgrade/converge.d.ts +6 -0
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +59 -2
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/upgrade/gcp-lookup.d.ts +6 -0
- package/dist/upgrade/gcp-lookup.d.ts.map +1 -1
- package/dist/upgrade/gcp-lookup.js +25 -0
- package/dist/upgrade/gcp-lookup.js.map +1 -1
- package/dist/upgrade/release-images.d.ts +12 -0
- package/dist/upgrade/release-images.d.ts.map +1 -1
- package/dist/upgrade/release-images.js +31 -0
- package/dist/upgrade/release-images.js.map +1 -1
- package/dist/upgrade/upgrade.d.ts +5 -0
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +28 -1
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-assess.d.ts +82 -0
- package/dist/upgrader-assess.d.ts.map +1 -0
- package/dist/upgrader-assess.js +116 -0
- package/dist/upgrader-assess.js.map +1 -0
- package/dist/upgrader-gcp.d.ts +17 -0
- package/dist/upgrader-gcp.d.ts.map +1 -0
- package/dist/upgrader-gcp.js +87 -0
- package/dist/upgrader-gcp.js.map +1 -0
- package/dist/upgrader-job.d.ts +131 -0
- package/dist/upgrader-job.d.ts.map +1 -0
- package/dist/upgrader-job.js +259 -0
- package/dist/upgrader-job.js.map +1 -0
- package/dist/upgrader-reporter.d.ts +35 -0
- package/dist/upgrader-reporter.d.ts.map +1 -0
- package/dist/upgrader-reporter.js +79 -0
- package/dist/upgrader-reporter.js.map +1 -0
- package/dist/upgrader-setup.d.ts +59 -0
- package/dist/upgrader-setup.d.ts.map +1 -0
- package/dist/upgrader-setup.js +308 -0
- package/dist/upgrader-setup.js.map +1 -0
- package/dist/wizard.d.ts +6 -0
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +32 -4
- package/dist/wizard.js.map +1 -1
- package/dist/wizards/gcp-firebase.d.ts +3 -0
- package/dist/wizards/gcp-firebase.d.ts.map +1 -1
- package/dist/wizards/gcp-firebase.js +1 -0
- package/dist/wizards/gcp-firebase.js.map +1 -1
- package/dist/wizards/gcp-install-job.d.ts +74 -0
- package/dist/wizards/gcp-install-job.d.ts.map +1 -0
- package/dist/wizards/gcp-install-job.js +398 -0
- package/dist/wizards/gcp-install-job.js.map +1 -0
- package/dist/wizards/gcp-prompts.d.ts +3 -0
- package/dist/wizards/gcp-prompts.d.ts.map +1 -0
- package/dist/wizards/gcp-prompts.js +25 -0
- package/dist/wizards/gcp-prompts.js.map +1 -0
- package/dist/wizards/gcp-pulumi.d.ts +9 -2
- package/dist/wizards/gcp-pulumi.d.ts.map +1 -1
- package/dist/wizards/gcp-pulumi.js +8 -6
- package/dist/wizards/gcp-pulumi.js.map +1 -1
- package/dist/wizards/gcp.d.ts +40 -0
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +291 -83
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +6 -4
- package/src/adapter.ts +97 -0
- package/src/adc-impersonation.ts +176 -0
- package/src/admin-domain.test.ts +254 -0
- package/src/admin-domain.ts +245 -0
- package/src/admin-memory.test.ts +151 -0
- package/src/agent/apply.test.ts +185 -0
- package/src/agent/apply.ts +124 -0
- package/src/agent/cli-provenance.test.ts +159 -0
- package/src/agent/cli-provenance.ts +191 -0
- package/src/agent/command.test.ts +77 -0
- package/src/agent/command.ts +564 -0
- package/src/agent/config.ts +86 -0
- package/src/agent/lock-config.test.ts +97 -0
- package/src/agent/lock.ts +110 -0
- package/src/agent/queue.test.ts +10 -0
- package/src/agent/queue.ts +91 -0
- package/src/agent/run.test.ts +382 -0
- package/src/agent/run.ts +295 -0
- package/src/agent/systemd.ts +209 -0
- package/src/agent/units.test.ts +223 -0
- package/src/agent/units.ts +163 -0
- package/src/azure-front-door.test.ts +119 -0
- package/src/azure-front-door.ts +56 -0
- package/src/bootstrap-token.ts +20 -0
- package/src/caddy.ts +112 -0
- package/src/cdn-copy.ts +91 -0
- package/src/cert-wait.test.ts +194 -0
- package/src/cert-wait.ts +163 -0
- package/src/checkpoint-secrets.test.ts +380 -0
- package/src/checkpoint-secrets.ts +283 -0
- package/src/cli-routing.test.ts +45 -0
- package/src/cli-routing.ts +43 -0
- package/src/cli.ts +1491 -0
- package/src/cloudsql-iam-user.ts +147 -0
- package/src/compose.ts +334 -0
- package/src/default-firewall.test.ts +411 -0
- package/src/default-firewall.ts +342 -0
- package/src/deployed-release.test.ts +231 -0
- package/src/deployed-release.ts +220 -0
- package/src/dns/cloudflare.ts +182 -0
- package/src/dns/groups.test.ts +76 -0
- package/src/dns/groups.ts +86 -0
- package/src/dns/index.ts +56 -0
- package/src/dns/manual.ts +130 -0
- package/src/dns/types.ts +27 -0
- package/src/gateway-credentials.test.ts +180 -0
- package/src/gateway-credentials.ts +144 -0
- package/src/gcloud-components.test.ts +76 -0
- package/src/gcloud-components.ts +85 -0
- package/src/gcloud-retry.ts +41 -0
- package/src/gcloud-runner.test.ts +61 -0
- package/src/gcloud.test.ts +99 -0
- package/src/gcloud.ts +322 -0
- package/src/gcp-names.ts +135 -0
- package/src/index.test.ts +332 -0
- package/src/index.ts +114 -0
- package/src/infra-contract.ts +32 -0
- package/src/install-job-runner.ts +166 -0
- package/src/install-job.test.ts +566 -0
- package/src/install-job.ts +542 -0
- package/src/install-state.test.ts +239 -0
- package/src/install-state.ts +583 -0
- package/src/internal-token.ts +35 -0
- package/src/lifecycle.ts +900 -0
- package/src/mcp-iap-stacks.test.ts +58 -0
- package/src/mcp-iap.test.ts +149 -0
- package/src/mcp-iap.ts +131 -0
- package/src/media-volume.test.ts +296 -0
- package/src/migration-runner.ts +365 -0
- package/src/operator-access-stacks.test.ts +55 -0
- package/src/operator-access.test.ts +332 -0
- package/src/operator-access.ts +554 -0
- package/src/ops-backend.test.ts +320 -0
- package/src/ops-backend.ts +414 -0
- package/src/provisioner-identity.test.ts +482 -0
- package/src/provisioner-identity.ts +407 -0
- package/src/redirects-emit.ts +175 -0
- package/src/region-data.ts +428 -0
- package/src/regions.test.ts +277 -0
- package/src/regions.ts +370 -0
- package/src/release-contract.ts +57 -0
- package/src/release-manifest.test.ts +293 -0
- package/src/release-manifest.ts +430 -0
- package/src/run-mode.ts +40 -0
- package/src/runtime-secrets.test.ts +627 -0
- package/src/runtime-secrets.ts +754 -0
- package/src/self-command.test.ts +14 -0
- package/src/self-command.ts +21 -0
- package/src/self-hosted-compose.test.ts +177 -0
- package/src/self-hosted-release.ts +240 -0
- package/src/self-hosted-roles.integration.test.ts +161 -0
- package/src/self-hosted-roles.test.ts +124 -0
- package/src/self-hosted-roles.ts +203 -0
- package/src/self-hosted-upgrade.test.ts +394 -0
- package/src/self-hosted-upgrade.ts +548 -0
- package/src/stack-contract-parity.test.ts +199 -0
- package/src/stack-contract.test.ts +391 -0
- package/src/stack-contract.ts +888 -0
- package/src/stack-converge-secrets.test.ts +421 -0
- package/src/stack-converge.test.ts +833 -0
- package/src/stack-converge.ts +909 -0
- package/src/staging-admin.test.ts +246 -0
- package/src/staging-admin.ts +324 -0
- package/src/state-attach.test.ts +211 -0
- package/src/state-attach.ts +132 -0
- package/src/state-backend.test.ts +145 -0
- package/src/state-backend.ts +262 -0
- package/src/state-command.ts +293 -0
- package/src/state-migrate.test.ts +479 -0
- package/src/state-migrate.ts +434 -0
- package/src/state-pulumi.ts +289 -0
- package/src/state-store.test.ts +212 -0
- package/src/state-store.ts +243 -0
- package/src/upgrade/app-roll.ts +486 -0
- package/src/upgrade/backup.ts +109 -0
- package/src/upgrade/converge.ts +437 -0
- package/src/upgrade/gcp-lookup.ts +167 -0
- package/src/upgrade/health.test.ts +279 -0
- package/src/upgrade/health.ts +305 -0
- package/src/upgrade/release-images.test.ts +119 -0
- package/src/upgrade/release-images.ts +391 -0
- package/src/upgrade/reporter.ts +46 -0
- package/src/upgrade/upgrade.test.ts +626 -0
- package/src/upgrade/upgrade.ts +249 -0
- package/src/upgrader-assess.test.ts +265 -0
- package/src/upgrader-assess.ts +233 -0
- package/src/upgrader-gcp.ts +123 -0
- package/src/upgrader-job.test.ts +408 -0
- package/src/upgrader-job.ts +499 -0
- package/src/upgrader-reporter.test.ts +46 -0
- package/src/upgrader-reporter.ts +105 -0
- package/src/upgrader-setup.test.ts +169 -0
- package/src/upgrader-setup.ts +355 -0
- package/src/wizard.ts +425 -0
- package/src/wizards/gcp-cost.ts +188 -0
- package/src/wizards/gcp-firebase.ts +72 -0
- package/src/wizards/gcp-install-job.ts +506 -0
- package/src/wizards/gcp-prompts.ts +26 -0
- package/src/wizards/gcp-pulumi.test.ts +246 -0
- package/src/wizards/gcp-pulumi.ts +516 -0
- package/src/wizards/gcp.ts +1935 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GCP pre-populates `default-allow-ssh` (tcp:22), `default-allow-rdp`
|
|
5
|
+
* (tcp:3389), `default-allow-icmp` (from 0.0.0.0/0) and
|
|
6
|
+
* `default-allow-internal` (all ports from 10.128.0.0/9) on the `default`
|
|
7
|
+
* network when Compute Engine is enabled; audits flag them on every Caelo
|
|
8
|
+
* install. The wizard and `upgrade` delete them — but only in their stock
|
|
9
|
+
* shape, and the internal rule only while no VM sits on that network. The
|
|
10
|
+
* fake gcloud answers with what `gcloud compute … list --format=json` returns.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, expect, it } from "bun:test";
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import { resolve } from "node:path";
|
|
16
|
+
import {
|
|
17
|
+
isStockDefaultRule,
|
|
18
|
+
type LiveFirewallRule,
|
|
19
|
+
removeDefaultFirewallRules,
|
|
20
|
+
} from "./default-firewall.js";
|
|
21
|
+
import type { GcloudResult } from "./gcloud.js";
|
|
22
|
+
import { ABSENT_DEFAULT_FIREWALL_RULES } from "./stack-contract.js";
|
|
23
|
+
|
|
24
|
+
const ok = (stdout = ""): GcloudResult => ({ ok: true, stdout, stderr: "", exitCode: 0 });
|
|
25
|
+
const fail = (stderr: string): GcloudResult => ({ ok: false, stdout: "", stderr, exitCode: 1 });
|
|
26
|
+
|
|
27
|
+
const DEFAULT_NETWORK =
|
|
28
|
+
"https://www.googleapis.com/compute/v1/projects/acme/global/networks/default";
|
|
29
|
+
|
|
30
|
+
/** A rule exactly as GCP auto-creates it (as the Compute API returns it). */
|
|
31
|
+
function stock(
|
|
32
|
+
name: string,
|
|
33
|
+
allowed: LiveFirewallRule["allowed"],
|
|
34
|
+
sourceRanges = ["0.0.0.0/0"],
|
|
35
|
+
): LiveFirewallRule {
|
|
36
|
+
return {
|
|
37
|
+
name,
|
|
38
|
+
network: DEFAULT_NETWORK,
|
|
39
|
+
direction: "INGRESS",
|
|
40
|
+
priority: 65534,
|
|
41
|
+
sourceRanges,
|
|
42
|
+
allowed,
|
|
43
|
+
} as LiveFirewallRule;
|
|
44
|
+
}
|
|
45
|
+
const SSH = stock("default-allow-ssh", [{ IPProtocol: "tcp", ports: ["22"] }]);
|
|
46
|
+
const RDP = stock("default-allow-rdp", [{ IPProtocol: "tcp", ports: ["3389"] }]);
|
|
47
|
+
const ICMP = stock("default-allow-icmp", [{ IPProtocol: "icmp" }]);
|
|
48
|
+
const INTERNAL = stock(
|
|
49
|
+
"default-allow-internal",
|
|
50
|
+
[
|
|
51
|
+
{ IPProtocol: "tcp", ports: ["0-65535"] },
|
|
52
|
+
{ IPProtocol: "udp", ports: ["0-65535"] },
|
|
53
|
+
{ IPProtocol: "icmp" },
|
|
54
|
+
],
|
|
55
|
+
["10.128.0.0/9"],
|
|
56
|
+
);
|
|
57
|
+
const ALL = [SSH, RDP, ICMP, INTERNAL];
|
|
58
|
+
|
|
59
|
+
const vm = (name: string, network: string) => ({
|
|
60
|
+
name,
|
|
61
|
+
zone: "https://www.googleapis.com/compute/v1/projects/acme/zones/europe-west1-b",
|
|
62
|
+
networkInterfaces: [{ network }],
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Fake gcloud answering firewall `list`/`delete`, `instances list` and
|
|
67
|
+
* `project-info describe` (Shared VPC status); records every call.
|
|
68
|
+
*/
|
|
69
|
+
function fakeGcloud(
|
|
70
|
+
list: GcloudResult,
|
|
71
|
+
del: (name: string) => GcloudResult = () => ok(),
|
|
72
|
+
instances: GcloudResult = ok("[]"),
|
|
73
|
+
xpnStatus: GcloudResult = ok("UNSPECIFIED_XPN_PROJECT_STATUS\n"),
|
|
74
|
+
) {
|
|
75
|
+
const calls: string[] = [];
|
|
76
|
+
const run = async (args: string[]) => {
|
|
77
|
+
calls.push(args.join(" "));
|
|
78
|
+
if (args[1] === "instances" && args[2] === "list") return instances;
|
|
79
|
+
if (args[1] === "project-info" && args[2] === "describe") return xpnStatus;
|
|
80
|
+
if (args[2] === "list") return list;
|
|
81
|
+
if (args[2] === "delete") return del(args[3] ?? "");
|
|
82
|
+
throw new Error(`unexpected gcloud call: ${args.join(" ")}`);
|
|
83
|
+
};
|
|
84
|
+
return { run, calls };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const LIST_CALL =
|
|
88
|
+
"compute firewall-rules list --project=acme --filter=name=(default-allow-ssh default-allow-rdp default-allow-icmp default-allow-internal) --format=json";
|
|
89
|
+
const INSTANCES_CALL =
|
|
90
|
+
"compute instances list --project=acme --format=json(name,zone,networkInterfaces[].network)";
|
|
91
|
+
const deletes = (calls: string[]) =>
|
|
92
|
+
calls.filter((c) => c.startsWith("compute firewall-rules delete"));
|
|
93
|
+
|
|
94
|
+
describe("removeDefaultFirewallRules", () => {
|
|
95
|
+
it("deletes all four stock rules on a fresh project", async () => {
|
|
96
|
+
const { run, calls } = fakeGcloud(ok(JSON.stringify(ALL)));
|
|
97
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
98
|
+
expect(calls[0]).toBe(LIST_CALL);
|
|
99
|
+
expect(calls).toContain(INSTANCES_CALL);
|
|
100
|
+
expect(deletes(calls)).toEqual([
|
|
101
|
+
"compute firewall-rules delete default-allow-ssh --project=acme --quiet",
|
|
102
|
+
"compute firewall-rules delete default-allow-rdp --project=acme --quiet",
|
|
103
|
+
"compute firewall-rules delete default-allow-icmp --project=acme --quiet",
|
|
104
|
+
"compute firewall-rules delete default-allow-internal --project=acme --quiet",
|
|
105
|
+
]);
|
|
106
|
+
expect(outcomes.map((o) => o.status)).toEqual(["applied", "applied", "applied", "applied"]);
|
|
107
|
+
expect(outcomes.map((o) => o.id)).toEqual([
|
|
108
|
+
"firewall rule default-allow-ssh (tcp:22 from 0.0.0.0/0)",
|
|
109
|
+
"firewall rule default-allow-rdp (tcp:3389 from 0.0.0.0/0)",
|
|
110
|
+
"firewall rule default-allow-icmp (icmp from 0.0.0.0/0)",
|
|
111
|
+
"firewall rule default-allow-internal (tcp:0-65535 udp:0-65535 icmp from 10.128.0.0/9)",
|
|
112
|
+
]);
|
|
113
|
+
expect(outcomes.every((o) => o.onFailure === "warn")).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("is a read-only no-op when the rules are already gone", async () => {
|
|
117
|
+
const { run, calls } = fakeGcloud(ok("[]"));
|
|
118
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
119
|
+
expect(calls).toEqual([LIST_CALL]);
|
|
120
|
+
expect(outcomes.every((o) => o.status === "present")).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("deletes only the rule that is still there", async () => {
|
|
124
|
+
const { run, calls } = fakeGcloud(ok(JSON.stringify([RDP])));
|
|
125
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
126
|
+
expect(deletes(calls)).toEqual([
|
|
127
|
+
"compute firewall-rules delete default-allow-rdp --project=acme --quiet",
|
|
128
|
+
]);
|
|
129
|
+
expect(outcomes.map((o) => o.status)).toEqual(["present", "applied", "present", "present"]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("checks for VMs only when the internal rule is there to delete", async () => {
|
|
133
|
+
const { run, calls } = fakeGcloud(ok(JSON.stringify([SSH, ICMP])));
|
|
134
|
+
await removeDefaultFirewallRules("acme", { run });
|
|
135
|
+
expect(calls).not.toContain(INSTANCES_CALL);
|
|
136
|
+
expect(calls.some((c) => c.startsWith("compute project-info"))).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("leaves default-allow-internal in place while VMs sit on the default network", async () => {
|
|
140
|
+
const { run, calls } = fakeGcloud(
|
|
141
|
+
ok(JSON.stringify(ALL)),
|
|
142
|
+
() => ok(),
|
|
143
|
+
ok(
|
|
144
|
+
JSON.stringify([
|
|
145
|
+
vm("legacy-worker", DEFAULT_NETWORK),
|
|
146
|
+
vm("vpc-only-vm", DEFAULT_NETWORK.replace(/default$/, "caelo-production-vpc")),
|
|
147
|
+
]),
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
151
|
+
expect(deletes(calls)).not.toContain(
|
|
152
|
+
"compute firewall-rules delete default-allow-internal --project=acme --quiet",
|
|
153
|
+
);
|
|
154
|
+
expect(deletes(calls)).toHaveLength(3);
|
|
155
|
+
const internal = outcomes[3];
|
|
156
|
+
expect(internal?.status).toBe("failed");
|
|
157
|
+
expect(internal?.onFailure).toBe("warn");
|
|
158
|
+
expect(internal?.error).toContain("1 VM instance(s) are on the default network");
|
|
159
|
+
expect(internal?.error).toContain("europe-west1-b/legacy-worker");
|
|
160
|
+
expect(internal?.error).not.toContain("vpc-only-vm");
|
|
161
|
+
expect(internal?.error).toContain(
|
|
162
|
+
"gcloud compute firewall-rules delete default-allow-internal --project=acme",
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("leaves default-allow-internal in place on a Shared VPC host", async () => {
|
|
167
|
+
const { run, calls } = fakeGcloud(
|
|
168
|
+
ok(JSON.stringify([INTERNAL])),
|
|
169
|
+
() => ok(),
|
|
170
|
+
ok("[]"),
|
|
171
|
+
ok("HOST\n"),
|
|
172
|
+
);
|
|
173
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
174
|
+
expect(deletes(calls)).toEqual([]);
|
|
175
|
+
expect(calls).not.toContain(INSTANCES_CALL);
|
|
176
|
+
expect(outcomes[3]?.status).toBe("failed");
|
|
177
|
+
expect(outcomes[3]?.onFailure).toBe("warn");
|
|
178
|
+
expect(outcomes[3]?.error).toContain("Shared VPC host");
|
|
179
|
+
expect(outcomes[3]?.error).toContain(
|
|
180
|
+
"gcloud compute shared-vpc list-associated-resources acme",
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("leaves default-allow-internal in place when the Shared VPC status is unknown", async () => {
|
|
185
|
+
const { run, calls } = fakeGcloud(
|
|
186
|
+
ok(JSON.stringify([INTERNAL])),
|
|
187
|
+
() => ok(),
|
|
188
|
+
ok("[]"),
|
|
189
|
+
fail("ERROR: PERMISSION_DENIED: compute.projects.get"),
|
|
190
|
+
);
|
|
191
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
192
|
+
expect(deletes(calls)).toEqual([]);
|
|
193
|
+
expect(outcomes[3]?.status).toBe("failed");
|
|
194
|
+
expect(outcomes[3]?.error).toContain("compute.projects.get");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("leaves default-allow-internal in place when the instance list is partial", async () => {
|
|
198
|
+
const { run, calls } = fakeGcloud(ok(JSON.stringify([INTERNAL])), () => ok(), {
|
|
199
|
+
ok: true,
|
|
200
|
+
stdout: "[]",
|
|
201
|
+
stderr:
|
|
202
|
+
"WARNING: Some requests did not succeed.\n - The resource 'projects/acme/zones/europe-west1-c' is not ready (unreachable)",
|
|
203
|
+
exitCode: 0,
|
|
204
|
+
});
|
|
205
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
206
|
+
expect(deletes(calls)).toEqual([]);
|
|
207
|
+
expect(outcomes[3]?.status).toBe("failed");
|
|
208
|
+
expect(outcomes[3]?.error).toContain("incomplete instance list");
|
|
209
|
+
expect(outcomes[3]?.error).toContain("left it in place");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("deletes default-allow-internal when VMs exist only on other networks", async () => {
|
|
213
|
+
const { run, calls } = fakeGcloud(
|
|
214
|
+
ok(JSON.stringify([INTERNAL])),
|
|
215
|
+
() => ok(),
|
|
216
|
+
ok(JSON.stringify([vm("x", DEFAULT_NETWORK.replace(/default$/, "caelo-production-vpc"))])),
|
|
217
|
+
);
|
|
218
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
219
|
+
expect(deletes(calls)).toEqual([
|
|
220
|
+
"compute firewall-rules delete default-allow-internal --project=acme --quiet",
|
|
221
|
+
]);
|
|
222
|
+
expect(outcomes[3]?.status).toBe("applied");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("leaves default-allow-internal in place when the VMs cannot be listed", async () => {
|
|
226
|
+
const { run, calls } = fakeGcloud(
|
|
227
|
+
ok(JSON.stringify([INTERNAL])),
|
|
228
|
+
() => ok(),
|
|
229
|
+
fail("ERROR: PERMISSION_DENIED: compute.instances.list"),
|
|
230
|
+
);
|
|
231
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
232
|
+
expect(deletes(calls)).toEqual([]);
|
|
233
|
+
expect(outcomes[3]?.status).toBe("failed");
|
|
234
|
+
expect(outcomes[3]?.error).toContain("PERMISSION_DENIED");
|
|
235
|
+
expect(outcomes[3]?.error).toContain("left it in place");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("leaves a customised rule in place and reports it with the next step", async () => {
|
|
239
|
+
const narrowed = { ...SSH, sourceRanges: ["203.0.113.0/24"] };
|
|
240
|
+
const { run, calls } = fakeGcloud(ok(JSON.stringify([narrowed, RDP])));
|
|
241
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
242
|
+
expect(deletes(calls)).toEqual([
|
|
243
|
+
"compute firewall-rules delete default-allow-rdp --project=acme --quiet",
|
|
244
|
+
]);
|
|
245
|
+
const ssh = outcomes[0];
|
|
246
|
+
expect(ssh?.status).toBe("failed");
|
|
247
|
+
expect(ssh?.onFailure).toBe("warn");
|
|
248
|
+
expect(ssh?.error).toContain("not GCP's auto-created rule");
|
|
249
|
+
expect(ssh?.error).toContain("from 203.0.113.0/24");
|
|
250
|
+
expect(ssh?.error).toContain(
|
|
251
|
+
"gcloud compute firewall-rules delete default-allow-ssh --project=acme",
|
|
252
|
+
);
|
|
253
|
+
expect(outcomes[1]?.status).toBe("applied");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("is a no-op when Compute Engine is not enabled (no default network)", async () => {
|
|
257
|
+
const { run, calls } = fakeGcloud(
|
|
258
|
+
fail(
|
|
259
|
+
"ERROR: (gcloud.compute.firewall-rules.list) Some requests did not succeed:\n" +
|
|
260
|
+
" - Compute Engine API has not been used in project 42 before or it is disabled. " +
|
|
261
|
+
"Enable it by visiting https://console.developers.google.com/apis/api/compute.googleapis.com/overview?project=42 " +
|
|
262
|
+
"then retry. (reason: SERVICE_DISABLED)",
|
|
263
|
+
),
|
|
264
|
+
);
|
|
265
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
266
|
+
expect(calls).toEqual([LIST_CALL]);
|
|
267
|
+
expect(outcomes.every((o) => o.status === "present")).toBe(true);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("is a no-op when the default network was never created", async () => {
|
|
271
|
+
// Org policy compute.skipDefaultNetworkCreation, or the network was
|
|
272
|
+
// deleted: the list simply has no default-network rules.
|
|
273
|
+
const { run, calls } = fakeGcloud(ok("[]"));
|
|
274
|
+
expect(
|
|
275
|
+
(await removeDefaultFirewallRules("acme", { run })).every((o) => o.status === "present"),
|
|
276
|
+
).toBe(true);
|
|
277
|
+
expect(deletes(calls)).toEqual([]);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("reports (warn) when the rules cannot be listed", async () => {
|
|
281
|
+
const { run } = fakeGcloud(fail("ERROR: PERMISSION_DENIED: compute.firewalls.list"));
|
|
282
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
283
|
+
expect(outcomes.map((o) => o.status)).toEqual(["failed", "failed", "failed", "failed"]);
|
|
284
|
+
expect(outcomes[0]?.error).toContain("PERMISSION_DENIED");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("reports (warn) a failed delete with the role it needs", async () => {
|
|
288
|
+
const { run } = fakeGcloud(ok(JSON.stringify([SSH])), () =>
|
|
289
|
+
fail(
|
|
290
|
+
"ERROR: Required 'compute.firewalls.delete' permission for 'projects/acme/global/firewalls/default-allow-ssh'",
|
|
291
|
+
),
|
|
292
|
+
);
|
|
293
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
294
|
+
expect(outcomes[0]?.status).toBe("failed");
|
|
295
|
+
expect(outcomes[0]?.error).toContain("roles/compute.securityAdmin");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("treats a rule deleted between list and delete as gone", async () => {
|
|
299
|
+
const { run } = fakeGcloud(ok(JSON.stringify([SSH])), () =>
|
|
300
|
+
fail("ERROR: The resource 'projects/acme/global/firewalls/default-allow-ssh' was not found"),
|
|
301
|
+
);
|
|
302
|
+
const outcomes = await removeDefaultFirewallRules("acme", { run });
|
|
303
|
+
expect(outcomes[0]?.status).toBe("present");
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const contractRule = (name: string) => {
|
|
308
|
+
const rule = ABSENT_DEFAULT_FIREWALL_RULES.find((r) => r.name === name);
|
|
309
|
+
if (!rule) throw new Error(`${name} missing from the contract`);
|
|
310
|
+
return rule;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
describe("isStockDefaultRule", () => {
|
|
314
|
+
const ssh = contractRule("default-allow-ssh");
|
|
315
|
+
const icmp = contractRule("default-allow-icmp");
|
|
316
|
+
const internal = contractRule("default-allow-internal");
|
|
317
|
+
|
|
318
|
+
it("matches all four documented stock shapes", () => {
|
|
319
|
+
for (const live of ALL) {
|
|
320
|
+
expect(isStockDefaultRule(live, contractRule(live.name))).toBe(true);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("matches the internal rule whatever order the API lists its protocols in", () => {
|
|
325
|
+
const reordered = { ...INTERNAL, allowed: [...(INTERNAL.allowed ?? [])].reverse() };
|
|
326
|
+
expect(isStockDefaultRule(reordered, internal)).toBe(true);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it.each<[string, LiveFirewallRule]>([
|
|
330
|
+
["another source range", { ...INTERNAL, sourceRanges: ["10.240.0.0/16"] }],
|
|
331
|
+
["an extra source range", { ...INTERNAL, sourceRanges: ["10.128.0.0/9", "0.0.0.0/0"] }],
|
|
332
|
+
[
|
|
333
|
+
"narrowed tcp ports",
|
|
334
|
+
{
|
|
335
|
+
...INTERNAL,
|
|
336
|
+
allowed: [
|
|
337
|
+
{ IPProtocol: "tcp", ports: ["5432"] },
|
|
338
|
+
{ IPProtocol: "udp", ports: ["0-65535"] },
|
|
339
|
+
{ IPProtocol: "icmp" },
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
["icmp dropped", { ...INTERNAL, allowed: (INTERNAL.allowed ?? []).slice(0, 2) }],
|
|
344
|
+
["target tags", { ...INTERNAL, targetTags: ["db"] }],
|
|
345
|
+
])("does not match an internal rule with %s", (_label, rule) => {
|
|
346
|
+
expect(isStockDefaultRule(rule, internal)).toBe(false);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it.each<[string, LiveFirewallRule]>([
|
|
350
|
+
["a narrowed source range", { ...ICMP, sourceRanges: ["35.191.0.0/16"] }],
|
|
351
|
+
["an extra protocol", { ...ICMP, allowed: [{ IPProtocol: "icmp" }, { IPProtocol: "tcp" }] }],
|
|
352
|
+
["target tags", { ...ICMP, targetTags: ["pingable"] }],
|
|
353
|
+
])("does not match an icmp rule with %s", (_label, rule) => {
|
|
354
|
+
expect(isStockDefaultRule(rule, icmp)).toBe(false);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("matches the rule as GCP creates it, ignoring priority/description/disabled", () => {
|
|
358
|
+
expect(isStockDefaultRule(SSH, ssh)).toBe(true);
|
|
359
|
+
expect(
|
|
360
|
+
isStockDefaultRule(
|
|
361
|
+
{ ...SSH, priority: 1000, description: "x", disabled: true } as LiveFirewallRule,
|
|
362
|
+
ssh,
|
|
363
|
+
),
|
|
364
|
+
).toBe(true);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it.each<[string, LiveFirewallRule]>([
|
|
368
|
+
["narrowed source range", { ...SSH, sourceRanges: ["10.0.0.0/8"] }],
|
|
369
|
+
["extra source range", { ...SSH, sourceRanges: ["0.0.0.0/0", "10.0.0.0/8"] }],
|
|
370
|
+
["other port", { ...SSH, allowed: [{ IPProtocol: "tcp", ports: ["2222"] }] }],
|
|
371
|
+
["extra port", { ...SSH, allowed: [{ IPProtocol: "tcp", ports: ["22", "80"] }] }],
|
|
372
|
+
["all ports", { ...SSH, allowed: [{ IPProtocol: "tcp" }] }],
|
|
373
|
+
["target tags", { ...SSH, targetTags: ["bastion"] }],
|
|
374
|
+
[
|
|
375
|
+
"target service account",
|
|
376
|
+
{ ...SSH, targetServiceAccounts: ["vm@acme.iam.gserviceaccount.com"] },
|
|
377
|
+
],
|
|
378
|
+
[
|
|
379
|
+
"another network",
|
|
380
|
+
{ ...SSH, network: DEFAULT_NETWORK.replace(/default$/, "caelo-production-vpc") },
|
|
381
|
+
],
|
|
382
|
+
["egress", { ...SSH, direction: "EGRESS" }],
|
|
383
|
+
])("does not match a rule with %s", (_label, rule) => {
|
|
384
|
+
expect(isStockDefaultRule(rule, ssh)).toBe(false);
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Deleting `default-allow-internal` is only safe because Caelo attaches
|
|
390
|
+
* nothing to the `default` network. Pin that in both GCP stacks: the only
|
|
391
|
+
* VPC is the stack's own, and every Cloud Run Direct VPC egress interface
|
|
392
|
+
* points at it.
|
|
393
|
+
*/
|
|
394
|
+
describe.each(["gcp", "gcp-firebase"])("%s stack — nothing on the default network", (provider) => {
|
|
395
|
+
const src = readFileSync(resolve(import.meta.dir, `../stacks/${provider}/index.ts`), "utf8");
|
|
396
|
+
|
|
397
|
+
it("creates its own VPC and attaches Cloud Run only to it", () => {
|
|
398
|
+
expect(src.match(/new gcp\.compute\.Network\(/g)).toHaveLength(1);
|
|
399
|
+
expect(src).toMatch(/new gcp\.compute\.Network\(\s*`\$\{namePrefix\}-vpc`/);
|
|
400
|
+
const interfaces = src.match(/networkInterfaces: \[[^\]]*\]/g) ?? [];
|
|
401
|
+
expect(interfaces.length).toBeGreaterThan(0);
|
|
402
|
+
for (const ni of interfaces) {
|
|
403
|
+
expect(ni).toBe("networkInterfaces: [{ network: network.name, subnetwork: subnet.name }]");
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("never names the default network or creates VMs / connectors", () => {
|
|
408
|
+
expect(src).not.toMatch(/networks\/default\b|network:\s*["']default["']/);
|
|
409
|
+
expect(src).not.toMatch(/compute\.Instance\b|compute\.InstanceTemplate|vpcaccess\.Connector/);
|
|
410
|
+
});
|
|
411
|
+
});
|