@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,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudTrail Logger Middleware
|
|
3
|
+
*
|
|
4
|
+
* Loga todos os acessos (permitidos e negados) em CloudTrail
|
|
5
|
+
* para auditoria e compliance
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const logger = require('../utils/logger');
|
|
9
|
+
|
|
10
|
+
class CloudTrailLoggerMiddleware {
|
|
11
|
+
constructor(cloudtrailService) {
|
|
12
|
+
this.cloudtrailService = cloudtrailService;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Middleware para logar eventos no CloudTrail
|
|
17
|
+
*/
|
|
18
|
+
middleware(serviceName = 'unknown') {
|
|
19
|
+
return async (req, res, next) => {
|
|
20
|
+
try {
|
|
21
|
+
// Interceptar resposta para capturar status
|
|
22
|
+
const originalJson = res.json;
|
|
23
|
+
const originalSend = res.send;
|
|
24
|
+
|
|
25
|
+
let responseData = null;
|
|
26
|
+
let responseStatus = res.statusCode;
|
|
27
|
+
|
|
28
|
+
res.json = function(data) {
|
|
29
|
+
responseData = data;
|
|
30
|
+
responseStatus = res.statusCode;
|
|
31
|
+
return originalJson.call(this, data);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
res.send = function(data) {
|
|
35
|
+
responseData = data;
|
|
36
|
+
responseStatus = res.statusCode;
|
|
37
|
+
return originalSend.call(this, data);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Logar após resposta ser enviada
|
|
41
|
+
res.on('finish', async () => {
|
|
42
|
+
try {
|
|
43
|
+
await this.logToCloudTrail(
|
|
44
|
+
req,
|
|
45
|
+
responseStatus,
|
|
46
|
+
serviceName,
|
|
47
|
+
responseData
|
|
48
|
+
);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
logger.debug('Error logging to CloudTrail:', error.message);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
next();
|
|
55
|
+
} catch (error) {
|
|
56
|
+
logger.error('CloudTrail logger error:', error.message);
|
|
57
|
+
next();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Registrar evento no CloudTrail
|
|
64
|
+
*/
|
|
65
|
+
async logToCloudTrail(req, statusCode, serviceName, responseData) {
|
|
66
|
+
try {
|
|
67
|
+
if (!this.cloudtrailService || !this.cloudtrailService.simulador) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Extrair informações do request
|
|
72
|
+
const {
|
|
73
|
+
method,
|
|
74
|
+
path,
|
|
75
|
+
headers,
|
|
76
|
+
body,
|
|
77
|
+
principal,
|
|
78
|
+
accountId,
|
|
79
|
+
roleArn,
|
|
80
|
+
policyDecision
|
|
81
|
+
} = req;
|
|
82
|
+
|
|
83
|
+
const eventName = this.mapRequestToEventName(method, path, serviceName);
|
|
84
|
+
const resourceName = this.mapPathToResourceName(path);
|
|
85
|
+
const sourceIPAddress = this.getSourceIP(req);
|
|
86
|
+
|
|
87
|
+
// Determinar sucesso/falha
|
|
88
|
+
const isSuccess = statusCode >= 200 && statusCode < 400;
|
|
89
|
+
const isAccessDenied = statusCode === 403;
|
|
90
|
+
|
|
91
|
+
// Construir evento CloudTrail
|
|
92
|
+
const event = {
|
|
93
|
+
eventTime: new Date(),
|
|
94
|
+
eventName,
|
|
95
|
+
eventSource: `${serviceName}.amazonaws.com`,
|
|
96
|
+
awsRegion: headers['x-amz-target']?.split('.')[0] || 'us-east-1',
|
|
97
|
+
sourceIPAddress,
|
|
98
|
+
userAgent: headers['user-agent'] || 'AWS-CLI',
|
|
99
|
+
userIdentity: {
|
|
100
|
+
type: principal?.type || 'Unknown',
|
|
101
|
+
principalId: principal?.roleArn || principal?.credentials?.accessKeyId || 'Unknown',
|
|
102
|
+
arn: roleArn || 'Unknown',
|
|
103
|
+
accountId: accountId || 'Unknown',
|
|
104
|
+
invokedBy: principal?.sessionName || 'Unknown',
|
|
105
|
+
accessKeyId: principal?.credentials?.accessKeyId || 'Unknown',
|
|
106
|
+
sessionContext: {
|
|
107
|
+
attributes: {
|
|
108
|
+
mfaAuthenticated: !!principal?.credentials?.sessionToken,
|
|
109
|
+
creationDate: principal?.issuedAt
|
|
110
|
+
},
|
|
111
|
+
sessionIssuer: {
|
|
112
|
+
type: 'Role',
|
|
113
|
+
principalId: roleArn || 'Unknown',
|
|
114
|
+
arn: roleArn || 'Unknown',
|
|
115
|
+
accountId: accountId || 'Unknown',
|
|
116
|
+
userName: principal?.sessionName || 'Unknown'
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
requestParameters: {
|
|
121
|
+
method,
|
|
122
|
+
path,
|
|
123
|
+
resourceName,
|
|
124
|
+
...this.extractRequestParams(body)
|
|
125
|
+
},
|
|
126
|
+
responseElements: isAccessDenied ? null : this.extractResponseElements(responseData),
|
|
127
|
+
additionalEventData: {
|
|
128
|
+
x_amzn_requestId: req.requestId,
|
|
129
|
+
principalOrgId: accountId,
|
|
130
|
+
policyDecision: policyDecision || null
|
|
131
|
+
},
|
|
132
|
+
requestId: req.requestId,
|
|
133
|
+
eventId: this.generateEventId(),
|
|
134
|
+
resources: [
|
|
135
|
+
{
|
|
136
|
+
arn: this.constructResourceARN(serviceName, resourceName, accountId),
|
|
137
|
+
accountId,
|
|
138
|
+
type: this.mapServiceToResourceType(serviceName)
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
eventType: 'AwsApiCall',
|
|
142
|
+
recipientAccountId: accountId || 'Unknown',
|
|
143
|
+
sharedEventID: this.generateEventId(),
|
|
144
|
+
vpcEndpointId: null
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Logar resultado
|
|
148
|
+
if (isSuccess) {
|
|
149
|
+
event.requestStatus = 'Success';
|
|
150
|
+
logger.debug(`CloudTrail: ${eventName} - SUCCESS`, {
|
|
151
|
+
principal: roleArn,
|
|
152
|
+
resource: resourceName,
|
|
153
|
+
account: accountId
|
|
154
|
+
});
|
|
155
|
+
} else if (isAccessDenied) {
|
|
156
|
+
event.requestStatus = 'AccessDenied';
|
|
157
|
+
event.errorCode = 'AccessDenied';
|
|
158
|
+
event.errorMessage = policyDecision?.message || 'User is not authorized';
|
|
159
|
+
logger.debug(`CloudTrail: ${eventName} - ACCESS DENIED`, {
|
|
160
|
+
principal: roleArn,
|
|
161
|
+
deniedBy: policyDecision?.deniedBy,
|
|
162
|
+
resource: resourceName
|
|
163
|
+
});
|
|
164
|
+
} else {
|
|
165
|
+
event.requestStatus = 'Failure';
|
|
166
|
+
event.errorCode = this.mapStatusToErrorCode(statusCode);
|
|
167
|
+
logger.debug(`CloudTrail: ${eventName} - FAILURE (${statusCode})`, {
|
|
168
|
+
principal: roleArn,
|
|
169
|
+
resource: resourceName
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Registrar no CloudTrail
|
|
174
|
+
this.cloudtrailService.simulador.recordEvent(event);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
logger.debug('Error constructing CloudTrail event:', error.message);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Mapear HTTP method + path → CloudTrail event name
|
|
182
|
+
*/
|
|
183
|
+
mapRequestToEventName(method, path, serviceName) {
|
|
184
|
+
const pathParts = path.toLowerCase().split('/').filter(p => p);
|
|
185
|
+
const resource = pathParts[0] || 'unknown';
|
|
186
|
+
|
|
187
|
+
const methodMap = {
|
|
188
|
+
POST: 'Create',
|
|
189
|
+
PUT: 'Put',
|
|
190
|
+
GET: 'Get',
|
|
191
|
+
DELETE: 'Delete',
|
|
192
|
+
PATCH: 'Update',
|
|
193
|
+
HEAD: 'Head'
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const verb = methodMap[method] || 'Invoke';
|
|
197
|
+
const resourceName = resource.charAt(0).toUpperCase() + resource.slice(1, -1);
|
|
198
|
+
|
|
199
|
+
return `${verb}${resourceName}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Extrair nome do recurso do path
|
|
204
|
+
*/
|
|
205
|
+
mapPathToResourceName(path) {
|
|
206
|
+
const parts = path.split('/').filter(p => p && p !== '__admin');
|
|
207
|
+
return parts[0] || 'resource';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Obter IP de origem
|
|
212
|
+
*/
|
|
213
|
+
getSourceIP(req) {
|
|
214
|
+
return (
|
|
215
|
+
req.headers['x-forwarded-for']?.split(',')[0] ||
|
|
216
|
+
req.socket.remoteAddress ||
|
|
217
|
+
'unknown'
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Extrair parâmetros do request
|
|
223
|
+
*/
|
|
224
|
+
extractRequestParams(body) {
|
|
225
|
+
if (!body) return {};
|
|
226
|
+
|
|
227
|
+
if (typeof body === 'string') {
|
|
228
|
+
try {
|
|
229
|
+
return JSON.parse(body);
|
|
230
|
+
} catch (e) {
|
|
231
|
+
return { rawBody: body.substring(0, 100) };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (typeof body === 'object') {
|
|
236
|
+
return body;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Extrair elementos da resposta (sem dados sensíveis)
|
|
244
|
+
*/
|
|
245
|
+
extractResponseElements(data) {
|
|
246
|
+
if (!data) return null;
|
|
247
|
+
|
|
248
|
+
if (typeof data === 'object') {
|
|
249
|
+
return {
|
|
250
|
+
...data,
|
|
251
|
+
// Remover dados sensíveis
|
|
252
|
+
SecretAccessKey: '***',
|
|
253
|
+
Password: '***',
|
|
254
|
+
PrivateKey: '***'
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Construir ARN do recurso
|
|
263
|
+
*/
|
|
264
|
+
constructResourceARN(serviceName, resourceName, accountId) {
|
|
265
|
+
const account = accountId || '*';
|
|
266
|
+
const region = '*';
|
|
267
|
+
|
|
268
|
+
switch (serviceName) {
|
|
269
|
+
case 's3':
|
|
270
|
+
return `arn:aws:s3:::${resourceName}`;
|
|
271
|
+
case 'sqs':
|
|
272
|
+
return `arn:aws:sqs:${region}:${account}:${resourceName}`;
|
|
273
|
+
case 'sns':
|
|
274
|
+
return `arn:aws:sns:${region}:${account}:${resourceName}`;
|
|
275
|
+
case 'dynamodb':
|
|
276
|
+
return `arn:aws:dynamodb:${region}:${account}:table/${resourceName}`;
|
|
277
|
+
case 'lambda':
|
|
278
|
+
return `arn:aws:lambda:${region}:${account}:function:${resourceName}`;
|
|
279
|
+
case 'kms':
|
|
280
|
+
return `arn:aws:kms:${region}:${account}:key/${resourceName}`;
|
|
281
|
+
case 'ec2':
|
|
282
|
+
return `arn:aws:ec2:${region}:${account}:${resourceName}/*`;
|
|
283
|
+
default:
|
|
284
|
+
return `arn:aws:${serviceName}:${region}:${account}:${resourceName}`;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Mapear service → resource type do CloudTrail
|
|
290
|
+
*/
|
|
291
|
+
mapServiceToResourceType(serviceName) {
|
|
292
|
+
const typeMap = {
|
|
293
|
+
s3: 'AWS::S3::Bucket',
|
|
294
|
+
sqs: 'AWS::SQS::Queue',
|
|
295
|
+
sns: 'AWS::SNS::Topic',
|
|
296
|
+
dynamodb: 'AWS::DynamoDB::Table',
|
|
297
|
+
lambda: 'AWS::Lambda::Function',
|
|
298
|
+
kms: 'AWS::KMS::Key',
|
|
299
|
+
ec2: 'AWS::EC2::Instance',
|
|
300
|
+
iam: 'AWS::IAM::Role',
|
|
301
|
+
sts: 'AWS::STS::AssumedRole'
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
return typeMap[serviceName] || 'AWS::Unknown::Resource';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Mapear HTTP status → CloudTrail error code
|
|
309
|
+
*/
|
|
310
|
+
mapStatusToErrorCode(statusCode) {
|
|
311
|
+
const codeMap = {
|
|
312
|
+
400: 'BadRequest',
|
|
313
|
+
401: 'Unauthorized',
|
|
314
|
+
403: 'AccessDenied',
|
|
315
|
+
404: 'NotFound',
|
|
316
|
+
409: 'Conflict',
|
|
317
|
+
429: 'ThrottlingException',
|
|
318
|
+
500: 'InternalError',
|
|
319
|
+
503: 'ServiceUnavailable'
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
return codeMap[statusCode] || 'UnknownError';
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Gerar ID único para evento
|
|
327
|
+
*/
|
|
328
|
+
generateEventId() {
|
|
329
|
+
return require('crypto').randomUUID();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
module.exports = CloudTrailLoggerMiddleware;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handler Middleware
|
|
3
|
+
*
|
|
4
|
+
* Formata erros no padrão AWS
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
const logger = require('../utils/logger');
|
|
9
|
+
|
|
10
|
+
class ErrorHandlerMiddleware {
|
|
11
|
+
/**
|
|
12
|
+
* Middleware Express para tratamento de erros
|
|
13
|
+
*/
|
|
14
|
+
static errorHandler() {
|
|
15
|
+
return (err, req, res, next) => {
|
|
16
|
+
const requestId = req.requestId || ErrorHandlerMiddleware.generateRequestId();
|
|
17
|
+
|
|
18
|
+
logger.error('Request error:', {
|
|
19
|
+
requestId,
|
|
20
|
+
message: err.message,
|
|
21
|
+
stack: err.stack,
|
|
22
|
+
path: req.path,
|
|
23
|
+
method: req.method
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Se já foi enviada resposta, não fazer nada
|
|
27
|
+
if (res.headersSent) {
|
|
28
|
+
return next(err);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Erro de autorização
|
|
32
|
+
if (err.code === 'AccessDenied' || err.statusCode === 403) {
|
|
33
|
+
return res.status(403).json({
|
|
34
|
+
code: 'AccessDenied',
|
|
35
|
+
message: err.message || 'Access Denied',
|
|
36
|
+
requestId
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Erro de validação
|
|
41
|
+
if (err.code === 'ValidationException' || err.statusCode === 400) {
|
|
42
|
+
return res.status(400).json({
|
|
43
|
+
code: 'ValidationException',
|
|
44
|
+
message: err.message || 'Invalid request',
|
|
45
|
+
requestId
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Erro de recurso não encontrado
|
|
50
|
+
if (err.code === 'NotFound' || err.statusCode === 404) {
|
|
51
|
+
return res.status(404).json({
|
|
52
|
+
code: 'NotFound',
|
|
53
|
+
message: err.message || 'Resource not found',
|
|
54
|
+
requestId
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Erro de conflito
|
|
59
|
+
if (err.code === 'ConflictException' || err.statusCode === 409) {
|
|
60
|
+
return res.status(409).json({
|
|
61
|
+
code: 'ConflictException',
|
|
62
|
+
message: err.message || 'Resource already exists',
|
|
63
|
+
requestId
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Erro interno
|
|
68
|
+
if (err.statusCode === 500 || !err.statusCode) {
|
|
69
|
+
return res.status(500).json({
|
|
70
|
+
code: 'InternalError',
|
|
71
|
+
message: 'Internal server error',
|
|
72
|
+
requestId
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Erro genérico
|
|
77
|
+
res.status(err.statusCode || 500).json({
|
|
78
|
+
code: err.code || 'UnknownError',
|
|
79
|
+
message: err.message || 'An error occurred',
|
|
80
|
+
requestId
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Middleware para capturar rotas não encontradas
|
|
87
|
+
*/
|
|
88
|
+
static notFound() {
|
|
89
|
+
return (req, res) => {
|
|
90
|
+
const requestId = ErrorHandlerMiddleware.generateRequestId();
|
|
91
|
+
|
|
92
|
+
res.status(404).json({
|
|
93
|
+
code: 'NotFound',
|
|
94
|
+
message: `${req.method} ${req.path} not found`,
|
|
95
|
+
requestId
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Gera request ID único
|
|
102
|
+
*/
|
|
103
|
+
static generateRequestId() {
|
|
104
|
+
return crypto.randomBytes(8).toString('hex').toUpperCase();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Middleware para adicionar requestId em cada requisição
|
|
109
|
+
*/
|
|
110
|
+
static requestIdMiddleware() {
|
|
111
|
+
return (req, res, next) => {
|
|
112
|
+
req.requestId = ErrorHandlerMiddleware.generateRequestId();
|
|
113
|
+
res.set('X-Amz-Request-Id', req.requestId);
|
|
114
|
+
next();
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Wrapper para async route handlers (catch errors automaticamente)
|
|
120
|
+
*/
|
|
121
|
+
static asyncHandler(fn) {
|
|
122
|
+
return (req, res, next) => {
|
|
123
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Criar erro no formato AWS
|
|
129
|
+
*/
|
|
130
|
+
static createError(code, message, statusCode = 400) {
|
|
131
|
+
const error = new Error(message);
|
|
132
|
+
error.code = code;
|
|
133
|
+
error.statusCode = statusCode;
|
|
134
|
+
return error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = ErrorHandlerMiddleware;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware Index
|
|
3
|
+
*
|
|
4
|
+
* Exporta todos os middlewares para fácil integração nos serviços
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const AuthorizationMiddleware = require('./authorization');
|
|
8
|
+
const PolicyEnforcementMiddleware = require('./policy-enforcement');
|
|
9
|
+
const ErrorHandlerMiddleware = require('./error-handler');
|
|
10
|
+
const CloudTrailLoggerMiddleware = require('./cloudtrail-logger');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Factory para criar middlewares configurados
|
|
14
|
+
*/
|
|
15
|
+
class MiddlewareFactory {
|
|
16
|
+
static createAuthorizationMiddleware(organizationsService, stsService) {
|
|
17
|
+
const auth = new AuthorizationMiddleware(organizationsService, stsService);
|
|
18
|
+
return auth.middleware();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static createPolicyEnforcementMiddleware(organizationsService, service = 'unknown') {
|
|
22
|
+
const enforcement = new PolicyEnforcementMiddleware(organizationsService);
|
|
23
|
+
return enforcement.middleware(service);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static createErrorHandler() {
|
|
27
|
+
return ErrorHandlerMiddleware.errorHandler();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static createNotFoundHandler() {
|
|
31
|
+
return ErrorHandlerMiddleware.notFound();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static createRequestIdMiddleware() {
|
|
35
|
+
return ErrorHandlerMiddleware.requestIdMiddleware();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static asyncHandler(fn) {
|
|
39
|
+
return ErrorHandlerMiddleware.asyncHandler(fn);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static createError(code, message, statusCode = 400) {
|
|
43
|
+
return ErrorHandlerMiddleware.createError(code, message, statusCode);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Setup padrão para um serviço
|
|
49
|
+
* Registra middlewares na ordem correta
|
|
50
|
+
*/
|
|
51
|
+
function setupServiceMiddleware(app, organizationsService, stsService, serviceName = 'unknown') {
|
|
52
|
+
// 1. RequestId (primeiro, para tracking)
|
|
53
|
+
app.use(ErrorHandlerMiddleware.requestIdMiddleware());
|
|
54
|
+
|
|
55
|
+
// 2. Authorization (extrair principal)
|
|
56
|
+
app.use(MiddlewareFactory.createAuthorizationMiddleware(organizationsService, stsService));
|
|
57
|
+
|
|
58
|
+
// 3. Policy Enforcement (validar SCP + IAM)
|
|
59
|
+
app.use(MiddlewareFactory.createPolicyEnforcementMiddleware(organizationsService, serviceName));
|
|
60
|
+
|
|
61
|
+
// 4. CloudTrail Logging (logar eventos permitidos e negados)
|
|
62
|
+
const cloudtrailService = organizationsService?.organizationsSimulator?.server?.app?.locals?.cloudtrailService;
|
|
63
|
+
if (cloudtrailService) {
|
|
64
|
+
const cloudtrailLogger = new CloudTrailLoggerMiddleware(cloudtrailService);
|
|
65
|
+
app.use(cloudtrailLogger.middleware(serviceName));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 5. Error handler (último)
|
|
69
|
+
app.use(ErrorHandlerMiddleware.errorHandler());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
AuthorizationMiddleware,
|
|
74
|
+
PolicyEnforcementMiddleware,
|
|
75
|
+
ErrorHandlerMiddleware,
|
|
76
|
+
CloudTrailLoggerMiddleware,
|
|
77
|
+
MiddlewareFactory,
|
|
78
|
+
setupServiceMiddleware
|
|
79
|
+
};
|