@gugananuvem/aws-local-simulator 1.0.35 → 1.0.39

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.
Files changed (149) hide show
  1. package/README.md +662 -658
  2. package/package.json +10 -3
  3. package/src/config/default-config.js +71 -0
  4. package/src/config/env-loader.js +4 -2
  5. package/src/middleware/authorization.js +271 -0
  6. package/src/middleware/cloudtrail-logger.js +333 -0
  7. package/src/middleware/error-handler.js +138 -0
  8. package/src/middleware/index.js +79 -0
  9. package/src/middleware/policy-enforcement.js +407 -0
  10. package/src/server.js +74 -4
  11. package/src/services/acm/index.js +66 -0
  12. package/src/services/acm/models/certificate.js +202 -0
  13. package/src/services/acm/server.js +224 -0
  14. package/src/services/acm/simulator.js +293 -0
  15. package/src/services/alb/index.js +104 -0
  16. package/src/services/alb/models/loadBalancer.js +88 -0
  17. package/src/services/alb/models/targetGroup.js +128 -0
  18. package/src/services/alb/server.js +160 -0
  19. package/src/services/alb/simulator.js +262 -0
  20. package/src/services/apigateway/index.js +22 -0
  21. package/src/services/apigateway/server.js +247 -35
  22. package/src/services/apigateway/simulator.js +780 -26
  23. package/src/services/asg/index.js +100 -0
  24. package/src/services/asg/models/autoScalingGroup.js +110 -0
  25. package/src/services/asg/models/launchTemplate.js +41 -0
  26. package/src/services/asg/models/scalingPolicy.js +51 -0
  27. package/src/services/asg/server.js +236 -0
  28. package/src/services/asg/simulator.js +410 -0
  29. package/src/services/aurora/cluster-manager.js +731 -0
  30. package/src/services/aurora/data-api.js +514 -0
  31. package/src/services/aurora/db-engine.js +270 -0
  32. package/src/services/aurora/index.js +159 -0
  33. package/src/services/aurora/rds-api.js +189 -0
  34. package/src/services/aurora/server.js +213 -0
  35. package/src/services/aurora/sql-translator.js +334 -0
  36. package/src/services/cloudformation/index.js +2 -2
  37. package/src/services/cloudformation/server.js +1 -0
  38. package/src/services/cloudfront/cache-engine.js +225 -0
  39. package/src/services/cloudfront/geo-restrictions.js +377 -0
  40. package/src/services/cloudfront/index.js +220 -0
  41. package/src/services/cloudfront/models/behavior.js +158 -0
  42. package/src/services/cloudfront/models/distribution.js +284 -0
  43. package/src/services/cloudfront/models/index.js +99 -0
  44. package/src/services/cloudfront/models/invalidation.js +109 -0
  45. package/src/services/cloudfront/models/origin.js +155 -0
  46. package/src/services/cloudfront/origin-router.js +552 -0
  47. package/src/services/cloudfront/origin-validator.js +398 -0
  48. package/src/services/cloudfront/server.js +512 -0
  49. package/src/services/cloudfront/signed-url-generator.js +377 -0
  50. package/src/services/cloudfront/simulator.js +425 -0
  51. package/src/services/cloudfront/store.js +326 -0
  52. package/src/services/cloudshell/env-builder.js +27 -0
  53. package/src/services/cloudshell/index.js +48 -0
  54. package/src/services/cloudshell/routes.js +60 -0
  55. package/src/services/cloudshell/session-manager.js +226 -0
  56. package/src/services/cloudshell/static/.gitkeep +0 -0
  57. package/src/services/cloudshell/static/index.html +406 -0
  58. package/src/services/cloudshell/websocket-handler.js +107 -0
  59. package/src/services/cloudtrail/index.js +2 -3
  60. package/src/services/cloudtrail/server.js +68 -0
  61. package/src/services/cognito/server.js +4 -1
  62. package/src/services/dynamodb/server.js +4 -1
  63. package/src/services/dynamodb/simulator.js +2 -1
  64. package/src/services/ec2/index.js +101 -0
  65. package/src/services/ec2/instanceServer.js +311 -0
  66. package/src/services/ec2/models/ami.js +39 -0
  67. package/src/services/ec2/models/instance.js +98 -0
  68. package/src/services/ec2/models/networkInterface.js +74 -0
  69. package/src/services/ec2/models/securityGroup.js +71 -0
  70. package/src/services/ec2/server.js +350 -0
  71. package/src/services/ec2/simulator.js +436 -0
  72. package/src/services/eventbridge/server.js +36 -0
  73. package/src/services/eventbridge/simulator.js +230 -45
  74. package/src/services/iam/index.js +65 -0
  75. package/src/services/iam/middleware.js +99 -0
  76. package/src/services/iam/server.js +331 -0
  77. package/src/services/iam/simulator.js +1432 -0
  78. package/src/services/index.js +28 -0
  79. package/src/services/kinesis-analytics/index.js +105 -0
  80. package/src/services/kinesis-analytics/models/application.js +325 -0
  81. package/src/services/kinesis-analytics/models/index.js +12 -0
  82. package/src/services/kinesis-analytics/server.js +250 -0
  83. package/src/services/kinesis-analytics/simulator.js +407 -0
  84. package/src/services/kinesis-analytics/store.js +176 -0
  85. package/src/services/kinesis-data-streams/index.js +106 -0
  86. package/src/services/kinesis-data-streams/models/index.js +12 -0
  87. package/src/services/kinesis-data-streams/models/record.js +70 -0
  88. package/src/services/kinesis-data-streams/models/stream.js +280 -0
  89. package/src/services/kinesis-data-streams/server.js +255 -0
  90. package/src/services/kinesis-data-streams/simulator.js +514 -0
  91. package/src/services/kinesis-data-streams/store.js +163 -0
  92. package/src/services/kinesis-firehose/index.js +106 -0
  93. package/src/services/kinesis-firehose/models/delivery-stream.js +265 -0
  94. package/src/services/kinesis-firehose/models/index.js +10 -0
  95. package/src/services/kinesis-firehose/server.js +215 -0
  96. package/src/services/kinesis-firehose/simulator.js +388 -0
  97. package/src/services/kinesis-firehose/store.js +163 -0
  98. package/src/services/kms/server.js +4 -1
  99. package/src/services/lambda/index.js +26 -0
  100. package/src/services/lambda/server.js +12 -1
  101. package/src/services/lambda/simulator.js +4 -0
  102. package/src/services/opensearch/index.js +105 -0
  103. package/src/services/opensearch/models/document.js +115 -0
  104. package/src/services/opensearch/models/index.js +73 -0
  105. package/src/services/opensearch/server.js +310 -0
  106. package/src/services/opensearch/simulator.js +605 -0
  107. package/src/services/opensearch/store.js +162 -0
  108. package/src/services/organizations/control-tower-simulator.js +321 -0
  109. package/src/services/organizations/control-tower-store.js +182 -0
  110. package/src/services/organizations/iam-integration.js +413 -0
  111. package/src/services/organizations/index.js +172 -0
  112. package/src/services/organizations/models/index.js +13 -0
  113. package/src/services/organizations/models/invitation.js +48 -0
  114. package/src/services/organizations/models/organization.js +566 -0
  115. package/src/services/organizations/models/role.js +244 -0
  116. package/src/services/organizations/models/scp.js +131 -0
  117. package/src/services/organizations/server.js +736 -0
  118. package/src/services/organizations/simulator.js +593 -0
  119. package/src/services/organizations/store.js +235 -0
  120. package/src/services/organizations/vpc-integration.js +453 -0
  121. package/src/services/route53/index.js +67 -0
  122. package/src/services/route53/models/healthCheck.js +101 -0
  123. package/src/services/route53/models/hostedZone.js +37 -0
  124. package/src/services/route53/models/resourceRecord.js +106 -0
  125. package/src/services/route53/server.js +841 -0
  126. package/src/services/route53/simulator.js +350 -0
  127. package/src/services/s3/index.js +62 -0
  128. package/src/services/s3/presigned-url-generator.js +337 -0
  129. package/src/services/s3/server.js +231 -0
  130. package/src/services/s3/simulator.js +551 -4
  131. package/src/services/sns/index.js +31 -0
  132. package/src/services/sns/server.js +9 -0
  133. package/src/services/sqs/index.js +38 -0
  134. package/src/services/sqs/server.js +57 -4
  135. package/src/services/sqs/simulator.js +423 -33
  136. package/src/services/sts/simulator.js +209 -17
  137. package/src/services/vpc/README.md +459 -0
  138. package/src/services/vpc/ec2ResponseFormatter.js +431 -0
  139. package/src/services/vpc/index.js +90 -0
  140. package/src/services/vpc/models/internetGateway.js +71 -0
  141. package/src/services/vpc/models/routeTable.js +160 -0
  142. package/src/services/vpc/models/subnet.js +48 -0
  143. package/src/services/vpc/models/vpc.js +37 -0
  144. package/src/services/vpc/models/vpcEndpoint.js +100 -0
  145. package/src/services/vpc/models/vpcGateway.js +134 -0
  146. package/src/services/vpc/server.js +836 -0
  147. package/src/services/vpc/simulator.js +613 -0
  148. package/src/utils/policy-cache.js +215 -0
  149. package/aws-config +0 -154
@@ -0,0 +1,836 @@
1
+ /**
2
+ * VPC Server
3
+ * Gerencia o servidor HTTP e as rotas do VPC
4
+ */
5
+
6
+ const express = require('express');
7
+ const logger = require('../../utils/logger');
8
+ const EC2ResponseFormatter = require('./ec2ResponseFormatter');
9
+
10
+ class VPCServer {
11
+ constructor(port, config) {
12
+ this.port = port;
13
+ this.config = config;
14
+ this.app = express();
15
+ this.simulator = null;
16
+ }
17
+
18
+ setupMiddlewares() {
19
+ this.app.use(express.json());
20
+ this.app.use(express.urlencoded({ extended: true }));
21
+ }
22
+
23
+ async initialize() {
24
+ logger.debug(`Inicializando VPC Server na porta ${this.port}`);
25
+ this.setupMiddlewares();
26
+ this.setupRoutes();
27
+ }
28
+
29
+ setupRoutes() {
30
+ // ========== VPC Routes ==========
31
+
32
+ // Criar VPC
33
+ this.app.post('/__admin/vpcs/create', (req, res) => {
34
+ try {
35
+ const { CidrBlock, Tags, EnableDnsHostnames, EnableDnsSupport } = req.body;
36
+
37
+ if (!CidrBlock) {
38
+ return res.status(400).json({ error: 'CidrBlock is required' });
39
+ }
40
+
41
+ const vpc = this.simulator.createVPC(CidrBlock, Tags || [], {
42
+ EnableDnsHostnames,
43
+ EnableDnsSupport
44
+ });
45
+
46
+ res.json({ Vpc: vpc.toJSON() });
47
+ } catch (error) {
48
+ logger.error(`Error creating VPC: ${error.message}`);
49
+ res.status(400).json({ error: error.message });
50
+ }
51
+ });
52
+
53
+ // Listar VPCs
54
+ this.app.get('/__admin/vpcs', (req, res) => {
55
+ try {
56
+ const filters = {};
57
+ if (req.query.vpcIds) {
58
+ filters.vpcIds = Array.isArray(req.query.vpcIds)
59
+ ? req.query.vpcIds
60
+ : [req.query.vpcIds];
61
+ }
62
+
63
+ const vpcs = this.simulator.describeVPCs(filters);
64
+ res.json({ Vpcs: vpcs.map(v => v.toJSON()) });
65
+ } catch (error) {
66
+ logger.error(`Error listing VPCs: ${error.message}`);
67
+ res.status(400).json({ error: error.message });
68
+ }
69
+ });
70
+
71
+ // Obter VPC específico
72
+ this.app.get('/__admin/vpcs/:vpcId', (req, res) => {
73
+ try {
74
+ const vpc = this.simulator.getVPC(req.params.vpcId);
75
+ res.json({ Vpc: vpc.toJSON() });
76
+ } catch (error) {
77
+ logger.error(`Error getting VPC: ${error.message}`);
78
+ res.status(404).json({ error: error.message });
79
+ }
80
+ });
81
+
82
+ // Deletar VPC
83
+ this.app.delete('/__admin/vpcs/:vpcId', (req, res) => {
84
+ try {
85
+ this.simulator.deleteVPC(req.params.vpcId);
86
+ res.json({ success: true });
87
+ } catch (error) {
88
+ logger.error(`Error deleting VPC: ${error.message}`);
89
+ res.status(400).json({ error: error.message });
90
+ }
91
+ });
92
+
93
+ // Modificar atributos VPC
94
+ this.app.post('/__admin/vpcs/:vpcId/attributes', (req, res) => {
95
+ try {
96
+ const { attribute, value } = req.body;
97
+
98
+ if (!attribute) {
99
+ return res.status(400).json({ error: 'Attribute is required' });
100
+ }
101
+
102
+ const vpc = this.simulator.modifyVPCAttribute(req.params.vpcId, attribute, value);
103
+ res.json({ Vpc: vpc.toJSON() });
104
+ } catch (error) {
105
+ logger.error(`Error modifying VPC attribute: ${error.message}`);
106
+ res.status(400).json({ error: error.message });
107
+ }
108
+ });
109
+
110
+ // ========== Subnet Routes ==========
111
+
112
+ // Criar Subnet
113
+ this.app.post('/__admin/subnets/create', (req, res) => {
114
+ try {
115
+ const { VpcId, CidrBlock, AvailabilityZone, Tags, MapPublicIpOnLaunch } = req.body;
116
+
117
+ if (!VpcId || !CidrBlock || !AvailabilityZone) {
118
+ return res
119
+ .status(400)
120
+ .json({ error: 'VpcId, CidrBlock and AvailabilityZone are required' });
121
+ }
122
+
123
+ const subnet = this.simulator.createSubnet(
124
+ VpcId,
125
+ CidrBlock,
126
+ AvailabilityZone,
127
+ Tags || [],
128
+ { MapPublicIpOnLaunch }
129
+ );
130
+
131
+ res.json({ Subnet: subnet.toJSON() });
132
+ } catch (error) {
133
+ logger.error(`Error creating Subnet: ${error.message}`);
134
+ res.status(400).json({ error: error.message });
135
+ }
136
+ });
137
+
138
+ // Listar Subnets
139
+ this.app.get('/__admin/subnets', (req, res) => {
140
+ try {
141
+ const filters = {};
142
+ if (req.query.vpcIds) {
143
+ filters.vpcIds = Array.isArray(req.query.vpcIds)
144
+ ? req.query.vpcIds
145
+ : [req.query.vpcIds];
146
+ }
147
+
148
+ const subnets = this.simulator.describeSubnets(filters);
149
+ res.json({ Subnets: subnets.map(s => s.toJSON()) });
150
+ } catch (error) {
151
+ logger.error(`Error listing Subnets: ${error.message}`);
152
+ res.status(400).json({ error: error.message });
153
+ }
154
+ });
155
+
156
+ // Obter Subnet
157
+ this.app.get('/__admin/subnets/:subnetId', (req, res) => {
158
+ try {
159
+ const subnet = this.simulator.getSubnet(req.params.subnetId);
160
+ res.json({ Subnet: subnet.toJSON() });
161
+ } catch (error) {
162
+ logger.error(`Error getting Subnet: ${error.message}`);
163
+ res.status(404).json({ error: error.message });
164
+ }
165
+ });
166
+
167
+ // Deletar Subnet
168
+ this.app.delete('/__admin/subnets/:subnetId', (req, res) => {
169
+ try {
170
+ this.simulator.deleteSubnet(req.params.subnetId);
171
+ res.json({ success: true });
172
+ } catch (error) {
173
+ logger.error(`Error deleting Subnet: ${error.message}`);
174
+ res.status(400).json({ error: error.message });
175
+ }
176
+ });
177
+
178
+ // ========== Route Table Routes ==========
179
+
180
+ // Criar Route Table
181
+ this.app.post('/__admin/route-tables/create', (req, res) => {
182
+ try {
183
+ const { VpcId, Tags } = req.body;
184
+
185
+ if (!VpcId) {
186
+ return res.status(400).json({ error: 'VpcId is required' });
187
+ }
188
+
189
+ const routeTable = this.simulator.createRouteTable(VpcId, Tags || []);
190
+ res.json({ RouteTable: routeTable.toJSON() });
191
+ } catch (error) {
192
+ logger.error(`Error creating Route Table: ${error.message}`);
193
+ res.status(400).json({ error: error.message });
194
+ }
195
+ });
196
+
197
+ // Listar Route Tables
198
+ this.app.get('/__admin/route-tables', (req, res) => {
199
+ try {
200
+ const filters = {};
201
+ if (req.query.vpcIds) {
202
+ filters.vpcIds = Array.isArray(req.query.vpcIds)
203
+ ? req.query.vpcIds
204
+ : [req.query.vpcIds];
205
+ }
206
+
207
+ const routeTables = this.simulator.describeRouteTables(filters);
208
+ res.json({ RouteTables: routeTables.map(rt => rt.toJSON()) });
209
+ } catch (error) {
210
+ logger.error(`Error listing Route Tables: ${error.message}`);
211
+ res.status(400).json({ error: error.message });
212
+ }
213
+ });
214
+
215
+ // Obter Route Table
216
+ this.app.get('/__admin/route-tables/:rtId', (req, res) => {
217
+ try {
218
+ const routeTable = this.simulator.getRouteTable(req.params.rtId);
219
+ res.json({ RouteTable: routeTable.toJSON() });
220
+ } catch (error) {
221
+ logger.error(`Error getting Route Table: ${error.message}`);
222
+ res.status(404).json({ error: error.message });
223
+ }
224
+ });
225
+
226
+ // Deletar Route Table
227
+ this.app.delete('/__admin/route-tables/:rtId', (req, res) => {
228
+ try {
229
+ this.simulator.deleteRouteTable(req.params.rtId);
230
+ res.json({ success: true });
231
+ } catch (error) {
232
+ logger.error(`Error deleting Route Table: ${error.message}`);
233
+ res.status(400).json({ error: error.message });
234
+ }
235
+ });
236
+
237
+ // Adicionar Rota
238
+ this.app.post('/__admin/route-tables/:rtId/routes', (req, res) => {
239
+ try {
240
+ const { DestinationCidrBlock, Target, TargetType } = req.body;
241
+
242
+ if (!DestinationCidrBlock || !Target) {
243
+ return res
244
+ .status(400)
245
+ .json({ error: 'DestinationCidrBlock and Target are required' });
246
+ }
247
+
248
+ const route = this.simulator.addRoute(
249
+ req.params.rtId,
250
+ DestinationCidrBlock,
251
+ Target,
252
+ TargetType || 'igw'
253
+ );
254
+
255
+ res.json({ Route: route.toJSON() });
256
+ } catch (error) {
257
+ logger.error(`Error adding route: ${error.message}`);
258
+ res.status(400).json({ error: error.message });
259
+ }
260
+ });
261
+
262
+ // Remover Rota
263
+ this.app.delete('/__admin/route-tables/:rtId/routes/:cidr', (req, res) => {
264
+ try {
265
+ const cidr = decodeURIComponent(req.params.cidr);
266
+ this.simulator.removeRoute(req.params.rtId, cidr);
267
+ res.json({ success: true });
268
+ } catch (error) {
269
+ logger.error(`Error removing route: ${error.message}`);
270
+ res.status(400).json({ error: error.message });
271
+ }
272
+ });
273
+
274
+ // Associar Route Table com Subnet
275
+ this.app.post('/__admin/route-tables/:rtId/associations', (req, res) => {
276
+ try {
277
+ const { SubnetId } = req.body;
278
+
279
+ if (!SubnetId) {
280
+ return res.status(400).json({ error: 'SubnetId is required' });
281
+ }
282
+
283
+ const association = this.simulator.associateRouteTable(req.params.rtId, SubnetId);
284
+ res.json({ Association: association.toJSON() });
285
+ } catch (error) {
286
+ logger.error(`Error associating route table: ${error.message}`);
287
+ res.status(400).json({ error: error.message });
288
+ }
289
+ });
290
+
291
+ // Desassociar Route Table
292
+ this.app.delete('/__admin/route-tables/:rtId/associations/:subnetId', (req, res) => {
293
+ try {
294
+ this.simulator.disassociateRouteTable(req.params.rtId, req.params.subnetId);
295
+ res.json({ success: true });
296
+ } catch (error) {
297
+ logger.error(`Error disassociating route table: ${error.message}`);
298
+ res.status(400).json({ error: error.message });
299
+ }
300
+ });
301
+
302
+ // ========== Internet Gateway Routes ==========
303
+
304
+ // Criar Internet Gateway
305
+ this.app.post('/__admin/internet-gateways/create', (req, res) => {
306
+ try {
307
+ const { Tags } = req.body;
308
+ const igw = this.simulator.createInternetGateway(Tags || []);
309
+ res.json({ InternetGateway: igw.toJSON() });
310
+ } catch (error) {
311
+ logger.error(`Error creating IGW: ${error.message}`);
312
+ res.status(400).json({ error: error.message });
313
+ }
314
+ });
315
+
316
+ // Listar Internet Gateways
317
+ this.app.get('/__admin/internet-gateways', (req, res) => {
318
+ try {
319
+ const igws = this.simulator.describeInternetGateways();
320
+ res.json({ InternetGateways: igws.map(igw => igw.toJSON()) });
321
+ } catch (error) {
322
+ logger.error(`Error listing IGWs: ${error.message}`);
323
+ res.status(400).json({ error: error.message });
324
+ }
325
+ });
326
+
327
+ // Obter Internet Gateway
328
+ this.app.get('/__admin/internet-gateways/:igwId', (req, res) => {
329
+ try {
330
+ const igw = this.simulator.getInternetGateway(req.params.igwId);
331
+ res.json({ InternetGateway: igw.toJSON() });
332
+ } catch (error) {
333
+ logger.error(`Error getting IGW: ${error.message}`);
334
+ res.status(404).json({ error: error.message });
335
+ }
336
+ });
337
+
338
+ // Anexar IGW
339
+ this.app.post('/__admin/internet-gateways/:igwId/attach', (req, res) => {
340
+ try {
341
+ const { VpcId } = req.body;
342
+
343
+ if (!VpcId) {
344
+ return res.status(400).json({ error: 'VpcId is required' });
345
+ }
346
+
347
+ const attachment = this.simulator.attachInternetGateway(req.params.igwId, VpcId);
348
+ res.json({ Attachment: attachment.toJSON() });
349
+ } catch (error) {
350
+ logger.error(`Error attaching IGW: ${error.message}`);
351
+ res.status(400).json({ error: error.message });
352
+ }
353
+ });
354
+
355
+ // Desanexar IGW
356
+ this.app.post('/__admin/internet-gateways/:igwId/detach', (req, res) => {
357
+ try {
358
+ const { VpcId } = req.body;
359
+
360
+ if (!VpcId) {
361
+ return res.status(400).json({ error: 'VpcId is required' });
362
+ }
363
+
364
+ this.simulator.detachInternetGateway(req.params.igwId, VpcId);
365
+ res.json({ success: true });
366
+ } catch (error) {
367
+ logger.error(`Error detaching IGW: ${error.message}`);
368
+ res.status(400).json({ error: error.message });
369
+ }
370
+ });
371
+
372
+ // Deletar IGW
373
+ this.app.delete('/__admin/internet-gateways/:igwId', (req, res) => {
374
+ try {
375
+ this.simulator.deleteInternetGateway(req.params.igwId);
376
+ res.json({ success: true });
377
+ } catch (error) {
378
+ logger.error(`Error deleting IGW: ${error.message}`);
379
+ res.status(400).json({ error: error.message });
380
+ }
381
+ });
382
+
383
+ // ========== VPC Endpoint Routes ==========
384
+
385
+ // Criar VPC Endpoint
386
+ this.app.post('/__admin/vpc-endpoints/create', (req, res) => {
387
+ try {
388
+ const { VpcId, ServiceName, VpcEndpointType, RouteTableIds, SubnetIds } = req.body;
389
+
390
+ if (!VpcId || !ServiceName) {
391
+ return res.status(400).json({ error: 'VpcId and ServiceName are required' });
392
+ }
393
+
394
+ const endpoint = this.simulator.createVPCEndpoint(VpcId, ServiceName, {
395
+ VpcEndpointType,
396
+ RouteTableIds,
397
+ SubnetIds
398
+ });
399
+
400
+ res.json({ VpcEndpoint: endpoint.toJSON() });
401
+ } catch (error) {
402
+ logger.error(`Error creating VPC Endpoint: ${error.message}`);
403
+ res.status(400).json({ error: error.message });
404
+ }
405
+ });
406
+
407
+ // Listar VPC Endpoints
408
+ this.app.get('/__admin/vpc-endpoints', (req, res) => {
409
+ try {
410
+ const filters = {};
411
+ if (req.query.vpcIds) {
412
+ filters.vpcIds = Array.isArray(req.query.vpcIds)
413
+ ? req.query.vpcIds
414
+ : [req.query.vpcIds];
415
+ }
416
+
417
+ const endpoints = this.simulator.describeVPCEndpoints(filters);
418
+ res.json({ VpcEndpoints: endpoints.map(e => e.toJSON()) });
419
+ } catch (error) {
420
+ logger.error(`Error listing VPC Endpoints: ${error.message}`);
421
+ res.status(400).json({ error: error.message });
422
+ }
423
+ });
424
+
425
+ // Obter VPC Endpoint
426
+ this.app.get('/__admin/vpc-endpoints/:epId', (req, res) => {
427
+ try {
428
+ const endpoint = this.simulator.getVPCEndpoint(req.params.epId);
429
+ res.json({ VpcEndpoint: endpoint.toJSON() });
430
+ } catch (error) {
431
+ logger.error(`Error getting VPC Endpoint: ${error.message}`);
432
+ res.status(404).json({ error: error.message });
433
+ }
434
+ });
435
+
436
+ // Modificar VPC Endpoint
437
+ this.app.post('/__admin/vpc-endpoints/:epId/modify', (req, res) => {
438
+ try {
439
+ const changes = req.body;
440
+ const endpoint = this.simulator.modifyVPCEndpoint(req.params.epId, changes);
441
+ res.json({ VpcEndpoint: endpoint.toJSON() });
442
+ } catch (error) {
443
+ logger.error(`Error modifying VPC Endpoint: ${error.message}`);
444
+ res.status(400).json({ error: error.message });
445
+ }
446
+ });
447
+
448
+ // Deletar VPC Endpoint
449
+ this.app.delete('/__admin/vpc-endpoints/:epId', (req, res) => {
450
+ try {
451
+ this.simulator.deleteVPCEndpoint(req.params.epId);
452
+ res.json({ success: true });
453
+ } catch (error) {
454
+ logger.error(`Error deleting VPC Endpoint: ${error.message}`);
455
+ res.status(400).json({ error: error.message });
456
+ }
457
+ });
458
+
459
+ // ========== Health Check ==========
460
+
461
+ this.app.get('/__admin/health', (req, res) => {
462
+ res.json({ status: 'ok', service: 'vpc' });
463
+ });
464
+
465
+ // Listar todos os recursos
466
+ this.app.get('/__admin/resources', (req, res) => {
467
+ try {
468
+ const resources = this.simulator.getAllResources();
469
+ res.json({
470
+ vpcs: resources.vpcs.map(v => v.toJSON()),
471
+ subnets: resources.subnets.map(s => s.toJSON()),
472
+ routeTables: resources.routeTables.map(rt => rt.toJSON()),
473
+ internetGateways: resources.internetGateways.map(igw => igw.toJSON()),
474
+ vpcEndpoints: resources.vpcEndpoints.map(e => e.toJSON()),
475
+ natGateways: resources.natGateways.map(ng => ng.toJSON())
476
+ });
477
+ } catch (error) {
478
+ logger.error(`Error fetching resources: ${error.message}`);
479
+ res.status(400).json({ error: error.message });
480
+ }
481
+ });
482
+
483
+ // ========== EC2 Query Protocol Internal Routes ==========
484
+ // (Kept for backward compatibility with REST API)
485
+ // These routes are now also handled by the catch-all POST / handler above
486
+
487
+ // DeleteRouteTable
488
+ this.app.post('/__internal/delete-route-table', (req, res) => {
489
+ try {
490
+ const routeTableId = req.body.RouteTableId;
491
+ if (!routeTableId) {
492
+ throw new Error('RouteTableId is required');
493
+ }
494
+
495
+ this.simulator.deleteRouteTable(routeTableId);
496
+ const xmlResponse = EC2ResponseFormatter.formatDeleteRouteTableResponse();
497
+ res.set('Content-Type', 'text/xml');
498
+ res.send(xmlResponse);
499
+ } catch (error) {
500
+ logger.error(`Error deleting Route Table: ${error.message}`);
501
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
502
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
503
+ }
504
+ });
505
+
506
+ // Catch-all POST / for EC2 Query Protocol (AWS CLI)
507
+ this.app.post('/', (req, res) => {
508
+ try {
509
+ const action = req.body?.Action || '';
510
+ logger.debug(`EC2 Query Protocol Action: ${action}`);
511
+
512
+ // Mapear ações EC2 para handlers
513
+ switch (action) {
514
+ case 'DescribeVpcs':
515
+ return this._handleDescribeVpcs(req, res);
516
+ case 'CreateVpc':
517
+ return this._handleCreateVpc(req, res);
518
+ case 'DeleteVpc':
519
+ return this._handleDeleteVpc(req, res);
520
+ case 'DescribeSubnets':
521
+ return this._handleDescribeSubnets(req, res);
522
+ case 'CreateSubnet':
523
+ return this._handleCreateSubnet(req, res);
524
+ case 'DeleteSubnet':
525
+ return this._handleDeleteSubnet(req, res);
526
+ case 'DescribeInternetGateways':
527
+ return this._handleDescribeIgws(req, res);
528
+ case 'CreateInternetGateway':
529
+ return this._handleCreateIgw(req, res);
530
+ case 'AttachInternetGateway':
531
+ return this._handleAttachIgw(req, res);
532
+ case 'DetachInternetGateway':
533
+ return this._handleDetachIgw(req, res);
534
+ case 'DeleteInternetGateway':
535
+ return this._handleDeleteIgw(req, res);
536
+ case 'DescribeRouteTables':
537
+ return this._handleDescribeRouteTables(req, res);
538
+ case 'CreateRouteTable':
539
+ return this._handleCreateRouteTable(req, res);
540
+ case 'CreateRoute':
541
+ return this._handleCreateRoute(req, res);
542
+ case 'DeleteRouteTable':
543
+ return this._handleDeleteRouteTable(req, res);
544
+ default:
545
+ const xmlError = EC2ResponseFormatter.formatErrorResponse(
546
+ 'InvalidAction',
547
+ `Unknown Action: ${action}`
548
+ );
549
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
550
+ }
551
+ } catch (error) {
552
+ logger.error(`Error handling EC2 Query: ${error.message}`);
553
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
554
+ res.status(500).set('Content-Type', 'text/xml').send(xmlError);
555
+ }
556
+ });
557
+ }
558
+
559
+ // EC2 Query Protocol handlers
560
+ _handleDescribeVpcs(req, res) {
561
+ try {
562
+ const vpcs = this.simulator.describeVPCs();
563
+ const xmlResponse = EC2ResponseFormatter.formatDescribeVpcsResponse(
564
+ vpcs.map(vpc => vpc.toJSON())
565
+ );
566
+ res.set('Content-Type', 'text/xml');
567
+ res.send(xmlResponse);
568
+ } catch (error) {
569
+ logger.error(`Error describing VPCs: ${error.message}`);
570
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
571
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
572
+ }
573
+ }
574
+
575
+ _handleCreateVpc(req, res) {
576
+ try {
577
+ const cidrBlock = req.body.CidrBlock;
578
+ if (!cidrBlock) {
579
+ throw new Error('CidrBlock is required');
580
+ }
581
+
582
+ const vpc = this.simulator.createVPC(cidrBlock);
583
+ const xmlResponse = EC2ResponseFormatter.formatCreateVpcResponse(vpc.toJSON());
584
+ res.set('Content-Type', 'text/xml');
585
+ res.send(xmlResponse);
586
+ } catch (error) {
587
+ logger.error(`Error creating VPC: ${error.message}`);
588
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
589
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
590
+ }
591
+ }
592
+
593
+ _handleDeleteVpc(req, res) {
594
+ try {
595
+ const vpcId = req.body.VpcId;
596
+ if (!vpcId) {
597
+ throw new Error('VpcId is required');
598
+ }
599
+
600
+ this.simulator.deleteVPC(vpcId);
601
+ const xmlResponse = EC2ResponseFormatter.formatDeleteVpcResponse();
602
+ res.set('Content-Type', 'text/xml');
603
+ res.send(xmlResponse);
604
+ } catch (error) {
605
+ logger.error(`Error deleting VPC: ${error.message}`);
606
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
607
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
608
+ }
609
+ }
610
+
611
+ _handleDescribeSubnets(req, res) {
612
+ try {
613
+ const subnets = this.simulator.describeSubnets();
614
+ const xmlResponse = EC2ResponseFormatter.formatDescribeSubnetsResponse(
615
+ subnets.map(subnet => subnet.toJSON())
616
+ );
617
+ res.set('Content-Type', 'text/xml');
618
+ res.send(xmlResponse);
619
+ } catch (error) {
620
+ logger.error(`Error describing Subnets: ${error.message}`);
621
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
622
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
623
+ }
624
+ }
625
+
626
+ _handleCreateSubnet(req, res) {
627
+ try {
628
+ const { VpcId, CidrBlock, AvailabilityZone } = req.body;
629
+ if (!VpcId || !CidrBlock || !AvailabilityZone) {
630
+ throw new Error('VpcId, CidrBlock and AvailabilityZone are required');
631
+ }
632
+
633
+ const subnet = this.simulator.createSubnet(VpcId, CidrBlock, AvailabilityZone);
634
+ const xmlResponse = EC2ResponseFormatter.formatCreateSubnetResponse(subnet.toJSON());
635
+ res.set('Content-Type', 'text/xml');
636
+ res.send(xmlResponse);
637
+ } catch (error) {
638
+ logger.error(`Error creating Subnet: ${error.message}`);
639
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
640
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
641
+ }
642
+ }
643
+
644
+ _handleDeleteSubnet(req, res) {
645
+ try {
646
+ const subnetId = req.body.SubnetId;
647
+ if (!subnetId) {
648
+ throw new Error('SubnetId is required');
649
+ }
650
+
651
+ this.simulator.deleteSubnet(subnetId);
652
+ const xmlResponse = EC2ResponseFormatter.formatDeleteSubnetResponse();
653
+ res.set('Content-Type', 'text/xml');
654
+ res.send(xmlResponse);
655
+ } catch (error) {
656
+ logger.error(`Error deleting Subnet: ${error.message}`);
657
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
658
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
659
+ }
660
+ }
661
+
662
+ _handleDescribeIgws(req, res) {
663
+ try {
664
+ const igws = this.simulator.describeInternetGateways();
665
+ const xmlResponse = EC2ResponseFormatter.formatDescribeIgwResponse(
666
+ igws.map(igw => igw.toJSON())
667
+ );
668
+ res.set('Content-Type', 'text/xml');
669
+ res.send(xmlResponse);
670
+ } catch (error) {
671
+ logger.error(`Error describing IGWs: ${error.message}`);
672
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
673
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
674
+ }
675
+ }
676
+
677
+ _handleCreateIgw(req, res) {
678
+ try {
679
+ const igw = this.simulator.createInternetGateway();
680
+ const xmlResponse = EC2ResponseFormatter.formatCreateIgwResponse(igw.toJSON());
681
+ res.set('Content-Type', 'text/xml');
682
+ res.send(xmlResponse);
683
+ } catch (error) {
684
+ logger.error(`Error creating IGW: ${error.message}`);
685
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
686
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
687
+ }
688
+ }
689
+
690
+ _handleAttachIgw(req, res) {
691
+ try {
692
+ const { InternetGatewayId, VpcId } = req.body;
693
+ if (!InternetGatewayId || !VpcId) {
694
+ throw new Error('InternetGatewayId and VpcId are required');
695
+ }
696
+
697
+ this.simulator.attachInternetGateway(InternetGatewayId, VpcId);
698
+ const xmlResponse = EC2ResponseFormatter.formatAttachIgwResponse();
699
+ res.set('Content-Type', 'text/xml');
700
+ res.send(xmlResponse);
701
+ } catch (error) {
702
+ logger.error(`Error attaching IGW: ${error.message}`);
703
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
704
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
705
+ }
706
+ }
707
+
708
+ _handleDetachIgw(req, res) {
709
+ try {
710
+ const { InternetGatewayId, VpcId } = req.body;
711
+ if (!InternetGatewayId || !VpcId) {
712
+ throw new Error('InternetGatewayId and VpcId are required');
713
+ }
714
+
715
+ this.simulator.detachInternetGateway(InternetGatewayId, VpcId);
716
+ const xmlResponse = EC2ResponseFormatter.formatDetachIgwResponse();
717
+ res.set('Content-Type', 'text/xml');
718
+ res.send(xmlResponse);
719
+ } catch (error) {
720
+ logger.error(`Error detaching IGW: ${error.message}`);
721
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
722
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
723
+ }
724
+ }
725
+
726
+ _handleDeleteIgw(req, res) {
727
+ try {
728
+ const internetGatewayId = req.body.InternetGatewayId;
729
+ if (!internetGatewayId) {
730
+ throw new Error('InternetGatewayId is required');
731
+ }
732
+
733
+ this.simulator.deleteInternetGateway(internetGatewayId);
734
+ const xmlResponse = EC2ResponseFormatter.formatDeleteIgwResponse();
735
+ res.set('Content-Type', 'text/xml');
736
+ res.send(xmlResponse);
737
+ } catch (error) {
738
+ logger.error(`Error deleting IGW: ${error.message}`);
739
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
740
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
741
+ }
742
+ }
743
+
744
+ _handleDescribeRouteTables(req, res) {
745
+ try {
746
+ const routeTables = this.simulator.describeRouteTables();
747
+ const xmlResponse = EC2ResponseFormatter.formatDescribeRouteTablesResponse(
748
+ routeTables.map(rt => rt.toJSON())
749
+ );
750
+ res.set('Content-Type', 'text/xml');
751
+ res.send(xmlResponse);
752
+ } catch (error) {
753
+ logger.error(`Error describing Route Tables: ${error.message}`);
754
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
755
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
756
+ }
757
+ }
758
+
759
+ _handleCreateRouteTable(req, res) {
760
+ try {
761
+ const vpcId = req.body.VpcId;
762
+ if (!vpcId) {
763
+ throw new Error('VpcId is required');
764
+ }
765
+
766
+ const routeTable = this.simulator.createRouteTable(vpcId);
767
+ const xmlResponse = EC2ResponseFormatter.formatCreateRouteTableResponse(
768
+ routeTable.toJSON()
769
+ );
770
+ res.set('Content-Type', 'text/xml');
771
+ res.send(xmlResponse);
772
+ } catch (error) {
773
+ logger.error(`Error creating Route Table: ${error.message}`);
774
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
775
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
776
+ }
777
+ }
778
+
779
+ _handleCreateRoute(req, res) {
780
+ try {
781
+ const { RouteTableId, DestinationCidrBlock, GatewayId } = req.body;
782
+ if (!RouteTableId || !DestinationCidrBlock || !GatewayId) {
783
+ throw new Error('RouteTableId, DestinationCidrBlock and GatewayId are required');
784
+ }
785
+
786
+ this.simulator.addRoute(RouteTableId, DestinationCidrBlock, GatewayId);
787
+ const xmlResponse = EC2ResponseFormatter.formatCreateRouteResponse();
788
+ res.set('Content-Type', 'text/xml');
789
+ res.send(xmlResponse);
790
+ } catch (error) {
791
+ logger.error(`Error creating Route: ${error.message}`);
792
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
793
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
794
+ }
795
+ }
796
+
797
+ _handleDeleteRouteTable(req, res) {
798
+ try {
799
+ const routeTableId = req.body.RouteTableId;
800
+ if (!routeTableId) {
801
+ throw new Error('RouteTableId is required');
802
+ }
803
+
804
+ this.simulator.deleteRouteTable(routeTableId);
805
+ const xmlResponse = EC2ResponseFormatter.formatDeleteRouteTableResponse();
806
+ res.set('Content-Type', 'text/xml');
807
+ res.send(xmlResponse);
808
+ } catch (error) {
809
+ logger.error(`Error deleting Route Table: ${error.message}`);
810
+ const xmlError = EC2ResponseFormatter.formatErrorResponse('InternalError', error.message);
811
+ res.status(400).set('Content-Type', 'text/xml').send(xmlError);
812
+ }
813
+ }
814
+
815
+ async start() {
816
+ return new Promise((resolve) => {
817
+ this.server = this.app.listen(this.port, () => {
818
+ logger.info(`VPC Server running on http://localhost:${this.port}`);
819
+ resolve();
820
+ });
821
+ });
822
+ }
823
+
824
+ async stop() {
825
+ if (this.server) {
826
+ return new Promise((resolve) => {
827
+ this.server.close(() => {
828
+ logger.info('VPC Server stopped');
829
+ resolve();
830
+ });
831
+ });
832
+ }
833
+ }
834
+ }
835
+
836
+ module.exports = VPCServer;