@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,552 @@
1
+ /**
2
+ * CloudFront Origin Router
3
+ * Redireciona requisições para origins reais (API Gateway, EC2/ALB, Lambda, S3, HTTP)
4
+ * Permite que CloudFront funcione como reverse proxy para serviços reais
5
+ */
6
+
7
+ const http = require('http');
8
+ const https = require('https');
9
+
10
+ class OriginRouter {
11
+ constructor(servicesRegistry = {}) {
12
+ this.servicesRegistry = servicesRegistry;
13
+ this.requestCache = new Map();
14
+ this.cacheExpiry = 5 * 60 * 1000; // 5 minutes
15
+ }
16
+
17
+ /**
18
+ * Rotear requisição para origin real
19
+ * @param {object} origin - Configuração da origin
20
+ * @param {string} path - Path da requisição
21
+ * @param {object} request - Objeto de requisição
22
+ * @returns {Promise<object>} Response da origin
23
+ */
24
+ async routeToOrigin(origin, path, request = {}) {
25
+ try {
26
+ // Detectar tipo de origin
27
+ const originType = this._detectOriginType(origin);
28
+
29
+ switch (originType) {
30
+ case 'apigateway':
31
+ return await this._routeToApiGateway(origin, path, request);
32
+
33
+ case 'alb':
34
+ case 'ec2':
35
+ return await this._routeToALB(origin, path, request);
36
+
37
+ case 's3':
38
+ return await this._routeToS3(origin, path, request);
39
+
40
+ case 'lambda':
41
+ return await this._routeToLambda(origin, path, request);
42
+
43
+ case 'http':
44
+ default:
45
+ return await this._routeToHttp(origin, path, request);
46
+ }
47
+ } catch (error) {
48
+ return {
49
+ status: 502,
50
+ headers: { 'Content-Type': 'application/json' },
51
+ body: JSON.stringify({
52
+ error: 'Bad Gateway',
53
+ message: `Could not reach origin: ${error.message}`
54
+ }),
55
+ timestamp: new Date().toISOString(),
56
+ originError: true
57
+ };
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Registrar serviço para routing
63
+ * @param {string} serviceName - Nome do serviço (apigateway, ec2, etc)
64
+ * @param {object} serviceInstance - Instância do serviço
65
+ */
66
+ registerService(serviceName, serviceInstance) {
67
+ this.servicesRegistry[serviceName] = serviceInstance;
68
+ }
69
+
70
+ /**
71
+ * Private Methods
72
+ */
73
+
74
+ _detectOriginType(origin) {
75
+ if (!origin) return 'http';
76
+
77
+ const domain = origin.DomainName || origin.domainName || '';
78
+
79
+ // API Gateway
80
+ if (domain.includes('apigateway') || domain.includes('execute-api')) {
81
+ return 'apigateway';
82
+ }
83
+
84
+ // ALB/Load Balancer
85
+ if (domain.includes('elb.amazonaws.com') ||
86
+ domain.includes('alb') ||
87
+ origin.ALBTargetGroup) {
88
+ return 'alb';
89
+ }
90
+
91
+ // EC2
92
+ if (domain.includes('ec2') || domain.includes('compute')) {
93
+ return 'ec2';
94
+ }
95
+
96
+ // S3
97
+ if (domain.includes('s3.amazonaws.com') ||
98
+ domain.includes('s3-') ||
99
+ origin.S3OriginConfig) {
100
+ return 's3';
101
+ }
102
+
103
+ // Lambda
104
+ if (domain.includes('lambda') || domain.startsWith('arn:aws:lambda')) {
105
+ return 'lambda';
106
+ }
107
+
108
+ // HTTP/HTTPS
109
+ return 'http';
110
+ }
111
+
112
+ /**
113
+ * Rotear para API Gateway
114
+ */
115
+ async _routeToApiGateway(origin, path, request) {
116
+ const apiGateway = this.servicesRegistry.apigateway;
117
+
118
+ if (!apiGateway) {
119
+ return this._simulatedResponse('API Gateway service not available');
120
+ }
121
+
122
+ try {
123
+ const simulator = apiGateway.getSimulator?.() || apiGateway.simulator;
124
+
125
+ if (!simulator) {
126
+ return this._simulatedResponse('API Gateway simulator not available');
127
+ }
128
+
129
+ // Extrair API ID e stage da domain
130
+ // Formato: {api-id}.execute-api.{region}.amazonaws.com/{stage}
131
+ const domain = origin.DomainName || origin.domainName || '';
132
+ const stagePath = (origin.originPath || '') + path;
133
+
134
+ // Simular requisição ao API Gateway
135
+ const response = simulator.processRequest?.({
136
+ method: request.method || 'GET',
137
+ path: stagePath,
138
+ headers: request.headers || {},
139
+ body: request.body
140
+ });
141
+
142
+ if (response) {
143
+ return {
144
+ status: response.status || 200,
145
+ headers: response.headers || { 'Content-Type': 'application/json' },
146
+ body: response.body || JSON.stringify(response),
147
+ timestamp: new Date().toISOString(),
148
+ origin: 'apigateway',
149
+ originDomain: domain
150
+ };
151
+ }
152
+
153
+ return this._simulatedResponse('API Gateway request processed', 200);
154
+ } catch (error) {
155
+ return this._errorResponse(`API Gateway error: ${error.message}`, 502);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Rotear para ALB/EC2
161
+ */
162
+ async _routeToALB(origin, path, request) {
163
+ const ec2Service = this.servicesRegistry.ec2 || this.servicesRegistry.alb;
164
+
165
+ if (!ec2Service) {
166
+ return this._simulatedResponse('ALB/EC2 service not available');
167
+ }
168
+
169
+ try {
170
+ const simulator = ec2Service.getSimulator?.() || ec2Service.simulator;
171
+
172
+ if (!simulator) {
173
+ return this._simulatedResponse('ALB simulator not available');
174
+ }
175
+
176
+ // Processar requisição no ALB
177
+ const fullPath = (origin.OriginPath || origin.originPath || '') + path;
178
+
179
+ // Simulação: o ALB roteia para instâncias EC2
180
+ const response = simulator.handleRequest?.({
181
+ method: request.method || 'GET',
182
+ path: fullPath,
183
+ headers: request.headers || {},
184
+ host: origin.DomainName || origin.domainName,
185
+ body: request.body
186
+ });
187
+
188
+ if (response) {
189
+ return {
190
+ status: response.status || 200,
191
+ headers: response.headers || { 'Content-Type': 'text/html' },
192
+ body: response.body || 'Response from ALB',
193
+ timestamp: new Date().toISOString(),
194
+ origin: 'alb',
195
+ originDomain: origin.DomainName || origin.domainName
196
+ };
197
+ }
198
+
199
+ return this._simulatedResponse('ALB request processed', 200);
200
+ } catch (error) {
201
+ return this._errorResponse(`ALB error: ${error.message}`, 502);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Rotear para S3
207
+ */
208
+ async _routeToS3(origin, path, request) {
209
+ const s3Service = this.servicesRegistry.s3;
210
+
211
+ if (!s3Service) {
212
+ return this._simulatedResponse('S3 service not available');
213
+ }
214
+
215
+ try {
216
+ const simulator = s3Service.getSimulator?.() || s3Service.simulator;
217
+
218
+ if (!simulator) {
219
+ return this._simulatedResponse('S3 simulator not available');
220
+ }
221
+
222
+ // Extrair bucket name da domain
223
+ // Formato: bucket.s3.amazonaws.com ou s3.amazonaws.com/bucket
224
+ const domain = origin.DomainName || origin.domainName || '';
225
+ let bucketName = domain.split('.')[0];
226
+
227
+ // Se for virtual-hosted style
228
+ if (domain.includes('s3.amazonaws.com')) {
229
+ const pathSegments = path.split('/');
230
+ bucketName = pathSegments[1];
231
+ }
232
+
233
+ const objectPath = path.replace(`/${bucketName}`, '');
234
+
235
+ // Verificar se CloudFront tem permissão no bucket
236
+ const bucketPolicy = simulator.getBucketPolicy?.(bucketName);
237
+
238
+ if (bucketPolicy) {
239
+ // Se há política, verificar se permite acesso do CloudFront
240
+ const hasCloudFrontAccess = this._checkS3BucketPolicy(
241
+ bucketPolicy,
242
+ origin,
243
+ request
244
+ );
245
+
246
+ if (!hasCloudFrontAccess) {
247
+ return this._errorResponse(
248
+ `Access Denied to S3 bucket: ${bucketName}. CloudFront does not have permission.`,
249
+ 403
250
+ );
251
+ }
252
+ }
253
+
254
+ // Buscar objeto no S3
255
+ try {
256
+ const object = simulator.getObject(bucketName, objectPath);
257
+
258
+ if (object && object.error) {
259
+ // Objeto não existe ou bucket não existe
260
+ return this._errorResponse(
261
+ object.error.message,
262
+ object.status || 404
263
+ );
264
+ }
265
+
266
+ if (object) {
267
+ return {
268
+ status: 200,
269
+ headers: {
270
+ 'Content-Type': object.contentType || 'application/octet-stream',
271
+ 'Content-Length': object.size || 0,
272
+ 'Last-Modified': object.lastModified || new Date().toUTCString(),
273
+ 'ETag': object.etag || ''
274
+ },
275
+ body: object.content,
276
+ timestamp: new Date().toISOString(),
277
+ origin: 's3',
278
+ bucket: bucketName
279
+ };
280
+ }
281
+ } catch (e) {
282
+ // Object not found
283
+ }
284
+
285
+ return this._errorResponse('S3 object not found', 404);
286
+ } catch (error) {
287
+ return this._errorResponse(`S3 error: ${error.message}`, 502);
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Rotear para Lambda
293
+ */
294
+ async _routeToLambda(origin, path, request) {
295
+ const lambdaService = this.servicesRegistry.lambda;
296
+
297
+ if (!lambdaService) {
298
+ return this._simulatedResponse('Lambda service not available');
299
+ }
300
+
301
+ try {
302
+ const simulator = lambdaService.getSimulator?.() || lambdaService.simulator;
303
+
304
+ if (!simulator) {
305
+ return this._simulatedResponse('Lambda simulator not available');
306
+ }
307
+
308
+ // Invocar função Lambda
309
+ const functionName = this._extractLambdaFunctionName(origin);
310
+
311
+ if (!functionName) {
312
+ return this._errorResponse('Invalid Lambda function name', 400);
313
+ }
314
+
315
+ // Preparar payload
316
+ const payload = {
317
+ path: path,
318
+ method: request.method || 'GET',
319
+ headers: request.headers || {},
320
+ body: request.body,
321
+ queryStringParameters: this._parseQueryString(path)
322
+ };
323
+
324
+ // Invocar Lambda
325
+ const response = await simulator.invoke(functionName, payload);
326
+
327
+ if (response) {
328
+ return {
329
+ status: response.statusCode || 200,
330
+ headers: response.headers || { 'Content-Type': 'application/json' },
331
+ body: response.body,
332
+ timestamp: new Date().toISOString(),
333
+ origin: 'lambda',
334
+ function: functionName
335
+ };
336
+ }
337
+
338
+ return this._simulatedResponse('Lambda invocation processed', 200);
339
+ } catch (error) {
340
+ return this._errorResponse(`Lambda error: ${error.message}`, 502);
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Rotear para HTTP/HTTPS externo
346
+ */
347
+ async _routeToHttp(origin, path, request) {
348
+ return new Promise((resolve, reject) => {
349
+ try {
350
+ const domain = origin.DomainName || origin.domainName || '';
351
+ const protocol = origin.CustomOriginConfig?.OriginProtocolPolicy?.includes('https') || domain.includes('https')
352
+ ? 'https'
353
+ : 'http';
354
+
355
+ const fullPath = (origin.OriginPath || origin.originPath || '') + path;
356
+ const url = new URL(`${protocol}://${domain}${fullPath}`);
357
+
358
+ const client = protocol === 'https' ? https : http;
359
+
360
+ const options = {
361
+ hostname: url.hostname,
362
+ port: url.port || (protocol === 'https' ? 443 : 80),
363
+ path: url.pathname + url.search,
364
+ method: request.method || 'GET',
365
+ headers: {
366
+ 'Host': domain,
367
+ ...request.headers
368
+ },
369
+ timeout: 5000
370
+ };
371
+
372
+ const req = client.request(options, (res) => {
373
+ let data = '';
374
+
375
+ res.on('data', (chunk) => {
376
+ data += chunk;
377
+ });
378
+
379
+ res.on('end', () => {
380
+ resolve({
381
+ status: res.statusCode || 200,
382
+ headers: res.headers || {},
383
+ body: data,
384
+ timestamp: new Date().toISOString(),
385
+ origin: 'http',
386
+ originDomain: domain
387
+ });
388
+ });
389
+ });
390
+
391
+ req.on('error', (error) => {
392
+ reject(new Error(`HTTP request failed: ${error.message}`));
393
+ });
394
+
395
+ req.on('timeout', () => {
396
+ req.destroy();
397
+ reject(new Error('HTTP request timeout'));
398
+ });
399
+
400
+ if (request.body) {
401
+ req.write(request.body);
402
+ }
403
+
404
+ req.end();
405
+ } catch (error) {
406
+ reject(error);
407
+ }
408
+ }).catch(error => {
409
+ return this._errorResponse(`HTTP error: ${error.message}`, 502);
410
+ });
411
+ }
412
+
413
+ /**
414
+ * Utility Methods
415
+ */
416
+
417
+ _extractLambdaFunctionName(origin) {
418
+ const domain = origin.DomainName || origin.domainName || '';
419
+
420
+ // ARN format: arn:aws:lambda:region:account:function:name:version
421
+ if (domain.startsWith('arn:aws:lambda')) {
422
+ const parts = domain.split(':');
423
+ return parts[6] || parts[5];
424
+ }
425
+
426
+ // Simple function name
427
+ return domain.split('.')[0];
428
+ }
429
+
430
+ _parseQueryString(path) {
431
+ const [, queryString] = path.split('?');
432
+ if (!queryString) return null;
433
+
434
+ const params = {};
435
+ queryString.split('&').forEach(param => {
436
+ const [key, value] = param.split('=');
437
+ params[decodeURIComponent(key)] = decodeURIComponent(value || '');
438
+ });
439
+
440
+ return params;
441
+ }
442
+
443
+ _simulatedResponse(message, status = 200) {
444
+ return {
445
+ status: status,
446
+ headers: { 'Content-Type': 'application/json' },
447
+ body: JSON.stringify({ message }),
448
+ timestamp: new Date().toISOString(),
449
+ simulated: true
450
+ };
451
+ }
452
+
453
+ _errorResponse(message, status = 500) {
454
+ return {
455
+ status: status,
456
+ headers: { 'Content-Type': 'application/json' },
457
+ body: JSON.stringify({ error: message }),
458
+ timestamp: new Date().toISOString(),
459
+ error: true
460
+ };
461
+ }
462
+
463
+ /**
464
+ * Verificar se CloudFront tem acesso ao bucket S3
465
+ * Valida a bucket policy para permitir CloudFront
466
+ */
467
+ _checkS3BucketPolicy(bucketPolicy, origin, request) {
468
+ if (!bucketPolicy) {
469
+ return true; // Sem política = bucket público
470
+ }
471
+
472
+ try {
473
+ // Padrão típico de CloudFront no S3
474
+ const policyString = typeof bucketPolicy === 'string'
475
+ ? bucketPolicy
476
+ : JSON.stringify(bucketPolicy);
477
+
478
+ const policy = typeof bucketPolicy === 'string'
479
+ ? JSON.parse(policyString)
480
+ : bucketPolicy;
481
+
482
+ if (!policy.Statement || !Array.isArray(policy.Statement)) {
483
+ return false; // Policy inválida
484
+ }
485
+
486
+ // Verificar se há permissão para GetObject
487
+ for (const statement of policy.Statement) {
488
+ // Verificar Effect
489
+ if (statement.Effect !== 'Allow') {
490
+ continue;
491
+ }
492
+
493
+ // Verificar Action
494
+ const actions = Array.isArray(statement.Action)
495
+ ? statement.Action
496
+ : [statement.Action];
497
+
498
+ const hasGetObject = actions.includes('s3:GetObject') ||
499
+ actions.includes('s3:*') ||
500
+ actions.includes('*');
501
+
502
+ if (!hasGetObject) {
503
+ continue;
504
+ }
505
+
506
+ // Verificar Principal (CloudFront pode ser identificado por ARN ou service)
507
+ const principal = statement.Principal;
508
+ if (!principal) {
509
+ continue;
510
+ }
511
+
512
+ // Principal "*" = público
513
+ if (principal === '*') {
514
+ return true;
515
+ }
516
+
517
+ // CloudFront Service Principal
518
+ if (principal === 'cloudfront.amazonaws.com' ||
519
+ (principal.Service && principal.Service === 'cloudfront.amazonaws.com')) {
520
+ return true;
521
+ }
522
+
523
+ // Origin Access Identity (OAI)
524
+ if (origin.S3OriginConfig?.OriginAccessIdentity) {
525
+ const oaiArn = `arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity/${origin.S3OriginConfig.OriginAccessIdentity.split('/').pop()}`;
526
+
527
+ const principalArns = Array.isArray(principal.AWS)
528
+ ? principal.AWS
529
+ : (principal.AWS ? [principal.AWS] : []);
530
+
531
+ if (principalArns.includes(oaiArn) || principalArns.includes('*')) {
532
+ return true;
533
+ }
534
+ }
535
+
536
+ // Service Principal format
537
+ if (principal.Service &&
538
+ (principal.Service === 'cloudfront.amazonaws.com' || principal.Service === '*')) {
539
+ return true;
540
+ }
541
+ }
542
+
543
+ // Nenhum statement permitiu
544
+ return false;
545
+ } catch (error) {
546
+ // Se não conseguir validar, denegar por segurança
547
+ return false;
548
+ }
549
+ }
550
+ }
551
+
552
+ module.exports = OriginRouter;