1import * as AWS from "@/AWS";
2import { ClientVpnAuthorizationRule } from "@/AWS/EC2/ClientVpnAuthorizationRule.ts";
3import { ClientVpnEndpoint } from "@/AWS/EC2/ClientVpnEndpoint.ts";
4import * as Alchemy from "@/index.ts";
5import * as Provider from "@/Provider";
6import * as Test from "@/Test/Alchemy";
7import * as ec2 from "@distilled.cloud/aws/ec2";
8import { expect } from "alchemy-test";
9import * as Effect from "effect/Effect";
10import {
11 assertClientVpnAuthorizationDeleted,
12 assertClientVpnCertificateDeleted,
13 clientVpnEndpointProps,
14 clientVpnTestTimeout,
15 importClientVpnCertificate,
16 readClientVpnAuthorizationRules,
17 waitForClientVpn,
18} from "./fixtures/client-vpn.ts";
19
20const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
21 providers: AWS.providers(),
22});
23const certificate = beforeAll(
24 importClientVpnCertificate("ClientVpnAuthorizationPrerequisites"),
25);
26const Stack = Alchemy.Stack(
27 "ClientVpnAuthorizationPrerequisites",
28 { providers: AWS.providers(), state: Alchemy.localState() },
29 Effect.gen(function* () {
30 const certificateArn = yield* certificate;
31 const endpoint = yield* ClientVpnEndpoint(
32 "Endpoint",
33 clientVpnEndpointProps(certificateArn),
34 );
35 return { endpoint };
36 }),
37);
38const prerequisites = beforeAll(deploy(Stack), {
39 timeout: clientVpnTestTimeout,
40});
41afterAll(
42 destroy(Stack).pipe(
43 Effect.andThen(
44 certificate.pipe(Effect.flatMap(assertClientVpnCertificateDeleted)),
45 ),
46 ),
47 { timeout: clientVpnTestTimeout },
48);
49
50test.provider(
51 "creates, lists, replaces, and deletes authorization rules without a subnet association",
52 (stack) =>
53 Effect.gen(function* () {
54 yield* stack.destroy();
55 const { endpoint } = yield* prerequisites;
56 const clientVpnEndpointId = endpoint.clientVpnEndpointId;
57 const deployRule = (targetNetworkCidr: string, description?: string) =>
58 stack.deploy(
59 ClientVpnAuthorizationRule("Authorization", {
60 clientVpnEndpointId,
61 targetNetworkCidr,
62 authorizeAllGroups: true,
63 description,
64 }),
65 );
66 const created = yield* deployRule(
67 "10.171.0.0/16",
68 "Initial authorization",
69 );
70 expect(created.clientVpnEndpointId).toBe(clientVpnEndpointId);
71 expect(created.targetNetworkCidr).toBe("10.171.0.0/16");
72 const initial = yield* waitForClientVpn(
73 readClientVpnAuthorizationRules(clientVpnEndpointId),
74 (rules) =>
75 rules.some(
76 (rule) =>
77 rule.DestinationCidr === "10.171.0.0/16" &&
78 rule.Status?.Code === "active",
79 ),
80 "active authorization",
81 );
82 expect(initial).toContainEqual(
83 expect.objectContaining({
84 DestinationCidr: "10.171.0.0/16",
85 AccessAll: true,
86 Description: "Initial authorization",
87 }),
88 );
89 const provider = yield* Provider.findProvider(ClientVpnAuthorizationRule);
90 const listed = yield* waitForClientVpn(
91 provider.list(),
92 (rules) =>
93 rules.some(
94 (rule) =>
95 rule.clientVpnEndpointId === clientVpnEndpointId &&
96 rule.targetNetworkCidr === "10.171.0.0/16",
97 ),
98 "authorization provider list",
99 );
100 expect(listed).toContainEqual(
101 expect.objectContaining({
102 clientVpnEndpointId,
103 targetNetworkCidr: "10.171.0.0/16",
104 authorizeAllGroups: true,
105 }),
106 );
107
108 yield* deployRule("10.171.0.0/16", "Initial authorization");
109 expect(
110 (yield* readClientVpnAuthorizationRules(clientVpnEndpointId)).filter(
111 (rule) =>
112 rule.DestinationCidr === "10.171.0.0/16" &&
113 rule.Status?.Code === "active",
114 ),
115 ).toHaveLength(1);
116
117 // AWS has no modify-authorization API; a description change must revoke and recreate.
118 yield* deployRule("10.171.0.0/16", "Updated authorization");
119 const updated = yield* waitForClientVpn(
120 readClientVpnAuthorizationRules(clientVpnEndpointId),
121 (rules) =>
122 rules.some(
123 (rule) =>
124 rule.DestinationCidr === "10.171.0.0/16" &&
125 rule.Description === "Updated authorization" &&
126 rule.Status?.Code === "active",
127 ),
128 "replaced authorization description",
129 );
130 expect(
131 updated.filter(
132 (rule) =>
133 rule.DestinationCidr === "10.171.0.0/16" &&
134 rule.Status?.Code === "active",
135 ),
136 ).toHaveLength(1);
137
138 const replaced = yield* deployRule("10.172.0.0/16");
139 expect(replaced.targetNetworkCidr).toBe("10.172.0.0/16");
140 const replacement = yield* waitForClientVpn(
141 readClientVpnAuthorizationRules(clientVpnEndpointId),
142 (rules) =>
143 rules.some(
144 (rule) =>
145 rule.DestinationCidr === "10.172.0.0/16" &&
146 rule.Status?.Code === "active",
147 ),
148 "replacement authorization CIDR",
149 );
150 expect(
151 replacement.find(
152 (rule) =>
153 rule.DestinationCidr === "10.172.0.0/16" &&
154 rule.Status?.Code === "active",
155 )?.Description ?? "",
156 ).toBe("");
157 yield* assertClientVpnAuthorizationDeleted(
158 clientVpnEndpointId,
159 "10.171.0.0/16",
160 );
161 yield* stack.destroy();
162 yield* assertClientVpnAuthorizationDeleted(
163 clientVpnEndpointId,
164 "10.172.0.0/16",
165 );
166 }),
167 {
168 tags: ["provider:aws", "provider:aws:acm", "provider:aws:ec2", "live"],
169 timeout: clientVpnTestTimeout,
170 },
171);
172
173test.provider(
174 "recreates an authorization rule revoked out of band",
175 (stack) =>
176 Effect.gen(function* () {
177 yield* stack.destroy();
178 const { endpoint } = yield* prerequisites;
179 const clientVpnEndpointId = endpoint.clientVpnEndpointId;
180 const targetNetworkCidr = "10.173.0.0/16";
181 const program = ClientVpnAuthorizationRule("Authorization", {
182 clientVpnEndpointId,
183 targetNetworkCidr,
184 authorizeAllGroups: true,
185 description: "Managed authorization",
186 });
187 yield* stack.deploy(program);
188 yield* ec2.revokeClientVpnIngress({
189 ClientVpnEndpointId: clientVpnEndpointId,
190 TargetNetworkCidr: targetNetworkCidr,
191 RevokeAllGroups: true,
192 });
193 yield* assertClientVpnAuthorizationDeleted(
194 clientVpnEndpointId,
195 targetNetworkCidr,
196 );
197 yield* stack.deploy(program);
198 const repaired = yield* waitForClientVpn(
199 readClientVpnAuthorizationRules(clientVpnEndpointId),
200 (rules) =>
201 rules.some(
202 (rule) =>
203 rule.DestinationCidr === targetNetworkCidr &&
204 rule.Status?.Code === "active",
205 ),
206 "recreated authorization",
207 );
208 expect(repaired).toContainEqual(
209 expect.objectContaining({
210 DestinationCidr: targetNetworkCidr,
211 AccessAll: true,
212 Description: "Managed authorization",
213 }),
214 );
215 yield* stack.destroy();
216 yield* assertClientVpnAuthorizationDeleted(
217 clientVpnEndpointId,
218 targetNetworkCidr,
219 );
220 }),
221 {
222 tags: ["provider:aws", "provider:aws:acm", "provider:aws:ec2", "live"],
223 timeout: clientVpnTestTimeout,
224 },
225);
226
227test.provider(
228 "discovers an authorization rule by natural key without prior state and deletes it",
229 (stack) =>
230 Effect.gen(function* () {
231 yield* stack.destroy();
232 const { endpoint } = yield* prerequisites;
233 const clientVpnEndpointId = endpoint.clientVpnEndpointId;
234 const targetNetworkCidr = "10.174.0.0/16";
235 const description = "Cold natural-key discovery";
236 yield* ec2.authorizeClientVpnIngress({
237 ClientVpnEndpointId: clientVpnEndpointId,
238 TargetNetworkCidr: targetNetworkCidr,
239 AuthorizeAllGroups: true,
240 Description: description,
241 });
242 const preexisting = yield* waitForClientVpn(
243 readClientVpnAuthorizationRules(clientVpnEndpointId),
244 (rules) =>
245 rules.some(
246 (rule) =>
247 rule.DestinationCidr === targetNetworkCidr &&
248 rule.AccessAll === true &&
249 rule.Status?.Code === "active",
250 ),
251 "SDK-created authorization",
252 );
253 const original = preexisting.filter(
254 (rule) =>
255 rule.DestinationCidr === targetNetworkCidr && rule.AccessAll === true,
256 );
257 expect(original).toHaveLength(1);
258
259 const managed = yield* stack.deploy(
260 ClientVpnAuthorizationRule("Authorization", {
261 clientVpnEndpointId,
262 targetNetworkCidr,
263 authorizeAllGroups: true,
264 description,
265 }),
266 );
267 expect(managed).toMatchObject({
268 clientVpnEndpointId,
269 targetNetworkCidr,
270 authorizeAllGroups: true,
271 description,
272 status: "active",
273 });
274 const observed =
275 yield* readClientVpnAuthorizationRules(clientVpnEndpointId);
276 expect(
277 observed.filter(
278 (rule) =>
279 rule.DestinationCidr === targetNetworkCidr &&
280 rule.AccessAll === true,
281 ),
282 ).toEqual(original);
283
284 yield* stack.destroy();
285 yield* assertClientVpnAuthorizationDeleted(
286 clientVpnEndpointId,
287 targetNetworkCidr,
288 );
289 }),
290 {
291 tags: ["provider:aws", "provider:aws:acm", "provider:aws:ec2", "live"],
292 timeout: clientVpnTestTimeout,
293 },
294);