@gugananuvem/aws-local-simulator 1.0.34 → 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.
- package/README.md +662 -658
- package/package.json +11 -4
- package/src/config/default-config.js +71 -0
- package/src/config/env-loader.js +4 -2
- package/src/middleware/authorization.js +271 -0
- package/src/middleware/cloudtrail-logger.js +333 -0
- package/src/middleware/error-handler.js +138 -0
- package/src/middleware/index.js +79 -0
- package/src/middleware/policy-enforcement.js +407 -0
- package/src/server.js +74 -4
- package/src/services/acm/index.js +66 -0
- package/src/services/acm/models/certificate.js +202 -0
- package/src/services/acm/server.js +224 -0
- package/src/services/acm/simulator.js +293 -0
- package/src/services/alb/index.js +104 -0
- package/src/services/alb/models/loadBalancer.js +88 -0
- package/src/services/alb/models/targetGroup.js +128 -0
- package/src/services/alb/server.js +160 -0
- package/src/services/alb/simulator.js +262 -0
- package/src/services/apigateway/index.js +22 -0
- package/src/services/apigateway/server.js +247 -35
- package/src/services/apigateway/simulator.js +780 -26
- package/src/services/asg/index.js +100 -0
- package/src/services/asg/models/autoScalingGroup.js +110 -0
- package/src/services/asg/models/launchTemplate.js +41 -0
- package/src/services/asg/models/scalingPolicy.js +51 -0
- package/src/services/asg/server.js +236 -0
- package/src/services/asg/simulator.js +410 -0
- package/src/services/aurora/cluster-manager.js +731 -0
- package/src/services/aurora/data-api.js +514 -0
- package/src/services/aurora/db-engine.js +270 -0
- package/src/services/aurora/index.js +159 -0
- package/src/services/aurora/rds-api.js +189 -0
- package/src/services/aurora/server.js +213 -0
- package/src/services/aurora/sql-translator.js +334 -0
- package/src/services/cloudformation/index.js +2 -2
- package/src/services/cloudformation/server.js +1 -0
- package/src/services/cloudfront/cache-engine.js +225 -0
- package/src/services/cloudfront/geo-restrictions.js +377 -0
- package/src/services/cloudfront/index.js +220 -0
- package/src/services/cloudfront/models/behavior.js +158 -0
- package/src/services/cloudfront/models/distribution.js +284 -0
- package/src/services/cloudfront/models/index.js +99 -0
- package/src/services/cloudfront/models/invalidation.js +109 -0
- package/src/services/cloudfront/models/origin.js +155 -0
- package/src/services/cloudfront/origin-router.js +552 -0
- package/src/services/cloudfront/origin-validator.js +398 -0
- package/src/services/cloudfront/server.js +512 -0
- package/src/services/cloudfront/signed-url-generator.js +377 -0
- package/src/services/cloudfront/simulator.js +425 -0
- package/src/services/cloudfront/store.js +326 -0
- package/src/services/cloudshell/env-builder.js +27 -0
- package/src/services/cloudshell/index.js +48 -0
- package/src/services/cloudshell/routes.js +60 -0
- package/src/services/cloudshell/session-manager.js +226 -0
- package/src/services/cloudshell/static/.gitkeep +0 -0
- package/src/services/cloudshell/static/index.html +406 -0
- package/src/services/cloudshell/websocket-handler.js +107 -0
- package/src/services/cloudtrail/index.js +2 -3
- package/src/services/cloudtrail/server.js +68 -0
- package/src/services/cognito/server.js +4 -1
- package/src/services/dynamodb/server.js +4 -1
- package/src/services/dynamodb/simulator.js +2 -1
- package/src/services/ec2/index.js +101 -0
- package/src/services/ec2/instanceServer.js +311 -0
- package/src/services/ec2/models/ami.js +39 -0
- package/src/services/ec2/models/instance.js +98 -0
- package/src/services/ec2/models/networkInterface.js +74 -0
- package/src/services/ec2/models/securityGroup.js +71 -0
- package/src/services/ec2/server.js +350 -0
- package/src/services/ec2/simulator.js +436 -0
- package/src/services/eventbridge/server.js +36 -0
- package/src/services/eventbridge/simulator.js +230 -45
- package/src/services/iam/index.js +65 -0
- package/src/services/iam/middleware.js +99 -0
- package/src/services/iam/server.js +331 -0
- package/src/services/iam/simulator.js +1432 -0
- package/src/services/index.js +28 -0
- package/src/services/kinesis-analytics/index.js +105 -0
- package/src/services/kinesis-analytics/models/application.js +325 -0
- package/src/services/kinesis-analytics/models/index.js +12 -0
- package/src/services/kinesis-analytics/server.js +250 -0
- package/src/services/kinesis-analytics/simulator.js +407 -0
- package/src/services/kinesis-analytics/store.js +176 -0
- package/src/services/kinesis-data-streams/index.js +106 -0
- package/src/services/kinesis-data-streams/models/index.js +12 -0
- package/src/services/kinesis-data-streams/models/record.js +70 -0
- package/src/services/kinesis-data-streams/models/stream.js +280 -0
- package/src/services/kinesis-data-streams/server.js +255 -0
- package/src/services/kinesis-data-streams/simulator.js +514 -0
- package/src/services/kinesis-data-streams/store.js +163 -0
- package/src/services/kinesis-firehose/index.js +106 -0
- package/src/services/kinesis-firehose/models/delivery-stream.js +265 -0
- package/src/services/kinesis-firehose/models/index.js +10 -0
- package/src/services/kinesis-firehose/server.js +215 -0
- package/src/services/kinesis-firehose/simulator.js +388 -0
- package/src/services/kinesis-firehose/store.js +163 -0
- package/src/services/kms/server.js +4 -1
- package/src/services/lambda/handler-loader.js +50 -5
- package/src/services/lambda/index.js +26 -0
- package/src/services/lambda/server.js +18 -12
- package/src/services/lambda/simulator.js +73 -17
- package/src/services/opensearch/index.js +105 -0
- package/src/services/opensearch/models/document.js +115 -0
- package/src/services/opensearch/models/index.js +73 -0
- package/src/services/opensearch/server.js +310 -0
- package/src/services/opensearch/simulator.js +605 -0
- package/src/services/opensearch/store.js +162 -0
- package/src/services/organizations/control-tower-simulator.js +321 -0
- package/src/services/organizations/control-tower-store.js +182 -0
- package/src/services/organizations/iam-integration.js +413 -0
- package/src/services/organizations/index.js +172 -0
- package/src/services/organizations/models/index.js +13 -0
- package/src/services/organizations/models/invitation.js +48 -0
- package/src/services/organizations/models/organization.js +566 -0
- package/src/services/organizations/models/role.js +244 -0
- package/src/services/organizations/models/scp.js +131 -0
- package/src/services/organizations/server.js +736 -0
- package/src/services/organizations/simulator.js +593 -0
- package/src/services/organizations/store.js +235 -0
- package/src/services/organizations/vpc-integration.js +453 -0
- package/src/services/route53/index.js +67 -0
- package/src/services/route53/models/healthCheck.js +101 -0
- package/src/services/route53/models/hostedZone.js +37 -0
- package/src/services/route53/models/resourceRecord.js +106 -0
- package/src/services/route53/server.js +841 -0
- package/src/services/route53/simulator.js +350 -0
- package/src/services/s3/index.js +62 -0
- package/src/services/s3/presigned-url-generator.js +337 -0
- package/src/services/s3/server.js +231 -0
- package/src/services/s3/simulator.js +551 -4
- package/src/services/sns/index.js +31 -0
- package/src/services/sns/server.js +9 -0
- package/src/services/sqs/index.js +38 -0
- package/src/services/sqs/server.js +57 -4
- package/src/services/sqs/simulator.js +423 -33
- package/src/services/sts/simulator.js +209 -17
- package/src/services/vpc/README.md +459 -0
- package/src/services/vpc/ec2ResponseFormatter.js +431 -0
- package/src/services/vpc/index.js +90 -0
- package/src/services/vpc/models/internetGateway.js +71 -0
- package/src/services/vpc/models/routeTable.js +160 -0
- package/src/services/vpc/models/subnet.js +48 -0
- package/src/services/vpc/models/vpc.js +37 -0
- package/src/services/vpc/models/vpcEndpoint.js +100 -0
- package/src/services/vpc/models/vpcGateway.js +134 -0
- package/src/services/vpc/server.js +836 -0
- package/src/services/vpc/simulator.js +613 -0
- package/src/utils/policy-cache.js +215 -0
- 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;
|