@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.
Files changed (149) hide show
  1. package/README.md +662 -658
  2. package/package.json +10 -3
  3. package/src/config/default-config.js +71 -0
  4. package/src/config/env-loader.js +4 -2
  5. package/src/middleware/authorization.js +271 -0
  6. package/src/middleware/cloudtrail-logger.js +333 -0
  7. package/src/middleware/error-handler.js +138 -0
  8. package/src/middleware/index.js +79 -0
  9. package/src/middleware/policy-enforcement.js +407 -0
  10. package/src/server.js +74 -4
  11. package/src/services/acm/index.js +66 -0
  12. package/src/services/acm/models/certificate.js +202 -0
  13. package/src/services/acm/server.js +224 -0
  14. package/src/services/acm/simulator.js +293 -0
  15. package/src/services/alb/index.js +104 -0
  16. package/src/services/alb/models/loadBalancer.js +88 -0
  17. package/src/services/alb/models/targetGroup.js +128 -0
  18. package/src/services/alb/server.js +160 -0
  19. package/src/services/alb/simulator.js +262 -0
  20. package/src/services/apigateway/index.js +22 -0
  21. package/src/services/apigateway/server.js +247 -35
  22. package/src/services/apigateway/simulator.js +780 -26
  23. package/src/services/asg/index.js +100 -0
  24. package/src/services/asg/models/autoScalingGroup.js +110 -0
  25. package/src/services/asg/models/launchTemplate.js +41 -0
  26. package/src/services/asg/models/scalingPolicy.js +51 -0
  27. package/src/services/asg/server.js +236 -0
  28. package/src/services/asg/simulator.js +410 -0
  29. package/src/services/aurora/cluster-manager.js +731 -0
  30. package/src/services/aurora/data-api.js +514 -0
  31. package/src/services/aurora/db-engine.js +270 -0
  32. package/src/services/aurora/index.js +159 -0
  33. package/src/services/aurora/rds-api.js +189 -0
  34. package/src/services/aurora/server.js +213 -0
  35. package/src/services/aurora/sql-translator.js +334 -0
  36. package/src/services/cloudformation/index.js +2 -2
  37. package/src/services/cloudformation/server.js +1 -0
  38. package/src/services/cloudfront/cache-engine.js +225 -0
  39. package/src/services/cloudfront/geo-restrictions.js +377 -0
  40. package/src/services/cloudfront/index.js +220 -0
  41. package/src/services/cloudfront/models/behavior.js +158 -0
  42. package/src/services/cloudfront/models/distribution.js +284 -0
  43. package/src/services/cloudfront/models/index.js +99 -0
  44. package/src/services/cloudfront/models/invalidation.js +109 -0
  45. package/src/services/cloudfront/models/origin.js +155 -0
  46. package/src/services/cloudfront/origin-router.js +552 -0
  47. package/src/services/cloudfront/origin-validator.js +398 -0
  48. package/src/services/cloudfront/server.js +512 -0
  49. package/src/services/cloudfront/signed-url-generator.js +377 -0
  50. package/src/services/cloudfront/simulator.js +425 -0
  51. package/src/services/cloudfront/store.js +326 -0
  52. package/src/services/cloudshell/env-builder.js +27 -0
  53. package/src/services/cloudshell/index.js +48 -0
  54. package/src/services/cloudshell/routes.js +60 -0
  55. package/src/services/cloudshell/session-manager.js +226 -0
  56. package/src/services/cloudshell/static/.gitkeep +0 -0
  57. package/src/services/cloudshell/static/index.html +406 -0
  58. package/src/services/cloudshell/websocket-handler.js +107 -0
  59. package/src/services/cloudtrail/index.js +2 -3
  60. package/src/services/cloudtrail/server.js +68 -0
  61. package/src/services/cognito/server.js +4 -1
  62. package/src/services/dynamodb/server.js +4 -1
  63. package/src/services/dynamodb/simulator.js +2 -1
  64. package/src/services/ec2/index.js +101 -0
  65. package/src/services/ec2/instanceServer.js +311 -0
  66. package/src/services/ec2/models/ami.js +39 -0
  67. package/src/services/ec2/models/instance.js +98 -0
  68. package/src/services/ec2/models/networkInterface.js +74 -0
  69. package/src/services/ec2/models/securityGroup.js +71 -0
  70. package/src/services/ec2/server.js +350 -0
  71. package/src/services/ec2/simulator.js +436 -0
  72. package/src/services/eventbridge/server.js +36 -0
  73. package/src/services/eventbridge/simulator.js +230 -45
  74. package/src/services/iam/index.js +65 -0
  75. package/src/services/iam/middleware.js +99 -0
  76. package/src/services/iam/server.js +331 -0
  77. package/src/services/iam/simulator.js +1432 -0
  78. package/src/services/index.js +28 -0
  79. package/src/services/kinesis-analytics/index.js +105 -0
  80. package/src/services/kinesis-analytics/models/application.js +325 -0
  81. package/src/services/kinesis-analytics/models/index.js +12 -0
  82. package/src/services/kinesis-analytics/server.js +250 -0
  83. package/src/services/kinesis-analytics/simulator.js +407 -0
  84. package/src/services/kinesis-analytics/store.js +176 -0
  85. package/src/services/kinesis-data-streams/index.js +106 -0
  86. package/src/services/kinesis-data-streams/models/index.js +12 -0
  87. package/src/services/kinesis-data-streams/models/record.js +70 -0
  88. package/src/services/kinesis-data-streams/models/stream.js +280 -0
  89. package/src/services/kinesis-data-streams/server.js +255 -0
  90. package/src/services/kinesis-data-streams/simulator.js +514 -0
  91. package/src/services/kinesis-data-streams/store.js +163 -0
  92. package/src/services/kinesis-firehose/index.js +106 -0
  93. package/src/services/kinesis-firehose/models/delivery-stream.js +265 -0
  94. package/src/services/kinesis-firehose/models/index.js +10 -0
  95. package/src/services/kinesis-firehose/server.js +215 -0
  96. package/src/services/kinesis-firehose/simulator.js +388 -0
  97. package/src/services/kinesis-firehose/store.js +163 -0
  98. package/src/services/kms/server.js +4 -1
  99. package/src/services/lambda/index.js +26 -0
  100. package/src/services/lambda/server.js +12 -1
  101. package/src/services/lambda/simulator.js +4 -0
  102. package/src/services/opensearch/index.js +105 -0
  103. package/src/services/opensearch/models/document.js +115 -0
  104. package/src/services/opensearch/models/index.js +73 -0
  105. package/src/services/opensearch/server.js +310 -0
  106. package/src/services/opensearch/simulator.js +605 -0
  107. package/src/services/opensearch/store.js +162 -0
  108. package/src/services/organizations/control-tower-simulator.js +321 -0
  109. package/src/services/organizations/control-tower-store.js +182 -0
  110. package/src/services/organizations/iam-integration.js +413 -0
  111. package/src/services/organizations/index.js +172 -0
  112. package/src/services/organizations/models/index.js +13 -0
  113. package/src/services/organizations/models/invitation.js +48 -0
  114. package/src/services/organizations/models/organization.js +566 -0
  115. package/src/services/organizations/models/role.js +244 -0
  116. package/src/services/organizations/models/scp.js +131 -0
  117. package/src/services/organizations/server.js +736 -0
  118. package/src/services/organizations/simulator.js +593 -0
  119. package/src/services/organizations/store.js +235 -0
  120. package/src/services/organizations/vpc-integration.js +453 -0
  121. package/src/services/route53/index.js +67 -0
  122. package/src/services/route53/models/healthCheck.js +101 -0
  123. package/src/services/route53/models/hostedZone.js +37 -0
  124. package/src/services/route53/models/resourceRecord.js +106 -0
  125. package/src/services/route53/server.js +841 -0
  126. package/src/services/route53/simulator.js +350 -0
  127. package/src/services/s3/index.js +62 -0
  128. package/src/services/s3/presigned-url-generator.js +337 -0
  129. package/src/services/s3/server.js +231 -0
  130. package/src/services/s3/simulator.js +551 -4
  131. package/src/services/sns/index.js +31 -0
  132. package/src/services/sns/server.js +9 -0
  133. package/src/services/sqs/index.js +38 -0
  134. package/src/services/sqs/server.js +57 -4
  135. package/src/services/sqs/simulator.js +423 -33
  136. package/src/services/sts/simulator.js +209 -17
  137. package/src/services/vpc/README.md +459 -0
  138. package/src/services/vpc/ec2ResponseFormatter.js +431 -0
  139. package/src/services/vpc/index.js +90 -0
  140. package/src/services/vpc/models/internetGateway.js +71 -0
  141. package/src/services/vpc/models/routeTable.js +160 -0
  142. package/src/services/vpc/models/subnet.js +48 -0
  143. package/src/services/vpc/models/vpc.js +37 -0
  144. package/src/services/vpc/models/vpcEndpoint.js +100 -0
  145. package/src/services/vpc/models/vpcGateway.js +134 -0
  146. package/src/services/vpc/server.js +836 -0
  147. package/src/services/vpc/simulator.js +613 -0
  148. package/src/utils/policy-cache.js +215 -0
  149. 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;