@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,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IAM Integration Module
|
|
3
|
+
* Integra Organizations/SCP com IAM para avaliação completa de permissões
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const logger = require('../../utils/logger');
|
|
7
|
+
|
|
8
|
+
class IAMIntegration {
|
|
9
|
+
constructor(organizationsSimulator, iamService) {
|
|
10
|
+
this.organizationsSimulator = organizationsSimulator;
|
|
11
|
+
this.iamService = iamService;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Avalia ação completa: SCP + IAM Policy
|
|
16
|
+
* Retorna: allowed/denied com motivo detalhado
|
|
17
|
+
*/
|
|
18
|
+
evaluateAction(principalArn, action, resource = '*', accountId = null, context = {}) {
|
|
19
|
+
logger.debug(`Evaluating action: ${action} on ${resource} by ${principalArn}`);
|
|
20
|
+
|
|
21
|
+
// 1. Extrai account ID do principal ARN
|
|
22
|
+
if (!accountId) {
|
|
23
|
+
const match = principalArn.match(/:(\d+):/);
|
|
24
|
+
accountId = match ? match[1] : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!accountId) {
|
|
28
|
+
return {
|
|
29
|
+
allowed: false,
|
|
30
|
+
deniedBy: ['UnknownAccount'],
|
|
31
|
+
reason: 'Could not determine account ID from principal ARN',
|
|
32
|
+
principal: principalArn,
|
|
33
|
+
action,
|
|
34
|
+
resource
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. Avalia SCPs primeiro (mais restritivas)
|
|
39
|
+
const scpEvaluation = this.organizationsSimulator.evaluateSCPs(accountId, action, resource);
|
|
40
|
+
|
|
41
|
+
if (!scpEvaluation.allowed) {
|
|
42
|
+
return {
|
|
43
|
+
allowed: false,
|
|
44
|
+
deniedBy: scpEvaluation.deniedBy || ['SCP'],
|
|
45
|
+
reason: 'Denied by Service Control Policy',
|
|
46
|
+
principal: principalArn,
|
|
47
|
+
action,
|
|
48
|
+
resource,
|
|
49
|
+
policy: 'SCP'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 3. Se houver IAM service, avalia IAM policies
|
|
54
|
+
if (this.iamService) {
|
|
55
|
+
const iamEvaluation = this.evaluateIAMPolicies(principalArn, action, resource, context);
|
|
56
|
+
|
|
57
|
+
if (!iamEvaluation.allowed) {
|
|
58
|
+
return {
|
|
59
|
+
allowed: false,
|
|
60
|
+
deniedBy: iamEvaluation.deniedBy || ['IAM Policy'],
|
|
61
|
+
reason: iamEvaluation.reason || 'Denied by IAM policy',
|
|
62
|
+
principal: principalArn,
|
|
63
|
+
action,
|
|
64
|
+
resource,
|
|
65
|
+
policy: 'IAM'
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 4. Tudo permitido
|
|
71
|
+
return {
|
|
72
|
+
allowed: true,
|
|
73
|
+
principal: principalArn,
|
|
74
|
+
action,
|
|
75
|
+
resource,
|
|
76
|
+
evaluatedBy: ['SCP', 'IAM']
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Avalia IAM policies associadas ao principal
|
|
82
|
+
*/
|
|
83
|
+
evaluateIAMPolicies(principalArn, action, resource, context = {}) {
|
|
84
|
+
try {
|
|
85
|
+
// Extrai o tipo de principal (user, role, assumedRole)
|
|
86
|
+
const principalType = this.extractPrincipalType(principalArn);
|
|
87
|
+
|
|
88
|
+
if (principalType === 'unknown') {
|
|
89
|
+
return {
|
|
90
|
+
allowed: false,
|
|
91
|
+
deniedBy: ['UnknownPrincipalType'],
|
|
92
|
+
reason: 'Unknown principal type'
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Obtém policies do principal
|
|
97
|
+
const policies = this.getPoliciesForPrincipal(principalArn, principalType);
|
|
98
|
+
|
|
99
|
+
if (policies.length === 0) {
|
|
100
|
+
return {
|
|
101
|
+
allowed: false,
|
|
102
|
+
deniedBy: ['NoPolicies'],
|
|
103
|
+
reason: 'No policies attached to principal'
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Avalia cada policy
|
|
108
|
+
for (const policy of policies) {
|
|
109
|
+
if (policy.Statement) {
|
|
110
|
+
for (const statement of policy.Statement) {
|
|
111
|
+
if (this.statementMatches(statement, action, resource, context)) {
|
|
112
|
+
if (statement.Effect === 'Deny') {
|
|
113
|
+
return {
|
|
114
|
+
allowed: false,
|
|
115
|
+
deniedBy: [policy.name || 'InlinePolicy'],
|
|
116
|
+
reason: 'Explicitly denied by policy'
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (statement.Effect === 'Allow') {
|
|
121
|
+
return {
|
|
122
|
+
allowed: true,
|
|
123
|
+
allowedBy: [policy.name || 'InlinePolicy']
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Nenhuma policy permitiu explicitamente
|
|
132
|
+
return {
|
|
133
|
+
allowed: false,
|
|
134
|
+
deniedBy: ['ImplicitDeny'],
|
|
135
|
+
reason: 'No policy explicitly allows this action'
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
} catch (error) {
|
|
139
|
+
logger.error(`Error evaluating IAM policies: ${error.message}`);
|
|
140
|
+
return {
|
|
141
|
+
allowed: false,
|
|
142
|
+
deniedBy: ['EvaluationError'],
|
|
143
|
+
reason: error.message
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Extrai tipo de principal
|
|
150
|
+
*/
|
|
151
|
+
extractPrincipalType(principalArn) {
|
|
152
|
+
if (principalArn.includes(':iam::') && principalArn.includes(':user/')) {
|
|
153
|
+
return 'user';
|
|
154
|
+
}
|
|
155
|
+
if (principalArn.includes(':iam::') && principalArn.includes(':role/')) {
|
|
156
|
+
return 'role';
|
|
157
|
+
}
|
|
158
|
+
if (principalArn.includes(':sts::') && principalArn.includes(':assumed-role/')) {
|
|
159
|
+
return 'assumed-role';
|
|
160
|
+
}
|
|
161
|
+
if (principalArn === '*') {
|
|
162
|
+
return 'everyone';
|
|
163
|
+
}
|
|
164
|
+
return 'unknown';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Obtém policies para um principal
|
|
169
|
+
*/
|
|
170
|
+
getPoliciesForPrincipal(principalArn, principalType) {
|
|
171
|
+
if (!this.iamService) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const policies = [];
|
|
177
|
+
|
|
178
|
+
if (principalType === 'user' || principalType === 'assumed-role') {
|
|
179
|
+
// Extrai nome do usuário do ARN
|
|
180
|
+
const userMatch = principalArn.match(/user\/(.+?)(?::.*)?$/);
|
|
181
|
+
if (userMatch && this.iamService.simulator) {
|
|
182
|
+
const userName = userMatch[1];
|
|
183
|
+
const user = this.iamService.simulator.users?.get(userName);
|
|
184
|
+
|
|
185
|
+
if (user && user.policies) {
|
|
186
|
+
policies.push(...Array.from(user.policies.values()));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (principalType === 'role' || principalType === 'assumed-role') {
|
|
192
|
+
// Extrai nome da role
|
|
193
|
+
const roleMatch = principalArn.match(/role\/(.+?)(?::.*)?$/);
|
|
194
|
+
if (roleMatch) {
|
|
195
|
+
const roleName = roleMatch[1];
|
|
196
|
+
|
|
197
|
+
// Procura em Organizations roles
|
|
198
|
+
for (const [, role] of this.organizationsSimulator.roles) {
|
|
199
|
+
if (role.name === roleName) {
|
|
200
|
+
// Retorna inline policies
|
|
201
|
+
for (const [, policy] of role.inlinePolicies) {
|
|
202
|
+
policies.push(policy);
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Se IAM service disponível, procura lá também
|
|
209
|
+
if (this.iamService && this.iamService.simulator) {
|
|
210
|
+
const role = this.iamService.simulator.roles?.get(roleName);
|
|
211
|
+
if (role) {
|
|
212
|
+
// Retorna policies inline
|
|
213
|
+
if (role.inline_policies) {
|
|
214
|
+
for (const [, policy] of role.inline_policies) {
|
|
215
|
+
policies.push(policy);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return policies;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
logger.warn(`Could not get policies for ${principalArn}: ${error.message}`);
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Verifica se um statement match com a ação/recurso
|
|
232
|
+
*/
|
|
233
|
+
statementMatches(statement, action, resource, context = {}) {
|
|
234
|
+
if (!statement.Action || !statement.Resource) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Verifica Action
|
|
239
|
+
const actionMatches = this.patternMatches(statement.Action, action);
|
|
240
|
+
if (!actionMatches) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Verifica Resource
|
|
245
|
+
const resourceMatches = this.patternMatches(statement.Resource, resource);
|
|
246
|
+
if (!resourceMatches) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Verifica Condition se houver
|
|
251
|
+
if (statement.Condition) {
|
|
252
|
+
return this.evaluateCondition(statement.Condition, context);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Verifica se padrão (com wildcards) match
|
|
260
|
+
*/
|
|
261
|
+
patternMatches(pattern, value) {
|
|
262
|
+
const patterns = Array.isArray(pattern) ? pattern : [pattern];
|
|
263
|
+
|
|
264
|
+
for (const p of patterns) {
|
|
265
|
+
if (p === '*') {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const regex = new RegExp('^' + p.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
|
|
270
|
+
if (regex.test(value)) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Avalia conditions (versão simplificada)
|
|
280
|
+
*/
|
|
281
|
+
evaluateCondition(condition, context = {}) {
|
|
282
|
+
// Implementação simplificada
|
|
283
|
+
// Em produção seria mais complexo com vários operadores
|
|
284
|
+
if (!condition) {
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
// Suporta StringEquals, StringLike, etc
|
|
290
|
+
for (const [operator, conditions] of Object.entries(condition)) {
|
|
291
|
+
for (const [key, values] of Object.entries(conditions)) {
|
|
292
|
+
const contextValue = context[key];
|
|
293
|
+
|
|
294
|
+
if (!contextValue) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const condValues = Array.isArray(values) ? values : [values];
|
|
299
|
+
|
|
300
|
+
switch (operator) {
|
|
301
|
+
case 'StringEquals':
|
|
302
|
+
if (!condValues.includes(contextValue)) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
|
|
307
|
+
case 'StringLike':
|
|
308
|
+
let matched = false;
|
|
309
|
+
for (const condValue of condValues) {
|
|
310
|
+
const regex = new RegExp('^' + condValue.replace(/\*/g, '.*') + '$');
|
|
311
|
+
if (regex.test(contextValue)) {
|
|
312
|
+
matched = true;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (!matched) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
break;
|
|
320
|
+
|
|
321
|
+
default:
|
|
322
|
+
// Operadores desconhecidos são ignorados
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return true;
|
|
329
|
+
} catch (error) {
|
|
330
|
+
logger.warn(`Error evaluating condition: ${error.message}`);
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Avalia AssumeRole - pode o principal assumir a role?
|
|
337
|
+
*/
|
|
338
|
+
canAssumeRole(principalArn, roleArn) {
|
|
339
|
+
try {
|
|
340
|
+
// Procura role pelo ARN
|
|
341
|
+
let targetRole = null;
|
|
342
|
+
|
|
343
|
+
for (const [, role] of this.organizationsSimulator.roles) {
|
|
344
|
+
if (role.arn === roleArn) {
|
|
345
|
+
targetRole = role;
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!targetRole) {
|
|
351
|
+
return {
|
|
352
|
+
allowed: false,
|
|
353
|
+
reason: 'Role not found'
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Verifica trust policy
|
|
358
|
+
if (!targetRole.trustPolicy || !targetRole.trustPolicy.Statement) {
|
|
359
|
+
return {
|
|
360
|
+
allowed: false,
|
|
361
|
+
reason: 'No trust policy defined'
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Valida trust policy
|
|
366
|
+
for (const statement of targetRole.trustPolicy.Statement) {
|
|
367
|
+
if (statement.Effect === 'Allow') {
|
|
368
|
+
if (statement.Principal?.AWS) {
|
|
369
|
+
if (this.patternMatches(statement.Principal.AWS, principalArn)) {
|
|
370
|
+
return {
|
|
371
|
+
allowed: true,
|
|
372
|
+
reason: 'Allowed by trust policy'
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (statement.Principal === '*') {
|
|
378
|
+
return {
|
|
379
|
+
allowed: true,
|
|
380
|
+
reason: 'Allowed by wildcard principal'
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
allowed: false,
|
|
388
|
+
reason: 'Not allowed by trust policy'
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
} catch (error) {
|
|
392
|
+
logger.error(`Error checking AssumeRole: ${error.message}`);
|
|
393
|
+
return {
|
|
394
|
+
allowed: false,
|
|
395
|
+
reason: error.message
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Gera audit log de avaliação
|
|
402
|
+
*/
|
|
403
|
+
auditEvaluation(evaluation) {
|
|
404
|
+
const level = evaluation.allowed ? 'INFO' : 'WARN';
|
|
405
|
+
const message = evaluation.allowed
|
|
406
|
+
? `Action allowed: ${evaluation.action} on ${evaluation.resource}`
|
|
407
|
+
: `Action denied: ${evaluation.action} on ${evaluation.resource} - ${evaluation.reason}`;
|
|
408
|
+
|
|
409
|
+
logger.log(level, `[Policy Evaluation] ${message} - Principal: ${evaluation.principal}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
module.exports = IAMIntegration;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Organizations & Control Tower Service - Ponto de entrada
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const OrganizationsServer = require('./server');
|
|
6
|
+
const OrganizationsSimulator = require('./simulator');
|
|
7
|
+
const ControlTowerSimulator = require('./control-tower-simulator');
|
|
8
|
+
|
|
9
|
+
class OrganizationsService {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.name = 'organizations';
|
|
13
|
+
this.port = config.ports.organizations || 4580;
|
|
14
|
+
this.server = null;
|
|
15
|
+
this.organizationsSimulator = null;
|
|
16
|
+
this.controlTowerSimulator = null;
|
|
17
|
+
this.isRunning = false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async initialize() {
|
|
21
|
+
const logger = require('../../utils/logger');
|
|
22
|
+
logger.debug(`Inicializando Organizations Service na porta ${this.port}...`);
|
|
23
|
+
|
|
24
|
+
// Inicializa simuladores
|
|
25
|
+
this.organizationsSimulator = new OrganizationsSimulator(this.config);
|
|
26
|
+
await this.organizationsSimulator.initialize();
|
|
27
|
+
|
|
28
|
+
this.controlTowerSimulator = new ControlTowerSimulator(this.config, this.organizationsSimulator);
|
|
29
|
+
await this.controlTowerSimulator.initialize();
|
|
30
|
+
|
|
31
|
+
// Inicializa server
|
|
32
|
+
this.server = new OrganizationsServer(this.port, this.config);
|
|
33
|
+
this.server.organizationsSimulator = this.organizationsSimulator;
|
|
34
|
+
this.server.controlTowerSimulator = this.controlTowerSimulator;
|
|
35
|
+
|
|
36
|
+
await this.server.initialize();
|
|
37
|
+
|
|
38
|
+
logger.debug('Organizations Service inicializado');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
injectDependencies(server) {
|
|
42
|
+
// Injetar dependências de outros serviços (IAM, VPC, EC2, etc)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async start() {
|
|
46
|
+
const logger = require('../../utils/logger');
|
|
47
|
+
|
|
48
|
+
if (this.isRunning) {
|
|
49
|
+
logger.warn('Organizations Service já está em execução');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
await this.server.start();
|
|
55
|
+
this.isRunning = true;
|
|
56
|
+
logger.info('Organizations Service iniciado com sucesso');
|
|
57
|
+
} catch (error) {
|
|
58
|
+
logger.error(`Erro ao iniciar Organizations Service: ${error.message}`);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async stop() {
|
|
64
|
+
const logger = require('../../utils/logger');
|
|
65
|
+
|
|
66
|
+
if (!this.isRunning) {
|
|
67
|
+
logger.warn('Organizations Service não está em execução');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await this.server.stop();
|
|
73
|
+
this.isRunning = false;
|
|
74
|
+
logger.info('Organizations Service parado com sucesso');
|
|
75
|
+
} catch (error) {
|
|
76
|
+
logger.error(`Erro ao parar Organizations Service: ${error.message}`);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async reset() {
|
|
82
|
+
const logger = require('../../utils/logger');
|
|
83
|
+
try {
|
|
84
|
+
await this.organizationsSimulator.reset();
|
|
85
|
+
await this.controlTowerSimulator.reset();
|
|
86
|
+
logger.info('Organizations Service resetado');
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logger.error(`Erro ao resetar Organizations Service: ${error.message}`);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getStatus() {
|
|
94
|
+
const orgStats = this.organizationsSimulator?.getStats() || {};
|
|
95
|
+
const ctStats = this.controlTowerSimulator?.getStats() || {};
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
name: this.name,
|
|
99
|
+
running: this.isRunning,
|
|
100
|
+
port: this.port,
|
|
101
|
+
endpoint: `http://localhost:${this.port}`,
|
|
102
|
+
resources: {
|
|
103
|
+
organizationId: orgStats.organizationId,
|
|
104
|
+
accounts: orgStats.accountCount,
|
|
105
|
+
ous: orgStats.ouCount,
|
|
106
|
+
policies: orgStats.policyCount,
|
|
107
|
+
roles: orgStats.roleCount,
|
|
108
|
+
landingZones: ctStats.landingZonesCount,
|
|
109
|
+
guardrails: ctStats.guardrailsCount,
|
|
110
|
+
preventiveGuardrails: ctStats.preventiveGuardrailsCount,
|
|
111
|
+
detectiveGuardrails: ctStats.detectiveGuardrailsCount,
|
|
112
|
+
enabledGuardrails: ctStats.enabledGuardrailsCount
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Métodos auxiliares para integração com outros serviços
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Integração com IAM - Valida se um role pode assumir outro
|
|
121
|
+
*/
|
|
122
|
+
validateAssumeRole(assumingRoleId, targetRoleId) {
|
|
123
|
+
const assumingRole = this.organizationsSimulator.roles.get(assumingRoleId);
|
|
124
|
+
const targetRole = this.organizationsSimulator.roles.get(targetRoleId);
|
|
125
|
+
|
|
126
|
+
if (!assumingRole || !targetRole) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return targetRole.canAssumeRole(assumingRole.arn);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Integração com IAM - Avalia se ação é permitida
|
|
135
|
+
*/
|
|
136
|
+
evaluateIAMAction(roleId, action, resource = '*') {
|
|
137
|
+
const role = this.organizationsSimulator.roles.get(roleId);
|
|
138
|
+
if (!role) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return role.canPerformAction(action, resource);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Integração com VPC - Valida restrições regionais por SCP
|
|
147
|
+
*/
|
|
148
|
+
validateRegionalRestrictions(accountId, region, service) {
|
|
149
|
+
const evaluation = this.organizationsSimulator.evaluateSCPs(
|
|
150
|
+
accountId,
|
|
151
|
+
`${service}:*`,
|
|
152
|
+
`arn:aws:${service}:${region}:*:*`
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
return evaluation.allowed;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Integração com EC2 - Valida se instância pode ser criada
|
|
160
|
+
*/
|
|
161
|
+
validateEC2Instance(accountId, instanceType, region) {
|
|
162
|
+
// Verifica SCP
|
|
163
|
+
const scpEvaluation = this.evaluateSCPs(accountId, 'ec2:RunInstances');
|
|
164
|
+
|
|
165
|
+
// Verifica restrições regionais
|
|
166
|
+
const regionalCheck = this.validateRegionalRestrictions(accountId, region, 'ec2');
|
|
167
|
+
|
|
168
|
+
return scpEvaluation.allowed && regionalCheck;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = OrganizationsService;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Organizations Models
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const Organization = require('./organization');
|
|
6
|
+
const ServiceControlPolicy = require('./scp');
|
|
7
|
+
const OrganizationRole = require('./role');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
Organization,
|
|
11
|
+
ServiceControlPolicy,
|
|
12
|
+
OrganizationRole
|
|
13
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Organizations - Invitation (Handshake) Model
|
|
3
|
+
* Representa um convite para uma conta entrar na organização.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { v4: uuidv4 } = require('uuid');
|
|
7
|
+
|
|
8
|
+
const EXPIRATION_DAYS = 15;
|
|
9
|
+
|
|
10
|
+
class Invitation {
|
|
11
|
+
constructor(config = {}) {
|
|
12
|
+
this.id = config.id || `h-${uuidv4().substring(0, 8)}`;
|
|
13
|
+
this.organizationId = config.organizationId;
|
|
14
|
+
this.email = config.email;
|
|
15
|
+
this.accountId = config.accountId || null;
|
|
16
|
+
this.message = config.message || '';
|
|
17
|
+
this.state = config.state || 'REQUESTED'; // REQUESTED | ACCEPTED | DECLINED | CANCELED | EXPIRED
|
|
18
|
+
this.action = config.action || 'INVITE';
|
|
19
|
+
this.requestedTimestamp = config.requestedTimestamp || Math.floor(Date.now() / 1000);
|
|
20
|
+
this.expirationTimestamp =
|
|
21
|
+
config.expirationTimestamp || this.requestedTimestamp + EXPIRATION_DAYS * 24 * 60 * 60;
|
|
22
|
+
this.completedTimestamp = config.completedTimestamp || null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Formato Handshake do AWS Organizations.
|
|
27
|
+
*/
|
|
28
|
+
toHandshake() {
|
|
29
|
+
const parties = [
|
|
30
|
+
{ Id: this.organizationId, Type: 'ORGANIZATION' },
|
|
31
|
+
{ Id: this.email, Type: 'EMAIL' },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
Id: this.id,
|
|
36
|
+
Arn: `arn:aws:organizations::123456789012:handshake/o-${this.organizationId}/invite/${this.id}`,
|
|
37
|
+
Parties: parties,
|
|
38
|
+
State: this.state,
|
|
39
|
+
Action: this.action,
|
|
40
|
+
RequestedTimestamp: this.requestedTimestamp,
|
|
41
|
+
ExpirationTimestamp: this.expirationTimestamp,
|
|
42
|
+
CompletedTimestamp: this.completedTimestamp,
|
|
43
|
+
Resources: this.accountId ? [{ Type: 'ACCOUNT', Value: this.accountId }] : [],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = Invitation;
|