aws-security-mcp 0.6.0 → 0.6.1
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/dist/bin/aws-security-mcp.js +62 -475
- package/dist/bin/aws-security-mcp.js.map +1 -1
- package/dist/src/index.js +62 -475
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -237,7 +237,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
237
237
|
import { z } from "zod";
|
|
238
238
|
|
|
239
239
|
// src/version.ts
|
|
240
|
-
var VERSION = "0.6.
|
|
240
|
+
var VERSION = "0.6.1";
|
|
241
241
|
|
|
242
242
|
// src/utils/aws-client.ts
|
|
243
243
|
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
|
|
@@ -3046,126 +3046,37 @@ var SecurityHubFindingsScanner = class {
|
|
|
3046
3046
|
// src/scanners/guardduty-findings.ts
|
|
3047
3047
|
import {
|
|
3048
3048
|
GuardDutyClient as GuardDutyClient2,
|
|
3049
|
-
ListDetectorsCommand as ListDetectorsCommand2
|
|
3050
|
-
ListFindingsCommand,
|
|
3051
|
-
GetFindingsCommand as GetFindingsCommand2
|
|
3049
|
+
ListDetectorsCommand as ListDetectorsCommand2
|
|
3052
3050
|
} from "@aws-sdk/client-guardduty";
|
|
3053
|
-
function gdSeverityToScore(severity) {
|
|
3054
|
-
if (severity >= 7) return 8;
|
|
3055
|
-
if (severity >= 4) return 5.5;
|
|
3056
|
-
return 3;
|
|
3057
|
-
}
|
|
3058
3051
|
var GuardDutyFindingsScanner = class {
|
|
3059
3052
|
moduleName = "guardduty_findings";
|
|
3060
3053
|
async scan(ctx) {
|
|
3061
|
-
const { region
|
|
3054
|
+
const { region } = ctx;
|
|
3062
3055
|
const startMs = Date.now();
|
|
3063
|
-
const findings = [];
|
|
3064
3056
|
const warnings = [];
|
|
3065
|
-
let resourcesScanned = 0;
|
|
3066
3057
|
try {
|
|
3067
3058
|
const client = createClient(GuardDutyClient2, region, ctx.credentials);
|
|
3068
|
-
const
|
|
3069
|
-
const detectorIds =
|
|
3059
|
+
const resp = await client.send(new ListDetectorsCommand2({}));
|
|
3060
|
+
const detectorIds = resp.DetectorIds ?? [];
|
|
3070
3061
|
if (detectorIds.length === 0) {
|
|
3071
3062
|
warnings.push("GuardDuty is not enabled in this region (no detectors found).");
|
|
3072
|
-
return {
|
|
3073
|
-
module: this.moduleName,
|
|
3074
|
-
status: "success",
|
|
3075
|
-
warnings,
|
|
3076
|
-
resourcesScanned: 0,
|
|
3077
|
-
findingsCount: 0,
|
|
3078
|
-
scanTimeMs: Date.now() - startMs,
|
|
3079
|
-
findings: []
|
|
3080
|
-
};
|
|
3081
|
-
}
|
|
3082
|
-
const detectorId = detectorIds[0];
|
|
3083
|
-
let nextToken;
|
|
3084
|
-
const findingIds = [];
|
|
3085
|
-
do {
|
|
3086
|
-
const listResp = await client.send(
|
|
3087
|
-
new ListFindingsCommand({
|
|
3088
|
-
DetectorId: detectorId,
|
|
3089
|
-
FindingCriteria: {
|
|
3090
|
-
Criterion: {
|
|
3091
|
-
"service.archived": {
|
|
3092
|
-
Eq: ["false"]
|
|
3093
|
-
}
|
|
3094
|
-
}
|
|
3095
|
-
},
|
|
3096
|
-
MaxResults: 50,
|
|
3097
|
-
NextToken: nextToken
|
|
3098
|
-
})
|
|
3099
|
-
);
|
|
3100
|
-
findingIds.push(...listResp.FindingIds ?? []);
|
|
3101
|
-
nextToken = listResp.NextToken;
|
|
3102
|
-
} while (nextToken);
|
|
3103
|
-
resourcesScanned = findingIds.length;
|
|
3104
|
-
if (findingIds.length === 0) {
|
|
3105
|
-
return {
|
|
3106
|
-
module: this.moduleName,
|
|
3107
|
-
status: "success",
|
|
3108
|
-
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3109
|
-
resourcesScanned: 0,
|
|
3110
|
-
findingsCount: 0,
|
|
3111
|
-
scanTimeMs: Date.now() - startMs,
|
|
3112
|
-
findings: []
|
|
3113
|
-
};
|
|
3114
|
-
}
|
|
3115
|
-
for (let i = 0; i < findingIds.length; i += 50) {
|
|
3116
|
-
const batch = findingIds.slice(i, i + 50);
|
|
3117
|
-
const detailsResp = await client.send(
|
|
3118
|
-
new GetFindingsCommand2({
|
|
3119
|
-
DetectorId: detectorId,
|
|
3120
|
-
FindingIds: batch
|
|
3121
|
-
})
|
|
3122
|
-
);
|
|
3123
|
-
for (const gdf of detailsResp.Findings ?? []) {
|
|
3124
|
-
const gdSeverity = gdf.Severity ?? 0;
|
|
3125
|
-
const score = gdSeverityToScore(gdSeverity);
|
|
3126
|
-
const severity = severityFromScore(score);
|
|
3127
|
-
const resourceType = gdf.Resource?.ResourceType ?? "AWS::Unknown";
|
|
3128
|
-
const resourceId = gdf.Resource?.InstanceDetails?.InstanceId ?? gdf.Resource?.AccessKeyDetails?.AccessKeyId ?? gdf.Arn ?? "unknown";
|
|
3129
|
-
const resourceArn = gdf.Arn ?? `arn:${partition}:guardduty:${region}:${accountId}:detector/${detectorId}/finding/${gdf.Id ?? "unknown"}`;
|
|
3130
|
-
findings.push({
|
|
3131
|
-
severity,
|
|
3132
|
-
title: `[GuardDuty] ${gdf.Title ?? gdf.Type ?? "Finding"}`,
|
|
3133
|
-
resourceType,
|
|
3134
|
-
resourceId,
|
|
3135
|
-
resourceArn,
|
|
3136
|
-
region: gdf.Region ?? region,
|
|
3137
|
-
description: gdf.Description ?? gdf.Title ?? "No description",
|
|
3138
|
-
impact: `GuardDuty threat type: ${gdf.Type ?? "unknown"} (severity ${gdSeverity})`,
|
|
3139
|
-
riskScore: score,
|
|
3140
|
-
remediationSteps: [
|
|
3141
|
-
`Investigate ${gdf.Type ?? "unknown threat"}: ${gdf.Title ?? "threat detected"}`,
|
|
3142
|
-
gdf.Description ? `Details: ${gdf.Description.substring(0, 200)}` : "",
|
|
3143
|
-
"Isolate affected resources if compromise is confirmed.",
|
|
3144
|
-
"Review CloudTrail logs for related suspicious activity."
|
|
3145
|
-
].filter(Boolean),
|
|
3146
|
-
priority: priorityFromSeverity(severity),
|
|
3147
|
-
module: this.moduleName,
|
|
3148
|
-
accountId: gdf.AccountId ?? accountId
|
|
3149
|
-
});
|
|
3150
|
-
}
|
|
3151
3063
|
}
|
|
3152
3064
|
return {
|
|
3153
3065
|
module: this.moduleName,
|
|
3154
3066
|
status: "success",
|
|
3155
3067
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3156
|
-
resourcesScanned,
|
|
3157
|
-
findingsCount:
|
|
3068
|
+
resourcesScanned: 0,
|
|
3069
|
+
findingsCount: 0,
|
|
3158
3070
|
scanTimeMs: Date.now() - startMs,
|
|
3159
|
-
findings
|
|
3071
|
+
findings: []
|
|
3160
3072
|
};
|
|
3161
3073
|
} catch (err) {
|
|
3162
3074
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3163
3075
|
return {
|
|
3164
3076
|
module: this.moduleName,
|
|
3165
3077
|
status: "error",
|
|
3166
|
-
error: `GuardDuty
|
|
3167
|
-
|
|
3168
|
-
resourcesScanned,
|
|
3078
|
+
error: `GuardDuty detection check failed: ${msg}`,
|
|
3079
|
+
resourcesScanned: 0,
|
|
3169
3080
|
findingsCount: 0,
|
|
3170
3081
|
scanTimeMs: Date.now() - startMs,
|
|
3171
3082
|
findings: []
|
|
@@ -3177,164 +3088,59 @@ var GuardDutyFindingsScanner = class {
|
|
|
3177
3088
|
// src/scanners/inspector-findings.ts
|
|
3178
3089
|
import {
|
|
3179
3090
|
Inspector2Client as Inspector2Client2,
|
|
3180
|
-
|
|
3091
|
+
BatchGetAccountStatusCommand as BatchGetAccountStatusCommand2
|
|
3181
3092
|
} from "@aws-sdk/client-inspector2";
|
|
3182
|
-
function inspectorSeverityToScore(label) {
|
|
3183
|
-
switch (label) {
|
|
3184
|
-
case "CRITICAL":
|
|
3185
|
-
return 9.5;
|
|
3186
|
-
case "HIGH":
|
|
3187
|
-
return 8;
|
|
3188
|
-
case "MEDIUM":
|
|
3189
|
-
return 5.5;
|
|
3190
|
-
case "LOW":
|
|
3191
|
-
return 3;
|
|
3192
|
-
case "INFORMATIONAL":
|
|
3193
|
-
return null;
|
|
3194
|
-
case "UNTRIAGED":
|
|
3195
|
-
return 5.5;
|
|
3196
|
-
default:
|
|
3197
|
-
return null;
|
|
3198
|
-
}
|
|
3199
|
-
}
|
|
3200
3093
|
var InspectorFindingsScanner = class {
|
|
3201
3094
|
moduleName = "inspector_findings";
|
|
3202
3095
|
async scan(ctx) {
|
|
3203
|
-
const { region
|
|
3096
|
+
const { region } = ctx;
|
|
3204
3097
|
const startMs = Date.now();
|
|
3205
|
-
const findings = [];
|
|
3206
3098
|
const warnings = [];
|
|
3207
|
-
let resourcesScanned = 0;
|
|
3208
3099
|
try {
|
|
3209
3100
|
const client = createClient(Inspector2Client2, region, ctx.credentials);
|
|
3210
|
-
|
|
3211
|
-
const
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
const
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
}
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
const severity = severityFromScore(score);
|
|
3229
|
-
const cveId = f.packageVulnerabilityDetails?.vulnerabilityId;
|
|
3230
|
-
const titleBase = f.title ?? "Inspector Finding";
|
|
3231
|
-
const title = cveId ? `[${cveId}] ${titleBase}` : titleBase;
|
|
3232
|
-
const resourceId = f.resources?.[0]?.id ?? "unknown";
|
|
3233
|
-
const resourceType = f.resources?.[0]?.type ?? "AWS::Unknown";
|
|
3234
|
-
const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:inspector2:${region}:${accountId}:finding/${f.findingArn ?? "unknown"}`;
|
|
3235
|
-
const remediationSteps = [];
|
|
3236
|
-
const vulnPkgs = f.packageVulnerabilityDetails?.vulnerablePackages;
|
|
3237
|
-
if (vulnPkgs?.length) {
|
|
3238
|
-
for (const pkg of vulnPkgs.slice(0, 3)) {
|
|
3239
|
-
const name = pkg.name ?? "unknown-package";
|
|
3240
|
-
const installed = pkg.version ?? "unknown";
|
|
3241
|
-
const fixed = pkg.fixedInVersion ?? "latest";
|
|
3242
|
-
const cveRef = cveId ? ` to fix ${cveId}` : "";
|
|
3243
|
-
remediationSteps.push(`Update ${name} from ${installed} to ${fixed}${cveRef}`);
|
|
3244
|
-
}
|
|
3245
|
-
} else if (f.remediation?.recommendation?.text) {
|
|
3246
|
-
remediationSteps.push(f.remediation.recommendation.text);
|
|
3247
|
-
}
|
|
3248
|
-
const genericPatterns = ["See References", "None Provided", "Review the finding"];
|
|
3249
|
-
if (remediationSteps.length === 0 || genericPatterns.some((p) => remediationSteps[0]?.startsWith(p))) {
|
|
3250
|
-
remediationSteps.length = 0;
|
|
3251
|
-
const rawTitle = f.title ?? "";
|
|
3252
|
-
if (rawTitle.includes("KB")) {
|
|
3253
|
-
const kbMatch = rawTitle.match(/KB\d+/);
|
|
3254
|
-
const kb = kbMatch ? kbMatch[0] : "patch";
|
|
3255
|
-
remediationSteps.push(`Install Windows patch ${kb} via WSUS or AWS Systems Manager Patch Manager`);
|
|
3256
|
-
remediationSteps.push(`Run: aws ssm send-command --document-name "AWS-InstallWindowsUpdates" --targets "Key=InstanceIds,Values=${resourceId}"`);
|
|
3257
|
-
if (kbMatch) {
|
|
3258
|
-
remediationSteps.push(`Microsoft KB article: https://support.microsoft.com/help/${kb}`);
|
|
3259
|
-
}
|
|
3260
|
-
} else if (rawTitle.includes("CVE-") || cveId) {
|
|
3261
|
-
const cveMatch = rawTitle.match(/CVE-[\d-]+/);
|
|
3262
|
-
const cve = cveMatch ? cveMatch[0] : cveId ?? "vulnerability";
|
|
3263
|
-
remediationSteps.push(`Fix ${cve}: update the affected software package to the latest patched version`);
|
|
3264
|
-
} else {
|
|
3265
|
-
remediationSteps.push(`Review and remediate: ${rawTitle}`);
|
|
3266
|
-
}
|
|
3267
|
-
}
|
|
3268
|
-
if (f.remediation?.recommendation?.Url) {
|
|
3269
|
-
remediationSteps.push(`Documentation: ${f.remediation.recommendation.Url}`);
|
|
3270
|
-
}
|
|
3271
|
-
if (f.packageVulnerabilityDetails?.referenceUrls?.length) {
|
|
3272
|
-
remediationSteps.push(`CVE references: ${f.packageVulnerabilityDetails.referenceUrls.slice(0, 3).join(", ")}`);
|
|
3273
|
-
}
|
|
3274
|
-
const description = f.description ?? titleBase;
|
|
3275
|
-
const impact = cveId ? `Vulnerability ${cveId} \u2014 CVSS: ${f.packageVulnerabilityDetails?.cvss?.[0]?.baseScore ?? "N/A"}` : `Inspector finding type: ${f.type ?? "unknown"}`;
|
|
3276
|
-
findings.push({
|
|
3277
|
-
severity,
|
|
3278
|
-
title,
|
|
3279
|
-
resourceType,
|
|
3280
|
-
resourceId,
|
|
3281
|
-
resourceArn,
|
|
3282
|
-
region,
|
|
3283
|
-
description,
|
|
3284
|
-
impact,
|
|
3285
|
-
riskScore: score,
|
|
3286
|
-
remediationSteps,
|
|
3287
|
-
priority: priorityFromSeverity(severity),
|
|
3288
|
-
module: this.moduleName,
|
|
3289
|
-
accountId: f.awsAccountId ?? accountId
|
|
3290
|
-
});
|
|
3101
|
+
const resp = await client.send(new BatchGetAccountStatusCommand2({ accountIds: [] }));
|
|
3102
|
+
const account = resp.accounts?.[0];
|
|
3103
|
+
if (!account || account.state?.status !== "ENABLED") {
|
|
3104
|
+
warnings.push("Inspector is not enabled in this region. Enable it to scan for software vulnerabilities.");
|
|
3105
|
+
} else {
|
|
3106
|
+
const rs = account.resourceState;
|
|
3107
|
+
const types = [
|
|
3108
|
+
{ name: "EC2", status: rs?.ec2?.status },
|
|
3109
|
+
{ name: "Lambda", status: rs?.lambda?.status },
|
|
3110
|
+
{ name: "ECR", status: rs?.ecr?.status },
|
|
3111
|
+
{ name: "Lambda Code", status: rs?.lambdaCode?.status },
|
|
3112
|
+
{ name: "Code Repository", status: rs?.codeRepository?.status }
|
|
3113
|
+
];
|
|
3114
|
+
const disabled = types.filter((t) => t.status && t.status !== "ENABLED");
|
|
3115
|
+
if (disabled.length > 0) {
|
|
3116
|
+
warnings.push(
|
|
3117
|
+
`Inspector scan types not enabled: ${disabled.map((t) => t.name).join(", ")}. Enable them for full vulnerability coverage.`
|
|
3118
|
+
);
|
|
3291
3119
|
}
|
|
3292
|
-
|
|
3293
|
-
} while (nextToken);
|
|
3120
|
+
}
|
|
3294
3121
|
return {
|
|
3295
3122
|
module: this.moduleName,
|
|
3296
3123
|
status: "success",
|
|
3297
3124
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3298
|
-
resourcesScanned,
|
|
3299
|
-
findingsCount:
|
|
3125
|
+
resourcesScanned: 0,
|
|
3126
|
+
findingsCount: 0,
|
|
3300
3127
|
scanTimeMs: Date.now() - startMs,
|
|
3301
|
-
findings
|
|
3128
|
+
findings: []
|
|
3302
3129
|
};
|
|
3303
3130
|
} catch (err) {
|
|
3304
3131
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3305
3132
|
const errName = err instanceof Error ? err.name : "";
|
|
3306
3133
|
const isAccessDenied2 = errName === "AccessDeniedException" || msg.includes("AccessDeniedException");
|
|
3307
|
-
const isNotEnabled2 = msg.includes("not enabled") || msg.includes("not subscribed");
|
|
3308
3134
|
if (isAccessDenied2) {
|
|
3309
|
-
warnings.push("Insufficient permissions to access Inspector. Grant inspector2:
|
|
3310
|
-
|
|
3311
|
-
module: this.moduleName,
|
|
3312
|
-
status: "success",
|
|
3313
|
-
warnings,
|
|
3314
|
-
resourcesScanned: 0,
|
|
3315
|
-
findingsCount: 0,
|
|
3316
|
-
scanTimeMs: Date.now() - startMs,
|
|
3317
|
-
findings: []
|
|
3318
|
-
};
|
|
3319
|
-
}
|
|
3320
|
-
if (isNotEnabled2) {
|
|
3135
|
+
warnings.push("Insufficient permissions to access Inspector. Grant inspector2:BatchGetAccountStatus to check enablement.");
|
|
3136
|
+
} else {
|
|
3321
3137
|
warnings.push("Inspector is not enabled in this region. Enable it to scan for software vulnerabilities.");
|
|
3322
|
-
return {
|
|
3323
|
-
module: this.moduleName,
|
|
3324
|
-
status: "success",
|
|
3325
|
-
warnings,
|
|
3326
|
-
resourcesScanned: 0,
|
|
3327
|
-
findingsCount: 0,
|
|
3328
|
-
scanTimeMs: Date.now() - startMs,
|
|
3329
|
-
findings: []
|
|
3330
|
-
};
|
|
3331
3138
|
}
|
|
3332
3139
|
return {
|
|
3333
3140
|
module: this.moduleName,
|
|
3334
|
-
status: "
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
resourcesScanned,
|
|
3141
|
+
status: "success",
|
|
3142
|
+
warnings,
|
|
3143
|
+
resourcesScanned: 0,
|
|
3338
3144
|
findingsCount: 0,
|
|
3339
3145
|
scanTimeMs: Date.now() - startMs,
|
|
3340
3146
|
findings: []
|
|
@@ -3507,128 +3313,29 @@ var TrustedAdvisorFindingsScanner = class {
|
|
|
3507
3313
|
// src/scanners/config-rules-findings.ts
|
|
3508
3314
|
import {
|
|
3509
3315
|
ConfigServiceClient as ConfigServiceClient2,
|
|
3510
|
-
|
|
3511
|
-
GetComplianceDetailsByConfigRuleCommand
|
|
3316
|
+
DescribeConfigurationRecordersCommand as DescribeConfigurationRecordersCommand2
|
|
3512
3317
|
} from "@aws-sdk/client-config-service";
|
|
3513
|
-
var SECURITY_RULE_PATTERNS = [
|
|
3514
|
-
"securitygroup",
|
|
3515
|
-
"security-group",
|
|
3516
|
-
"encryption",
|
|
3517
|
-
"encrypted",
|
|
3518
|
-
"public",
|
|
3519
|
-
"unrestricted",
|
|
3520
|
-
"mfa",
|
|
3521
|
-
"password",
|
|
3522
|
-
"access-key",
|
|
3523
|
-
"root",
|
|
3524
|
-
"admin",
|
|
3525
|
-
"logging",
|
|
3526
|
-
"cloudtrail",
|
|
3527
|
-
"iam",
|
|
3528
|
-
"kms",
|
|
3529
|
-
"ssl",
|
|
3530
|
-
"tls",
|
|
3531
|
-
"vpc-flow",
|
|
3532
|
-
"guardduty",
|
|
3533
|
-
"securityhub"
|
|
3534
|
-
];
|
|
3535
|
-
function ruleIsSecurityRelated(ruleName) {
|
|
3536
|
-
const lower = ruleName.toLowerCase();
|
|
3537
|
-
return SECURITY_RULE_PATTERNS.some((pat) => lower.includes(pat));
|
|
3538
|
-
}
|
|
3539
3318
|
var ConfigRulesFindingsScanner = class {
|
|
3540
3319
|
moduleName = "config_rules_findings";
|
|
3541
3320
|
async scan(ctx) {
|
|
3542
|
-
const { region
|
|
3321
|
+
const { region } = ctx;
|
|
3543
3322
|
const startMs = Date.now();
|
|
3544
|
-
const findings = [];
|
|
3545
3323
|
const warnings = [];
|
|
3546
|
-
let resourcesScanned = 0;
|
|
3547
3324
|
try {
|
|
3548
3325
|
const client = createClient(ConfigServiceClient2, region, ctx.credentials);
|
|
3549
|
-
|
|
3550
|
-
const
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
new DescribeComplianceByConfigRuleCommand({ NextToken: nextToken })
|
|
3554
|
-
);
|
|
3555
|
-
for (const rule of resp.ComplianceByConfigRules ?? []) {
|
|
3556
|
-
resourcesScanned++;
|
|
3557
|
-
if (rule.Compliance?.ComplianceType === "NON_COMPLIANT") {
|
|
3558
|
-
nonCompliantRules.push(rule);
|
|
3559
|
-
}
|
|
3560
|
-
}
|
|
3561
|
-
nextToken = resp.NextToken;
|
|
3562
|
-
} while (nextToken);
|
|
3563
|
-
if (resourcesScanned === 0) {
|
|
3564
|
-
warnings.push("AWS Config is not enabled in this region or no Config Rules are defined.");
|
|
3565
|
-
return {
|
|
3566
|
-
module: this.moduleName,
|
|
3567
|
-
status: "success",
|
|
3568
|
-
warnings,
|
|
3569
|
-
resourcesScanned: 0,
|
|
3570
|
-
findingsCount: 0,
|
|
3571
|
-
scanTimeMs: Date.now() - startMs,
|
|
3572
|
-
findings: []
|
|
3573
|
-
};
|
|
3574
|
-
}
|
|
3575
|
-
for (const rule of nonCompliantRules) {
|
|
3576
|
-
const ruleName = rule.ConfigRuleName ?? "unknown";
|
|
3577
|
-
try {
|
|
3578
|
-
let detailToken;
|
|
3579
|
-
do {
|
|
3580
|
-
const detailResp = await client.send(
|
|
3581
|
-
new GetComplianceDetailsByConfigRuleCommand({
|
|
3582
|
-
ConfigRuleName: ruleName,
|
|
3583
|
-
ComplianceTypes: ["NON_COMPLIANT"],
|
|
3584
|
-
NextToken: detailToken
|
|
3585
|
-
})
|
|
3586
|
-
);
|
|
3587
|
-
for (const evalResult of detailResp.EvaluationResults ?? []) {
|
|
3588
|
-
const qualifier = evalResult.EvaluationResultIdentifier?.EvaluationResultQualifier;
|
|
3589
|
-
const resourceType = qualifier?.ResourceType ?? "AWS::Unknown";
|
|
3590
|
-
const resourceId = qualifier?.ResourceId ?? "unknown";
|
|
3591
|
-
const annotation = evalResult.Annotation;
|
|
3592
|
-
const isSecurityRule = ruleIsSecurityRelated(ruleName);
|
|
3593
|
-
const riskScore = isSecurityRule ? 7.5 : 5.5;
|
|
3594
|
-
const severity = severityFromScore(riskScore);
|
|
3595
|
-
const descParts = [`Config Rule: ${ruleName}`, `Resource Type: ${resourceType}`];
|
|
3596
|
-
if (annotation) descParts.push(`Annotation: ${annotation}`);
|
|
3597
|
-
findings.push({
|
|
3598
|
-
severity,
|
|
3599
|
-
title: `Config Rule: ${ruleName} - ${resourceType}/${resourceId} Non-Compliant`,
|
|
3600
|
-
resourceType,
|
|
3601
|
-
resourceId,
|
|
3602
|
-
resourceArn: resourceId,
|
|
3603
|
-
region,
|
|
3604
|
-
description: descParts.join(". "),
|
|
3605
|
-
impact: `Resource is non-compliant with Config Rule: ${ruleName}`,
|
|
3606
|
-
riskScore,
|
|
3607
|
-
remediationSteps: [
|
|
3608
|
-
`Fix Config Rule violation: ${ruleName}`,
|
|
3609
|
-
annotation ? `Details: ${annotation}` : "",
|
|
3610
|
-
`Resource: ${resourceType}/${resourceId}`
|
|
3611
|
-
].filter(Boolean),
|
|
3612
|
-
priority: priorityFromSeverity(severity),
|
|
3613
|
-
module: this.moduleName,
|
|
3614
|
-
accountId
|
|
3615
|
-
});
|
|
3616
|
-
}
|
|
3617
|
-
detailToken = detailResp.NextToken;
|
|
3618
|
-
} while (detailToken);
|
|
3619
|
-
} catch (detailErr) {
|
|
3620
|
-
const msg = detailErr instanceof Error ? detailErr.message : String(detailErr);
|
|
3621
|
-
warnings.push(`Failed to get details for rule ${ruleName}: ${msg}`);
|
|
3622
|
-
}
|
|
3326
|
+
const resp = await client.send(new DescribeConfigurationRecordersCommand2({}));
|
|
3327
|
+
const recorders = resp.ConfigurationRecorders ?? [];
|
|
3328
|
+
if (recorders.length === 0) {
|
|
3329
|
+
warnings.push("AWS Config is not enabled in this region.");
|
|
3623
3330
|
}
|
|
3624
3331
|
return {
|
|
3625
3332
|
module: this.moduleName,
|
|
3626
3333
|
status: "success",
|
|
3627
3334
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3628
|
-
resourcesScanned,
|
|
3629
|
-
findingsCount:
|
|
3335
|
+
resourcesScanned: 0,
|
|
3336
|
+
findingsCount: 0,
|
|
3630
3337
|
scanTimeMs: Date.now() - startMs,
|
|
3631
|
-
findings
|
|
3338
|
+
findings: []
|
|
3632
3339
|
};
|
|
3633
3340
|
} catch (err) {
|
|
3634
3341
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -3647,9 +3354,8 @@ var ConfigRulesFindingsScanner = class {
|
|
|
3647
3354
|
return {
|
|
3648
3355
|
module: this.moduleName,
|
|
3649
3356
|
status: "error",
|
|
3650
|
-
error: `Config Rules
|
|
3651
|
-
|
|
3652
|
-
resourcesScanned,
|
|
3357
|
+
error: `Config Rules detection check failed: ${msg}`,
|
|
3358
|
+
resourcesScanned: 0,
|
|
3653
3359
|
findingsCount: 0,
|
|
3654
3360
|
scanTimeMs: Date.now() - startMs,
|
|
3655
3361
|
findings: []
|
|
@@ -3661,146 +3367,50 @@ var ConfigRulesFindingsScanner = class {
|
|
|
3661
3367
|
// src/scanners/access-analyzer-findings.ts
|
|
3662
3368
|
import {
|
|
3663
3369
|
AccessAnalyzerClient,
|
|
3664
|
-
ListAnalyzersCommand
|
|
3665
|
-
ListFindingsV2Command
|
|
3370
|
+
ListAnalyzersCommand
|
|
3666
3371
|
} from "@aws-sdk/client-accessanalyzer";
|
|
3667
|
-
function findingTypeToScore(findingType) {
|
|
3668
|
-
const ft = findingType;
|
|
3669
|
-
switch (ft) {
|
|
3670
|
-
case "ExternalAccess":
|
|
3671
|
-
return 8;
|
|
3672
|
-
case "UnusedIAMRole":
|
|
3673
|
-
case "UnusedIAMUserAccessKey":
|
|
3674
|
-
case "UnusedIAMUserPassword":
|
|
3675
|
-
return 5.5;
|
|
3676
|
-
case "UnusedPermission":
|
|
3677
|
-
return 3;
|
|
3678
|
-
default:
|
|
3679
|
-
return 5.5;
|
|
3680
|
-
}
|
|
3681
|
-
}
|
|
3682
|
-
var UNUSED_FINDING_TYPES = /* @__PURE__ */ new Set([
|
|
3683
|
-
"UnusedIAMRole",
|
|
3684
|
-
"UnusedIAMUserAccessKey",
|
|
3685
|
-
"UnusedIAMUserPassword",
|
|
3686
|
-
"UnusedPermission"
|
|
3687
|
-
]);
|
|
3688
|
-
function isSecurityRelevant(findingType) {
|
|
3689
|
-
const ft = findingType;
|
|
3690
|
-
return ft === "ExternalAccess" || UNUSED_FINDING_TYPES.has(ft ?? "");
|
|
3691
|
-
}
|
|
3692
|
-
function isExternalAccess(findingType) {
|
|
3693
|
-
return findingType === "ExternalAccess";
|
|
3694
|
-
}
|
|
3695
3372
|
var AccessAnalyzerFindingsScanner = class {
|
|
3696
3373
|
moduleName = "access_analyzer_findings";
|
|
3697
3374
|
async scan(ctx) {
|
|
3698
|
-
const { region
|
|
3375
|
+
const { region } = ctx;
|
|
3699
3376
|
const startMs = Date.now();
|
|
3700
|
-
const findings = [];
|
|
3701
3377
|
const warnings = [];
|
|
3702
|
-
let resourcesScanned = 0;
|
|
3703
3378
|
try {
|
|
3704
3379
|
const client = createClient(AccessAnalyzerClient, region, ctx.credentials);
|
|
3705
3380
|
let analyzerToken;
|
|
3706
|
-
|
|
3381
|
+
let hasActiveAnalyzer = false;
|
|
3707
3382
|
do {
|
|
3708
3383
|
const resp = await client.send(
|
|
3709
3384
|
new ListAnalyzersCommand({ nextToken: analyzerToken })
|
|
3710
3385
|
);
|
|
3711
3386
|
for (const analyzer of resp.analyzers ?? []) {
|
|
3712
3387
|
if (analyzer.status === "ACTIVE") {
|
|
3713
|
-
|
|
3388
|
+
hasActiveAnalyzer = true;
|
|
3389
|
+
break;
|
|
3714
3390
|
}
|
|
3715
3391
|
}
|
|
3392
|
+
if (hasActiveAnalyzer) break;
|
|
3716
3393
|
analyzerToken = resp.nextToken;
|
|
3717
3394
|
} while (analyzerToken);
|
|
3718
|
-
if (
|
|
3395
|
+
if (!hasActiveAnalyzer) {
|
|
3719
3396
|
warnings.push("No IAM Access Analyzer found. Create an analyzer to detect external access to your resources.");
|
|
3720
|
-
return {
|
|
3721
|
-
module: this.moduleName,
|
|
3722
|
-
status: "success",
|
|
3723
|
-
warnings,
|
|
3724
|
-
resourcesScanned: 0,
|
|
3725
|
-
findingsCount: 0,
|
|
3726
|
-
scanTimeMs: Date.now() - startMs,
|
|
3727
|
-
findings: []
|
|
3728
|
-
};
|
|
3729
|
-
}
|
|
3730
|
-
for (const analyzer of analyzers) {
|
|
3731
|
-
const analyzerArn = analyzer.arn ?? "unknown";
|
|
3732
|
-
let findingToken;
|
|
3733
|
-
do {
|
|
3734
|
-
const listResp = await client.send(
|
|
3735
|
-
new ListFindingsV2Command({
|
|
3736
|
-
analyzerArn,
|
|
3737
|
-
filter: {
|
|
3738
|
-
status: { eq: ["ACTIVE"] }
|
|
3739
|
-
},
|
|
3740
|
-
nextToken: findingToken
|
|
3741
|
-
})
|
|
3742
|
-
);
|
|
3743
|
-
for (const aaf of listResp.findings ?? []) {
|
|
3744
|
-
if (!isSecurityRelevant(aaf.findingType)) {
|
|
3745
|
-
continue;
|
|
3746
|
-
}
|
|
3747
|
-
resourcesScanned++;
|
|
3748
|
-
const score = findingTypeToScore(aaf.findingType);
|
|
3749
|
-
const severity = severityFromScore(score);
|
|
3750
|
-
const resourceArn = aaf.resource ?? "unknown";
|
|
3751
|
-
const resourceType = aaf.resourceType ?? "AWS::Unknown";
|
|
3752
|
-
const resourceId = resourceArn.split("/").pop() ?? resourceArn.split(":").pop() ?? "unknown";
|
|
3753
|
-
const external = isExternalAccess(aaf.findingType);
|
|
3754
|
-
const descParts = [`Resource Type: ${resourceType}`];
|
|
3755
|
-
if (aaf.resourceOwnerAccount) descParts.push(`Owner Account: ${aaf.resourceOwnerAccount}`);
|
|
3756
|
-
if (aaf.findingType) descParts.push(`Finding Type: ${aaf.findingType}`);
|
|
3757
|
-
const title = buildFindingTitle(aaf);
|
|
3758
|
-
const impact = external ? `Resource is accessible from outside the account. Type: ${aaf.findingType ?? "unknown"}` : `Unused access detected \u2014 review and remove to follow least-privilege. Type: ${aaf.findingType ?? "unknown"}`;
|
|
3759
|
-
const remediationSteps = external ? [
|
|
3760
|
-
`Restrict external access on ${resourceType} ${resourceId}`,
|
|
3761
|
-
"Remove or narrow the resource policy to eliminate unintended external access.",
|
|
3762
|
-
`Resource ARN: ${resourceArn}`
|
|
3763
|
-
] : [
|
|
3764
|
-
`Remove unused access on ${resourceType} ${resourceId}`,
|
|
3765
|
-
"Remove unused permissions, roles, or credentials to follow least-privilege.",
|
|
3766
|
-
`Resource ARN: ${resourceArn}`
|
|
3767
|
-
];
|
|
3768
|
-
findings.push({
|
|
3769
|
-
severity,
|
|
3770
|
-
title,
|
|
3771
|
-
resourceType: mapResourceType(resourceType),
|
|
3772
|
-
resourceId,
|
|
3773
|
-
resourceArn,
|
|
3774
|
-
region,
|
|
3775
|
-
description: descParts.join(". "),
|
|
3776
|
-
impact,
|
|
3777
|
-
riskScore: score,
|
|
3778
|
-
remediationSteps,
|
|
3779
|
-
priority: priorityFromSeverity(severity),
|
|
3780
|
-
module: this.moduleName,
|
|
3781
|
-
accountId: aaf.resourceOwnerAccount ?? accountId
|
|
3782
|
-
});
|
|
3783
|
-
}
|
|
3784
|
-
findingToken = listResp.nextToken;
|
|
3785
|
-
} while (findingToken);
|
|
3786
3397
|
}
|
|
3787
3398
|
return {
|
|
3788
3399
|
module: this.moduleName,
|
|
3789
3400
|
status: "success",
|
|
3790
3401
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3791
|
-
resourcesScanned,
|
|
3792
|
-
findingsCount:
|
|
3402
|
+
resourcesScanned: 0,
|
|
3403
|
+
findingsCount: 0,
|
|
3793
3404
|
scanTimeMs: Date.now() - startMs,
|
|
3794
|
-
findings
|
|
3405
|
+
findings: []
|
|
3795
3406
|
};
|
|
3796
3407
|
} catch (err) {
|
|
3797
3408
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3798
3409
|
return {
|
|
3799
3410
|
module: this.moduleName,
|
|
3800
3411
|
status: "error",
|
|
3801
|
-
error: `Access Analyzer
|
|
3802
|
-
|
|
3803
|
-
resourcesScanned,
|
|
3412
|
+
error: `Access Analyzer detection check failed: ${msg}`,
|
|
3413
|
+
resourcesScanned: 0,
|
|
3804
3414
|
findingsCount: 0,
|
|
3805
3415
|
scanTimeMs: Date.now() - startMs,
|
|
3806
3416
|
findings: []
|
|
@@ -3808,29 +3418,6 @@ var AccessAnalyzerFindingsScanner = class {
|
|
|
3808
3418
|
}
|
|
3809
3419
|
}
|
|
3810
3420
|
};
|
|
3811
|
-
function buildFindingTitle(finding) {
|
|
3812
|
-
const resourceType = finding.resourceType ?? "Resource";
|
|
3813
|
-
const resource = finding.resource ? finding.resource.split("/").pop() ?? finding.resource.split(":").pop() ?? finding.resource : "unknown";
|
|
3814
|
-
const label = isExternalAccess(finding.findingType) ? "external access detected" : "unused access detected";
|
|
3815
|
-
return `[Access Analyzer] ${resourceType} ${resource} \u2014 ${label}`;
|
|
3816
|
-
}
|
|
3817
|
-
function mapResourceType(aaType) {
|
|
3818
|
-
const mapping = {
|
|
3819
|
-
"AWS::S3::Bucket": "AWS::S3::Bucket",
|
|
3820
|
-
"AWS::IAM::Role": "AWS::IAM::Role",
|
|
3821
|
-
"AWS::SQS::Queue": "AWS::SQS::Queue",
|
|
3822
|
-
"AWS::Lambda::Function": "AWS::Lambda::Function",
|
|
3823
|
-
"AWS::Lambda::LayerVersion": "AWS::Lambda::LayerVersion",
|
|
3824
|
-
"AWS::KMS::Key": "AWS::KMS::Key",
|
|
3825
|
-
"AWS::SecretsManager::Secret": "AWS::SecretsManager::Secret",
|
|
3826
|
-
"AWS::SNS::Topic": "AWS::SNS::Topic",
|
|
3827
|
-
"AWS::EFS::FileSystem": "AWS::EFS::FileSystem",
|
|
3828
|
-
"AWS::RDS::DBSnapshot": "AWS::RDS::DBSnapshot",
|
|
3829
|
-
"AWS::RDS::DBClusterSnapshot": "AWS::RDS::DBClusterSnapshot",
|
|
3830
|
-
"AWS::ECR::Repository": "AWS::ECR::Repository"
|
|
3831
|
-
};
|
|
3832
|
-
return mapping[aaType] ?? aaType;
|
|
3833
|
-
}
|
|
3834
3421
|
|
|
3835
3422
|
// src/scanners/patch-compliance-findings.ts
|
|
3836
3423
|
import {
|