infrawise 0.5.0 → 0.7.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.
- package/README.md +26 -14
- package/dist/adapters/aws.js +152 -72
- package/dist/adapters/dynamodb.js +17 -27
- package/dist/adapters/logs.js +12 -21
- package/dist/adapters/mongodb.js +10 -16
- package/dist/adapters/mysql.js +8 -17
- package/dist/adapters/postgres.js +9 -13
- package/dist/adapters/terraform.js +14 -56
- package/dist/analyzers/aws-services.js +34 -17
- package/dist/analyzers/dynamodb.js +6 -12
- package/dist/analyzers/index.js +13 -41
- package/dist/analyzers/mongodb.js +4 -9
- package/dist/analyzers/mysql.js +4 -9
- package/dist/analyzers/postgres.js +3 -9
- package/dist/analyzers/rds.js +5 -13
- package/dist/analyzers/terraform.js +1 -5
- package/dist/cli/commands/analyze.js +131 -158
- package/dist/cli/commands/auth.js +24 -30
- package/dist/cli/commands/dev.js +46 -84
- package/dist/cli/commands/doctor.js +51 -74
- package/dist/cli/commands/init.js +48 -73
- package/dist/cli/index.js +21 -23
- package/dist/cli/utils.js +40 -86
- package/dist/context/index.js +49 -85
- package/dist/core/cache.js +5 -43
- package/dist/core/config.js +50 -83
- package/dist/core/errors.js +7 -17
- package/dist/core/index.js +4 -22
- package/dist/core/logger.js +4 -10
- package/dist/graph/index.js +73 -33
- package/dist/server/index.js +116 -93
- package/dist/types.js +1 -2
- package/package.json +32 -30
package/dist/analyzers/rds.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RDSNoMultiAZAnalyzer = exports.RDSNoDeletionProtectionAnalyzer = exports.RDSUnencryptedAnalyzer = exports.RDSNoBackupAnalyzer = exports.RDSPubliclyAccessibleAnalyzer = void 0;
|
|
4
1
|
// ─── RDS ─────────────────────────────────────────────────────────────────────
|
|
5
|
-
class RDSPubliclyAccessibleAnalyzer {
|
|
2
|
+
export class RDSPubliclyAccessibleAnalyzer {
|
|
6
3
|
name = 'RDSPubliclyAccessibleAnalyzer';
|
|
7
4
|
async analyze(graph) {
|
|
8
5
|
const findings = [];
|
|
@@ -22,8 +19,7 @@ class RDSPubliclyAccessibleAnalyzer {
|
|
|
22
19
|
return findings;
|
|
23
20
|
}
|
|
24
21
|
}
|
|
25
|
-
|
|
26
|
-
class RDSNoBackupAnalyzer {
|
|
22
|
+
export class RDSNoBackupAnalyzer {
|
|
27
23
|
name = 'RDSNoBackupAnalyzer';
|
|
28
24
|
async analyze(graph) {
|
|
29
25
|
const findings = [];
|
|
@@ -43,8 +39,7 @@ class RDSNoBackupAnalyzer {
|
|
|
43
39
|
return findings;
|
|
44
40
|
}
|
|
45
41
|
}
|
|
46
|
-
|
|
47
|
-
class RDSUnencryptedAnalyzer {
|
|
42
|
+
export class RDSUnencryptedAnalyzer {
|
|
48
43
|
name = 'RDSUnencryptedAnalyzer';
|
|
49
44
|
async analyze(graph) {
|
|
50
45
|
const findings = [];
|
|
@@ -64,8 +59,7 @@ class RDSUnencryptedAnalyzer {
|
|
|
64
59
|
return findings;
|
|
65
60
|
}
|
|
66
61
|
}
|
|
67
|
-
|
|
68
|
-
class RDSNoDeletionProtectionAnalyzer {
|
|
62
|
+
export class RDSNoDeletionProtectionAnalyzer {
|
|
69
63
|
name = 'RDSNoDeletionProtectionAnalyzer';
|
|
70
64
|
async analyze(graph) {
|
|
71
65
|
const findings = [];
|
|
@@ -85,8 +79,7 @@ class RDSNoDeletionProtectionAnalyzer {
|
|
|
85
79
|
return findings;
|
|
86
80
|
}
|
|
87
81
|
}
|
|
88
|
-
|
|
89
|
-
class RDSNoMultiAZAnalyzer {
|
|
82
|
+
export class RDSNoMultiAZAnalyzer {
|
|
90
83
|
name = 'RDSNoMultiAZAnalyzer';
|
|
91
84
|
async analyze(graph) {
|
|
92
85
|
const findings = [];
|
|
@@ -106,4 +99,3 @@ class RDSNoMultiAZAnalyzer {
|
|
|
106
99
|
return findings;
|
|
107
100
|
}
|
|
108
101
|
}
|
|
109
|
-
exports.RDSNoMultiAZAnalyzer = RDSNoMultiAZAnalyzer;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.IaCDriftAnalyzer = void 0;
|
|
4
|
-
class IaCDriftAnalyzer {
|
|
1
|
+
export class IaCDriftAnalyzer {
|
|
5
2
|
name = 'IaCDriftAnalyzer';
|
|
6
3
|
iacSchema = null;
|
|
7
4
|
setIaCSchema(schema) {
|
|
@@ -92,4 +89,3 @@ class IaCDriftAnalyzer {
|
|
|
92
89
|
return findings;
|
|
93
90
|
}
|
|
94
91
|
}
|
|
95
|
-
exports.IaCDriftAnalyzer = IaCDriftAnalyzer;
|
|
@@ -1,70 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.runAnalyze = runAnalyze;
|
|
40
|
-
exports.runCodeRefresh = runCodeRefresh;
|
|
41
|
-
const path = __importStar(require("path"));
|
|
42
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
-
const ora_1 = __importDefault(require("ora"));
|
|
44
|
-
const core_1 = require("../../core");
|
|
45
|
-
const dynamodb_1 = require("../../adapters/dynamodb");
|
|
46
|
-
const postgres_1 = require("../../adapters/postgres");
|
|
47
|
-
const mysql_1 = require("../../adapters/mysql");
|
|
48
|
-
const mongodb_1 = require("../../adapters/mongodb");
|
|
49
|
-
const terraform_1 = require("../../adapters/terraform");
|
|
50
|
-
const aws_1 = require("../../adapters/aws");
|
|
51
|
-
const logs_1 = require("../../adapters/logs");
|
|
52
|
-
const context_1 = require("../../context");
|
|
53
|
-
const graph_1 = require("../../graph");
|
|
54
|
-
const analyzers_1 = require("../../analyzers");
|
|
55
|
-
const utils_1 = require("../utils");
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { loadConfig, formatError, writeCache, readCache } from '../../core/index.js';
|
|
5
|
+
import { extractDynamoMetadata } from '../../adapters/dynamodb.js';
|
|
6
|
+
import { extractPostgresMetadata } from '../../adapters/postgres.js';
|
|
7
|
+
import { extractMySQLMetadata } from '../../adapters/mysql.js';
|
|
8
|
+
import { extractMongoMetadata } from '../../adapters/mongodb.js';
|
|
9
|
+
import { extractIaCSchema } from '../../adapters/terraform.js';
|
|
10
|
+
import { extractSQSMetadata, extractSNSMetadata, extractSSMMetadata, extractSecretsMetadata, extractLambdaMetadata, extractEventBridgeMetadata, extractRDSMetadata, } from '../../adapters/aws.js';
|
|
11
|
+
import { extractLogsMetadata } from '../../adapters/logs.js';
|
|
12
|
+
import { scanRepository } from '../../context/index.js';
|
|
13
|
+
import { buildGraph } from '../../graph/index.js';
|
|
14
|
+
import { runAllAnalyzers, IaCDriftAnalyzer, FullTableScanAnalyzer, MissingGSIAnalyzer, HotPartitionAnalyzer, MissingIndexAnalyzer, NplusOneAnalyzer, LargeSelectAnalyzer, MissingMySQLIndexAnalyzer, MySQLFullTableScanAnalyzer, MissingMongoIndexAnalyzer, MongoCollectionScanAnalyzer, MissingDLQAnalyzer, UnencryptedQueueAnalyzer, LargeQueueBacklogAnalyzer, MissingSecretRotationAnalyzer, MissingLogRetentionAnalyzer, LambdaDefaultMemoryAnalyzer, LambdaHighTimeoutAnalyzer, LambdaMissingTriggerDLQAnalyzer, RDSPubliclyAccessibleAnalyzer, RDSNoBackupAnalyzer, RDSUnencryptedAnalyzer, RDSNoDeletionProtectionAnalyzer, RDSNoMultiAZAnalyzer, } from '../../analyzers/index.js';
|
|
15
|
+
import { printFinding, printSummaryBox, log, printHeader } from '../utils.js';
|
|
56
16
|
function mkSpinner(text) {
|
|
57
|
-
return (
|
|
17
|
+
return ora({ text: chalk.dim(text), color: 'cyan' }).start();
|
|
58
18
|
}
|
|
59
|
-
async function runAnalyze(options = {}) {
|
|
60
|
-
|
|
19
|
+
export async function runAnalyze(options = {}) {
|
|
20
|
+
printHeader('Running Analysis');
|
|
61
21
|
let config;
|
|
62
22
|
try {
|
|
63
|
-
config =
|
|
64
|
-
|
|
23
|
+
config = loadConfig(options.config);
|
|
24
|
+
log.success('Config loaded', options.config ?? 'infrawise.yaml');
|
|
65
25
|
}
|
|
66
26
|
catch (err) {
|
|
67
|
-
console.error(
|
|
27
|
+
console.error(formatError(err));
|
|
68
28
|
process.exit(1);
|
|
69
29
|
}
|
|
70
30
|
const repoPath = options.repo ?? process.cwd();
|
|
@@ -78,137 +38,149 @@ async function runAnalyze(options = {}) {
|
|
|
78
38
|
if (config.dynamodb?.enabled === true) {
|
|
79
39
|
const s = mkSpinner('Extracting DynamoDB tables...');
|
|
80
40
|
try {
|
|
81
|
-
const result = await
|
|
41
|
+
const result = await extractDynamoMetadata(config);
|
|
82
42
|
dynamoMeta.push(...result);
|
|
83
|
-
s.succeed(
|
|
43
|
+
s.succeed(chalk.green('DynamoDB') + chalk.dim(` ${result.length} table(s)`));
|
|
84
44
|
}
|
|
85
45
|
catch (err) {
|
|
86
|
-
s.warn(
|
|
46
|
+
s.warn(chalk.yellow('DynamoDB skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
87
47
|
}
|
|
88
48
|
}
|
|
89
49
|
// ── PostgreSQL ──────────────────────────────────────────────────────────────
|
|
90
50
|
if (config.postgres?.enabled && config.postgres.connectionString) {
|
|
91
51
|
const s = mkSpinner('Extracting PostgreSQL schema...');
|
|
92
52
|
try {
|
|
93
|
-
const result = await
|
|
53
|
+
const result = await extractPostgresMetadata(config.postgres.connectionString);
|
|
94
54
|
postgresMeta.push(...result);
|
|
95
|
-
s.succeed(
|
|
55
|
+
s.succeed(chalk.green('PostgreSQL') + chalk.dim(` ${result.length} table(s)`));
|
|
96
56
|
}
|
|
97
57
|
catch (err) {
|
|
98
|
-
s.warn(
|
|
58
|
+
s.warn(chalk.yellow('PostgreSQL skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
99
59
|
}
|
|
100
60
|
}
|
|
101
61
|
// ── MySQL ───────────────────────────────────────────────────────────────────
|
|
102
62
|
if (config.mysql?.enabled && config.mysql.connectionString) {
|
|
103
63
|
const s = mkSpinner('Extracting MySQL schema...');
|
|
104
64
|
try {
|
|
105
|
-
const result = await
|
|
65
|
+
const result = await extractMySQLMetadata(config.mysql.connectionString);
|
|
106
66
|
mysqlMeta.push(...result);
|
|
107
|
-
s.succeed(
|
|
67
|
+
s.succeed(chalk.green('MySQL') + chalk.dim(` ${result.length} table(s)`));
|
|
108
68
|
}
|
|
109
69
|
catch (err) {
|
|
110
|
-
s.warn(
|
|
70
|
+
s.warn(chalk.yellow('MySQL skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
111
71
|
}
|
|
112
72
|
}
|
|
113
73
|
// ── MongoDB ─────────────────────────────────────────────────────────────────
|
|
114
74
|
if (config.mongodb?.enabled && config.mongodb.connectionString) {
|
|
115
75
|
const s = mkSpinner('Extracting MongoDB schema...');
|
|
116
76
|
try {
|
|
117
|
-
const result = await
|
|
77
|
+
const result = await extractMongoMetadata(config.mongodb.connectionString, config.mongodb.databases);
|
|
118
78
|
mongoMeta.push(...result);
|
|
119
|
-
s.succeed(
|
|
79
|
+
s.succeed(chalk.green('MongoDB') + chalk.dim(` ${result.length} collection(s)`));
|
|
120
80
|
}
|
|
121
81
|
catch (err) {
|
|
122
|
-
s.warn(
|
|
82
|
+
s.warn(chalk.yellow('MongoDB skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
123
83
|
}
|
|
124
84
|
}
|
|
125
85
|
// ── SQS ─────────────────────────────────────────────────────────────────────
|
|
126
86
|
if (config.sqs?.enabled === true) {
|
|
127
87
|
const s = mkSpinner('Extracting SQS queues...');
|
|
128
88
|
try {
|
|
129
|
-
const result = await
|
|
89
|
+
const result = await extractSQSMetadata(awsCfg);
|
|
130
90
|
servicesMeta.sqs = result;
|
|
131
|
-
s.succeed(
|
|
91
|
+
s.succeed(chalk.green('SQS') + chalk.dim(` ${result.length} queue(s)`));
|
|
132
92
|
}
|
|
133
93
|
catch (err) {
|
|
134
|
-
s.warn(
|
|
94
|
+
s.warn(chalk.yellow('SQS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
135
95
|
}
|
|
136
96
|
}
|
|
137
97
|
// ── SNS ─────────────────────────────────────────────────────────────────────
|
|
138
98
|
if (config.sns?.enabled === true) {
|
|
139
99
|
const s = mkSpinner('Extracting SNS topics...');
|
|
140
100
|
try {
|
|
141
|
-
const result = await
|
|
101
|
+
const result = await extractSNSMetadata(awsCfg);
|
|
142
102
|
servicesMeta.sns = result;
|
|
143
|
-
s.succeed(
|
|
103
|
+
s.succeed(chalk.green('SNS') + chalk.dim(` ${result.length} topic(s)`));
|
|
144
104
|
}
|
|
145
105
|
catch (err) {
|
|
146
|
-
s.warn(
|
|
106
|
+
s.warn(chalk.yellow('SNS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
147
107
|
}
|
|
148
108
|
}
|
|
149
109
|
// ── SSM Parameter Store ──────────────────────────────────────────────────────
|
|
150
110
|
if (config.ssm?.enabled === true) {
|
|
151
111
|
const s = mkSpinner('Extracting SSM parameters...');
|
|
152
112
|
try {
|
|
153
|
-
const result = await
|
|
113
|
+
const result = await extractSSMMetadata({ ...awsCfg, paths: config.ssm?.paths });
|
|
154
114
|
servicesMeta.ssm = result;
|
|
155
|
-
s.succeed(
|
|
115
|
+
s.succeed(chalk.green('SSM') + chalk.dim(` ${result.length} parameter(s) `) + chalk.dim('(metadata only, no values)'));
|
|
156
116
|
}
|
|
157
117
|
catch (err) {
|
|
158
|
-
s.warn(
|
|
118
|
+
s.warn(chalk.yellow('SSM skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
159
119
|
}
|
|
160
120
|
}
|
|
161
121
|
// ── Secrets Manager ──────────────────────────────────────────────────────────
|
|
162
122
|
if (config.secretsManager?.enabled === true) {
|
|
163
123
|
const s = mkSpinner('Extracting Secrets Manager metadata...');
|
|
164
124
|
try {
|
|
165
|
-
const result = await
|
|
125
|
+
const result = await extractSecretsMetadata(awsCfg);
|
|
166
126
|
servicesMeta.secrets = result;
|
|
167
|
-
s.succeed(
|
|
127
|
+
s.succeed(chalk.green('Secrets Manager') + chalk.dim(` ${result.length} secret(s) `) + chalk.dim('(names/rotation only, no values)'));
|
|
168
128
|
}
|
|
169
129
|
catch (err) {
|
|
170
|
-
s.warn(
|
|
130
|
+
s.warn(chalk.yellow('Secrets Manager skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
171
131
|
}
|
|
172
132
|
}
|
|
173
133
|
// ── Lambda ───────────────────────────────────────────────────────────────────
|
|
174
134
|
if (config.lambda?.enabled === true) {
|
|
175
135
|
const s = mkSpinner('Extracting Lambda functions...');
|
|
176
136
|
try {
|
|
177
|
-
const result = await (
|
|
137
|
+
const result = await extractLambdaMetadata(awsCfg, config.lambda?.includeFunctions);
|
|
178
138
|
servicesMeta.lambda = result;
|
|
179
|
-
s.succeed(
|
|
139
|
+
s.succeed(chalk.green('Lambda') + chalk.dim(` ${result.length} function(s)`));
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
s.warn(chalk.yellow('Lambda skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// ── EventBridge ──────────────────────────────────────────────────────────────
|
|
146
|
+
if (config.eventbridge?.enabled === true) {
|
|
147
|
+
const s = mkSpinner('Extracting EventBridge rules...');
|
|
148
|
+
try {
|
|
149
|
+
const result = await extractEventBridgeMetadata(awsCfg);
|
|
150
|
+
servicesMeta.eventbridge = result;
|
|
151
|
+
s.succeed(chalk.green('EventBridge') + chalk.dim(` ${result.length} rule(s)`));
|
|
180
152
|
}
|
|
181
153
|
catch (err) {
|
|
182
|
-
s.warn(
|
|
154
|
+
s.warn(chalk.yellow('EventBridge skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
183
155
|
}
|
|
184
156
|
}
|
|
185
157
|
// ── RDS ──────────────────────────────────────────────────────────────────────
|
|
186
158
|
if (config.rds?.enabled === true) {
|
|
187
159
|
const s = mkSpinner('Extracting RDS instances...');
|
|
188
160
|
try {
|
|
189
|
-
const result = await
|
|
161
|
+
const result = await extractRDSMetadata(awsCfg);
|
|
190
162
|
servicesMeta.rds = result;
|
|
191
|
-
s.succeed(
|
|
163
|
+
s.succeed(chalk.green('RDS') + chalk.dim(` ${result.length} instance(s)`));
|
|
192
164
|
}
|
|
193
165
|
catch (err) {
|
|
194
|
-
s.warn(
|
|
166
|
+
s.warn(chalk.yellow('RDS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
195
167
|
}
|
|
196
168
|
}
|
|
197
169
|
// ── CloudWatch Logs ──────────────────────────────────────────────────────────
|
|
198
170
|
if (config.cloudwatchLogs?.enabled) {
|
|
199
171
|
const s = mkSpinner('Sampling CloudWatch Logs (errors only, max 50 groups)...');
|
|
200
172
|
try {
|
|
201
|
-
const result = await
|
|
173
|
+
const result = await extractLogsMetadata({
|
|
202
174
|
...awsCfg,
|
|
203
175
|
logGroupPrefixes: config.cloudwatchLogs.logGroupPrefixes,
|
|
204
176
|
windowHours: config.cloudwatchLogs.windowHours,
|
|
205
177
|
});
|
|
206
178
|
servicesMeta.logs = result;
|
|
207
179
|
const errorGroups = result.filter((lg) => lg.errorCount > 0).length;
|
|
208
|
-
s.succeed(
|
|
180
|
+
s.succeed(chalk.green('CloudWatch Logs') + chalk.dim(` ${result.length} group(s), ${errorGroups} with errors`));
|
|
209
181
|
}
|
|
210
182
|
catch (err) {
|
|
211
|
-
s.warn(
|
|
183
|
+
s.warn(chalk.yellow('CloudWatch Logs skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
212
184
|
}
|
|
213
185
|
}
|
|
214
186
|
// ── IaC schema (Terraform / CloudFormation / CDK) ────────────────────────────
|
|
@@ -216,17 +188,17 @@ async function runAnalyze(options = {}) {
|
|
|
216
188
|
{
|
|
217
189
|
const s = mkSpinner('Extracting IaC schema (Terraform / CloudFormation / CDK)...');
|
|
218
190
|
try {
|
|
219
|
-
const iacSchema = await
|
|
191
|
+
const iacSchema = await extractIaCSchema(repoPath);
|
|
220
192
|
const total = iacSchema.dynamoTables.length + iacSchema.rdsInstances.length +
|
|
221
193
|
iacSchema.mongoClusters.length + iacSchema.queues.length + iacSchema.topics.length +
|
|
222
194
|
iacSchema.lambdas.length + iacSchema.buckets.length + iacSchema.parameters.length +
|
|
223
195
|
iacSchema.secrets.length + iacSchema.apiGateways.length;
|
|
224
|
-
iacDriftAnalyzer = new
|
|
196
|
+
iacDriftAnalyzer = new IaCDriftAnalyzer();
|
|
225
197
|
iacDriftAnalyzer.setIaCSchema(iacSchema);
|
|
226
|
-
s.succeed(
|
|
198
|
+
s.succeed(chalk.green('IaC schema') + chalk.dim(` ${total} resource(s) across TF/CFN/CDK`));
|
|
227
199
|
}
|
|
228
200
|
catch (err) {
|
|
229
|
-
s.warn(
|
|
201
|
+
s.warn(chalk.yellow('IaC scan skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
230
202
|
}
|
|
231
203
|
}
|
|
232
204
|
// ── Repository scan ──────────────────────────────────────────────────────────
|
|
@@ -234,11 +206,11 @@ async function runAnalyze(options = {}) {
|
|
|
234
206
|
{
|
|
235
207
|
const s = mkSpinner(`Scanning ${path.basename(repoPath)} for service usage...`);
|
|
236
208
|
try {
|
|
237
|
-
operations = await
|
|
238
|
-
s.succeed(
|
|
209
|
+
operations = await scanRepository(repoPath);
|
|
210
|
+
s.succeed(chalk.green('Repository scanned') + chalk.dim(` ${operations.length} service operation(s) found`));
|
|
239
211
|
}
|
|
240
212
|
catch (err) {
|
|
241
|
-
s.warn(
|
|
213
|
+
s.warn(chalk.yellow('Repository scan failed') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
242
214
|
operations = [];
|
|
243
215
|
}
|
|
244
216
|
}
|
|
@@ -246,8 +218,8 @@ async function runAnalyze(options = {}) {
|
|
|
246
218
|
let graph;
|
|
247
219
|
{
|
|
248
220
|
const s = mkSpinner('Building infrastructure graph...');
|
|
249
|
-
graph =
|
|
250
|
-
s.succeed(
|
|
221
|
+
graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
|
|
222
|
+
s.succeed(chalk.green('Graph built') + chalk.dim(` ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
|
|
251
223
|
}
|
|
252
224
|
// ── Run analyzers ────────────────────────────────────────────────────────────
|
|
253
225
|
let findings;
|
|
@@ -255,75 +227,76 @@ async function runAnalyze(options = {}) {
|
|
|
255
227
|
const s = mkSpinner('Running analyzers...');
|
|
256
228
|
const analyzers = [
|
|
257
229
|
...(config.dynamodb?.enabled === true ? [
|
|
258
|
-
new
|
|
259
|
-
new
|
|
260
|
-
new
|
|
230
|
+
new FullTableScanAnalyzer(),
|
|
231
|
+
new MissingGSIAnalyzer(),
|
|
232
|
+
new HotPartitionAnalyzer(),
|
|
261
233
|
] : []),
|
|
262
234
|
...(config.postgres?.enabled ? [
|
|
263
|
-
new
|
|
264
|
-
new
|
|
265
|
-
new
|
|
235
|
+
new MissingIndexAnalyzer(),
|
|
236
|
+
new NplusOneAnalyzer(),
|
|
237
|
+
new LargeSelectAnalyzer(),
|
|
266
238
|
] : []),
|
|
267
239
|
...(config.mysql?.enabled ? [
|
|
268
|
-
new
|
|
269
|
-
new
|
|
240
|
+
new MissingMySQLIndexAnalyzer(),
|
|
241
|
+
new MySQLFullTableScanAnalyzer(),
|
|
270
242
|
] : []),
|
|
271
243
|
...(config.mongodb?.enabled ? [
|
|
272
|
-
new
|
|
273
|
-
new
|
|
244
|
+
new MissingMongoIndexAnalyzer(),
|
|
245
|
+
new MongoCollectionScanAnalyzer(),
|
|
274
246
|
] : []),
|
|
275
247
|
...(config.sqs?.enabled === true ? [
|
|
276
|
-
new
|
|
277
|
-
new
|
|
278
|
-
new
|
|
248
|
+
new MissingDLQAnalyzer(),
|
|
249
|
+
new UnencryptedQueueAnalyzer(),
|
|
250
|
+
new LargeQueueBacklogAnalyzer(),
|
|
279
251
|
] : []),
|
|
280
252
|
...(config.secretsManager?.enabled === true ? [
|
|
281
|
-
new
|
|
253
|
+
new MissingSecretRotationAnalyzer(),
|
|
282
254
|
] : []),
|
|
283
255
|
...(config.cloudwatchLogs?.enabled ? [
|
|
284
|
-
new
|
|
256
|
+
new MissingLogRetentionAnalyzer(),
|
|
285
257
|
] : []),
|
|
286
258
|
...(config.lambda?.enabled === true ? [
|
|
287
|
-
new
|
|
288
|
-
new
|
|
259
|
+
new LambdaDefaultMemoryAnalyzer(),
|
|
260
|
+
new LambdaHighTimeoutAnalyzer(),
|
|
261
|
+
new LambdaMissingTriggerDLQAnalyzer(),
|
|
289
262
|
] : []),
|
|
290
263
|
...(config.rds?.enabled === true ? [
|
|
291
|
-
new
|
|
292
|
-
new
|
|
293
|
-
new
|
|
294
|
-
new
|
|
295
|
-
new
|
|
264
|
+
new RDSPubliclyAccessibleAnalyzer(),
|
|
265
|
+
new RDSNoBackupAnalyzer(),
|
|
266
|
+
new RDSUnencryptedAnalyzer(),
|
|
267
|
+
new RDSNoDeletionProtectionAnalyzer(),
|
|
268
|
+
new RDSNoMultiAZAnalyzer(),
|
|
296
269
|
] : []),
|
|
297
270
|
...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
|
|
298
271
|
];
|
|
299
|
-
findings = await
|
|
300
|
-
s.succeed(
|
|
272
|
+
findings = await runAllAnalyzers(graph, analyzers);
|
|
273
|
+
s.succeed(chalk.green('Analysis complete') + chalk.dim(` ${findings.length} finding(s)`));
|
|
301
274
|
}
|
|
302
275
|
// ── Cache ─────────────────────────────────────────────────────────────────────
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
276
|
+
writeCache('graph', graph);
|
|
277
|
+
writeCache('findings', findings);
|
|
278
|
+
writeCache('operations', operations);
|
|
279
|
+
writeCache('meta', { dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta });
|
|
307
280
|
// ── Output ────────────────────────────────────────────────────────────────────
|
|
308
281
|
console.log('');
|
|
309
282
|
if (findings.length === 0) {
|
|
310
|
-
console.log(` ${
|
|
283
|
+
console.log(` ${chalk.green.bold('✓ No issues found!')} ${chalk.dim('Your infrastructure looks clean.')}`);
|
|
311
284
|
}
|
|
312
285
|
else {
|
|
313
|
-
console.log(
|
|
314
|
-
findings.forEach((f, i) =>
|
|
315
|
-
|
|
286
|
+
console.log(chalk.bold(` Findings`) + chalk.dim(` ${findings.length} total`));
|
|
287
|
+
findings.forEach((f, i) => printFinding(f, i));
|
|
288
|
+
printSummaryBox(findings);
|
|
316
289
|
if (findings.some((f) => f.severity === 'high')) {
|
|
317
|
-
console.log(`\n ${
|
|
290
|
+
console.log(`\n ${chalk.red.bold('Action required:')} ${chalk.red('High severity issues detected.')}`);
|
|
318
291
|
}
|
|
319
292
|
}
|
|
320
293
|
console.log('');
|
|
321
|
-
|
|
322
|
-
|
|
294
|
+
log.dim(`Results cached in .infrawise/cache/`);
|
|
295
|
+
log.info(`Run ${chalk.cyan('infrawise dev')} to explore via the MCP server`);
|
|
323
296
|
console.log('');
|
|
324
297
|
}
|
|
325
|
-
async function runCodeRefresh(repoPath, config) {
|
|
326
|
-
const cached =
|
|
298
|
+
export async function runCodeRefresh(repoPath, config) {
|
|
299
|
+
const cached = readCache('meta', Infinity);
|
|
327
300
|
const dynamoMeta = cached?.dynamoMeta ?? [];
|
|
328
301
|
const postgresMeta = cached?.postgresMeta ?? [];
|
|
329
302
|
const mysqlMeta = cached?.mysqlMeta ?? [];
|
|
@@ -332,8 +305,8 @@ async function runCodeRefresh(repoPath, config) {
|
|
|
332
305
|
// Re-run IaC schema (pure file scan, no AWS calls)
|
|
333
306
|
let iacDriftAnalyzer;
|
|
334
307
|
try {
|
|
335
|
-
const iacSchema = await
|
|
336
|
-
iacDriftAnalyzer = new
|
|
308
|
+
const iacSchema = await extractIaCSchema(repoPath);
|
|
309
|
+
iacDriftAnalyzer = new IaCDriftAnalyzer();
|
|
337
310
|
iacDriftAnalyzer.setIaCSchema(iacSchema);
|
|
338
311
|
}
|
|
339
312
|
catch {
|
|
@@ -342,42 +315,42 @@ async function runCodeRefresh(repoPath, config) {
|
|
|
342
315
|
// Re-run repo scan
|
|
343
316
|
let operations;
|
|
344
317
|
try {
|
|
345
|
-
operations = await
|
|
318
|
+
operations = await scanRepository(repoPath);
|
|
346
319
|
}
|
|
347
320
|
catch {
|
|
348
321
|
operations = [];
|
|
349
322
|
}
|
|
350
|
-
const graph =
|
|
323
|
+
const graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
|
|
351
324
|
const analyzers = [
|
|
352
325
|
...(config.dynamodb?.enabled === true ? [
|
|
353
|
-
new
|
|
326
|
+
new FullTableScanAnalyzer(), new MissingGSIAnalyzer(), new HotPartitionAnalyzer(),
|
|
354
327
|
] : []),
|
|
355
328
|
...(config.postgres?.enabled ? [
|
|
356
|
-
new
|
|
329
|
+
new MissingIndexAnalyzer(), new NplusOneAnalyzer(), new LargeSelectAnalyzer(),
|
|
357
330
|
] : []),
|
|
358
331
|
...(config.mysql?.enabled ? [
|
|
359
|
-
new
|
|
332
|
+
new MissingMySQLIndexAnalyzer(), new MySQLFullTableScanAnalyzer(),
|
|
360
333
|
] : []),
|
|
361
334
|
...(config.mongodb?.enabled ? [
|
|
362
|
-
new
|
|
335
|
+
new MissingMongoIndexAnalyzer(), new MongoCollectionScanAnalyzer(),
|
|
363
336
|
] : []),
|
|
364
337
|
...(config.sqs?.enabled === true ? [
|
|
365
|
-
new
|
|
338
|
+
new MissingDLQAnalyzer(), new UnencryptedQueueAnalyzer(), new LargeQueueBacklogAnalyzer(),
|
|
366
339
|
] : []),
|
|
367
|
-
...(config.secretsManager?.enabled === true ? [new
|
|
368
|
-
...(config.cloudwatchLogs?.enabled ? [new
|
|
340
|
+
...(config.secretsManager?.enabled === true ? [new MissingSecretRotationAnalyzer()] : []),
|
|
341
|
+
...(config.cloudwatchLogs?.enabled ? [new MissingLogRetentionAnalyzer()] : []),
|
|
369
342
|
...(config.lambda?.enabled === true ? [
|
|
370
|
-
new
|
|
343
|
+
new LambdaDefaultMemoryAnalyzer(), new LambdaHighTimeoutAnalyzer(),
|
|
371
344
|
] : []),
|
|
372
345
|
...(config.rds?.enabled === true ? [
|
|
373
|
-
new
|
|
374
|
-
new
|
|
346
|
+
new RDSPubliclyAccessibleAnalyzer(), new RDSNoBackupAnalyzer(), new RDSUnencryptedAnalyzer(),
|
|
347
|
+
new RDSNoDeletionProtectionAnalyzer(), new RDSNoMultiAZAnalyzer(),
|
|
375
348
|
] : []),
|
|
376
349
|
...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
|
|
377
350
|
];
|
|
378
|
-
const findings = await
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
351
|
+
const findings = await runAllAnalyzers(graph, analyzers);
|
|
352
|
+
writeCache('graph', graph);
|
|
353
|
+
writeCache('findings', findings);
|
|
354
|
+
writeCache('operations', operations);
|
|
382
355
|
return { graph, findings };
|
|
383
356
|
}
|