@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gugananuvem/aws-local-simulator",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.39",
|
|
4
4
|
"description": "Simulador local completo para serviços AWS",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"@aws-sdk/client-s3": "^3.0.0",
|
|
19
19
|
"@aws-sdk/client-sqs": "^3.0.0",
|
|
20
20
|
"@aws-sdk/lib-dynamodb": "^3.0.0",
|
|
21
|
+
"axios": "1.20.0",
|
|
21
22
|
"better-sqlite3": "11.10.0",
|
|
22
23
|
"child_process": "1.0.2",
|
|
23
24
|
"cors": "^2.8.5",
|
|
@@ -27,13 +28,19 @@
|
|
|
27
28
|
"fs": "0.0.1-security",
|
|
28
29
|
"glob": "10.5.0",
|
|
29
30
|
"http": "0.0.1-security",
|
|
31
|
+
"http-proxy-middleware": "^2.0.6",
|
|
30
32
|
"js-yaml": "4.1.1",
|
|
31
33
|
"jsonwebtoken": "^9.0.2",
|
|
34
|
+
"luxon": "3.7.2",
|
|
32
35
|
"mkdirp": "^3.0.1",
|
|
36
|
+
"node-pty": "1.1.0",
|
|
33
37
|
"path": "0.12.7",
|
|
34
38
|
"url": "0.11.4",
|
|
35
39
|
"urlpattern-polyfill": "^10.0.0",
|
|
36
|
-
"uuid": "14.0.0"
|
|
40
|
+
"uuid": "14.0.0",
|
|
41
|
+
"ws": "8.21.0",
|
|
42
|
+
"xml2js": "0.6.2",
|
|
43
|
+
"xmlbuilder": "15.1.1"
|
|
37
44
|
},
|
|
38
45
|
"keywords": [
|
|
39
46
|
"aws",
|
|
@@ -67,6 +74,6 @@
|
|
|
67
74
|
"publishConfig": {
|
|
68
75
|
"directory": "dist"
|
|
69
76
|
},
|
|
70
|
-
"buildDate": "2026-
|
|
77
|
+
"buildDate": "2026-10-09T19:19:07.184Z",
|
|
71
78
|
"published": true
|
|
72
79
|
}
|
|
@@ -19,6 +19,16 @@ module.exports = {
|
|
|
19
19
|
"parameter-store": true,
|
|
20
20
|
kms: true,
|
|
21
21
|
kinesis: true,
|
|
22
|
+
ec2: true,
|
|
23
|
+
alb: true,
|
|
24
|
+
asg: true,
|
|
25
|
+
opensearch: true,
|
|
26
|
+
"kinesis-data-streams": true,
|
|
27
|
+
"kinesis-firehose": true,
|
|
28
|
+
"kinesis-analytics": true,
|
|
29
|
+
organizations: true,
|
|
30
|
+
route53: true,
|
|
31
|
+
acm: true,
|
|
22
32
|
},
|
|
23
33
|
|
|
24
34
|
// Portas padrão
|
|
@@ -38,6 +48,17 @@ module.exports = {
|
|
|
38
48
|
parameterStore: 4002,
|
|
39
49
|
kms: 4000,
|
|
40
50
|
kinesis: 4568,
|
|
51
|
+
"kinesis-data-streams": 4568,
|
|
52
|
+
"kinesis-firehose": 4569,
|
|
53
|
+
"kinesis-analytics": 4570,
|
|
54
|
+
ec2: 4020,
|
|
55
|
+
alb: 4021,
|
|
56
|
+
asg: 4022,
|
|
57
|
+
opensearch: 9200,
|
|
58
|
+
organizations: 4581,
|
|
59
|
+
cloudformation: 4582,
|
|
60
|
+
route53: 4024,
|
|
61
|
+
acm: 4025,
|
|
41
62
|
},
|
|
42
63
|
apigateway: {
|
|
43
64
|
defaultCors: {
|
|
@@ -76,4 +97,54 @@ module.exports = {
|
|
|
76
97
|
|
|
77
98
|
// Porta do servidor de administração (Management API)
|
|
78
99
|
adminPort: 9999,
|
|
100
|
+
|
|
101
|
+
// Organizations Policy Enforcement
|
|
102
|
+
// Aplica SCP + IAM roles em todos os serviços (como na AWS real)
|
|
103
|
+
organizationsPolicyEnforcement: {
|
|
104
|
+
// Ativar/desativar validação de políticas globalmente
|
|
105
|
+
enabled: false,
|
|
106
|
+
|
|
107
|
+
// Exigir contexto de conta (STS AssumeRole)
|
|
108
|
+
// Se true, requisições sem credenciais são negadas
|
|
109
|
+
// Se false, requisições sem credenciais usam comportamento padrão
|
|
110
|
+
requireAccountContext: false,
|
|
111
|
+
|
|
112
|
+
// Configuração de avaliação de SCP
|
|
113
|
+
scpEvaluation: {
|
|
114
|
+
enabled: true,
|
|
115
|
+
// Log tentativas negadas
|
|
116
|
+
logDenials: true,
|
|
117
|
+
// Log tentativas permitidas
|
|
118
|
+
logAllowances: false,
|
|
119
|
+
// TTL do cache de avaliações (em ms)
|
|
120
|
+
cacheTTL: 60000, // 60 segundos
|
|
121
|
+
// Timeout para avaliação (em ms)
|
|
122
|
+
timeout: 5000
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// Serviços que têm enforcement habilitado
|
|
126
|
+
enforcedServices: [
|
|
127
|
+
's3',
|
|
128
|
+
'sqs',
|
|
129
|
+
'sns',
|
|
130
|
+
'dynamodb',
|
|
131
|
+
'cognito',
|
|
132
|
+
'kms',
|
|
133
|
+
'lambda',
|
|
134
|
+
'ec2',
|
|
135
|
+
'apigateway',
|
|
136
|
+
'cloudformation',
|
|
137
|
+
'iam',
|
|
138
|
+
'sts',
|
|
139
|
+
'opensearch',
|
|
140
|
+
'cloudwatch',
|
|
141
|
+
'cloudtrail'
|
|
142
|
+
],
|
|
143
|
+
|
|
144
|
+
// Duração padrão de temporary credentials (em segundos)
|
|
145
|
+
credentialDuration: 3600, // 1 hora
|
|
146
|
+
|
|
147
|
+
// Prefixo para SessionToken (para identificar tokens STS)
|
|
148
|
+
sessionTokenPrefix: 'AQOD'
|
|
149
|
+
},
|
|
79
150
|
};
|
package/src/config/env-loader.js
CHANGED
|
@@ -16,7 +16,8 @@ class EnvLoader {
|
|
|
16
16
|
sns: this.getEnvBool('AWS_LOCAL_SIMULATOR_SNS', false),
|
|
17
17
|
lambda: this.getEnvBool('AWS_LOCAL_SIMULATOR_LAMBDA', true),
|
|
18
18
|
apigateway: this.getEnvBool('AWS_LOCAL_SIMULATOR_APIGATEWAY', false),
|
|
19
|
-
eventbridge: this.getEnvBool('AWS_LOCAL_SIMULATOR_EVENTBRIDGE', false)
|
|
19
|
+
eventbridge: this.getEnvBool('AWS_LOCAL_SIMULATOR_EVENTBRIDGE', false),
|
|
20
|
+
vpc: this.getEnvBool('AWS_LOCAL_SIMULATOR_VPC', false)
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
// Carrega portas
|
|
@@ -27,7 +28,8 @@ class EnvLoader {
|
|
|
27
28
|
lambda: this.getEnvInt('AWS_LOCAL_SIMULATOR_LAMBDA_PORT', 3001),
|
|
28
29
|
sns: this.getEnvInt('AWS_LOCAL_SIMULATOR_SNS_PORT', 9911),
|
|
29
30
|
apigateway: this.getEnvInt('AWS_LOCAL_SIMULATOR_APIGATEWAY_PORT', 4567),
|
|
30
|
-
eventbridge: this.getEnvInt('AWS_LOCAL_SIMULATOR_EVENTBRIDGE_PORT', 4010)
|
|
31
|
+
eventbridge: this.getEnvInt('AWS_LOCAL_SIMULATOR_EVENTBRIDGE_PORT', 4010),
|
|
32
|
+
vpc: this.getEnvInt('AWS_LOCAL_SIMULATOR_VPC_PORT', 4014)
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
// Carrega diretório de dados
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization Middleware
|
|
3
|
+
*
|
|
4
|
+
* Extrai e valida o principal (usuário/role/assumed-role) das requisições
|
|
5
|
+
* Suporta:
|
|
6
|
+
* - AWS SigV4 (Authorization header)
|
|
7
|
+
* - SessionToken (X-Amz-Security-Token)
|
|
8
|
+
* - Headers customizados (X-Account-Id, X-Role-Arn)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const crypto = require('crypto');
|
|
12
|
+
const logger = require('../utils/logger');
|
|
13
|
+
|
|
14
|
+
class AuthorizationMiddleware {
|
|
15
|
+
constructor(organizationsService, stsService) {
|
|
16
|
+
this.organizationsService = organizationsService;
|
|
17
|
+
this.stsService = stsService;
|
|
18
|
+
this.principalCache = new Map();
|
|
19
|
+
this.cacheTTL = 300000; // 5 minutes
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Middleware Express para extrair principal
|
|
24
|
+
*/
|
|
25
|
+
middleware() {
|
|
26
|
+
return async (req, res, next) => {
|
|
27
|
+
try {
|
|
28
|
+
const enforced = req.app.locals.organizationsPolicyEnforcement?.enabled;
|
|
29
|
+
|
|
30
|
+
if (!enforced) {
|
|
31
|
+
// Se enforcement desativado, continua sem validar
|
|
32
|
+
req.principal = null;
|
|
33
|
+
req.credentials = null;
|
|
34
|
+
return next();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Extrair principal de múltiplas fontes
|
|
38
|
+
const principal = await this.extractPrincipal(req);
|
|
39
|
+
|
|
40
|
+
if (!principal) {
|
|
41
|
+
// Se enforcement ativado mas sem credenciais, nega
|
|
42
|
+
return res.status(403).json({
|
|
43
|
+
code: 'InvalidCredentials',
|
|
44
|
+
message: 'Missing or invalid AWS credentials. Set Authorization header or AWS_ACCESS_KEY_ID environment variable.',
|
|
45
|
+
requestId: this.generateRequestId()
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Validar se principal ainda existe e não expirou
|
|
50
|
+
const isValid = await this.validatePrincipal(principal);
|
|
51
|
+
if (!isValid) {
|
|
52
|
+
return res.status(403).json({
|
|
53
|
+
code: 'InvalidCredentials',
|
|
54
|
+
message: 'Invalid or expired credentials',
|
|
55
|
+
requestId: this.generateRequestId()
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
req.principal = principal;
|
|
60
|
+
req.credentials = principal.credentials;
|
|
61
|
+
req.accountId = principal.accountId;
|
|
62
|
+
req.roleArn = principal.roleArn;
|
|
63
|
+
|
|
64
|
+
next();
|
|
65
|
+
} catch (error) {
|
|
66
|
+
logger.error('Authorization error:', error.message);
|
|
67
|
+
res.status(500).json({
|
|
68
|
+
code: 'InternalError',
|
|
69
|
+
message: 'Internal error during authorization',
|
|
70
|
+
requestId: this.generateRequestId()
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Extrai principal de:
|
|
78
|
+
* 1. Authorization header (AWS SigV4)
|
|
79
|
+
* 2. X-Amz-Security-Token header (SessionToken de STS)
|
|
80
|
+
* 3. Headers customizados (X-Account-Id, X-Role-Arn)
|
|
81
|
+
*/
|
|
82
|
+
async extractPrincipal(req) {
|
|
83
|
+
// Opção 1: SessionToken do STS (SigV4 com assumed role)
|
|
84
|
+
const sessionToken = req.get('X-Amz-Security-Token');
|
|
85
|
+
if (sessionToken) {
|
|
86
|
+
return this.extractFromSessionToken(sessionToken);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Opção 2: Authorization header (AWS SigV4)
|
|
90
|
+
const authHeader = req.get('Authorization');
|
|
91
|
+
if (authHeader && authHeader.startsWith('AWS4-HMAC-SHA256')) {
|
|
92
|
+
return this.extractFromSigV4(authHeader, req);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Opção 3: Headers customizados (para testes/desenvolvimento)
|
|
96
|
+
const accountId = req.get('X-Account-Id');
|
|
97
|
+
const roleArn = req.get('X-Role-Arn');
|
|
98
|
+
if (accountId && roleArn) {
|
|
99
|
+
return {
|
|
100
|
+
accountId,
|
|
101
|
+
roleArn,
|
|
102
|
+
assumedRole: roleArn,
|
|
103
|
+
sessionName: req.get('X-Session-Name') || 'custom-session',
|
|
104
|
+
credentials: {
|
|
105
|
+
accessKeyId: req.get('X-Access-Key-Id'),
|
|
106
|
+
secretAccessKey: req.get('X-Secret-Access-Key'),
|
|
107
|
+
sessionToken: 'custom-token'
|
|
108
|
+
},
|
|
109
|
+
issuedAt: Date.now(),
|
|
110
|
+
expiresAt: Date.now() + 3600000,
|
|
111
|
+
type: 'custom'
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Opção 4: Environment variables (AWS CLI default)
|
|
116
|
+
if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
|
|
117
|
+
return {
|
|
118
|
+
accountId: process.env.AWS_ACCOUNT_ID || '123456789012',
|
|
119
|
+
roleArn: process.env.AWS_ROLE_ARN || 'arn:aws:iam::123456789012:root',
|
|
120
|
+
credentials: {
|
|
121
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
122
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
123
|
+
sessionToken: process.env.AWS_SESSION_TOKEN
|
|
124
|
+
},
|
|
125
|
+
issuedAt: Date.now(),
|
|
126
|
+
expiresAt: Date.now() + 3600000,
|
|
127
|
+
type: 'env'
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Extrai do SessionToken (JWT com assumed role info)
|
|
136
|
+
* Formato: header.payload.signature
|
|
137
|
+
*/
|
|
138
|
+
extractFromSessionToken(token) {
|
|
139
|
+
try {
|
|
140
|
+
// Parse JWT simples (sem validar assinatura por enquanto)
|
|
141
|
+
const parts = token.split('.');
|
|
142
|
+
if (parts.length !== 3) {
|
|
143
|
+
throw new Error('Invalid token format');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Decode payload (base64url)
|
|
147
|
+
const payload = JSON.parse(
|
|
148
|
+
Buffer.from(parts[1], 'base64url').toString('utf-8')
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Validar expiração
|
|
152
|
+
if (payload.expiresAt < Date.now()) {
|
|
153
|
+
return null; // Token expirado
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
accountId: payload.accountId,
|
|
158
|
+
roleArn: payload.assumedRole || payload.roleArn,
|
|
159
|
+
assumedRole: payload.assumedRole,
|
|
160
|
+
originalPrincipal: payload.principal,
|
|
161
|
+
sessionName: payload.sessionName,
|
|
162
|
+
credentials: {
|
|
163
|
+
accessKeyId: payload.accessKeyId,
|
|
164
|
+
secretAccessKey: payload.secretAccessKey,
|
|
165
|
+
sessionToken: token
|
|
166
|
+
},
|
|
167
|
+
issuedAt: payload.issuedAt,
|
|
168
|
+
expiresAt: payload.expiresAt,
|
|
169
|
+
type: 'sts'
|
|
170
|
+
};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
logger.debug('Failed to extract from SessionToken:', error.message);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Extrai do Authorization header (AWS SigV4)
|
|
179
|
+
* Formato: AWS4-HMAC-SHA256 Credential=AKIA.../20260930/us-east-1/service/aws4_request, ...
|
|
180
|
+
*/
|
|
181
|
+
extractFromSigV4(authHeader, req) {
|
|
182
|
+
try {
|
|
183
|
+
const credentialMatch = authHeader.match(/Credential=([^,]+)/);
|
|
184
|
+
if (!credentialMatch) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const credential = credentialMatch[1];
|
|
189
|
+
const [accessKeyId, date, region, service, signatureType] = credential.split('/');
|
|
190
|
+
|
|
191
|
+
// Lookup access key no STS para obter informações do principal
|
|
192
|
+
// Por enquanto, extrair do AccessKeyId
|
|
193
|
+
const accountId = accessKeyId.substring(4, 20); // ASIA prefix é para STS
|
|
194
|
+
const isTemporary = accessKeyId.startsWith('ASIA');
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
accountId,
|
|
198
|
+
credentials: {
|
|
199
|
+
accessKeyId,
|
|
200
|
+
date,
|
|
201
|
+
region,
|
|
202
|
+
service
|
|
203
|
+
},
|
|
204
|
+
isTemporary,
|
|
205
|
+
type: 'sigv4'
|
|
206
|
+
};
|
|
207
|
+
} catch (error) {
|
|
208
|
+
logger.debug('Failed to extract from SigV4:', error.message);
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Valida se principal ainda é válido
|
|
215
|
+
*/
|
|
216
|
+
async validatePrincipal(principal) {
|
|
217
|
+
// Verificar cache
|
|
218
|
+
const cacheKey = `principal_${principal.credentials?.accessKeyId || principal.roleArn}`;
|
|
219
|
+
const cached = this.principalCache.get(cacheKey);
|
|
220
|
+
|
|
221
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
222
|
+
return cached.valid;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
// Validar se role ainda existe na organização
|
|
227
|
+
if (this.organizationsService && principal.roleArn) {
|
|
228
|
+
const roleExists = this.organizationsService.organizationsSimulator
|
|
229
|
+
?.roles?.has(principal.roleArn);
|
|
230
|
+
|
|
231
|
+
if (!roleExists) {
|
|
232
|
+
this.principalCache.set(cacheKey, { valid: false, expiresAt: Date.now() + this.cacheTTL });
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Validar se credentials não expiraram
|
|
238
|
+
if (principal.expiresAt && principal.expiresAt < Date.now()) {
|
|
239
|
+
this.principalCache.set(cacheKey, { valid: false, expiresAt: Date.now() + this.cacheTTL });
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.principalCache.set(cacheKey, { valid: true, expiresAt: Date.now() + this.cacheTTL });
|
|
244
|
+
return true;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
logger.debug('Principal validation error:', error.message);
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Gera request ID único para rastreamento
|
|
253
|
+
*/
|
|
254
|
+
generateRequestId() {
|
|
255
|
+
return crypto.randomBytes(8).toString('hex').toUpperCase();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Limpa cache expirado
|
|
260
|
+
*/
|
|
261
|
+
cleanupCache() {
|
|
262
|
+
const now = Date.now();
|
|
263
|
+
for (const [key, value] of this.principalCache.entries()) {
|
|
264
|
+
if (value.expiresAt < now) {
|
|
265
|
+
this.principalCache.delete(key);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
module.exports = AuthorizationMiddleware;
|