@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,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACM Certificate Model
|
|
3
|
+
* Supports self-signed, email-validated, and DNS-validated certificates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class Certificate {
|
|
7
|
+
constructor(domainName, config = {}) {
|
|
8
|
+
this.CertificateArn = this.generateArn();
|
|
9
|
+
this.DomainName = domainName;
|
|
10
|
+
this.SubjectAlternativeNames = config.SubjectAlternativeNames || [domainName];
|
|
11
|
+
this.Serial = this.generateSerial();
|
|
12
|
+
this.Subject = `CN=${domainName}`;
|
|
13
|
+
this.Issuer = config.Issuer || 'AWS Certificate Manager';
|
|
14
|
+
this.CreatedDate = new Date().toISOString();
|
|
15
|
+
this.IssuedDate = null;
|
|
16
|
+
this.Status = 'PENDING_VALIDATION';
|
|
17
|
+
this.Type = config.Type || 'AMAZON_ISSUED';
|
|
18
|
+
this.ValidationMethod = config.ValidationMethod || 'DNS';
|
|
19
|
+
this.NotBefore = null;
|
|
20
|
+
this.NotAfter = null;
|
|
21
|
+
this.Signature = this.generateSignature();
|
|
22
|
+
this.SignatureAlgorithm = 'sha256WithRSAEncryption';
|
|
23
|
+
this.KeyAlgorithm = 'RSA_2048';
|
|
24
|
+
this.Tags = config.Tags || [];
|
|
25
|
+
this.ValidationEmails = [];
|
|
26
|
+
this.DomainValidationOptions = [];
|
|
27
|
+
this.InUseBy = [];
|
|
28
|
+
this.KeyUsages = ['Digital Signature', 'Key Encipherment'];
|
|
29
|
+
this.ExtendedKeyUsages = ['TLS Web Server Authentication'];
|
|
30
|
+
this.RenewalEligibility = 'INELIGIBLE';
|
|
31
|
+
this.Options = {
|
|
32
|
+
CertificateTransparencyLoggingPreference: 'ENABLED',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Initialize validation options
|
|
36
|
+
this.initializeValidationOptions(domainName, this.SubjectAlternativeNames);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
generateArn() {
|
|
40
|
+
const accountId = '123456789012';
|
|
41
|
+
const region = 'us-east-1';
|
|
42
|
+
const random = Math.random().toString(36).substring(2, 15);
|
|
43
|
+
return `arn:aws:acm:${region}:${accountId}:certificate/${random}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
generateSerial() {
|
|
47
|
+
let serial = '';
|
|
48
|
+
for (let i = 0; i < 32; i++) {
|
|
49
|
+
serial += Math.floor(Math.random() * 16).toString(16);
|
|
50
|
+
}
|
|
51
|
+
return serial.toUpperCase();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
generateSignature() {
|
|
55
|
+
let signature = '';
|
|
56
|
+
for (let i = 0; i < 256; i++) {
|
|
57
|
+
signature += Math.floor(Math.random() * 256)
|
|
58
|
+
.toString(16)
|
|
59
|
+
.padStart(2, '0');
|
|
60
|
+
}
|
|
61
|
+
return signature.toUpperCase();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
initializeValidationOptions(primaryDomain, altNames) {
|
|
65
|
+
const domains = [primaryDomain, ...altNames];
|
|
66
|
+
const uniqueDomains = [...new Set(domains)];
|
|
67
|
+
|
|
68
|
+
for (const domain of uniqueDomains) {
|
|
69
|
+
this.DomainValidationOptions.push({
|
|
70
|
+
DomainName: domain,
|
|
71
|
+
ValidationDomain: domain,
|
|
72
|
+
ValidationStatus: 'PENDING_VALIDATION',
|
|
73
|
+
ResourceRecord: {
|
|
74
|
+
Name: `_${this.generateValidationId()}.${domain}.`,
|
|
75
|
+
Type: 'CNAME',
|
|
76
|
+
Value: `_${this.generateValidationId()}.acm-validations.aws.`,
|
|
77
|
+
},
|
|
78
|
+
ValidationEmails: [`admin@${domain}`, `webmaster@${domain}`],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
generateValidationId() {
|
|
84
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
85
|
+
let id = '';
|
|
86
|
+
for (let i = 0; i < 16; i++) {
|
|
87
|
+
id += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
88
|
+
}
|
|
89
|
+
return id;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Validate certificate domain
|
|
94
|
+
*/
|
|
95
|
+
validateDomain(domain) {
|
|
96
|
+
const option = this.DomainValidationOptions.find(o => o.DomainName === domain);
|
|
97
|
+
if (!option) {
|
|
98
|
+
throw new Error(`Domain not found: ${domain}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
option.ValidationStatus = 'SUCCESS';
|
|
102
|
+
option.ValidationDate = new Date().toISOString();
|
|
103
|
+
|
|
104
|
+
// Check if all domains are validated
|
|
105
|
+
const allValidated = this.DomainValidationOptions.every(o => o.ValidationStatus === 'SUCCESS');
|
|
106
|
+
if (allValidated) {
|
|
107
|
+
this.Status = 'ISSUED';
|
|
108
|
+
this.IssuedDate = new Date().toISOString();
|
|
109
|
+
|
|
110
|
+
// Set expiration date (1 year from now)
|
|
111
|
+
const expirationDate = new Date();
|
|
112
|
+
expirationDate.setFullYear(expirationDate.getFullYear() + 1);
|
|
113
|
+
|
|
114
|
+
this.NotBefore = new Date().toISOString();
|
|
115
|
+
this.NotAfter = expirationDate.toISOString();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check if certificate is valid
|
|
121
|
+
*/
|
|
122
|
+
isValid() {
|
|
123
|
+
if (this.Status !== 'ISSUED') return false;
|
|
124
|
+
if (!this.NotAfter) return false;
|
|
125
|
+
|
|
126
|
+
const expirationDate = new Date(this.NotAfter);
|
|
127
|
+
return expirationDate > new Date();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Check if certificate is expiring soon (30 days)
|
|
132
|
+
*/
|
|
133
|
+
isExpiringsoon() {
|
|
134
|
+
if (!this.NotAfter) return false;
|
|
135
|
+
|
|
136
|
+
const expirationDate = new Date(this.NotAfter);
|
|
137
|
+
const thirtyDaysFromNow = new Date();
|
|
138
|
+
thirtyDaysFromNow.setDate(thirtyDaysFromNow.getDate() + 30);
|
|
139
|
+
|
|
140
|
+
return expirationDate <= thirtyDaysFromNow && expirationDate > new Date();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Renew certificate
|
|
145
|
+
*/
|
|
146
|
+
renew() {
|
|
147
|
+
if (!this.isValid()) {
|
|
148
|
+
throw new Error('Cannot renew invalid certificate');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Create renewal certificate
|
|
152
|
+
const newCert = new Certificate(this.DomainName, {
|
|
153
|
+
SubjectAlternativeNames: this.SubjectAlternativeNames,
|
|
154
|
+
Type: this.Type,
|
|
155
|
+
ValidationMethod: this.ValidationMethod,
|
|
156
|
+
Tags: this.Tags,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return newCert;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
toJSON() {
|
|
163
|
+
return {
|
|
164
|
+
CertificateArn: this.CertificateArn,
|
|
165
|
+
DomainName: this.DomainName,
|
|
166
|
+
SubjectAlternativeNames: this.SubjectAlternativeNames,
|
|
167
|
+
Serial: this.Serial,
|
|
168
|
+
Subject: this.Subject,
|
|
169
|
+
Issuer: this.Issuer,
|
|
170
|
+
CreatedDate: this.CreatedDate,
|
|
171
|
+
IssuedDate: this.IssuedDate,
|
|
172
|
+
Status: this.Status,
|
|
173
|
+
Type: this.Type,
|
|
174
|
+
ValidationMethod: this.ValidationMethod,
|
|
175
|
+
NotBefore: this.NotBefore,
|
|
176
|
+
NotAfter: this.NotAfter,
|
|
177
|
+
SignatureAlgorithm: this.SignatureAlgorithm,
|
|
178
|
+
KeyAlgorithm: this.KeyAlgorithm,
|
|
179
|
+
KeyUsages: this.KeyUsages,
|
|
180
|
+
ExtendedKeyUsages: this.ExtendedKeyUsages,
|
|
181
|
+
DomainValidationOptions: this.DomainValidationOptions,
|
|
182
|
+
RenewalEligibility: this.RenewalEligibility,
|
|
183
|
+
InUseBy: this.InUseBy,
|
|
184
|
+
Tags: this.Tags,
|
|
185
|
+
Options: this.Options,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
toJSONSummary() {
|
|
190
|
+
return {
|
|
191
|
+
CertificateArn: this.CertificateArn,
|
|
192
|
+
DomainName: this.DomainName,
|
|
193
|
+
Status: this.Status,
|
|
194
|
+
CreatedDate: this.CreatedDate,
|
|
195
|
+
IssuedDate: this.IssuedDate,
|
|
196
|
+
NotAfter: this.NotAfter,
|
|
197
|
+
Type: this.Type,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
module.exports = Certificate;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACM Server
|
|
3
|
+
* REST API para gerenciamento de certificados SSL/TLS
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const logger = require('../../utils/logger');
|
|
8
|
+
|
|
9
|
+
class ACMServer {
|
|
10
|
+
constructor(port, config) {
|
|
11
|
+
this.port = port;
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.app = express();
|
|
14
|
+
this.setupMiddlewares();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
setupMiddlewares() {
|
|
18
|
+
this.app.use(express.json());
|
|
19
|
+
this.app.use((req, res, next) => {
|
|
20
|
+
logger.info(`[ACM] ${req.method} ${req.path}`);
|
|
21
|
+
next();
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async initialize(simulator) {
|
|
26
|
+
this.simulator = simulator;
|
|
27
|
+
this.setupRoutes();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setupRoutes() {
|
|
31
|
+
// Certificates
|
|
32
|
+
this.app.post('/acm/request-certificate', (req, res) => this.requestCertificate(req, res));
|
|
33
|
+
this.app.get('/acm/list-certificates', (req, res) => this.listCertificates(req, res));
|
|
34
|
+
this.app.get('/acm/describe-certificate/:arn', (req, res) => this.describeCertificate(req, res));
|
|
35
|
+
this.app.post('/acm/validate-domain', (req, res) => this.validateDomain(req, res));
|
|
36
|
+
this.app.delete('/acm/delete-certificate/:arn', (req, res) => this.deleteCertificate(req, res));
|
|
37
|
+
this.app.post('/acm/renew-certificate', (req, res) => this.renewCertificate(req, res));
|
|
38
|
+
this.app.post('/acm/add-tags-to-certificate', (req, res) => this.addTagsToCertificate(req, res));
|
|
39
|
+
this.app.get('/acm/stats', (req, res) => this.getStats(req, res));
|
|
40
|
+
this.app.get('/acm/certificate-by-domain/:domain', (req, res) => this.listByDomain(req, res));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async requestCertificate(req, res) {
|
|
44
|
+
try {
|
|
45
|
+
const { DomainName, SubjectAlternativeNames, ValidationMethod, Tags, CertificateType } = req.body;
|
|
46
|
+
|
|
47
|
+
if (!DomainName) {
|
|
48
|
+
return res.status(400).json({ error: 'DomainName is required' });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const certificate = this.simulator.requestCertificate(DomainName, {
|
|
52
|
+
SubjectAlternativeNames,
|
|
53
|
+
ValidationMethod,
|
|
54
|
+
Tags,
|
|
55
|
+
CertificateType,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
res.status(201).json({
|
|
59
|
+
CertificateArn: certificate.CertificateArn,
|
|
60
|
+
Status: certificate.Status,
|
|
61
|
+
ValidationMethod: certificate.ValidationMethod,
|
|
62
|
+
DomainValidationOptions: certificate.DomainValidationOptions,
|
|
63
|
+
});
|
|
64
|
+
} catch (error) {
|
|
65
|
+
res.status(400).json({ error: error.message });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async listCertificates(req, res) {
|
|
70
|
+
try {
|
|
71
|
+
const { Status } = req.query;
|
|
72
|
+
const certificates = this.simulator.listCertificates(Status);
|
|
73
|
+
|
|
74
|
+
res.json({
|
|
75
|
+
CertificateSummaryList: certificates.map(c => c.toJSONSummary()),
|
|
76
|
+
NextToken: null,
|
|
77
|
+
});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
res.status(400).json({ error: error.message });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async describeCertificate(req, res) {
|
|
84
|
+
try {
|
|
85
|
+
const arn = decodeURIComponent(req.params.arn);
|
|
86
|
+
const certificate = this.simulator.describeCertificate(arn);
|
|
87
|
+
|
|
88
|
+
res.json({
|
|
89
|
+
Certificate: certificate.toJSON(),
|
|
90
|
+
});
|
|
91
|
+
} catch (error) {
|
|
92
|
+
res.status(404).json({ error: error.message });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async validateDomain(req, res) {
|
|
97
|
+
try {
|
|
98
|
+
const { CertificateArn, DomainName } = req.body;
|
|
99
|
+
|
|
100
|
+
if (!CertificateArn || !DomainName) {
|
|
101
|
+
return res.status(400).json({ error: 'CertificateArn and DomainName are required' });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const certificate = this.simulator.validateDomain(CertificateArn, DomainName);
|
|
105
|
+
|
|
106
|
+
res.json({
|
|
107
|
+
CertificateArn: certificate.CertificateArn,
|
|
108
|
+
Status: certificate.Status,
|
|
109
|
+
ValidatedDomains: certificate.DomainValidationOptions
|
|
110
|
+
.filter(o => o.ValidationStatus === 'SUCCESS')
|
|
111
|
+
.map(o => o.DomainName),
|
|
112
|
+
});
|
|
113
|
+
} catch (error) {
|
|
114
|
+
res.status(400).json({ error: error.message });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async deleteCertificate(req, res) {
|
|
119
|
+
try {
|
|
120
|
+
const arn = decodeURIComponent(req.params.arn);
|
|
121
|
+
this.simulator.deleteCertificate(arn);
|
|
122
|
+
|
|
123
|
+
res.json({
|
|
124
|
+
CertificateArn: arn,
|
|
125
|
+
Deleted: true,
|
|
126
|
+
});
|
|
127
|
+
} catch (error) {
|
|
128
|
+
res.status(400).json({ error: error.message });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async renewCertificate(req, res) {
|
|
133
|
+
try {
|
|
134
|
+
const { CertificateArn } = req.body;
|
|
135
|
+
|
|
136
|
+
if (!CertificateArn) {
|
|
137
|
+
return res.status(400).json({ error: 'CertificateArn is required' });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const newCertificate = this.simulator.renewCertificate(CertificateArn);
|
|
141
|
+
|
|
142
|
+
res.status(201).json({
|
|
143
|
+
CertificateArn: newCertificate.CertificateArn,
|
|
144
|
+
OldCertificateArn: CertificateArn,
|
|
145
|
+
Status: newCertificate.Status,
|
|
146
|
+
DomainValidationOptions: newCertificate.DomainValidationOptions,
|
|
147
|
+
});
|
|
148
|
+
} catch (error) {
|
|
149
|
+
res.status(400).json({ error: error.message });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async addTagsToCertificate(req, res) {
|
|
154
|
+
try {
|
|
155
|
+
const { CertificateArn, Tags } = req.body;
|
|
156
|
+
|
|
157
|
+
if (!CertificateArn || !Tags) {
|
|
158
|
+
return res.status(400).json({ error: 'CertificateArn and Tags are required' });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const certificate = this.simulator.addTagsToCertificate(CertificateArn, Tags);
|
|
162
|
+
|
|
163
|
+
res.json({
|
|
164
|
+
CertificateArn: certificate.CertificateArn,
|
|
165
|
+
Tags: certificate.Tags,
|
|
166
|
+
});
|
|
167
|
+
} catch (error) {
|
|
168
|
+
res.status(400).json({ error: error.message });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async getStats(req, res) {
|
|
173
|
+
try {
|
|
174
|
+
const stats = this.simulator.getCertificateStats();
|
|
175
|
+
|
|
176
|
+
res.json({
|
|
177
|
+
Statistics: {
|
|
178
|
+
TotalCertificates: stats.total,
|
|
179
|
+
IssuedCertificates: stats.issued,
|
|
180
|
+
PendingValidationCertificates: stats.pending,
|
|
181
|
+
ExpiringInThirtyDaysCertificates: stats.expiring_soon,
|
|
182
|
+
ExpiredCertificates: stats.expired,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
} catch (error) {
|
|
186
|
+
res.status(400).json({ error: error.message });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async listByDomain(req, res) {
|
|
191
|
+
try {
|
|
192
|
+
const domain = decodeURIComponent(req.params.domain);
|
|
193
|
+
const certificates = this.simulator.listCertificatesByDomain(domain);
|
|
194
|
+
|
|
195
|
+
res.json({
|
|
196
|
+
CertificateSummaryList: certificates.map(c => c.toJSONSummary()),
|
|
197
|
+
Domain: domain,
|
|
198
|
+
});
|
|
199
|
+
} catch (error) {
|
|
200
|
+
res.status(400).json({ error: error.message });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async start() {
|
|
205
|
+
return new Promise((resolve) => {
|
|
206
|
+
this.server = this.app.listen(this.port, () => {
|
|
207
|
+
logger.info(`✅ ACM Server running on port ${this.port}`);
|
|
208
|
+
resolve();
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async stop() {
|
|
214
|
+
return new Promise((resolve) => {
|
|
215
|
+
if (this.server) {
|
|
216
|
+
this.server.close(resolve);
|
|
217
|
+
} else {
|
|
218
|
+
resolve();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = ACMServer;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACM Simulator
|
|
3
|
+
* AWS Certificate Manager with DNS validation, renewal, and expiration tracking
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const Certificate = require('./models/certificate');
|
|
8
|
+
const LocalStore = require('../../utils/local-store');
|
|
9
|
+
const logger = require('../../utils/logger');
|
|
10
|
+
|
|
11
|
+
class ACMSimulator {
|
|
12
|
+
constructor(config = {}) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.certificates = new Map(); // arn -> certificate
|
|
15
|
+
this.certificatesByDomain = new Map(); // domain -> [arns]
|
|
16
|
+
|
|
17
|
+
// Inicializar persistência
|
|
18
|
+
const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR || config.dataDir || './.aws-local-simulator-data';
|
|
19
|
+
this.dataDir = path.join(dataDir, 'acm');
|
|
20
|
+
this.store = new LocalStore(this.dataDir);
|
|
21
|
+
|
|
22
|
+
// Renewal check interval
|
|
23
|
+
this.renewalCheckInterval = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async initialize() {
|
|
27
|
+
// 1. Carregar dados persistidos
|
|
28
|
+
await this.loadFromPersistence();
|
|
29
|
+
|
|
30
|
+
// 2. Criar certificados padrão se configurado
|
|
31
|
+
if (this.certificates.size === 0 && this.config.certificates && Array.isArray(this.config.certificates)) {
|
|
32
|
+
logger.info('📚 Carregando Certificados ACM da configuração inicial...');
|
|
33
|
+
for (const certConfig of this.config.certificates) {
|
|
34
|
+
this.requestCertificate(certConfig.DomainName, certConfig);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 3. Iniciar verificação de renovação
|
|
39
|
+
this.startRenewalCheckInterval();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Carrega dados persistidos do storage
|
|
44
|
+
*/
|
|
45
|
+
async loadFromPersistence() {
|
|
46
|
+
try {
|
|
47
|
+
const certificatesData = this.store.read('certificates');
|
|
48
|
+
if (Array.isArray(certificatesData)) {
|
|
49
|
+
logger.info(`📂 Carregando ${certificatesData.length} Certificados ACM do storage...`);
|
|
50
|
+
for (const certData of certificatesData) {
|
|
51
|
+
const cert = new Certificate(certData.DomainName, {
|
|
52
|
+
SubjectAlternativeNames: certData.SubjectAlternativeNames,
|
|
53
|
+
Type: certData.Type,
|
|
54
|
+
ValidationMethod: certData.ValidationMethod,
|
|
55
|
+
Tags: certData.Tags,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Restaurar estado do certificado
|
|
59
|
+
cert.CertificateArn = certData.CertificateArn;
|
|
60
|
+
cert.Serial = certData.Serial;
|
|
61
|
+
cert.Status = certData.Status;
|
|
62
|
+
cert.IssuedDate = certData.IssuedDate;
|
|
63
|
+
cert.CreatedDate = certData.CreatedDate;
|
|
64
|
+
cert.NotBefore = certData.NotBefore;
|
|
65
|
+
cert.NotAfter = certData.NotAfter;
|
|
66
|
+
cert.DomainValidationOptions = certData.DomainValidationOptions;
|
|
67
|
+
cert.InUseBy = certData.InUseBy || [];
|
|
68
|
+
|
|
69
|
+
this.certificates.set(cert.CertificateArn, cert);
|
|
70
|
+
|
|
71
|
+
// Indexar por domínio
|
|
72
|
+
if (!this.certificatesByDomain.has(cert.DomainName)) {
|
|
73
|
+
this.certificatesByDomain.set(cert.DomainName, []);
|
|
74
|
+
}
|
|
75
|
+
this.certificatesByDomain.get(cert.DomainName).push(cert.CertificateArn);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger.error(`Erro ao carregar persistência ACM: ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Persiste dados para storage
|
|
85
|
+
*/
|
|
86
|
+
persistData() {
|
|
87
|
+
try {
|
|
88
|
+
const certificatesData = Array.from(this.certificates.values()).map(cert => cert.toJSON());
|
|
89
|
+
this.store.write('certificates', certificatesData);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
logger.error(`Erro ao persistir ACM: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Solicita novo certificado
|
|
97
|
+
*/
|
|
98
|
+
requestCertificate(domainName, config = {}) {
|
|
99
|
+
// Validar domínio
|
|
100
|
+
if (!domainName || typeof domainName !== 'string') {
|
|
101
|
+
throw new Error('DomainName is required');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const certificate = new Certificate(domainName, {
|
|
105
|
+
SubjectAlternativeNames: config.SubjectAlternativeNames || [domainName],
|
|
106
|
+
Type: config.CertificateType || 'AMAZON_ISSUED',
|
|
107
|
+
ValidationMethod: config.ValidationMethod || 'DNS',
|
|
108
|
+
Tags: config.Tags || [],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
this.certificates.set(certificate.CertificateArn, certificate);
|
|
112
|
+
|
|
113
|
+
// Indexar por domínio
|
|
114
|
+
if (!this.certificatesByDomain.has(domainName)) {
|
|
115
|
+
this.certificatesByDomain.set(domainName, []);
|
|
116
|
+
}
|
|
117
|
+
this.certificatesByDomain.get(domainName).push(certificate.CertificateArn);
|
|
118
|
+
|
|
119
|
+
this.persistData();
|
|
120
|
+
logger.info(`✅ Certificado solicitado: ${domainName} (${certificate.CertificateArn})`);
|
|
121
|
+
return certificate;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Lista certificados
|
|
126
|
+
*/
|
|
127
|
+
listCertificates(statusFilter = null) {
|
|
128
|
+
let certs = Array.from(this.certificates.values());
|
|
129
|
+
|
|
130
|
+
if (statusFilter) {
|
|
131
|
+
certs = certs.filter(c => c.Status === statusFilter);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return certs;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Descreve certificado
|
|
139
|
+
*/
|
|
140
|
+
describeCertificate(arn) {
|
|
141
|
+
const certificate = this.certificates.get(arn);
|
|
142
|
+
if (!certificate) {
|
|
143
|
+
throw new Error(`Certificate not found: ${arn}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return certificate;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Valida domínio do certificado
|
|
151
|
+
*/
|
|
152
|
+
validateDomain(arn, domain) {
|
|
153
|
+
const certificate = this.certificates.get(arn);
|
|
154
|
+
if (!certificate) {
|
|
155
|
+
throw new Error(`Certificate not found: ${arn}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
certificate.validateDomain(domain);
|
|
159
|
+
this.persistData();
|
|
160
|
+
logger.info(`✅ Domínio validado: ${domain} no certificado ${arn}`);
|
|
161
|
+
return certificate;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Deleta certificado
|
|
166
|
+
*/
|
|
167
|
+
deleteCertificate(arn) {
|
|
168
|
+
const certificate = this.certificates.get(arn);
|
|
169
|
+
if (!certificate) {
|
|
170
|
+
throw new Error(`Certificate not found: ${arn}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (certificate.InUseBy && certificate.InUseBy.length > 0) {
|
|
174
|
+
throw new Error(`Cannot delete certificate in use by ${certificate.InUseBy.length} resources`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Remove da indexação por domínio
|
|
178
|
+
const domainArns = this.certificatesByDomain.get(certificate.DomainName);
|
|
179
|
+
if (domainArns) {
|
|
180
|
+
const index = domainArns.indexOf(arn);
|
|
181
|
+
if (index > -1) {
|
|
182
|
+
domainArns.splice(index, 1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this.certificates.delete(arn);
|
|
187
|
+
this.persistData();
|
|
188
|
+
logger.info(`✅ Certificado deletado: ${arn}`);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Renova certificado
|
|
194
|
+
*/
|
|
195
|
+
renewCertificate(arn) {
|
|
196
|
+
const oldCertificate = this.certificates.get(arn);
|
|
197
|
+
if (!oldCertificate) {
|
|
198
|
+
throw new Error(`Certificate not found: ${arn}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!oldCertificate.isValid()) {
|
|
202
|
+
throw new Error(`Cannot renew invalid certificate`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const newCertificate = oldCertificate.renew();
|
|
206
|
+
this.certificates.set(newCertificate.CertificateArn, newCertificate);
|
|
207
|
+
|
|
208
|
+
// Indexar por domínio
|
|
209
|
+
if (!this.certificatesByDomain.has(newCertificate.DomainName)) {
|
|
210
|
+
this.certificatesByDomain.set(newCertificate.DomainName, []);
|
|
211
|
+
}
|
|
212
|
+
this.certificatesByDomain.get(newCertificate.DomainName).push(newCertificate.CertificateArn);
|
|
213
|
+
|
|
214
|
+
this.persistData();
|
|
215
|
+
logger.info(`✅ Certificado renovado: ${newCertificate.CertificateArn}`);
|
|
216
|
+
return newCertificate;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Adiciona tag ao certificado
|
|
221
|
+
*/
|
|
222
|
+
addTagsToCertificate(arn, tags) {
|
|
223
|
+
const certificate = this.certificates.get(arn);
|
|
224
|
+
if (!certificate) {
|
|
225
|
+
throw new Error(`Certificate not found: ${arn}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const tag of tags) {
|
|
229
|
+
const existingTag = certificate.Tags.find(t => t.Key === tag.Key);
|
|
230
|
+
if (existingTag) {
|
|
231
|
+
existingTag.Value = tag.Value;
|
|
232
|
+
} else {
|
|
233
|
+
certificate.Tags.push(tag);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
this.persistData();
|
|
238
|
+
return certificate;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Lista certificados com filtro por domínio
|
|
243
|
+
*/
|
|
244
|
+
listCertificatesByDomain(domainName) {
|
|
245
|
+
const arns = this.certificatesByDomain.get(domainName) || [];
|
|
246
|
+
return arns.map(arn => this.certificates.get(arn));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Obter estatísticas de certificados
|
|
251
|
+
*/
|
|
252
|
+
getCertificateStats() {
|
|
253
|
+
const certs = Array.from(this.certificates.values());
|
|
254
|
+
return {
|
|
255
|
+
total: certs.length,
|
|
256
|
+
issued: certs.filter(c => c.Status === 'ISSUED').length,
|
|
257
|
+
pending: certs.filter(c => c.Status === 'PENDING_VALIDATION').length,
|
|
258
|
+
expiring_soon: certs.filter(c => c.isExpiringsoon()).length,
|
|
259
|
+
expired: certs.filter(c => !c.isValid()).length,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Inicia verificação de renovação periódica
|
|
265
|
+
*/
|
|
266
|
+
startRenewalCheckInterval() {
|
|
267
|
+
if (this.renewalCheckInterval) clearInterval(this.renewalCheckInterval);
|
|
268
|
+
|
|
269
|
+
this.renewalCheckInterval = setInterval(() => {
|
|
270
|
+
const certificatesToRenew = Array.from(this.certificates.values()).filter(c => c.isExpiringsoon());
|
|
271
|
+
|
|
272
|
+
for (const cert of certificatesToRenew) {
|
|
273
|
+
try {
|
|
274
|
+
this.renewCertificate(cert.CertificateArn);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
logger.warn(`Failed to auto-renew certificate: ${error.message}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}, 300000); // 5 minutos
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Para verificação de renovação periódica
|
|
284
|
+
*/
|
|
285
|
+
stopRenewalCheckInterval() {
|
|
286
|
+
if (this.renewalCheckInterval) {
|
|
287
|
+
clearInterval(this.renewalCheckInterval);
|
|
288
|
+
this.renewalCheckInterval = null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
module.exports = ACMSimulator;
|