@iacmp/cli 1.1.0

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.
@@ -0,0 +1,1039 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/commands/audit-all.ts
31
+ var audit_all_exports = {};
32
+ __export(audit_all_exports, {
33
+ default: () => AuditAll
34
+ });
35
+ module.exports = __toCommonJS(audit_all_exports);
36
+ var import_core5 = require("@oclif/core");
37
+
38
+ // src/commands/audit-security.ts
39
+ var import_core = require("@oclif/core");
40
+ var import_chalk = __toESM(require("chalk"));
41
+
42
+ // src/audit.ts
43
+ var fs2 = __toESM(require("fs"));
44
+ var path = __toESM(require("path"));
45
+
46
+ // src/utils.ts
47
+ var fs = __toESM(require("fs"));
48
+ function readJsonFile(filePath) {
49
+ let content;
50
+ try {
51
+ content = fs.readFileSync(filePath, "utf-8");
52
+ } catch (e) {
53
+ throw new Error(`Falha ao ler '${filePath}': ${errMessage(e)}`);
54
+ }
55
+ try {
56
+ return JSON.parse(content);
57
+ } catch (e) {
58
+ throw new Error(`JSON inv\xE1lido em '${filePath}': ${errMessage(e)}`);
59
+ }
60
+ }
61
+ function errMessage(e) {
62
+ if (e instanceof Error) return e.message;
63
+ if (typeof e === "string") return e;
64
+ try {
65
+ return JSON.stringify(e);
66
+ } catch {
67
+ return String(e);
68
+ }
69
+ }
70
+
71
+ // src/audit.ts
72
+ function readConfig(cwd) {
73
+ const configPath = path.join(cwd, "iacmp.json");
74
+ if (!fs2.existsSync(configPath)) throw new Error("iacmp.json n\xE3o encontrado. Rode: iacmp init");
75
+ const config = readJsonFile(configPath);
76
+ return {
77
+ name: config.name ?? path.basename(cwd),
78
+ provider: config.provider ?? "aws"
79
+ };
80
+ }
81
+ function resolveTsNode(projectDir) {
82
+ const dirs = [];
83
+ let dir = projectDir;
84
+ for (let i = 0; i < 5; i++) {
85
+ dirs.push(dir);
86
+ const parent = path.dirname(dir);
87
+ if (parent === dir) break;
88
+ dir = parent;
89
+ }
90
+ for (const d of dirs) {
91
+ const tsNodePath = path.join(d, "node_modules", "ts-node");
92
+ if (fs2.existsSync(tsNodePath)) return tsNodePath;
93
+ }
94
+ return null;
95
+ }
96
+ function findStackFiles(dir) {
97
+ const entries = fs2.readdirSync(dir, { withFileTypes: true });
98
+ const files = [];
99
+ for (const entry of entries) {
100
+ const full = path.join(dir, entry.name);
101
+ if (entry.isDirectory()) {
102
+ files.push(...findStackFiles(full));
103
+ } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".js")) {
104
+ files.push(full);
105
+ }
106
+ }
107
+ return files;
108
+ }
109
+ function loadStacks(cwd) {
110
+ const stacksDir = path.join(cwd, "stacks");
111
+ if (!fs2.existsSync(stacksDir)) throw new Error("Diret\xF3rio stacks/ n\xE3o encontrado.");
112
+ const stackFiles = findStackFiles(stacksDir);
113
+ if (stackFiles.length === 0) throw new Error("Nenhuma stack encontrada em stacks/");
114
+ const tsNodePath = resolveTsNode(cwd);
115
+ if (tsNodePath) {
116
+ require(tsNodePath).register({
117
+ transpileOnly: true,
118
+ skipProject: true,
119
+ compilerOptions: {
120
+ target: "ES2022",
121
+ module: "commonjs",
122
+ moduleResolution: "node",
123
+ esModuleInterop: true,
124
+ strict: false,
125
+ skipLibCheck: true
126
+ }
127
+ });
128
+ }
129
+ const result = [];
130
+ for (const stackPath of stackFiles) {
131
+ const stackName = path.basename(stackPath).replace(/\.(ts|js)$/, "");
132
+ try {
133
+ const mod = require(stackPath);
134
+ const raw = mod.default ?? mod.stack ?? mod;
135
+ if (!raw || typeof raw !== "object" || !("constructs" in raw)) {
136
+ console.warn(`[audit] ${path.basename(stackPath)} n\xE3o exporta uma Stack v\xE1lida \u2014 ignorado.`);
137
+ continue;
138
+ }
139
+ result.push({ name: stackName, stack: raw });
140
+ } catch (err) {
141
+ console.warn(`[audit] falha ao carregar ${path.basename(stackPath)}: ${errMessage(err)}`);
142
+ }
143
+ }
144
+ return result;
145
+ }
146
+ function saveReport(cwd, commandName, content) {
147
+ const auditDir = path.join(cwd, "audit");
148
+ if (!fs2.existsSync(auditDir)) fs2.mkdirSync(auditDir, { recursive: true });
149
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
150
+ const fileName = `${commandName}-${date}.md`;
151
+ const filePath = path.join(auditDir, fileName);
152
+ fs2.writeFileSync(filePath, content, "utf-8");
153
+ return path.join("audit", fileName);
154
+ }
155
+ function today() {
156
+ return (/* @__PURE__ */ new Date()).toLocaleDateString("pt-BR");
157
+ }
158
+
159
+ // src/commands/audit-security.ts
160
+ function shouldFail(failOn, critical, warnings) {
161
+ if (failOn === "critical") return critical > 0;
162
+ if (failOn === "warning") return critical > 0 || warnings > 0;
163
+ return false;
164
+ }
165
+ function analyzeStack(stackName, stack) {
166
+ const findings = [];
167
+ const ok = [];
168
+ for (const c of stack.constructs) {
169
+ const p = c.props;
170
+ let hasIssue = false;
171
+ if (c.type === "Storage.Bucket") {
172
+ if (p.publicAccess === true) {
173
+ findings.push({
174
+ level: "critical",
175
+ stackName,
176
+ construct: c,
177
+ title: `Storage.Bucket '${c.id}' \u2014 public access enabled`,
178
+ problem: "publicAccess is enabled. Anyone can read/list objects in this bucket.",
179
+ recommendation: "Set `publicAccess: false` unless this is an intentional static website bucket."
180
+ });
181
+ hasIssue = true;
182
+ }
183
+ if (p.versioning !== true) {
184
+ findings.push({
185
+ level: "warning",
186
+ stackName,
187
+ construct: c,
188
+ title: `Storage.Bucket '${c.id}' \u2014 versioning disabled`,
189
+ problem: "Versioning is not enabled. Deleted or overwritten objects cannot be recovered.",
190
+ recommendation: "Set `versioning: true` to enable object rollback."
191
+ });
192
+ hasIssue = true;
193
+ }
194
+ }
195
+ if (c.type === "Database.SQL") {
196
+ if (p.multiAz !== true) {
197
+ findings.push({
198
+ level: "warning",
199
+ stackName,
200
+ construct: c,
201
+ title: `Database.SQL '${c.id}' \u2014 no Multi-AZ`,
202
+ problem: "multiAz is not enabled. A failure in the availability zone will make the database unavailable.",
203
+ recommendation: "Set `multiAz: true` for high availability."
204
+ });
205
+ hasIssue = true;
206
+ }
207
+ }
208
+ if (c.type === "Function.Lambda") {
209
+ if (p.memory === void 0 || p.memory === null) {
210
+ findings.push({
211
+ level: "warning",
212
+ stackName,
213
+ construct: c,
214
+ title: `Function.Lambda '${c.id}' \u2014 memory not defined`,
215
+ problem: "memory is not set. The function will use the provider default, which may be insufficient.",
216
+ recommendation: "Set `memory` explicitly (e.g. 256 or 512 MB)."
217
+ });
218
+ hasIssue = true;
219
+ }
220
+ }
221
+ if (c.type === "Network.VPC") {
222
+ if (p.cidr === void 0 || p.cidr === null) {
223
+ findings.push({
224
+ level: "warning",
225
+ stackName,
226
+ construct: c,
227
+ title: `Network.VPC '${c.id}' \u2014 default CIDR`,
228
+ problem: "cidr is not defined. The provider default CIDR may conflict with existing networks.",
229
+ recommendation: 'Set `cidr` explicitly (e.g. "10.0.0.0/16").'
230
+ });
231
+ hasIssue = true;
232
+ }
233
+ }
234
+ if (c.type === "Compute.Instance") {
235
+ if (p.publicAccess === true) {
236
+ findings.push({
237
+ level: "critical",
238
+ stackName,
239
+ construct: c,
240
+ title: `Compute.Instance '${c.id}' \u2014 public access enabled`,
241
+ problem: "publicAccess is enabled. The instance is directly exposed to the internet.",
242
+ recommendation: "Disable public access and use a load balancer or bastion host."
243
+ });
244
+ hasIssue = true;
245
+ }
246
+ }
247
+ if (!hasIssue) ok.push(c);
248
+ }
249
+ return { findings, ok };
250
+ }
251
+ var AuditSecurity = class _AuditSecurity extends import_core.Command {
252
+ static description = "Audit stacks for security issues";
253
+ static examples = [
254
+ "$ iacmp audit-security",
255
+ "$ iacmp audit-security --fail-on=critical",
256
+ "$ iacmp audit-security --fail-on=warning"
257
+ ];
258
+ static flags = {
259
+ "fail-on": import_core.Flags.string({
260
+ description: "Sai com exit 1 quando h\xE1 achados no n\xEDvel indicado",
261
+ options: ["critical", "warning", "none"],
262
+ default: "none"
263
+ })
264
+ };
265
+ async run() {
266
+ const { flags } = await this.parse(_AuditSecurity);
267
+ const failOn = flags["fail-on"];
268
+ const cwd = process.cwd();
269
+ let config;
270
+ try {
271
+ config = readConfig(cwd);
272
+ } catch (err) {
273
+ this.error(err.message);
274
+ }
275
+ let stacks;
276
+ try {
277
+ stacks = loadStacks(cwd);
278
+ } catch (err) {
279
+ this.error(err.message);
280
+ }
281
+ const allFindings = [];
282
+ const allOk = [];
283
+ for (const { name, stack } of stacks) {
284
+ const { findings, ok } = analyzeStack(name, stack);
285
+ allFindings.push(...findings);
286
+ allOk.push(...ok);
287
+ }
288
+ const critical = allFindings.filter((f) => f.level === "critical");
289
+ const warnings = allFindings.filter((f) => f.level === "warning");
290
+ this.log(import_chalk.default.bold("\nSecurity Audit"));
291
+ this.log("\u2500".repeat(40));
292
+ this.log(`Critical issues: ${critical.length > 0 ? import_chalk.default.red(critical.length) : import_chalk.default.green(0)}`);
293
+ this.log(`Warnings: ${warnings.length > 0 ? import_chalk.default.yellow(warnings.length) : import_chalk.default.green(0)}`);
294
+ this.log(`OK: ${import_chalk.default.green(allOk.length)}`);
295
+ this.log("");
296
+ for (const f of critical) {
297
+ this.log(`${import_chalk.default.red("\u2717 [CRITICAL]")} ${f.title}`);
298
+ }
299
+ for (const f of warnings) {
300
+ this.log(`${import_chalk.default.yellow("\u26A0 [WARNING]")} ${f.title}`);
301
+ }
302
+ for (const c of allOk) {
303
+ this.log(`${import_chalk.default.green("\u2713")} ${c.type} '${c.id}' \u2014 OK`);
304
+ }
305
+ let md = `# Security Audit Report \u2014 ${config.name}
306
+ `;
307
+ md += `Date: ${today()}
308
+ `;
309
+ md += `Provider: ${config.provider}
310
+
311
+ `;
312
+ md += `## Summary
313
+ `;
314
+ md += `- Critical issues: ${critical.length}
315
+ `;
316
+ md += `- Warnings: ${warnings.length}
317
+ `;
318
+ md += `- OK: ${allOk.length}
319
+
320
+ `;
321
+ md += `## Findings
322
+
323
+ `;
324
+ for (const f of allFindings) {
325
+ const label = f.level === "critical" ? "CRITICAL" : "WARNING";
326
+ md += `### [${label}] ${f.title}
327
+ `;
328
+ md += `Stack: ${f.stackName}
329
+ `;
330
+ md += `Resource: ${f.construct.id} (${f.construct.type})
331
+ `;
332
+ md += `Problem: ${f.problem}
333
+ `;
334
+ md += `Recommendation: ${f.recommendation}
335
+
336
+ `;
337
+ }
338
+ if (allOk.length > 0) {
339
+ md += `## Resources with no issues
340
+ `;
341
+ for (const c of allOk) {
342
+ md += `- ${c.type} '${c.id}' \u2014 OK
343
+ `;
344
+ }
345
+ md += "\n";
346
+ }
347
+ const relPath = saveReport(cwd, "security", md);
348
+ this.log(`
349
+ Report saved to ${relPath}`);
350
+ if (shouldFail(failOn, critical.length, warnings.length)) {
351
+ this.exit(1);
352
+ }
353
+ }
354
+ };
355
+
356
+ // src/commands/audit-ha.ts
357
+ var import_core2 = require("@oclif/core");
358
+ var import_chalk2 = __toESM(require("chalk"));
359
+ function shouldFail2(failOn, critical, warnings) {
360
+ if (failOn === "critical") return critical > 0;
361
+ if (failOn === "warning") return critical > 0 || warnings > 0;
362
+ return false;
363
+ }
364
+ function analyzeStack2(stackName, stack) {
365
+ const findings = [];
366
+ const constructs = stack.constructs;
367
+ for (const c of constructs) {
368
+ const p = c.props;
369
+ if (c.type === "Database.SQL") {
370
+ if (p.multiAz !== true) {
371
+ findings.push({
372
+ status: "no-ha",
373
+ stackName,
374
+ constructId: c.id,
375
+ constructType: c.type,
376
+ title: `Database.SQL '${c.id}' \u2014 Single-AZ`,
377
+ detail: "multiAz is not enabled. A failure in the AZ will make the database unavailable.",
378
+ recommendation: "Set `multiAz: true`."
379
+ });
380
+ } else {
381
+ findings.push({
382
+ status: "ha-ok",
383
+ stackName,
384
+ constructId: c.id,
385
+ constructType: c.type,
386
+ title: `Database.SQL '${c.id}' \u2014 Multi-AZ enabled`,
387
+ detail: "Database is configured with Multi-AZ.",
388
+ recommendation: ""
389
+ });
390
+ }
391
+ }
392
+ if (c.type === "Network.VPC") {
393
+ const maxAzs = p.maxAzs;
394
+ if (maxAzs === void 0 || maxAzs === null || maxAzs < 2) {
395
+ findings.push({
396
+ status: "no-ha",
397
+ stackName,
398
+ constructId: c.id,
399
+ constructType: c.type,
400
+ title: `Network.VPC '${c.id}' \u2014 single AZ`,
401
+ detail: `maxAzs is ${maxAzs ?? "not defined"} (< 2). The network is restricted to a single availability zone.`,
402
+ recommendation: "Set `maxAzs: 2` or more."
403
+ });
404
+ } else {
405
+ findings.push({
406
+ status: "ha-ok",
407
+ stackName,
408
+ constructId: c.id,
409
+ constructType: c.type,
410
+ title: `Network.VPC '${c.id}' \u2014 ${maxAzs} AZs`,
411
+ detail: `Network is configured with ${maxAzs} availability zones.`,
412
+ recommendation: ""
413
+ });
414
+ }
415
+ }
416
+ if (c.type === "Function.Lambda") {
417
+ findings.push({
418
+ status: "ha-ok",
419
+ stackName,
420
+ constructId: c.id,
421
+ constructType: c.type,
422
+ title: `Function.Lambda '${c.id}' \u2014 native HA`,
423
+ detail: "Lambda functions are distributed across multiple AZs by the provider by default.",
424
+ recommendation: ""
425
+ });
426
+ }
427
+ if (c.type === "Storage.Bucket") {
428
+ findings.push({
429
+ status: "ha-ok",
430
+ stackName,
431
+ constructId: c.id,
432
+ constructType: c.type,
433
+ title: `Storage.Bucket '${c.id}' \u2014 native HA`,
434
+ detail: "Object storage buckets are automatically replicated by the provider.",
435
+ recommendation: ""
436
+ });
437
+ }
438
+ }
439
+ const vpcs = constructs.filter((c) => c.type === "Network.VPC");
440
+ if (vpcs.length === 0) {
441
+ findings.push({
442
+ status: "info",
443
+ stackName,
444
+ constructId: "stack",
445
+ constructType: "Stack",
446
+ title: `Stack '${stackName}' \u2014 no VPC`,
447
+ detail: "No Network.VPC found in the stack. No network isolation is defined.",
448
+ recommendation: "Consider adding a VPC to isolate network resources."
449
+ });
450
+ }
451
+ const instances = constructs.filter((c) => c.type === "Compute.Instance");
452
+ if (instances.length === 1) {
453
+ findings.push({
454
+ status: "info",
455
+ stackName,
456
+ constructId: instances[0].id,
457
+ constructType: "Compute.Instance",
458
+ title: `Compute.Instance '${instances[0].id}' \u2014 no redundancy`,
459
+ detail: "Only 1 compute instance found. If it fails, there is no backup instance.",
460
+ recommendation: "Add more instances or configure auto-scaling."
461
+ });
462
+ } else if (instances.length > 1) {
463
+ for (const inst of instances) {
464
+ findings.push({
465
+ status: "ha-ok",
466
+ stackName,
467
+ constructId: inst.id,
468
+ constructType: "Compute.Instance",
469
+ title: `Compute.Instance '${inst.id}' \u2014 redundancy detected`,
470
+ detail: `${instances.length} compute instances in the stack.`,
471
+ recommendation: ""
472
+ });
473
+ }
474
+ }
475
+ return findings;
476
+ }
477
+ var AuditHA = class _AuditHA extends import_core2.Command {
478
+ static description = "Audit stacks for high availability (HA) issues";
479
+ static examples = [
480
+ "$ iacmp audit-ha",
481
+ "$ iacmp audit-ha --fail-on=critical"
482
+ ];
483
+ static flags = {
484
+ "fail-on": import_core2.Flags.string({
485
+ description: "Sai com exit 1 quando h\xE1 achados no n\xEDvel indicado",
486
+ options: ["critical", "warning", "none"],
487
+ default: "none"
488
+ })
489
+ };
490
+ async run() {
491
+ const { flags } = await this.parse(_AuditHA);
492
+ const failOn = flags["fail-on"];
493
+ const cwd = process.cwd();
494
+ let config;
495
+ try {
496
+ config = readConfig(cwd);
497
+ } catch (err) {
498
+ this.error(err.message);
499
+ }
500
+ let stacks;
501
+ try {
502
+ stacks = loadStacks(cwd);
503
+ } catch (err) {
504
+ this.error(err.message);
505
+ }
506
+ const allFindings = [];
507
+ for (const { name, stack } of stacks) {
508
+ allFindings.push(...analyzeStack2(name, stack));
509
+ }
510
+ const noHA = allFindings.filter((f) => f.status === "no-ha");
511
+ const partial = allFindings.filter((f) => f.status === "ha-partial");
512
+ const haOk = allFindings.filter((f) => f.status === "ha-ok");
513
+ const infos = allFindings.filter((f) => f.status === "info");
514
+ this.log(import_chalk2.default.bold("\nHigh Availability (HA) Audit"));
515
+ this.log("\u2500".repeat(40));
516
+ this.log(`No HA: ${noHA.length > 0 ? import_chalk2.default.red(noHA.length) : import_chalk2.default.green(0)}`);
517
+ this.log(`Partial: ${partial.length > 0 ? import_chalk2.default.yellow(partial.length) : import_chalk2.default.green(0)}`);
518
+ this.log(`HA OK: ${import_chalk2.default.green(haOk.length)}`);
519
+ this.log("");
520
+ for (const f of noHA) {
521
+ this.log(`${import_chalk2.default.red("\u2717 [NO HA]")} ${f.title}`);
522
+ }
523
+ for (const f of partial) {
524
+ this.log(`${import_chalk2.default.yellow("\u26A0 [PARTIAL HA]")} ${f.title}`);
525
+ }
526
+ for (const f of infos) {
527
+ this.log(`${import_chalk2.default.yellow("\u26A0 [WARNING]")} ${f.title}`);
528
+ }
529
+ for (const f of haOk) {
530
+ this.log(`${import_chalk2.default.green("\u2713 [HA OK]")} ${f.title}`);
531
+ }
532
+ let md = `# High Availability (HA) Audit Report \u2014 ${config.name}
533
+ `;
534
+ md += `Date: ${today()}
535
+
536
+ `;
537
+ md += `## Summary
538
+ `;
539
+ md += `- No HA: ${noHA.length} resources
540
+ `;
541
+ md += `- Partial HA: ${partial.length} resources
542
+ `;
543
+ md += `- HA OK: ${haOk.length} resources
544
+
545
+ `;
546
+ md += `## Findings
547
+
548
+ `;
549
+ for (const f of [...noHA, ...partial, ...infos]) {
550
+ const label = f.status === "no-ha" ? "NO HA" : f.status === "ha-partial" ? "PARTIAL HA" : "WARNING";
551
+ md += `### [${label}] ${f.title}
552
+ `;
553
+ md += `Stack: ${f.stackName}
554
+ `;
555
+ md += `${f.detail}
556
+ `;
557
+ if (f.recommendation) md += `Recommendation: ${f.recommendation}
558
+ `;
559
+ md += "\n";
560
+ }
561
+ if (haOk.length > 0) {
562
+ md += `## Resources with HA
563
+ `;
564
+ for (const f of haOk) {
565
+ md += `- ${f.constructType} '${f.constructId}' \u2014 HA OK
566
+ `;
567
+ }
568
+ md += "\n";
569
+ }
570
+ const relPath = saveReport(cwd, "ha", md);
571
+ this.log(`
572
+ Report saved to ${relPath}`);
573
+ if (shouldFail2(failOn, noHA.length, partial.length + infos.length)) {
574
+ this.exit(1);
575
+ }
576
+ }
577
+ };
578
+
579
+ // src/commands/audit-dr.ts
580
+ var import_core3 = require("@oclif/core");
581
+ var import_chalk3 = __toESM(require("chalk"));
582
+ function shouldFail3(failOn, critical, warnings) {
583
+ if (failOn === "critical") return critical > 0;
584
+ if (failOn === "warning") return critical > 0 || warnings > 0;
585
+ return false;
586
+ }
587
+ function analyzeStack3(stackName, stack) {
588
+ const constructs = stack.constructs;
589
+ const checks = [];
590
+ const details = [];
591
+ const buckets = constructs.filter((c) => c.type === "Storage.Bucket");
592
+ const dbs = constructs.filter((c) => c.type === "Database.SQL");
593
+ const vpcs = constructs.filter((c) => c.type === "Network.VPC");
594
+ const instances = constructs.filter((c) => c.type === "Compute.Instance");
595
+ const bucketsWithVersioning = buckets.filter((c) => c.props.versioning === true);
596
+ checks.push({
597
+ label: "Buckets with versioning enabled",
598
+ passed: buckets.length === 0 || bucketsWithVersioning.length === buckets.length
599
+ });
600
+ for (const b of buckets) {
601
+ if (b.props.versioning !== true) {
602
+ details.push({
603
+ level: "no-dr",
604
+ title: `Storage.Bucket '${b.id}' \u2014 no versioning`,
605
+ detail: "Without versioning there is no object history. Deletions or overwrites are unrecoverable.",
606
+ recommendation: "Set `versioning: true`."
607
+ });
608
+ }
609
+ }
610
+ const dbsMultiAz = dbs.filter((c) => c.props.multiAz === true);
611
+ checks.push({
612
+ label: "Multi-AZ database",
613
+ passed: dbs.length === 0 || dbsMultiAz.length === dbs.length
614
+ });
615
+ for (const db of dbs) {
616
+ if (db.props.multiAz !== true) {
617
+ details.push({
618
+ level: "no-dr",
619
+ title: `Database.SQL '${db.id}' \u2014 Single-AZ`,
620
+ detail: "Single-AZ database. In case of AZ failure, restore may take hours.",
621
+ recommendation: "Set `multiAz: true` for automatic failover."
622
+ });
623
+ }
624
+ if (db.props.instanceType === void 0 || db.props.instanceType === null) {
625
+ details.push({
626
+ level: "warning",
627
+ title: `Database.SQL '${db.id}' \u2014 instanceType not defined`,
628
+ detail: "Default instance type may be insufficient for fast restore in DR scenarios.",
629
+ recommendation: "Set `instanceType` explicitly to ensure adequate performance."
630
+ });
631
+ }
632
+ }
633
+ const vpcsMultiAz = vpcs.filter((c) => {
634
+ const maxAzs = c.props.maxAzs;
635
+ return maxAzs !== void 0 && maxAzs >= 2;
636
+ });
637
+ checks.push({
638
+ label: "Network with multiple AZs",
639
+ passed: vpcs.length === 0 || vpcsMultiAz.length === vpcs.length
640
+ });
641
+ for (const vpc of vpcs) {
642
+ const maxAzs = vpc.props.maxAzs;
643
+ if (maxAzs === void 0 || maxAzs < 2) {
644
+ details.push({
645
+ level: "no-dr",
646
+ title: `Network.VPC '${vpc.id}' \u2014 single AZ`,
647
+ detail: `maxAzs is ${maxAzs ?? "not defined"}. Single-AZ network compromises DR.`,
648
+ recommendation: "Set `maxAzs: 2` or more."
649
+ });
650
+ }
651
+ }
652
+ if (dbs.length === 0 && buckets.length === 0) {
653
+ details.push({
654
+ level: "info",
655
+ title: "No persistent state detected",
656
+ detail: "No Database.SQL or Storage.Bucket found. The stack may be stateless."
657
+ });
658
+ }
659
+ if (instances.length > 0 && buckets.length === 0) {
660
+ details.push({
661
+ level: "warning",
662
+ title: "Compute without storage detected",
663
+ detail: "Compute instances found but no storage bucket. Where will data be stored in a DR event?",
664
+ recommendation: "Consider adding a Storage.Bucket for persistent data."
665
+ });
666
+ }
667
+ const passed = checks.filter((c) => c.passed).length;
668
+ const total = checks.length;
669
+ const score = total > 0 ? Math.round(passed / total * 10) : 10;
670
+ return { stackName, checks, details, score };
671
+ }
672
+ function scoreLabel(score) {
673
+ if (score <= 3) return "Critical";
674
+ if (score <= 5) return "Below expectations";
675
+ if (score <= 7) return "Adequate";
676
+ return "Excellent";
677
+ }
678
+ var AuditDR = class _AuditDR extends import_core3.Command {
679
+ static description = "Audit stacks for disaster recovery (DR) readiness";
680
+ static examples = [
681
+ "$ iacmp audit-dr",
682
+ "$ iacmp audit-dr --fail-on=critical"
683
+ ];
684
+ static flags = {
685
+ "fail-on": import_core3.Flags.string({
686
+ description: "Sai com exit 1 quando h\xE1 achados no n\xEDvel indicado",
687
+ options: ["critical", "warning", "none"],
688
+ default: "none"
689
+ })
690
+ };
691
+ async run() {
692
+ const { flags } = await this.parse(_AuditDR);
693
+ const failOn = flags["fail-on"];
694
+ const cwd = process.cwd();
695
+ let config;
696
+ try {
697
+ config = readConfig(cwd);
698
+ } catch (err) {
699
+ this.error(err.message);
700
+ }
701
+ let stacks;
702
+ try {
703
+ stacks = loadStacks(cwd);
704
+ } catch (err) {
705
+ this.error(err.message);
706
+ }
707
+ const results = [];
708
+ for (const { name, stack } of stacks) {
709
+ results.push(analyzeStack3(name, stack));
710
+ }
711
+ const globalScore = results.length > 0 ? Math.round(results.reduce((sum, r) => sum + r.score, 0) / results.length) : 10;
712
+ const scoreColor = globalScore <= 5 ? import_chalk3.default.red : globalScore <= 7 ? import_chalk3.default.yellow : import_chalk3.default.green;
713
+ this.log(import_chalk3.default.bold("\nDisaster Recovery (DR) Audit"));
714
+ this.log("\u2500".repeat(40));
715
+ this.log(`DR Score: ${scoreColor(`${globalScore}/10`)} \u2014 ${scoreLabel(globalScore)}`);
716
+ this.log("");
717
+ for (const r of results) {
718
+ for (const d of r.details) {
719
+ if (d.level === "no-dr") this.log(`${import_chalk3.default.red("\u2717 [NO DR]")} ${d.title}`);
720
+ else if (d.level === "warning") this.log(`${import_chalk3.default.yellow("\u26A0 [WARNING]")} ${d.title}`);
721
+ else if (d.level === "info") this.log(`${import_chalk3.default.cyan("\u2139 [INFO]")} ${d.title}`);
722
+ else this.log(`${import_chalk3.default.green("\u2713")} ${d.title}`);
723
+ }
724
+ }
725
+ let md = `# Disaster Recovery (DR) Audit Report \u2014 ${config.name}
726
+ `;
727
+ md += `Date: ${today()}
728
+
729
+ `;
730
+ md += `## DR Score
731
+ `;
732
+ md += `${globalScore}/10 \u2014 ${scoreLabel(globalScore)}
733
+
734
+ `;
735
+ for (const r of results) {
736
+ md += `## DR Checklist \u2014 Stack: ${r.stackName}
737
+ `;
738
+ for (const ch of r.checks) {
739
+ md += `- [${ch.passed ? "x" : " "}] ${ch.label}
740
+ `;
741
+ }
742
+ md += "\n";
743
+ }
744
+ md += `## Findings
745
+
746
+ `;
747
+ for (const r of results) {
748
+ for (const d of r.details) {
749
+ const label = d.level === "no-dr" ? "NO DR" : d.level === "warning" ? "WARNING" : d.level === "info" ? "INFO" : "OK";
750
+ md += `### [${label}] ${d.title}
751
+ `;
752
+ md += `Stack: ${r.stackName}
753
+ `;
754
+ md += `${d.detail}
755
+ `;
756
+ if (d.recommendation) md += `Recommendation: ${d.recommendation}
757
+ `;
758
+ md += "\n";
759
+ }
760
+ }
761
+ const relPath = saveReport(cwd, "dr", md);
762
+ this.log(`
763
+ Report saved to ${relPath}`);
764
+ let noDr = 0;
765
+ let warnings = 0;
766
+ for (const r of results) {
767
+ for (const d of r.details) {
768
+ if (d.level === "no-dr") noDr++;
769
+ else if (d.level === "warning") warnings++;
770
+ }
771
+ }
772
+ if (shouldFail3(failOn, noDr, warnings)) {
773
+ this.exit(1);
774
+ }
775
+ }
776
+ };
777
+
778
+ // src/commands/audit-improvements.ts
779
+ var import_core4 = require("@oclif/core");
780
+ var import_chalk4 = __toESM(require("chalk"));
781
+ function shouldFail4(failOn, critical, warnings) {
782
+ if (failOn === "critical") return critical > 0;
783
+ if (failOn === "warning") return critical > 0 || warnings > 0;
784
+ return false;
785
+ }
786
+ function analyzeStack4(stackName, stack) {
787
+ const constructs = stack.constructs;
788
+ const improvements = [];
789
+ const ok = [];
790
+ if (constructs.length === 0) {
791
+ improvements.push({
792
+ category: "CONFIGURATION",
793
+ constructId: stackName,
794
+ constructType: "Stack",
795
+ stackName,
796
+ title: `Stack '${stackName}' \u2014 empty`,
797
+ impact: "High",
798
+ current: "The stack has no constructs defined.",
799
+ suggestion: "Add constructs to the stack to make it functional.",
800
+ effort: "Varies by use case"
801
+ });
802
+ return { improvements, ok };
803
+ }
804
+ const instances = constructs.filter((c) => c.type === "Compute.Instance");
805
+ const buckets = constructs.filter((c) => c.type === "Storage.Bucket");
806
+ const dbs = constructs.filter((c) => c.type === "Database.SQL");
807
+ const lambdas = constructs.filter((c) => c.type === "Function.Lambda");
808
+ const vpcs = constructs.filter((c) => c.type === "Network.VPC");
809
+ for (const inst of instances) {
810
+ if (inst.props.instanceType === "small") {
811
+ improvements.push({
812
+ category: "PERFORMANCE",
813
+ constructId: inst.id,
814
+ constructType: inst.type,
815
+ stackName,
816
+ title: `Compute.Instance '${inst.id}' \u2014 small instance type`,
817
+ impact: "Medium",
818
+ current: `instanceType 'small' may be insufficient for production workloads.`,
819
+ suggestion: `Use 'medium' or 'large' for production. Consider adding auto-scaling.`,
820
+ effort: "Low (change 1 field in the stack)"
821
+ });
822
+ } else {
823
+ ok.push({ id: inst.id, type: inst.type, reason: "adequate instance type" });
824
+ }
825
+ }
826
+ if (instances.length > 1) {
827
+ improvements.push({
828
+ category: "ARCHITECTURE",
829
+ constructId: "stack",
830
+ constructType: "Stack",
831
+ stackName,
832
+ title: "Multiple instances without a load balancer",
833
+ impact: "High",
834
+ current: `${instances.length} compute instances detected with no load balancer defined.`,
835
+ suggestion: "Add a load balancer to distribute traffic and increase resilience. (future feature in iacmp)",
836
+ effort: "Medium (requires new resource in iacmp)"
837
+ });
838
+ }
839
+ for (const b of buckets) {
840
+ if (b.props.versioning !== true) {
841
+ improvements.push({
842
+ category: "DATA PROTECTION",
843
+ constructId: b.id,
844
+ constructType: b.type,
845
+ stackName,
846
+ title: `Storage.Bucket '${b.id}' \u2014 versioning disabled`,
847
+ impact: "Medium",
848
+ current: "Versioning is disabled. Deleted or overwritten objects are unrecoverable.",
849
+ suggestion: "Enable `versioning: true` to protect against accidental deletion.",
850
+ effort: "Low (change 1 field in the stack)"
851
+ });
852
+ } else {
853
+ ok.push({ id: b.id, type: b.type, reason: "versioning enabled" });
854
+ }
855
+ }
856
+ for (const db of dbs) {
857
+ if (db.props.multiAz !== true) {
858
+ improvements.push({
859
+ category: "AVAILABILITY",
860
+ constructId: db.id,
861
+ constructType: db.type,
862
+ stackName,
863
+ title: `Database.SQL '${db.id}' \u2014 no Multi-AZ or read replica`,
864
+ impact: "High",
865
+ current: "Single-AZ database with no read replica.",
866
+ suggestion: "Set `multiAz: true` and consider adding a read replica for query performance.",
867
+ effort: "Low (change 1 field; replica requires a new construct)"
868
+ });
869
+ } else {
870
+ ok.push({ id: db.id, type: db.type, reason: "Multi-AZ enabled" });
871
+ }
872
+ }
873
+ for (const vpc of vpcs) {
874
+ const maxAzs = vpc.props.maxAzs;
875
+ if (maxAzs === void 0 || maxAzs === null) {
876
+ improvements.push({
877
+ category: "ARCHITECTURE",
878
+ constructId: vpc.id,
879
+ constructType: vpc.type,
880
+ stackName,
881
+ title: `Network.VPC '${vpc.id}' \u2014 maxAzs not defined`,
882
+ impact: "Medium",
883
+ current: "maxAzs not defined. The provider may default to 1 AZ.",
884
+ suggestion: "Set `maxAzs: 3` for production with high availability.",
885
+ effort: "Low (change 1 field in the stack)"
886
+ });
887
+ } else {
888
+ ok.push({ id: vpc.id, type: vpc.type, reason: `${maxAzs} AZs configured` });
889
+ }
890
+ }
891
+ if (lambdas.length === 0 && instances.length > 0) {
892
+ improvements.push({
893
+ category: "ARCHITECTURE",
894
+ constructId: "stack",
895
+ constructType: "Stack",
896
+ stackName,
897
+ title: "No serverless functions detected",
898
+ impact: "Low",
899
+ current: `Stack has ${instances.length} compute instance(s) but no Function.Lambda.`,
900
+ suggestion: "For event-driven tasks, scheduled jobs, or webhooks, consider Function.Lambda instead of always-on instances.",
901
+ effort: "Medium (requires partial logic refactoring)"
902
+ });
903
+ }
904
+ for (const fn of lambdas) {
905
+ ok.push({ id: fn.id, type: fn.type, reason: "serverless \u2014 cost-efficient by nature" });
906
+ }
907
+ return { improvements, ok };
908
+ }
909
+ var AuditImprovements = class _AuditImprovements extends import_core4.Command {
910
+ static description = "Suggest architecture and performance improvements for stacks";
911
+ static examples = [
912
+ "$ iacmp audit-improvements",
913
+ "$ iacmp audit-improvements --fail-on=critical"
914
+ ];
915
+ static flags = {
916
+ "fail-on": import_core4.Flags.string({
917
+ description: "Sai com exit 1 quando h\xE1 melhorias no n\xEDvel indicado (critical = High impact, warning = qualquer)",
918
+ options: ["critical", "warning", "none"],
919
+ default: "none"
920
+ })
921
+ };
922
+ async run() {
923
+ const { flags } = await this.parse(_AuditImprovements);
924
+ const failOn = flags["fail-on"];
925
+ const cwd = process.cwd();
926
+ let config;
927
+ try {
928
+ config = readConfig(cwd);
929
+ } catch (err) {
930
+ this.error(err.message);
931
+ }
932
+ let stacks;
933
+ try {
934
+ stacks = loadStacks(cwd);
935
+ } catch (err) {
936
+ this.error(err.message);
937
+ }
938
+ const allImprovements = [];
939
+ const allOk = [];
940
+ for (const { name, stack } of stacks) {
941
+ const { improvements, ok } = analyzeStack4(name, stack);
942
+ allImprovements.push(...improvements);
943
+ for (const o of ok) allOk.push({ ...o, stackName: name });
944
+ }
945
+ this.log(import_chalk4.default.bold("\nImprovements Audit"));
946
+ this.log("\u2500".repeat(40));
947
+ this.log(`Improvements found: ${allImprovements.length > 0 ? import_chalk4.default.yellow(allImprovements.length) : import_chalk4.default.green(0)}`);
948
+ this.log("");
949
+ for (const m of allImprovements) {
950
+ const impactColor = m.impact === "High" ? import_chalk4.default.red : m.impact === "Medium" ? import_chalk4.default.yellow : import_chalk4.default.cyan;
951
+ this.log(`${import_chalk4.default.yellow("\u26A0")} [${m.category}] ${m.title} \u2014 Impact: ${impactColor(m.impact)}`);
952
+ }
953
+ for (const o of allOk) {
954
+ this.log(`${import_chalk4.default.green("\u2713")} ${o.type} '${o.id}' \u2014 ${o.reason}`);
955
+ }
956
+ let md = `# Improvements Audit Report \u2014 ${config.name}
957
+ `;
958
+ md += `Date: ${today()}
959
+
960
+ `;
961
+ md += `## Improvements found: ${allImprovements.length}
962
+
963
+ `;
964
+ for (const m of allImprovements) {
965
+ md += `### [${m.category}] ${m.title}
966
+ `;
967
+ md += `Impact: ${m.impact}
968
+ `;
969
+ md += `Stack: ${m.stackName}
970
+ `;
971
+ md += `Current situation: ${m.current}
972
+ `;
973
+ md += `Suggestion: ${m.suggestion}
974
+ `;
975
+ md += `Estimated effort: ${m.effort}
976
+
977
+ `;
978
+ }
979
+ if (allOk.length > 0) {
980
+ md += `## No suggestions
981
+ `;
982
+ for (const o of allOk) {
983
+ md += `- ${o.type} '${o.id}' \u2014 ${o.reason}
984
+ `;
985
+ }
986
+ md += "\n";
987
+ }
988
+ const relPath = saveReport(cwd, "improvements", md);
989
+ this.log(`
990
+ Report saved to ${relPath}`);
991
+ const highImpact = allImprovements.filter((m) => m.impact === "High").length;
992
+ const otherImpact = allImprovements.length - highImpact;
993
+ if (shouldFail4(failOn, highImpact, otherImpact)) {
994
+ this.exit(1);
995
+ }
996
+ }
997
+ };
998
+
999
+ // src/commands/audit-all.ts
1000
+ var AuditAll = class _AuditAll extends import_core5.Command {
1001
+ static description = "Run all audits and generate all reports";
1002
+ static examples = [
1003
+ "$ iacmp audit-all",
1004
+ "$ iacmp audit-all --fail-on=critical"
1005
+ ];
1006
+ static flags = {
1007
+ "fail-on": import_core5.Flags.string({
1008
+ description: "Sai com exit 1 quando qualquer audit acusa achados no n\xEDvel indicado",
1009
+ options: ["critical", "warning", "none"],
1010
+ default: "none"
1011
+ })
1012
+ };
1013
+ async run() {
1014
+ const { flags } = await this.parse(_AuditAll);
1015
+ const failOnArgs = flags["fail-on"] === "none" ? [] : [`--fail-on=${flags["fail-on"]}`];
1016
+ this.log("Running all audits...\n");
1017
+ let anyFailed = false;
1018
+ let hardError = null;
1019
+ for (const Cmd of [AuditSecurity, AuditHA, AuditDR, AuditImprovements]) {
1020
+ try {
1021
+ await Cmd.run(failOnArgs);
1022
+ } catch (e) {
1023
+ const code = e.oclif?.exit;
1024
+ if (code === 1) {
1025
+ anyFailed = true;
1026
+ } else {
1027
+ hardError = e;
1028
+ break;
1029
+ }
1030
+ }
1031
+ this.log("");
1032
+ }
1033
+ if (hardError) {
1034
+ this.error(hardError.message);
1035
+ }
1036
+ this.log("All audits complete. Reports saved to audit/");
1037
+ if (anyFailed) this.exit(1);
1038
+ }
1039
+ };