1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as compute from "@distilled.cloud/gcp/compute_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8
9const { test } = Test.make({ providers: GCP.providers() });
10
11const logLevel = Effect.provideService(
12 MinimumLogLevel,
13 process.env.DEBUG ? "Debug" : "Info",
14);
15
16const region = "us-central1";
17
18const waitUntilGone = (project: string, securityPolicy: string) =>
19 compute.getRegionSecurityPolicies({ project, region, securityPolicy }).pipe(
20 Effect.as("found" as const),
21 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
22 Effect.repeat({
23 schedule: Schedule.spaced("1 second"),
24 until: (status) => status === "gone",
25 times: 10,
26 }),
27 );
28
29const ruleAt = (
30 rules: compute.SecurityPolicyRule[] | undefined,
31 priority: number,
32) => (rules ?? []).find((rule) => rule.priority === priority);
33
34test.provider(
35 "create, update, and delete a regional security policy",
36 (stack) =>
37 Effect.gen(function* () {
38 yield* stack.destroy();
39
40 const created = yield* stack.deploy(
41 Effect.gen(function* () {
42 return yield* GCP.Compute.RegionSecurityPolicy("Armor", {
43 region,
44 description: "deny scanner",
45 labels: { env: "test" },
46 rules: [
47 {
48 action: "deny(403)",
49 priority: 1000,
50 description: "block scanner",
51 match: {
52 versionedExpr: "SRC_IPS_V1",
53 config: { srcIpRanges: ["9.9.9.0/24"] },
54 },
55 },
56 ],
57 });
58 }),
59 );
60
61 expect(created.securityPolicyName).toEqual(expect.any(String));
62 expect(created.region).toEqual(region);
63 expect(created.type).toEqual("CLOUD_ARMOR");
64 expect(created.description).toEqual("deny scanner");
65 expect(created.labels).toMatchObject({ env: "test" });
66 expect(ruleAt(created.rules, 1000)?.action).toEqual("deny(403)");
67 expect(ruleAt(created.rules, 2147483647)?.action).toEqual("allow");
68
69 const fetched = yield* compute.getRegionSecurityPolicies({
70 project: created.project,
71 region,
72 securityPolicy: created.securityPolicyName,
73 });
74 expect(fetched.name).toEqual(created.securityPolicyName);
75 expect(fetched.type).toEqual("CLOUD_ARMOR");
76 expect(fetched.description).toEqual("deny scanner");
77 expect(fetched.labels?.env).toEqual("test");
78 expect(fetched.labels?.["alchemy-id"]).toEqual(expect.any(String));
79 expect(ruleAt(fetched.rules, 1000)?.match?.config?.srcIpRanges).toEqual(
80 expect.arrayContaining(["9.9.9.0/24"]),
81 );
82
83 const listed = yield* compute.listRegionSecurityPolicies({
84 project: created.project,
85 region,
86 filter: "labels.alchemy-id:*",
87 maxResults: 500,
88 });
89 expect(
90 (listed.items ?? []).some(
91 (policy) => policy.name === created.securityPolicyName,
92 ),
93 ).toEqual(true);
94
95 const updated = yield* stack.deploy(
96 Effect.gen(function* () {
97 return yield* GCP.Compute.RegionSecurityPolicy("Armor", {
98 securityPolicyName: created.securityPolicyName,
99 region,
100 description: "updated armor",
101 labels: { env: "prod", role: "waf" },
102 advancedOptionsConfig: {
103 jsonParsing: "STANDARD",
104 logLevel: "VERBOSE",
105 },
106 rules: [
107 {
108 action: "deny(403)",
109 priority: 1000,
110 description: "block scanners",
111 match: {
112 versionedExpr: "SRC_IPS_V1",
113 config: { srcIpRanges: ["9.9.9.0/24", "8.8.8.8/32"] },
114 },
115 },
116 {
117 action: "allow",
118 priority: 2147483647,
119 match: {
120 versionedExpr: "SRC_IPS_V1",
121 config: { srcIpRanges: ["*"] },
122 },
123 },
124 ],
125 });
126 }),
127 );
128
129 expect(updated.securityPolicyName).toEqual(created.securityPolicyName);
130 expect(updated.securityPolicyId).toEqual(created.securityPolicyId);
131 expect(updated.description).toEqual("updated armor");
132 expect(updated.labels).toMatchObject({ env: "prod", role: "waf" });
133 expect(updated.advancedOptionsConfig?.jsonParsing).toEqual("STANDARD");
134 expect(updated.advancedOptionsConfig?.logLevel).toEqual("VERBOSE");
135 expect(
136 ruleAt(updated.rules, 1000)?.match?.config?.srcIpRanges?.sort(),
137 ).toEqual(["8.8.8.8/32", "9.9.9.0/24"]);
138
139 const fetchedUpdate = yield* compute.getRegionSecurityPolicies({
140 project: updated.project,
141 region,
142 securityPolicy: updated.securityPolicyName,
143 });
144 expect(fetchedUpdate.description).toEqual("updated armor");
145 expect(fetchedUpdate.labels?.env).toEqual("prod");
146 expect(fetchedUpdate.labels?.role).toEqual("waf");
147 expect(fetchedUpdate.advancedOptionsConfig?.jsonParsing).toEqual(
148 "STANDARD",
149 );
150
151 yield* stack.destroy();
152
153 const gone = yield* waitUntilGone(
154 created.project,
155 created.securityPolicyName,
156 );
157 expect(gone).toEqual("gone");
158 }).pipe(logLevel),
159 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 90_000 },
160);