@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.
- package/README.md +662 -658
- package/package.json +10 -3
- 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/index.js +26 -0
- package/src/services/lambda/server.js +12 -1
- package/src/services/lambda/simulator.js +4 -0
- 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,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy Enforcement Middleware
|
|
3
|
+
*
|
|
4
|
+
* Aplica validação de SCP + IAM policies em requisições
|
|
5
|
+
* Ordem real da AWS:
|
|
6
|
+
* 1. Extract principal
|
|
7
|
+
* 2. Get all applicable policies (SCP + IAM)
|
|
8
|
+
* 3. Evaluate (Deny > Allow > Implicit Deny)
|
|
9
|
+
* 4. Check conditions
|
|
10
|
+
* 5. Allow/Deny ação
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const logger = require('../utils/logger');
|
|
14
|
+
const PolicyCache = require('../utils/policy-cache');
|
|
15
|
+
|
|
16
|
+
class PolicyEnforcementMiddleware {
|
|
17
|
+
constructor(organizationsService, iamService) {
|
|
18
|
+
this.organizationsService = organizationsService;
|
|
19
|
+
this.iamService = iamService;
|
|
20
|
+
this.cache = new PolicyCache();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Middleware Express para enforçar políticas
|
|
25
|
+
*/
|
|
26
|
+
middleware(service = 'unknown', allowByDefault = true) {
|
|
27
|
+
return async (req, res, next) => {
|
|
28
|
+
try {
|
|
29
|
+
const enforced = req.app.locals.organizationsPolicyEnforcement?.enabled;
|
|
30
|
+
|
|
31
|
+
if (!enforced) {
|
|
32
|
+
// Se enforcement desativado, permite tudo
|
|
33
|
+
req.policyDecision = { allowed: true, reason: 'enforcement_disabled' };
|
|
34
|
+
return next();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Se não há principal, negar (já foi validado em authorization.js)
|
|
38
|
+
if (!req.principal && req.app.locals.organizationsPolicyEnforcement?.requireAccountContext) {
|
|
39
|
+
return this.accessDenied(res, {
|
|
40
|
+
principal: 'unknown',
|
|
41
|
+
action: `${service}:*`,
|
|
42
|
+
resource: '*',
|
|
43
|
+
reason: 'no_principal',
|
|
44
|
+
message: 'Missing principal context'
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!req.principal) {
|
|
49
|
+
// Sem principal mas enforcement desativado por account context
|
|
50
|
+
req.policyDecision = { allowed: true, reason: 'no_principal_enforcement_disabled' };
|
|
51
|
+
return next();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Extrair ação e recurso da requisição
|
|
55
|
+
const { action, resource } = this.extractActionAndResource(req, service);
|
|
56
|
+
|
|
57
|
+
// Verificar cache
|
|
58
|
+
const cacheKey = this.generateCacheKey(req.principal, action, resource);
|
|
59
|
+
const cached = this.cache.get(cacheKey);
|
|
60
|
+
|
|
61
|
+
if (cached) {
|
|
62
|
+
req.policyDecision = cached;
|
|
63
|
+
if (!cached.allowed) {
|
|
64
|
+
return this.accessDenied(res, cached);
|
|
65
|
+
}
|
|
66
|
+
return next();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Avaliar políticas
|
|
70
|
+
const decision = await this.evaluatePolicies(
|
|
71
|
+
req.principal,
|
|
72
|
+
action,
|
|
73
|
+
resource,
|
|
74
|
+
service,
|
|
75
|
+
req
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Cache resultado
|
|
79
|
+
this.cache.set(cacheKey, decision);
|
|
80
|
+
req.policyDecision = decision;
|
|
81
|
+
|
|
82
|
+
if (!decision.allowed) {
|
|
83
|
+
return this.accessDenied(res, decision);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Log de acesso permitido
|
|
87
|
+
if (req.app.locals.organizationsPolicyEnforcement?.scpEvaluation?.logAllowances) {
|
|
88
|
+
this.logAllowance(req.principal, action, resource, decision);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
next();
|
|
92
|
+
} catch (error) {
|
|
93
|
+
logger.error('Policy enforcement error:', error.message);
|
|
94
|
+
res.status(500).json({
|
|
95
|
+
code: 'InternalError',
|
|
96
|
+
message: 'Internal error during policy evaluation',
|
|
97
|
+
requestId: req.requestId || 'unknown'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Avalia todas as políticas aplicáveis
|
|
105
|
+
* Ordem: SCP → IAM
|
|
106
|
+
*/
|
|
107
|
+
async evaluatePolicies(principal, action, resource, service, req) {
|
|
108
|
+
try {
|
|
109
|
+
const accountId = principal.accountId;
|
|
110
|
+
const roleArn = principal.roleArn || principal.assumedRole;
|
|
111
|
+
|
|
112
|
+
// 1. Avaliar SCPs (Service Control Policies)
|
|
113
|
+
const scpDecision = await this.evaluateSCPs(accountId, action, resource, service);
|
|
114
|
+
|
|
115
|
+
// Explicit DENY em SCP → nega imediatamente
|
|
116
|
+
if (!scpDecision.allowed && scpDecision.reason === 'explicit_deny') {
|
|
117
|
+
return {
|
|
118
|
+
allowed: false,
|
|
119
|
+
principal: roleArn,
|
|
120
|
+
action,
|
|
121
|
+
resource,
|
|
122
|
+
account: accountId,
|
|
123
|
+
deniedBy: [{
|
|
124
|
+
type: 'SCP',
|
|
125
|
+
...scpDecision.deniedByPolicy
|
|
126
|
+
}],
|
|
127
|
+
allowedBy: [],
|
|
128
|
+
evaluationDetails: scpDecision
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 2. Avaliar IAM policies
|
|
133
|
+
const iamDecision = await this.evaluateIAM(roleArn, action, resource);
|
|
134
|
+
|
|
135
|
+
// Explicit DENY em IAM → nega imediatamente
|
|
136
|
+
if (!iamDecision.allowed && iamDecision.reason === 'explicit_deny') {
|
|
137
|
+
return {
|
|
138
|
+
allowed: false,
|
|
139
|
+
principal: roleArn,
|
|
140
|
+
action,
|
|
141
|
+
resource,
|
|
142
|
+
account: accountId,
|
|
143
|
+
deniedBy: [{
|
|
144
|
+
type: 'IAM',
|
|
145
|
+
...iamDecision.deniedByPolicy
|
|
146
|
+
}],
|
|
147
|
+
allowedBy: [],
|
|
148
|
+
evaluationDetails: iamDecision
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 3. SCP permite? IAM permite? → Allowed
|
|
153
|
+
if (scpDecision.allowed && iamDecision.allowed) {
|
|
154
|
+
return {
|
|
155
|
+
allowed: true,
|
|
156
|
+
principal: roleArn,
|
|
157
|
+
action,
|
|
158
|
+
resource,
|
|
159
|
+
account: accountId,
|
|
160
|
+
deniedBy: [],
|
|
161
|
+
allowedBy: [
|
|
162
|
+
{ type: 'SCP', details: scpDecision },
|
|
163
|
+
{ type: 'IAM', details: iamDecision }
|
|
164
|
+
],
|
|
165
|
+
evaluationDetails: { scpDecision, iamDecision }
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 4. Se SCP nega (implicit ou explicit) → Nega
|
|
170
|
+
if (!scpDecision.allowed) {
|
|
171
|
+
return {
|
|
172
|
+
allowed: false,
|
|
173
|
+
principal: roleArn,
|
|
174
|
+
action,
|
|
175
|
+
resource,
|
|
176
|
+
account: accountId,
|
|
177
|
+
deniedBy: [{
|
|
178
|
+
type: 'SCP',
|
|
179
|
+
...scpDecision.deniedByPolicy,
|
|
180
|
+
reason: scpDecision.reason
|
|
181
|
+
}],
|
|
182
|
+
allowedBy: iamDecision.allowed ? [{ type: 'IAM' }] : [],
|
|
183
|
+
evaluationDetails: scpDecision
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 5. Se IAM não permite (implicit deny) → Nega
|
|
188
|
+
if (!iamDecision.allowed) {
|
|
189
|
+
return {
|
|
190
|
+
allowed: false,
|
|
191
|
+
principal: roleArn,
|
|
192
|
+
action,
|
|
193
|
+
resource,
|
|
194
|
+
account: accountId,
|
|
195
|
+
deniedBy: [{
|
|
196
|
+
type: 'IAM',
|
|
197
|
+
reason: 'implicit_deny',
|
|
198
|
+
message: 'No explicit allow in principal policy'
|
|
199
|
+
}],
|
|
200
|
+
allowedBy: scpDecision.allowed ? [{ type: 'SCP' }] : [],
|
|
201
|
+
evaluationDetails: iamDecision
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
allowed: false,
|
|
207
|
+
principal: roleArn,
|
|
208
|
+
action,
|
|
209
|
+
resource,
|
|
210
|
+
account: accountId,
|
|
211
|
+
deniedBy: [{ type: 'Unknown', reason: 'implicit_deny' }],
|
|
212
|
+
allowedBy: []
|
|
213
|
+
};
|
|
214
|
+
} catch (error) {
|
|
215
|
+
logger.error('Policy evaluation error:', error.message);
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Avalia Service Control Policies
|
|
222
|
+
*/
|
|
223
|
+
async evaluateSCPs(accountId, action, resource, service) {
|
|
224
|
+
try {
|
|
225
|
+
if (!this.organizationsService?.organizationsSimulator) {
|
|
226
|
+
return { allowed: true, reason: 'organizations_not_available' };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const result = this.organizationsService.organizationsSimulator.evaluateSCPs(
|
|
230
|
+
accountId,
|
|
231
|
+
action,
|
|
232
|
+
resource || `arn:aws:${service}:*:${accountId}:*`
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
allowed: result.allowed,
|
|
237
|
+
reason: result.allowed ? 'explicit_allow' : 'implicit_deny',
|
|
238
|
+
evaluationResults: result.evaluationResults,
|
|
239
|
+
deniedByPolicy: result.denied ? {
|
|
240
|
+
policyId: result.denied.id,
|
|
241
|
+
name: result.denied.name,
|
|
242
|
+
effect: 'Deny'
|
|
243
|
+
} : null
|
|
244
|
+
};
|
|
245
|
+
} catch (error) {
|
|
246
|
+
logger.debug('SCP evaluation error:', error.message);
|
|
247
|
+
return { allowed: true, reason: 'evaluation_error' };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Avalia IAM policies
|
|
253
|
+
*/
|
|
254
|
+
async evaluateIAM(roleArn, action, resource) {
|
|
255
|
+
try {
|
|
256
|
+
if (!this.organizationsService?.organizationsSimulator?.iamIntegration) {
|
|
257
|
+
return { allowed: true, reason: 'iam_not_available' };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const result = this.organizationsService.organizationsSimulator.iamIntegration.evaluateAction(
|
|
261
|
+
roleArn,
|
|
262
|
+
action,
|
|
263
|
+
resource
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
allowed: result.allowed,
|
|
268
|
+
reason: result.allowed ? 'explicit_allow' : 'implicit_deny',
|
|
269
|
+
evaluationResults: result.evaluationResults
|
|
270
|
+
};
|
|
271
|
+
} catch (error) {
|
|
272
|
+
logger.debug('IAM evaluation error:', error.message);
|
|
273
|
+
return { allowed: true, reason: 'evaluation_error' };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Extrai action e resource da requisição
|
|
279
|
+
*/
|
|
280
|
+
extractActionAndResource(req, service) {
|
|
281
|
+
// Action: service:operation (ex: s3:PutObject, sqs:SendMessage)
|
|
282
|
+
const action = this.mapHttpMethodToAction(req.method, req.path, service);
|
|
283
|
+
|
|
284
|
+
// Resource: construir ARN baseado no path/body
|
|
285
|
+
const resource = this.mapPathToResource(req, service);
|
|
286
|
+
|
|
287
|
+
return { action, resource };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Map HTTP method + path → AWS action
|
|
292
|
+
*/
|
|
293
|
+
mapHttpMethodToAction(method, path, service) {
|
|
294
|
+
// Exemplos:
|
|
295
|
+
// POST /buckets → s3:CreateBucket
|
|
296
|
+
// PUT /object → s3:PutObject
|
|
297
|
+
// DELETE /queue → sqs:DeleteQueue
|
|
298
|
+
|
|
299
|
+
const pathParts = path.toLowerCase().split('/').filter(p => p);
|
|
300
|
+
|
|
301
|
+
const methodMap = {
|
|
302
|
+
POST: 'Create',
|
|
303
|
+
PUT: 'Put',
|
|
304
|
+
GET: 'Get',
|
|
305
|
+
DELETE: 'Delete',
|
|
306
|
+
PATCH: 'Update',
|
|
307
|
+
HEAD: 'Head'
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const verb = methodMap[method] || 'Invoke';
|
|
311
|
+
const resource = pathParts[0] || 'Resource';
|
|
312
|
+
const resourceName = resource.charAt(0).toUpperCase() + resource.slice(1, -1); // Remove 's'
|
|
313
|
+
|
|
314
|
+
return `${service}:${verb}${resourceName}`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Map path/params → AWS resource ARN
|
|
319
|
+
*/
|
|
320
|
+
mapPathToResource(req, service) {
|
|
321
|
+
const account = req.accountId || '*';
|
|
322
|
+
const region = req.get('X-Amz-Target')?.split('.')[0] || '*';
|
|
323
|
+
|
|
324
|
+
switch (service) {
|
|
325
|
+
case 's3':
|
|
326
|
+
// arn:aws:s3:::bucket-name
|
|
327
|
+
return req.body?.Bucket
|
|
328
|
+
? `arn:aws:s3:::${req.body.Bucket}`
|
|
329
|
+
: 'arn:aws:s3:::*';
|
|
330
|
+
|
|
331
|
+
case 'sqs':
|
|
332
|
+
// arn:aws:sqs:region:account:queue-name
|
|
333
|
+
return req.body?.QueueName
|
|
334
|
+
? `arn:aws:sqs:${region}:${account}:${req.body.QueueName}`
|
|
335
|
+
: `arn:aws:sqs:${region}:${account}:*`;
|
|
336
|
+
|
|
337
|
+
case 'sns':
|
|
338
|
+
// arn:aws:sns:region:account:topic-name
|
|
339
|
+
return req.body?.Name
|
|
340
|
+
? `arn:aws:sns:${region}:${account}:${req.body.Name}`
|
|
341
|
+
: `arn:aws:sns:${region}:${account}:*`;
|
|
342
|
+
|
|
343
|
+
case 'dynamodb':
|
|
344
|
+
// arn:aws:dynamodb:region:account:table/table-name
|
|
345
|
+
return req.body?.TableName
|
|
346
|
+
? `arn:aws:dynamodb:${region}:${account}:table/${req.body.TableName}`
|
|
347
|
+
: `arn:aws:dynamodb:${region}:${account}:table/*`;
|
|
348
|
+
|
|
349
|
+
case 'kms':
|
|
350
|
+
// arn:aws:kms:region:account:key/key-id
|
|
351
|
+
return req.body?.KeyId
|
|
352
|
+
? `arn:aws:kms:${region}:${account}:key/${req.body.KeyId}`
|
|
353
|
+
: `arn:aws:kms:${region}:${account}:key/*`;
|
|
354
|
+
|
|
355
|
+
case 'lambda':
|
|
356
|
+
// arn:aws:lambda:region:account:function:function-name
|
|
357
|
+
return req.body?.FunctionName
|
|
358
|
+
? `arn:aws:lambda:${region}:${account}:function:${req.body.FunctionName}`
|
|
359
|
+
: `arn:aws:lambda:${region}:${account}:function:*`;
|
|
360
|
+
|
|
361
|
+
default:
|
|
362
|
+
return `arn:aws:${service}:${region}:${account}:*`;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Gera cache key única
|
|
368
|
+
*/
|
|
369
|
+
generateCacheKey(principal, action, resource) {
|
|
370
|
+
const principalId = principal.roleArn || principal.credentials?.accessKeyId || 'unknown';
|
|
371
|
+
return `${principalId}|${action}|${resource}`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Retorna erro de acesso negado (formato AWS real)
|
|
376
|
+
*/
|
|
377
|
+
accessDenied(res, details) {
|
|
378
|
+
const deniedBy = details.deniedBy?.[0] || details;
|
|
379
|
+
|
|
380
|
+
logger.warn(`Access Denied: ${details.principal} → ${details.action}`, deniedBy);
|
|
381
|
+
|
|
382
|
+
return res.status(403).json({
|
|
383
|
+
code: 'AccessDenied',
|
|
384
|
+
message: `User: ${details.principal} is not authorized to perform: ${details.action} because ${deniedBy.reason || 'implicit deny'}`,
|
|
385
|
+
requestId: details.requestId || 'unknown',
|
|
386
|
+
evaluationDetails: {
|
|
387
|
+
principal: details.principal,
|
|
388
|
+
action: details.action,
|
|
389
|
+
resource: details.resource,
|
|
390
|
+
account: details.account,
|
|
391
|
+
deniedBy: details.deniedBy || [deniedBy],
|
|
392
|
+
allowedBy: details.allowedBy || []
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Log de acesso permitido (para auditoria)
|
|
399
|
+
*/
|
|
400
|
+
logAllowance(principal, action, resource, decision) {
|
|
401
|
+
logger.info(`Access Allowed: ${principal.roleArn} → ${action} on ${resource}`, {
|
|
402
|
+
allowedBy: decision.allowedBy
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
module.exports = PolicyEnforcementMiddleware;
|
package/src/server.js
CHANGED
|
@@ -29,6 +29,21 @@ const { SecretManagerService } = require("./services/secret-manager");
|
|
|
29
29
|
const { ParameterStoreService } = require("./services/parameter-store");
|
|
30
30
|
const { ConfigService } = require("./services/config");
|
|
31
31
|
const { AthenaService } = require("./services/athena");
|
|
32
|
+
const { AuroraService } = require("./services/aurora");
|
|
33
|
+
const IAMService = require("./services/iam");
|
|
34
|
+
const VPCService = require("./services/vpc");
|
|
35
|
+
const EC2Service = require("./services/ec2");
|
|
36
|
+
const ALBService = require("./services/alb");
|
|
37
|
+
const ASGService = require("./services/asg");
|
|
38
|
+
const OpenSearchService = require("./services/opensearch");
|
|
39
|
+
const KinesisDataStreamsService = require("./services/kinesis-data-streams");
|
|
40
|
+
const KinesisFirehoseService = require("./services/kinesis-firehose");
|
|
41
|
+
const KinesisAnalyticsService = require("./services/kinesis-analytics");
|
|
42
|
+
const OrganizationsService = require("./services/organizations");
|
|
43
|
+
const CloudFrontService = require("./services/cloudfront");
|
|
44
|
+
const Route53Service = require("./services/route53");
|
|
45
|
+
const ACMService = require("./services/acm");
|
|
46
|
+
const { initializeCloudShell } = require("./services/cloudshell");
|
|
32
47
|
|
|
33
48
|
class Server {
|
|
34
49
|
constructor(config) {
|
|
@@ -98,25 +113,29 @@ class Server {
|
|
|
98
113
|
other.depends.includes(def.name) &&
|
|
99
114
|
this.servicesMap.has(other.name)
|
|
100
115
|
);
|
|
116
|
+
const port = status.port || this.config.ports?.[def.name];
|
|
117
|
+
const endpoint = status.endpoint || (port ? `http://localhost:${port}` : null);
|
|
101
118
|
return {
|
|
102
119
|
name: def.name,
|
|
103
120
|
running: true,
|
|
104
121
|
enabled: true,
|
|
105
|
-
port
|
|
106
|
-
endpoint
|
|
122
|
+
port,
|
|
123
|
+
endpoint,
|
|
107
124
|
dependencies: def.depends,
|
|
108
125
|
category: def.category,
|
|
109
126
|
canDisable,
|
|
110
127
|
...status,
|
|
111
128
|
};
|
|
112
129
|
}
|
|
130
|
+
const port = this.config.ports?.[def.name];
|
|
113
131
|
return {
|
|
114
132
|
name: def.name,
|
|
115
133
|
running: false,
|
|
116
134
|
enabled: false,
|
|
117
|
-
port
|
|
118
|
-
endpoint: `http://localhost:${
|
|
135
|
+
port,
|
|
136
|
+
endpoint: port ? `http://localhost:${port}` : null,
|
|
119
137
|
dependencies: def.depends,
|
|
138
|
+
category: def.category,
|
|
120
139
|
canDisable: true,
|
|
121
140
|
};
|
|
122
141
|
});
|
|
@@ -181,18 +200,42 @@ class Server {
|
|
|
181
200
|
const port = this.config.adminPort || 9999;
|
|
182
201
|
this.managementServer = this.managementApp.listen(port, () => {
|
|
183
202
|
logger.info(`🔧 Management API rodando em http://localhost:${port}`);
|
|
203
|
+
this.cloudshell = initializeCloudShell(
|
|
204
|
+
this.managementApp,
|
|
205
|
+
this.managementServer,
|
|
206
|
+
() => this.getActiveServicesForCloudShell()
|
|
207
|
+
);
|
|
184
208
|
resolve();
|
|
185
209
|
});
|
|
186
210
|
this.managementServer.on("error", reject);
|
|
187
211
|
});
|
|
188
212
|
}
|
|
189
213
|
|
|
214
|
+
getActiveServicesForCloudShell() {
|
|
215
|
+
const services = [];
|
|
216
|
+
try {
|
|
217
|
+
for (const [name, service] of this.servicesMap) {
|
|
218
|
+
try {
|
|
219
|
+
const status = typeof service.getStatus === 'function' ? service.getStatus() : {};
|
|
220
|
+
services.push({ name, port: status.port || this.config.ports?.[name] });
|
|
221
|
+
} catch (e) {
|
|
222
|
+
// Skip services that fail to report status
|
|
223
|
+
services.push({ name, port: this.config.ports?.[name] });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} catch (e) {
|
|
227
|
+
// Return empty if servicesMap iteration fails
|
|
228
|
+
}
|
|
229
|
+
return services;
|
|
230
|
+
}
|
|
231
|
+
|
|
190
232
|
buildServiceRegistry() {
|
|
191
233
|
return [
|
|
192
234
|
//Armazenamento & BD
|
|
193
235
|
{ name: "dynamodb", class: DynamoDBService, depends: [], category: 'Armazenamento & Banco de Dados' },
|
|
194
236
|
{ name: "s3", class: S3Service, depends: [], category: 'Armazenamento & Banco de Dados' },
|
|
195
237
|
{ name: "athena", class: AthenaService, depends: [], category: 'Armazenamento & Banco de Dados' },
|
|
238
|
+
{ name: "aurora", class: AuroraService, depends: ['kms', 'secret-manager'], category: 'Armazenamento & Banco de Dados' },
|
|
196
239
|
|
|
197
240
|
//Computação
|
|
198
241
|
{ name: "lambda", class: LambdaService, depends: [], category: 'Computação' },
|
|
@@ -200,6 +243,7 @@ class Server {
|
|
|
200
243
|
|
|
201
244
|
{ name: "cognito", class: CognitoService, depends: ["lambda"], category: 'Segurança & Identidade' },
|
|
202
245
|
{ name: "sts", class: STSService, depends: [], category: 'Segurança & Identidade' },
|
|
246
|
+
{ name: "iam", class: IAMService, depends: ["sts"], category: 'Segurança & Identidade' },
|
|
203
247
|
{ name: "kms", class: KMSService, depends: [], category: 'Segurança & Identidade' },
|
|
204
248
|
{ name: "secret-manager", class: SecretManagerService, depends: [], category: 'Segurança & Identidade' },
|
|
205
249
|
{ name: "parameter-store", class: ParameterStoreService, depends: [], category: 'Segurança & Identidade' },
|
|
@@ -210,7 +254,23 @@ class Server {
|
|
|
210
254
|
{ name: "eventbridge", class: EventBridgeService, depends: [], category: 'Mensageria & Integração' },
|
|
211
255
|
|
|
212
256
|
//Networking
|
|
257
|
+
{ name: "vpc", class: VPCService, depends: [], category: 'Networking' },
|
|
213
258
|
{ name: "apigateway", class: APIGatewayService, depends: ["lambda", "cognito"], category: 'Networking' },
|
|
259
|
+
{ name: "route53", class: Route53Service, depends: [], category: 'Networking' },
|
|
260
|
+
{ name: "acm", class: ACMService, depends: [], category: 'Networking' },
|
|
261
|
+
|
|
262
|
+
//Computação em Nuvem
|
|
263
|
+
{ name: "ec2", class: EC2Service, depends: [], category: 'Computação em Nuvem' },
|
|
264
|
+
{ name: "alb", class: ALBService, depends: [], category: 'Computação em Nuvem' },
|
|
265
|
+
{ name: "asg", class: ASGService, depends: ["ec2", "alb"], category: 'Computação em Nuvem' },
|
|
266
|
+
|
|
267
|
+
//Busca e Análise
|
|
268
|
+
{ name: "opensearch", class: OpenSearchService, depends: [], category: 'Busca e Análise' },
|
|
269
|
+
|
|
270
|
+
//Streaming & Analytics
|
|
271
|
+
{ name: "kinesis-data-streams", class: KinesisDataStreamsService, depends: [], category: 'Streaming & Analytics' },
|
|
272
|
+
{ name: "kinesis-firehose", class: KinesisFirehoseService, depends: [], category: 'Streaming & Analytics' },
|
|
273
|
+
{ name: "kinesis-analytics", class: KinesisAnalyticsService, depends: [], category: 'Streaming & Analytics' },
|
|
214
274
|
|
|
215
275
|
//Observabilidade & Conformidade
|
|
216
276
|
{ name: "cloudwatch", class: CloudWatchService, depends: [], category: 'Observabilidade & Conformidade' },
|
|
@@ -219,6 +279,12 @@ class Server {
|
|
|
219
279
|
{ name: "config", class: ConfigService, depends: [], category: 'Observabilidade & Conformidade' },
|
|
220
280
|
{ name: "cloudformation", class: CloudFormationService, depends: [], category: 'Observabilidade & Conformidade' },
|
|
221
281
|
|
|
282
|
+
//Governança & Conformidade
|
|
283
|
+
{ name: "organizations", class: OrganizationsService, depends: ["iam"], category: 'Governança & Conformidade' },
|
|
284
|
+
|
|
285
|
+
//Content Delivery
|
|
286
|
+
{ name: "cloudfront", class: CloudFrontService, depends: ["s3", "lambda"], category: 'Content Delivery' },
|
|
287
|
+
|
|
222
288
|
];
|
|
223
289
|
}
|
|
224
290
|
|
|
@@ -270,6 +336,10 @@ class Server {
|
|
|
270
336
|
logger.info("\n🛑 Parando AWS Local Simulator...\n");
|
|
271
337
|
|
|
272
338
|
try {
|
|
339
|
+
if (this.cloudshell) {
|
|
340
|
+
this.cloudshell.shutdown();
|
|
341
|
+
}
|
|
342
|
+
|
|
273
343
|
const stopPromises = [...this.services].reverse().map((service) => service.stop());
|
|
274
344
|
await Promise.all(stopPromises);
|
|
275
345
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACM (AWS Certificate Manager) Service
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const ACMSimulator = require('./simulator');
|
|
6
|
+
const ACMServer = require('./server');
|
|
7
|
+
const logger = require('../../utils/logger');
|
|
8
|
+
|
|
9
|
+
class ACMService {
|
|
10
|
+
constructor(config = {}) {
|
|
11
|
+
this.name = 'acm';
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.simulator = null;
|
|
14
|
+
this.server = null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async initialize() {
|
|
18
|
+
this.simulator = new ACMSimulator(this.config);
|
|
19
|
+
await this.simulator.initialize();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
injectDependencies(server) {
|
|
23
|
+
// Para possíveis integrações futuras
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async start() {
|
|
27
|
+
try {
|
|
28
|
+
const port = this.config.port || 4025;
|
|
29
|
+
this.server = new ACMServer(port, this.config);
|
|
30
|
+
await this.server.initialize(this.simulator);
|
|
31
|
+
await this.server.start();
|
|
32
|
+
return true;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
logger.error(`ACM Service error: ${error.message}`);
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async stop() {
|
|
40
|
+
if (this.server) {
|
|
41
|
+
await this.server.stop();
|
|
42
|
+
}
|
|
43
|
+
if (this.simulator) {
|
|
44
|
+
this.simulator.stopRenewalCheckInterval();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getStatus() {
|
|
49
|
+
return {
|
|
50
|
+
name: 'acm',
|
|
51
|
+
port: this.config.port || 4025,
|
|
52
|
+
running: this.server ? true : false,
|
|
53
|
+
endpoint: `http://localhost:${this.config.port || 4025}`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async reset() {
|
|
58
|
+
if (this.simulator) {
|
|
59
|
+
this.simulator.certificates.clear();
|
|
60
|
+
this.simulator.certificatesByDomain.clear();
|
|
61
|
+
this.simulator.persistData();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = ACMService;
|