infrawise 0.5.0 → 0.6.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 +5 -11
- package/dist/adapters/aws.js +50 -64
- package/dist/adapters/dynamodb.js +17 -22
- package/dist/adapters/logs.js +12 -16
- 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 +7 -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 +118 -158
- package/dist/cli/commands/auth.js +24 -30
- package/dist/cli/commands/dev.js +45 -84
- package/dist/cli/commands/doctor.js +35 -74
- package/dist/cli/commands/init.js +33 -72
- 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 +43 -82
- 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 +15 -32
- package/dist/server/index.js +84 -89
- package/dist/types.js +1 -2
- package/package.json +31 -30
|
@@ -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, 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, 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,137 @@ 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);
|
|
178
138
|
servicesMeta.lambda = result;
|
|
179
|
-
s.succeed(
|
|
139
|
+
s.succeed(chalk.green('Lambda') + chalk.dim(` ${result.length} function(s)`));
|
|
180
140
|
}
|
|
181
141
|
catch (err) {
|
|
182
|
-
s.warn(
|
|
142
|
+
s.warn(chalk.yellow('Lambda skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
183
143
|
}
|
|
184
144
|
}
|
|
185
145
|
// ── RDS ──────────────────────────────────────────────────────────────────────
|
|
186
146
|
if (config.rds?.enabled === true) {
|
|
187
147
|
const s = mkSpinner('Extracting RDS instances...');
|
|
188
148
|
try {
|
|
189
|
-
const result = await
|
|
149
|
+
const result = await extractRDSMetadata(awsCfg);
|
|
190
150
|
servicesMeta.rds = result;
|
|
191
|
-
s.succeed(
|
|
151
|
+
s.succeed(chalk.green('RDS') + chalk.dim(` ${result.length} instance(s)`));
|
|
192
152
|
}
|
|
193
153
|
catch (err) {
|
|
194
|
-
s.warn(
|
|
154
|
+
s.warn(chalk.yellow('RDS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
195
155
|
}
|
|
196
156
|
}
|
|
197
157
|
// ── CloudWatch Logs ──────────────────────────────────────────────────────────
|
|
198
158
|
if (config.cloudwatchLogs?.enabled) {
|
|
199
159
|
const s = mkSpinner('Sampling CloudWatch Logs (errors only, max 50 groups)...');
|
|
200
160
|
try {
|
|
201
|
-
const result = await
|
|
161
|
+
const result = await extractLogsMetadata({
|
|
202
162
|
...awsCfg,
|
|
203
163
|
logGroupPrefixes: config.cloudwatchLogs.logGroupPrefixes,
|
|
204
164
|
windowHours: config.cloudwatchLogs.windowHours,
|
|
205
165
|
});
|
|
206
166
|
servicesMeta.logs = result;
|
|
207
167
|
const errorGroups = result.filter((lg) => lg.errorCount > 0).length;
|
|
208
|
-
s.succeed(
|
|
168
|
+
s.succeed(chalk.green('CloudWatch Logs') + chalk.dim(` ${result.length} group(s), ${errorGroups} with errors`));
|
|
209
169
|
}
|
|
210
170
|
catch (err) {
|
|
211
|
-
s.warn(
|
|
171
|
+
s.warn(chalk.yellow('CloudWatch Logs skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
212
172
|
}
|
|
213
173
|
}
|
|
214
174
|
// ── IaC schema (Terraform / CloudFormation / CDK) ────────────────────────────
|
|
@@ -216,17 +176,17 @@ async function runAnalyze(options = {}) {
|
|
|
216
176
|
{
|
|
217
177
|
const s = mkSpinner('Extracting IaC schema (Terraform / CloudFormation / CDK)...');
|
|
218
178
|
try {
|
|
219
|
-
const iacSchema = await
|
|
179
|
+
const iacSchema = await extractIaCSchema(repoPath);
|
|
220
180
|
const total = iacSchema.dynamoTables.length + iacSchema.rdsInstances.length +
|
|
221
181
|
iacSchema.mongoClusters.length + iacSchema.queues.length + iacSchema.topics.length +
|
|
222
182
|
iacSchema.lambdas.length + iacSchema.buckets.length + iacSchema.parameters.length +
|
|
223
183
|
iacSchema.secrets.length + iacSchema.apiGateways.length;
|
|
224
|
-
iacDriftAnalyzer = new
|
|
184
|
+
iacDriftAnalyzer = new IaCDriftAnalyzer();
|
|
225
185
|
iacDriftAnalyzer.setIaCSchema(iacSchema);
|
|
226
|
-
s.succeed(
|
|
186
|
+
s.succeed(chalk.green('IaC schema') + chalk.dim(` ${total} resource(s) across TF/CFN/CDK`));
|
|
227
187
|
}
|
|
228
188
|
catch (err) {
|
|
229
|
-
s.warn(
|
|
189
|
+
s.warn(chalk.yellow('IaC scan skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
230
190
|
}
|
|
231
191
|
}
|
|
232
192
|
// ── Repository scan ──────────────────────────────────────────────────────────
|
|
@@ -234,11 +194,11 @@ async function runAnalyze(options = {}) {
|
|
|
234
194
|
{
|
|
235
195
|
const s = mkSpinner(`Scanning ${path.basename(repoPath)} for service usage...`);
|
|
236
196
|
try {
|
|
237
|
-
operations = await
|
|
238
|
-
s.succeed(
|
|
197
|
+
operations = await scanRepository(repoPath);
|
|
198
|
+
s.succeed(chalk.green('Repository scanned') + chalk.dim(` ${operations.length} service operation(s) found`));
|
|
239
199
|
}
|
|
240
200
|
catch (err) {
|
|
241
|
-
s.warn(
|
|
201
|
+
s.warn(chalk.yellow('Repository scan failed') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
242
202
|
operations = [];
|
|
243
203
|
}
|
|
244
204
|
}
|
|
@@ -246,8 +206,8 @@ async function runAnalyze(options = {}) {
|
|
|
246
206
|
let graph;
|
|
247
207
|
{
|
|
248
208
|
const s = mkSpinner('Building infrastructure graph...');
|
|
249
|
-
graph =
|
|
250
|
-
s.succeed(
|
|
209
|
+
graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
|
|
210
|
+
s.succeed(chalk.green('Graph built') + chalk.dim(` ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
|
|
251
211
|
}
|
|
252
212
|
// ── Run analyzers ────────────────────────────────────────────────────────────
|
|
253
213
|
let findings;
|
|
@@ -255,75 +215,75 @@ async function runAnalyze(options = {}) {
|
|
|
255
215
|
const s = mkSpinner('Running analyzers...');
|
|
256
216
|
const analyzers = [
|
|
257
217
|
...(config.dynamodb?.enabled === true ? [
|
|
258
|
-
new
|
|
259
|
-
new
|
|
260
|
-
new
|
|
218
|
+
new FullTableScanAnalyzer(),
|
|
219
|
+
new MissingGSIAnalyzer(),
|
|
220
|
+
new HotPartitionAnalyzer(),
|
|
261
221
|
] : []),
|
|
262
222
|
...(config.postgres?.enabled ? [
|
|
263
|
-
new
|
|
264
|
-
new
|
|
265
|
-
new
|
|
223
|
+
new MissingIndexAnalyzer(),
|
|
224
|
+
new NplusOneAnalyzer(),
|
|
225
|
+
new LargeSelectAnalyzer(),
|
|
266
226
|
] : []),
|
|
267
227
|
...(config.mysql?.enabled ? [
|
|
268
|
-
new
|
|
269
|
-
new
|
|
228
|
+
new MissingMySQLIndexAnalyzer(),
|
|
229
|
+
new MySQLFullTableScanAnalyzer(),
|
|
270
230
|
] : []),
|
|
271
231
|
...(config.mongodb?.enabled ? [
|
|
272
|
-
new
|
|
273
|
-
new
|
|
232
|
+
new MissingMongoIndexAnalyzer(),
|
|
233
|
+
new MongoCollectionScanAnalyzer(),
|
|
274
234
|
] : []),
|
|
275
235
|
...(config.sqs?.enabled === true ? [
|
|
276
|
-
new
|
|
277
|
-
new
|
|
278
|
-
new
|
|
236
|
+
new MissingDLQAnalyzer(),
|
|
237
|
+
new UnencryptedQueueAnalyzer(),
|
|
238
|
+
new LargeQueueBacklogAnalyzer(),
|
|
279
239
|
] : []),
|
|
280
240
|
...(config.secretsManager?.enabled === true ? [
|
|
281
|
-
new
|
|
241
|
+
new MissingSecretRotationAnalyzer(),
|
|
282
242
|
] : []),
|
|
283
243
|
...(config.cloudwatchLogs?.enabled ? [
|
|
284
|
-
new
|
|
244
|
+
new MissingLogRetentionAnalyzer(),
|
|
285
245
|
] : []),
|
|
286
246
|
...(config.lambda?.enabled === true ? [
|
|
287
|
-
new
|
|
288
|
-
new
|
|
247
|
+
new LambdaDefaultMemoryAnalyzer(),
|
|
248
|
+
new LambdaHighTimeoutAnalyzer(),
|
|
289
249
|
] : []),
|
|
290
250
|
...(config.rds?.enabled === true ? [
|
|
291
|
-
new
|
|
292
|
-
new
|
|
293
|
-
new
|
|
294
|
-
new
|
|
295
|
-
new
|
|
251
|
+
new RDSPubliclyAccessibleAnalyzer(),
|
|
252
|
+
new RDSNoBackupAnalyzer(),
|
|
253
|
+
new RDSUnencryptedAnalyzer(),
|
|
254
|
+
new RDSNoDeletionProtectionAnalyzer(),
|
|
255
|
+
new RDSNoMultiAZAnalyzer(),
|
|
296
256
|
] : []),
|
|
297
257
|
...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
|
|
298
258
|
];
|
|
299
|
-
findings = await
|
|
300
|
-
s.succeed(
|
|
259
|
+
findings = await runAllAnalyzers(graph, analyzers);
|
|
260
|
+
s.succeed(chalk.green('Analysis complete') + chalk.dim(` ${findings.length} finding(s)`));
|
|
301
261
|
}
|
|
302
262
|
// ── Cache ─────────────────────────────────────────────────────────────────────
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
263
|
+
writeCache('graph', graph);
|
|
264
|
+
writeCache('findings', findings);
|
|
265
|
+
writeCache('operations', operations);
|
|
266
|
+
writeCache('meta', { dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta });
|
|
307
267
|
// ── Output ────────────────────────────────────────────────────────────────────
|
|
308
268
|
console.log('');
|
|
309
269
|
if (findings.length === 0) {
|
|
310
|
-
console.log(` ${
|
|
270
|
+
console.log(` ${chalk.green.bold('✓ No issues found!')} ${chalk.dim('Your infrastructure looks clean.')}`);
|
|
311
271
|
}
|
|
312
272
|
else {
|
|
313
|
-
console.log(
|
|
314
|
-
findings.forEach((f, i) =>
|
|
315
|
-
|
|
273
|
+
console.log(chalk.bold(` Findings`) + chalk.dim(` ${findings.length} total`));
|
|
274
|
+
findings.forEach((f, i) => printFinding(f, i));
|
|
275
|
+
printSummaryBox(findings);
|
|
316
276
|
if (findings.some((f) => f.severity === 'high')) {
|
|
317
|
-
console.log(`\n ${
|
|
277
|
+
console.log(`\n ${chalk.red.bold('Action required:')} ${chalk.red('High severity issues detected.')}`);
|
|
318
278
|
}
|
|
319
279
|
}
|
|
320
280
|
console.log('');
|
|
321
|
-
|
|
322
|
-
|
|
281
|
+
log.dim(`Results cached in .infrawise/cache/`);
|
|
282
|
+
log.info(`Run ${chalk.cyan('infrawise dev')} to explore via the MCP server`);
|
|
323
283
|
console.log('');
|
|
324
284
|
}
|
|
325
|
-
async function runCodeRefresh(repoPath, config) {
|
|
326
|
-
const cached =
|
|
285
|
+
export async function runCodeRefresh(repoPath, config) {
|
|
286
|
+
const cached = readCache('meta', Infinity);
|
|
327
287
|
const dynamoMeta = cached?.dynamoMeta ?? [];
|
|
328
288
|
const postgresMeta = cached?.postgresMeta ?? [];
|
|
329
289
|
const mysqlMeta = cached?.mysqlMeta ?? [];
|
|
@@ -332,8 +292,8 @@ async function runCodeRefresh(repoPath, config) {
|
|
|
332
292
|
// Re-run IaC schema (pure file scan, no AWS calls)
|
|
333
293
|
let iacDriftAnalyzer;
|
|
334
294
|
try {
|
|
335
|
-
const iacSchema = await
|
|
336
|
-
iacDriftAnalyzer = new
|
|
295
|
+
const iacSchema = await extractIaCSchema(repoPath);
|
|
296
|
+
iacDriftAnalyzer = new IaCDriftAnalyzer();
|
|
337
297
|
iacDriftAnalyzer.setIaCSchema(iacSchema);
|
|
338
298
|
}
|
|
339
299
|
catch {
|
|
@@ -342,42 +302,42 @@ async function runCodeRefresh(repoPath, config) {
|
|
|
342
302
|
// Re-run repo scan
|
|
343
303
|
let operations;
|
|
344
304
|
try {
|
|
345
|
-
operations = await
|
|
305
|
+
operations = await scanRepository(repoPath);
|
|
346
306
|
}
|
|
347
307
|
catch {
|
|
348
308
|
operations = [];
|
|
349
309
|
}
|
|
350
|
-
const graph =
|
|
310
|
+
const graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
|
|
351
311
|
const analyzers = [
|
|
352
312
|
...(config.dynamodb?.enabled === true ? [
|
|
353
|
-
new
|
|
313
|
+
new FullTableScanAnalyzer(), new MissingGSIAnalyzer(), new HotPartitionAnalyzer(),
|
|
354
314
|
] : []),
|
|
355
315
|
...(config.postgres?.enabled ? [
|
|
356
|
-
new
|
|
316
|
+
new MissingIndexAnalyzer(), new NplusOneAnalyzer(), new LargeSelectAnalyzer(),
|
|
357
317
|
] : []),
|
|
358
318
|
...(config.mysql?.enabled ? [
|
|
359
|
-
new
|
|
319
|
+
new MissingMySQLIndexAnalyzer(), new MySQLFullTableScanAnalyzer(),
|
|
360
320
|
] : []),
|
|
361
321
|
...(config.mongodb?.enabled ? [
|
|
362
|
-
new
|
|
322
|
+
new MissingMongoIndexAnalyzer(), new MongoCollectionScanAnalyzer(),
|
|
363
323
|
] : []),
|
|
364
324
|
...(config.sqs?.enabled === true ? [
|
|
365
|
-
new
|
|
325
|
+
new MissingDLQAnalyzer(), new UnencryptedQueueAnalyzer(), new LargeQueueBacklogAnalyzer(),
|
|
366
326
|
] : []),
|
|
367
|
-
...(config.secretsManager?.enabled === true ? [new
|
|
368
|
-
...(config.cloudwatchLogs?.enabled ? [new
|
|
327
|
+
...(config.secretsManager?.enabled === true ? [new MissingSecretRotationAnalyzer()] : []),
|
|
328
|
+
...(config.cloudwatchLogs?.enabled ? [new MissingLogRetentionAnalyzer()] : []),
|
|
369
329
|
...(config.lambda?.enabled === true ? [
|
|
370
|
-
new
|
|
330
|
+
new LambdaDefaultMemoryAnalyzer(), new LambdaHighTimeoutAnalyzer(),
|
|
371
331
|
] : []),
|
|
372
332
|
...(config.rds?.enabled === true ? [
|
|
373
|
-
new
|
|
374
|
-
new
|
|
333
|
+
new RDSPubliclyAccessibleAnalyzer(), new RDSNoBackupAnalyzer(), new RDSUnencryptedAnalyzer(),
|
|
334
|
+
new RDSNoDeletionProtectionAnalyzer(), new RDSNoMultiAZAnalyzer(),
|
|
375
335
|
] : []),
|
|
376
336
|
...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
|
|
377
337
|
];
|
|
378
|
-
const findings = await
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
338
|
+
const findings = await runAllAnalyzers(graph, analyzers);
|
|
339
|
+
writeCache('graph', graph);
|
|
340
|
+
writeCache('findings', findings);
|
|
341
|
+
writeCache('operations', operations);
|
|
382
342
|
return { graph, findings };
|
|
383
343
|
}
|
|
@@ -1,28 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const ora_1 = __importDefault(require("ora"));
|
|
10
|
-
const utils_1 = require("../utils");
|
|
11
|
-
const dynamodb_1 = require("../../adapters/dynamodb");
|
|
12
|
-
async function runAuth() {
|
|
13
|
-
(0, utils_1.printHeader)('AWS Authentication');
|
|
14
|
-
const profiles = (0, utils_1.readAWSProfiles)();
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { readAWSProfiles, log, printHeader } from '../utils.js';
|
|
5
|
+
import { validateDynamoAccess } from '../../adapters/dynamodb.js';
|
|
6
|
+
export async function runAuth() {
|
|
7
|
+
printHeader('AWS Authentication');
|
|
8
|
+
const profiles = readAWSProfiles();
|
|
15
9
|
if (profiles.length === 0) {
|
|
16
|
-
|
|
10
|
+
log.fail('No AWS profiles found');
|
|
17
11
|
console.log('');
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
log.info('Run ' + chalk.cyan('aws configure') + ' to set up credentials');
|
|
13
|
+
log.info('Or manually edit ' + chalk.dim('~/.aws/credentials'));
|
|
20
14
|
console.log('');
|
|
21
15
|
return;
|
|
22
16
|
}
|
|
23
|
-
|
|
17
|
+
log.success(`Found ${profiles.length} profile(s)`);
|
|
24
18
|
console.log('');
|
|
25
|
-
const { selectedProfile } = await
|
|
19
|
+
const { selectedProfile } = await inquirer.prompt([
|
|
26
20
|
{
|
|
27
21
|
type: 'list',
|
|
28
22
|
name: 'selectedProfile',
|
|
@@ -31,27 +25,27 @@ async function runAuth() {
|
|
|
31
25
|
},
|
|
32
26
|
]);
|
|
33
27
|
console.log('');
|
|
34
|
-
const spin = (
|
|
28
|
+
const spin = ora({ text: chalk.dim(`Validating "${selectedProfile}"...`), color: 'cyan' }).start();
|
|
35
29
|
const testConfig = {
|
|
36
30
|
project: 'auth-test',
|
|
37
31
|
aws: { profile: selectedProfile, region: 'us-east-1' },
|
|
38
32
|
};
|
|
39
|
-
const isValid = await
|
|
33
|
+
const isValid = await validateDynamoAccess(testConfig);
|
|
40
34
|
if (isValid) {
|
|
41
|
-
spin.succeed(
|
|
35
|
+
spin.succeed(chalk.green(`Profile "${chalk.bold(selectedProfile)}" is valid`));
|
|
42
36
|
console.log('');
|
|
43
|
-
console.log(
|
|
44
|
-
console.log(
|
|
37
|
+
console.log(chalk.dim(' Update your infrawise.yaml:'));
|
|
38
|
+
console.log(chalk.cyan(` aws:\n profile: ${selectedProfile}`));
|
|
45
39
|
}
|
|
46
40
|
else {
|
|
47
|
-
spin.fail(
|
|
41
|
+
spin.fail(chalk.red(`Profile "${chalk.bold(selectedProfile)}" cannot access DynamoDB`));
|
|
48
42
|
console.log('');
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
log.warn('Possible causes:');
|
|
44
|
+
log.dim('Missing IAM permissions — need dynamodb:ListTables, dynamodb:DescribeTable');
|
|
45
|
+
log.dim('Expired SSO — run: aws sso login');
|
|
46
|
+
log.dim('Wrong region — check your AWS config');
|
|
53
47
|
console.log('');
|
|
54
|
-
|
|
48
|
+
log.info(`Run ${chalk.cyan('infrawise doctor')} for a full diagnostic`);
|
|
55
49
|
}
|
|
56
50
|
console.log('');
|
|
57
51
|
}
|