defense-mcp-server 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/CHANGELOG.md +471 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/build/core/auto-installer.d.ts +102 -0
- package/build/core/auto-installer.d.ts.map +1 -0
- package/build/core/auto-installer.js +833 -0
- package/build/core/backup-manager.d.ts +63 -0
- package/build/core/backup-manager.d.ts.map +1 -0
- package/build/core/backup-manager.js +189 -0
- package/build/core/changelog.d.ts +75 -0
- package/build/core/changelog.d.ts.map +1 -0
- package/build/core/changelog.js +123 -0
- package/build/core/command-allowlist.d.ts +129 -0
- package/build/core/command-allowlist.d.ts.map +1 -0
- package/build/core/command-allowlist.js +849 -0
- package/build/core/config.d.ts +79 -0
- package/build/core/config.d.ts.map +1 -0
- package/build/core/config.js +193 -0
- package/build/core/dependency-validator.d.ts +106 -0
- package/build/core/dependency-validator.d.ts.map +1 -0
- package/build/core/dependency-validator.js +405 -0
- package/build/core/distro-adapter.d.ts +177 -0
- package/build/core/distro-adapter.d.ts.map +1 -0
- package/build/core/distro-adapter.js +481 -0
- package/build/core/distro.d.ts +68 -0
- package/build/core/distro.d.ts.map +1 -0
- package/build/core/distro.js +457 -0
- package/build/core/encrypted-state.d.ts +76 -0
- package/build/core/encrypted-state.d.ts.map +1 -0
- package/build/core/encrypted-state.js +209 -0
- package/build/core/executor.d.ts +56 -0
- package/build/core/executor.d.ts.map +1 -0
- package/build/core/executor.js +350 -0
- package/build/core/installer.d.ts +92 -0
- package/build/core/installer.d.ts.map +1 -0
- package/build/core/installer.js +1072 -0
- package/build/core/logger.d.ts +102 -0
- package/build/core/logger.d.ts.map +1 -0
- package/build/core/logger.js +132 -0
- package/build/core/parsers.d.ts +151 -0
- package/build/core/parsers.d.ts.map +1 -0
- package/build/core/parsers.js +479 -0
- package/build/core/policy-engine.d.ts +170 -0
- package/build/core/policy-engine.d.ts.map +1 -0
- package/build/core/policy-engine.js +656 -0
- package/build/core/preflight.d.ts +157 -0
- package/build/core/preflight.d.ts.map +1 -0
- package/build/core/preflight.js +638 -0
- package/build/core/privilege-manager.d.ts +108 -0
- package/build/core/privilege-manager.d.ts.map +1 -0
- package/build/core/privilege-manager.js +363 -0
- package/build/core/rate-limiter.d.ts +67 -0
- package/build/core/rate-limiter.d.ts.map +1 -0
- package/build/core/rate-limiter.js +129 -0
- package/build/core/rollback.d.ts +73 -0
- package/build/core/rollback.d.ts.map +1 -0
- package/build/core/rollback.js +278 -0
- package/build/core/safeguards.d.ts +58 -0
- package/build/core/safeguards.d.ts.map +1 -0
- package/build/core/safeguards.js +448 -0
- package/build/core/sanitizer.d.ts +118 -0
- package/build/core/sanitizer.d.ts.map +1 -0
- package/build/core/sanitizer.js +459 -0
- package/build/core/secure-fs.d.ts +67 -0
- package/build/core/secure-fs.d.ts.map +1 -0
- package/build/core/secure-fs.js +143 -0
- package/build/core/spawn-safe.d.ts +55 -0
- package/build/core/spawn-safe.d.ts.map +1 -0
- package/build/core/spawn-safe.js +146 -0
- package/build/core/sudo-guard.d.ts +145 -0
- package/build/core/sudo-guard.d.ts.map +1 -0
- package/build/core/sudo-guard.js +349 -0
- package/build/core/sudo-session.d.ts +100 -0
- package/build/core/sudo-session.d.ts.map +1 -0
- package/build/core/sudo-session.js +319 -0
- package/build/core/tool-dependencies.d.ts +61 -0
- package/build/core/tool-dependencies.d.ts.map +1 -0
- package/build/core/tool-dependencies.js +571 -0
- package/build/core/tool-registry.d.ts +111 -0
- package/build/core/tool-registry.d.ts.map +1 -0
- package/build/core/tool-registry.js +656 -0
- package/build/core/tool-wrapper.d.ts +73 -0
- package/build/core/tool-wrapper.d.ts.map +1 -0
- package/build/core/tool-wrapper.js +296 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +247 -0
- package/build/tools/access-control.d.ts +9 -0
- package/build/tools/access-control.d.ts.map +1 -0
- package/build/tools/access-control.js +1818 -0
- package/build/tools/api-security.d.ts +12 -0
- package/build/tools/api-security.d.ts.map +1 -0
- package/build/tools/api-security.js +901 -0
- package/build/tools/app-hardening.d.ts +11 -0
- package/build/tools/app-hardening.d.ts.map +1 -0
- package/build/tools/app-hardening.js +768 -0
- package/build/tools/backup.d.ts +8 -0
- package/build/tools/backup.d.ts.map +1 -0
- package/build/tools/backup.js +381 -0
- package/build/tools/cloud-security.d.ts +17 -0
- package/build/tools/cloud-security.d.ts.map +1 -0
- package/build/tools/cloud-security.js +739 -0
- package/build/tools/compliance.d.ts +10 -0
- package/build/tools/compliance.d.ts.map +1 -0
- package/build/tools/compliance.js +1225 -0
- package/build/tools/container-security.d.ts +14 -0
- package/build/tools/container-security.d.ts.map +1 -0
- package/build/tools/container-security.js +788 -0
- package/build/tools/deception.d.ts +13 -0
- package/build/tools/deception.d.ts.map +1 -0
- package/build/tools/deception.js +763 -0
- package/build/tools/dns-security.d.ts +93 -0
- package/build/tools/dns-security.d.ts.map +1 -0
- package/build/tools/dns-security.js +745 -0
- package/build/tools/drift-detection.d.ts +8 -0
- package/build/tools/drift-detection.d.ts.map +1 -0
- package/build/tools/drift-detection.js +326 -0
- package/build/tools/ebpf-security.d.ts +15 -0
- package/build/tools/ebpf-security.d.ts.map +1 -0
- package/build/tools/ebpf-security.js +294 -0
- package/build/tools/encryption.d.ts +9 -0
- package/build/tools/encryption.d.ts.map +1 -0
- package/build/tools/encryption.js +1667 -0
- package/build/tools/firewall.d.ts +9 -0
- package/build/tools/firewall.d.ts.map +1 -0
- package/build/tools/firewall.js +1398 -0
- package/build/tools/hardening.d.ts +10 -0
- package/build/tools/hardening.d.ts.map +1 -0
- package/build/tools/hardening.js +2654 -0
- package/build/tools/ids.d.ts +9 -0
- package/build/tools/ids.d.ts.map +1 -0
- package/build/tools/ids.js +624 -0
- package/build/tools/incident-response.d.ts +10 -0
- package/build/tools/incident-response.d.ts.map +1 -0
- package/build/tools/incident-response.js +1180 -0
- package/build/tools/logging.d.ts +12 -0
- package/build/tools/logging.d.ts.map +1 -0
- package/build/tools/logging.js +454 -0
- package/build/tools/malware.d.ts +10 -0
- package/build/tools/malware.d.ts.map +1 -0
- package/build/tools/malware.js +532 -0
- package/build/tools/meta.d.ts +11 -0
- package/build/tools/meta.d.ts.map +1 -0
- package/build/tools/meta.js +2278 -0
- package/build/tools/network-defense.d.ts +12 -0
- package/build/tools/network-defense.d.ts.map +1 -0
- package/build/tools/network-defense.js +760 -0
- package/build/tools/patch-management.d.ts +3 -0
- package/build/tools/patch-management.d.ts.map +1 -0
- package/build/tools/patch-management.js +708 -0
- package/build/tools/process-security.d.ts +12 -0
- package/build/tools/process-security.d.ts.map +1 -0
- package/build/tools/process-security.js +784 -0
- package/build/tools/reporting.d.ts +11 -0
- package/build/tools/reporting.d.ts.map +1 -0
- package/build/tools/reporting.js +559 -0
- package/build/tools/secrets.d.ts +9 -0
- package/build/tools/secrets.d.ts.map +1 -0
- package/build/tools/secrets.js +596 -0
- package/build/tools/siem-integration.d.ts +18 -0
- package/build/tools/siem-integration.d.ts.map +1 -0
- package/build/tools/siem-integration.js +754 -0
- package/build/tools/sudo-management.d.ts +18 -0
- package/build/tools/sudo-management.d.ts.map +1 -0
- package/build/tools/sudo-management.js +737 -0
- package/build/tools/supply-chain-security.d.ts +8 -0
- package/build/tools/supply-chain-security.d.ts.map +1 -0
- package/build/tools/supply-chain-security.js +256 -0
- package/build/tools/threat-intel.d.ts +22 -0
- package/build/tools/threat-intel.d.ts.map +1 -0
- package/build/tools/threat-intel.js +749 -0
- package/build/tools/vulnerability-management.d.ts +11 -0
- package/build/tools/vulnerability-management.d.ts.map +1 -0
- package/build/tools/vulnerability-management.js +667 -0
- package/build/tools/waf.d.ts +12 -0
- package/build/tools/waf.d.ts.map +1 -0
- package/build/tools/waf.js +843 -0
- package/build/tools/wireless-security.d.ts +19 -0
- package/build/tools/wireless-security.d.ts.map +1 -0
- package/build/tools/wireless-security.js +826 -0
- package/build/tools/zero-trust-network.d.ts +8 -0
- package/build/tools/zero-trust-network.d.ts.map +1 -0
- package/build/tools/zero-trust-network.js +367 -0
- package/docs/SAFEGUARDS.md +518 -0
- package/docs/TOOLS-REFERENCE.md +665 -0
- package/package.json +87 -0
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIEM integration tools for Kali Defense MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Registers 1 tool: siem_export (actions: configure_syslog_forward,
|
|
5
|
+
* configure_filebeat, audit_forwarding, test_connectivity)
|
|
6
|
+
*
|
|
7
|
+
* Provides syslog/rsyslog remote forwarding configuration, Filebeat
|
|
8
|
+
* auditing, comprehensive log forwarding audit with CIS benchmark
|
|
9
|
+
* references, and SIEM endpoint connectivity testing.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { spawnSafe } from "../core/spawn-safe.js";
|
|
13
|
+
import { createTextContent, createErrorContent, formatToolOutput, } from "../core/parsers.js";
|
|
14
|
+
// ── Constants ──────────────────────────────────────────────────────────────────
|
|
15
|
+
/** Critical log sources that should be forwarded per CIS benchmarks */
|
|
16
|
+
const CRITICAL_LOG_SOURCES = ["auth", "syslog", "kern", "audit"];
|
|
17
|
+
/** Log file paths corresponding to critical sources */
|
|
18
|
+
const LOG_SOURCE_FILES = {
|
|
19
|
+
auth: "/var/log/auth.log",
|
|
20
|
+
syslog: "/var/log/syslog",
|
|
21
|
+
kern: "/var/log/kern.log",
|
|
22
|
+
audit: "/var/log/audit/audit.log",
|
|
23
|
+
};
|
|
24
|
+
/** Hostname/IP validation pattern */
|
|
25
|
+
const SIEM_HOST_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,253}[a-zA-Z0-9]$/;
|
|
26
|
+
/**
|
|
27
|
+
* Run a command via spawnSafe and collect output as a promise.
|
|
28
|
+
* Handles errors gracefully — returns error info instead of throwing.
|
|
29
|
+
*/
|
|
30
|
+
async function runCommand(command, args, timeoutMs = 30_000) {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
let child;
|
|
33
|
+
try {
|
|
34
|
+
child = spawnSafe(command, args);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38
|
+
resolve({ stdout: "", stderr: msg, exitCode: -1 });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
let stdout = "";
|
|
42
|
+
let stderr = "";
|
|
43
|
+
let resolved = false;
|
|
44
|
+
const timer = setTimeout(() => {
|
|
45
|
+
if (!resolved) {
|
|
46
|
+
resolved = true;
|
|
47
|
+
child.kill("SIGTERM");
|
|
48
|
+
resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
|
|
49
|
+
}
|
|
50
|
+
}, timeoutMs);
|
|
51
|
+
child.stdout?.on("data", (data) => {
|
|
52
|
+
stdout += data.toString();
|
|
53
|
+
});
|
|
54
|
+
child.stderr?.on("data", (data) => {
|
|
55
|
+
stderr += data.toString();
|
|
56
|
+
});
|
|
57
|
+
child.on("close", (code) => {
|
|
58
|
+
if (!resolved) {
|
|
59
|
+
resolved = true;
|
|
60
|
+
clearTimeout(timer);
|
|
61
|
+
resolve({ stdout, stderr, exitCode: code ?? -1 });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
child.on("error", (err) => {
|
|
65
|
+
if (!resolved) {
|
|
66
|
+
resolved = true;
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
resolve({ stdout, stderr: err.message, exitCode: -1 });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// ── Host validation ────────────────────────────────────────────────────────────
|
|
74
|
+
/**
|
|
75
|
+
* Validate a SIEM host string (hostname or IP address).
|
|
76
|
+
* Returns true if the host looks reasonable.
|
|
77
|
+
*/
|
|
78
|
+
export function validateSiemHost(host) {
|
|
79
|
+
if (!host || host.trim().length === 0)
|
|
80
|
+
return false;
|
|
81
|
+
const trimmed = host.trim();
|
|
82
|
+
// Allow IP addresses (v4)
|
|
83
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(trimmed))
|
|
84
|
+
return true;
|
|
85
|
+
// Allow hostnames
|
|
86
|
+
if (SIEM_HOST_PATTERN.test(trimmed))
|
|
87
|
+
return true;
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
async function configureSyslogForward(siemHost, siemPort, protocol, logSources) {
|
|
91
|
+
const result = {
|
|
92
|
+
syslogDaemon: "unknown",
|
|
93
|
+
daemonInstalled: false,
|
|
94
|
+
existingForwardingRules: [],
|
|
95
|
+
rsyslogModules: { imtcp: false, imudp: false },
|
|
96
|
+
tlsSupport: false,
|
|
97
|
+
currentConfig: "",
|
|
98
|
+
recommendedConfig: "",
|
|
99
|
+
recommendations: [],
|
|
100
|
+
};
|
|
101
|
+
const effectivePort = siemPort ?? 514;
|
|
102
|
+
const effectiveProtocol = protocol ?? "tcp";
|
|
103
|
+
// Check which syslog daemon is installed
|
|
104
|
+
const rsyslogCheck = await runCommand("dpkg", ["-l", "rsyslog"], 10_000);
|
|
105
|
+
const syslogNgCheck = await runCommand("dpkg", ["-l", "syslog-ng"], 10_000);
|
|
106
|
+
if (rsyslogCheck.exitCode === 0 && rsyslogCheck.stdout.includes("ii")) {
|
|
107
|
+
result.syslogDaemon = "rsyslog";
|
|
108
|
+
result.daemonInstalled = true;
|
|
109
|
+
}
|
|
110
|
+
else if (syslogNgCheck.exitCode === 0 && syslogNgCheck.stdout.includes("ii")) {
|
|
111
|
+
result.syslogDaemon = "syslog-ng";
|
|
112
|
+
result.daemonInstalled = true;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
result.syslogDaemon = "none";
|
|
116
|
+
result.daemonInstalled = false;
|
|
117
|
+
result.recommendations.push("No syslog daemon found — install rsyslog: apt-get install rsyslog");
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
// Read current rsyslog config
|
|
121
|
+
const configResult = await runCommand("cat", ["/etc/rsyslog.conf"], 10_000);
|
|
122
|
+
if (configResult.exitCode === 0) {
|
|
123
|
+
result.currentConfig = configResult.stdout;
|
|
124
|
+
// Check for existing remote forwarding rules
|
|
125
|
+
// rsyslog forwarding rules can be:
|
|
126
|
+
// *.* @@host:port (TCP)
|
|
127
|
+
// *.* @host:port (UDP)
|
|
128
|
+
// auth.* @@host:port
|
|
129
|
+
const lines = configResult.stdout.split("\n");
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
const trimmed = line.trim();
|
|
132
|
+
if (trimmed.startsWith("#") || trimmed.length === 0)
|
|
133
|
+
continue;
|
|
134
|
+
// Match lines with @@ (TCP forwarding) or @ (UDP forwarding) after facility
|
|
135
|
+
if (/\s@@[^\s]/.test(trimmed) || /\s@[^@\s]/.test(trimmed)) {
|
|
136
|
+
result.existingForwardingRules.push(trimmed);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Check loaded modules
|
|
140
|
+
result.rsyslogModules.imtcp = configResult.stdout.includes("imtcp");
|
|
141
|
+
result.rsyslogModules.imudp = configResult.stdout.includes("imudp");
|
|
142
|
+
}
|
|
143
|
+
// Also check /etc/rsyslog.d/ for forwarding rules
|
|
144
|
+
const rsyslogDResult = await runCommand("cat", ["/etc/rsyslog.d/"], 10_000);
|
|
145
|
+
if (rsyslogDResult.exitCode === 0) {
|
|
146
|
+
const lines = rsyslogDResult.stdout.split("\n");
|
|
147
|
+
for (const line of lines) {
|
|
148
|
+
const trimmed = line.trim();
|
|
149
|
+
if (trimmed.startsWith("#") || trimmed.length === 0)
|
|
150
|
+
continue;
|
|
151
|
+
if (/\s@@[^\s]/.test(trimmed) || /\s@[^@\s]/.test(trimmed)) {
|
|
152
|
+
result.existingForwardingRules.push(trimmed);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Check for TLS support (gtls driver)
|
|
157
|
+
const tlsCheck = await runCommand("dpkg", ["-l", "rsyslog-gnutls"], 10_000);
|
|
158
|
+
result.tlsSupport = tlsCheck.exitCode === 0 && tlsCheck.stdout.includes("ii");
|
|
159
|
+
// Generate recommended config if siem_host provided
|
|
160
|
+
if (siemHost) {
|
|
161
|
+
const forwardPrefix = effectiveProtocol === "udp" ? "@" : "@@";
|
|
162
|
+
const sources = logSources && logSources.length > 0 ? logSources : ["*.*"];
|
|
163
|
+
let config = "# SIEM forwarding configuration\n";
|
|
164
|
+
if (effectiveProtocol === "tls") {
|
|
165
|
+
config += '# TLS configuration\n';
|
|
166
|
+
config += '$DefaultNetstreamDriverCAFile /etc/rsyslog.d/ca.pem\n';
|
|
167
|
+
config += '$DefaultNetstreamDriver gtls\n';
|
|
168
|
+
config += '$ActionSendStreamDriverMode 1\n';
|
|
169
|
+
config += '$ActionSendStreamDriverAuthMode x509/name\n';
|
|
170
|
+
if (!result.tlsSupport) {
|
|
171
|
+
result.recommendations.push("TLS requested but rsyslog-gnutls not installed — install: apt-get install rsyslog-gnutls");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
for (const source of sources) {
|
|
175
|
+
const facility = source === "*.*" ? "*.*" : `${source}.*`;
|
|
176
|
+
config += `${facility} ${forwardPrefix}${siemHost}:${effectivePort}\n`;
|
|
177
|
+
}
|
|
178
|
+
result.recommendedConfig = config;
|
|
179
|
+
}
|
|
180
|
+
// Recommendations
|
|
181
|
+
if (result.existingForwardingRules.length === 0) {
|
|
182
|
+
result.recommendations.push("No remote forwarding rules found — logs are not being forwarded to a SIEM");
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
result.recommendations.push(`Found ${result.existingForwardingRules.length} forwarding rule(s) — verify they target the correct SIEM`);
|
|
186
|
+
}
|
|
187
|
+
if (!result.rsyslogModules.imtcp && effectiveProtocol === "tcp") {
|
|
188
|
+
result.recommendations.push("imtcp module not loaded — TCP reception may not work");
|
|
189
|
+
}
|
|
190
|
+
if (!result.tlsSupport) {
|
|
191
|
+
result.recommendations.push("TLS not available — consider installing rsyslog-gnutls for encrypted log forwarding");
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
async function configureFilebeat(siemHost, siemPort) {
|
|
196
|
+
const result = {
|
|
197
|
+
installed: false,
|
|
198
|
+
version: "unknown",
|
|
199
|
+
enabledModules: [],
|
|
200
|
+
disabledModules: [],
|
|
201
|
+
outputConfig: "",
|
|
202
|
+
serviceStatus: "unknown",
|
|
203
|
+
serviceRunning: false,
|
|
204
|
+
configPath: "/etc/filebeat/filebeat.yml",
|
|
205
|
+
recommendedConfig: "",
|
|
206
|
+
recommendations: [],
|
|
207
|
+
};
|
|
208
|
+
const effectivePort = siemPort ?? 5044;
|
|
209
|
+
// Check if filebeat is installed
|
|
210
|
+
const whichResult = await runCommand("which", ["filebeat"], 10_000);
|
|
211
|
+
if (whichResult.exitCode !== 0) {
|
|
212
|
+
const dpkgResult = await runCommand("dpkg", ["-l", "filebeat"], 10_000);
|
|
213
|
+
if (dpkgResult.exitCode !== 0 || !dpkgResult.stdout.includes("ii")) {
|
|
214
|
+
result.installed = false;
|
|
215
|
+
result.recommendations.push("Filebeat is not installed — install from Elastic repository for log shipping");
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
result.installed = true;
|
|
220
|
+
// Get version
|
|
221
|
+
const versionResult = await runCommand("filebeat", ["version"], 10_000);
|
|
222
|
+
if (versionResult.exitCode === 0) {
|
|
223
|
+
result.version = versionResult.stdout.trim();
|
|
224
|
+
}
|
|
225
|
+
// Read config
|
|
226
|
+
const configResult = await runCommand("cat", ["/etc/filebeat/filebeat.yml"], 10_000);
|
|
227
|
+
if (configResult.exitCode === 0) {
|
|
228
|
+
result.outputConfig = configResult.stdout;
|
|
229
|
+
// Parse output section
|
|
230
|
+
const lines = configResult.stdout.split("\n");
|
|
231
|
+
let inOutput = false;
|
|
232
|
+
for (const line of lines) {
|
|
233
|
+
if (line.match(/^output\./)) {
|
|
234
|
+
inOutput = true;
|
|
235
|
+
}
|
|
236
|
+
if (inOutput && line.trim().length > 0 && !line.startsWith(" ") && !line.startsWith("\t") && !line.match(/^output\./)) {
|
|
237
|
+
inOutput = false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Check enabled modules
|
|
242
|
+
const modulesResult = await runCommand("filebeat", ["modules", "list"], 15_000);
|
|
243
|
+
if (modulesResult.exitCode === 0) {
|
|
244
|
+
const lines = modulesResult.stdout.split("\n");
|
|
245
|
+
let inEnabled = false;
|
|
246
|
+
let inDisabled = false;
|
|
247
|
+
for (const line of lines) {
|
|
248
|
+
const trimmed = line.trim();
|
|
249
|
+
if (trimmed === "Enabled:") {
|
|
250
|
+
inEnabled = true;
|
|
251
|
+
inDisabled = false;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (trimmed === "Disabled:") {
|
|
255
|
+
inEnabled = false;
|
|
256
|
+
inDisabled = true;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (trimmed.length > 0) {
|
|
260
|
+
if (inEnabled)
|
|
261
|
+
result.enabledModules.push(trimmed);
|
|
262
|
+
if (inDisabled)
|
|
263
|
+
result.disabledModules.push(trimmed);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Check service status
|
|
268
|
+
const serviceResult = await runCommand("systemctl", ["status", "filebeat"], 10_000);
|
|
269
|
+
if (serviceResult.exitCode === 0 || serviceResult.exitCode === 3) {
|
|
270
|
+
result.serviceStatus = serviceResult.stdout.trim();
|
|
271
|
+
result.serviceRunning = serviceResult.stdout.includes("active (running)");
|
|
272
|
+
}
|
|
273
|
+
// Generate recommended config if siem_host provided
|
|
274
|
+
if (siemHost) {
|
|
275
|
+
result.recommendedConfig =
|
|
276
|
+
"# Filebeat output configuration for Logstash\n" +
|
|
277
|
+
"output.logstash:\n" +
|
|
278
|
+
` hosts: ["${siemHost}:${effectivePort}"]\n` +
|
|
279
|
+
" ssl.enabled: true\n" +
|
|
280
|
+
" ssl.certificate_authorities: ['/etc/filebeat/ca.pem']\n";
|
|
281
|
+
}
|
|
282
|
+
// Recommendations
|
|
283
|
+
if (!result.serviceRunning) {
|
|
284
|
+
result.recommendations.push("Filebeat service is not running — start with: systemctl start filebeat");
|
|
285
|
+
}
|
|
286
|
+
if (result.enabledModules.length === 0) {
|
|
287
|
+
result.recommendations.push("No Filebeat modules enabled — enable system module: filebeat modules enable system");
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
async function auditForwarding(logSources) {
|
|
292
|
+
const result = {
|
|
293
|
+
rsyslogForwarding: false,
|
|
294
|
+
rsyslogRules: [],
|
|
295
|
+
filebeatRunning: false,
|
|
296
|
+
filebeatStatus: "unknown",
|
|
297
|
+
criticalSourcesCovered: [],
|
|
298
|
+
missingSourcesCount: 0,
|
|
299
|
+
logRotationConfig: "",
|
|
300
|
+
logRotationInterferes: false,
|
|
301
|
+
cisBenchmark: "CIS Benchmark 4.2.1 — Ensure logging is configured to a remote log host",
|
|
302
|
+
cisCompliant: false,
|
|
303
|
+
recommendations: [],
|
|
304
|
+
};
|
|
305
|
+
const sourcesToCheck = logSources && logSources.length > 0 ? logSources : CRITICAL_LOG_SOURCES;
|
|
306
|
+
// Check rsyslog forwarding rules
|
|
307
|
+
const rsyslogConfig = await runCommand("cat", ["/etc/rsyslog.conf"], 10_000);
|
|
308
|
+
if (rsyslogConfig.exitCode === 0) {
|
|
309
|
+
const lines = rsyslogConfig.stdout.split("\n");
|
|
310
|
+
for (const line of lines) {
|
|
311
|
+
const trimmed = line.trim();
|
|
312
|
+
if (trimmed.startsWith("#") || trimmed.length === 0)
|
|
313
|
+
continue;
|
|
314
|
+
// Match lines with @@ (TCP forwarding) or @ (UDP forwarding) after facility
|
|
315
|
+
if (/\s@@[^\s]/.test(trimmed) || /\s@[^@\s]/.test(trimmed)) {
|
|
316
|
+
result.rsyslogRules.push(trimmed);
|
|
317
|
+
result.rsyslogForwarding = true;
|
|
318
|
+
}
|
|
319
|
+
// Also check for action-based forwarding
|
|
320
|
+
if (trimmed.includes("action(type=\"omfwd\"")) {
|
|
321
|
+
result.rsyslogRules.push(trimmed);
|
|
322
|
+
result.rsyslogForwarding = true;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// Check filebeat status
|
|
327
|
+
const filebeatResult = await runCommand("systemctl", ["status", "filebeat"], 10_000);
|
|
328
|
+
if (filebeatResult.exitCode === 0 || filebeatResult.exitCode === 3) {
|
|
329
|
+
result.filebeatStatus = filebeatResult.stdout.trim();
|
|
330
|
+
result.filebeatRunning = filebeatResult.stdout.includes("active (running)");
|
|
331
|
+
}
|
|
332
|
+
// Check coverage of critical log sources
|
|
333
|
+
const configContent = rsyslogConfig.exitCode === 0 ? rsyslogConfig.stdout : "";
|
|
334
|
+
for (const source of sourcesToCheck) {
|
|
335
|
+
const logPath = LOG_SOURCE_FILES[source] ?? `/var/log/${source}.log`;
|
|
336
|
+
let forwarded = false;
|
|
337
|
+
// Check if rsyslog has a forwarding rule that covers this source
|
|
338
|
+
if (result.rsyslogForwarding) {
|
|
339
|
+
// Wildcard rule covers everything
|
|
340
|
+
if (result.rsyslogRules.some((r) => r.includes("*.*"))) {
|
|
341
|
+
forwarded = true;
|
|
342
|
+
}
|
|
343
|
+
// Specific facility rule
|
|
344
|
+
if (result.rsyslogRules.some((r) => r.includes(`${source}.*`))) {
|
|
345
|
+
forwarded = true;
|
|
346
|
+
}
|
|
347
|
+
// Check if config mentions this source with forwarding
|
|
348
|
+
if (configContent.includes(source) && result.rsyslogForwarding) {
|
|
349
|
+
forwarded = true;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Filebeat covers log sources via modules
|
|
353
|
+
if (result.filebeatRunning) {
|
|
354
|
+
forwarded = true; // Filebeat with system module covers standard logs
|
|
355
|
+
}
|
|
356
|
+
result.criticalSourcesCovered.push({ source, forwarded, path: logPath });
|
|
357
|
+
if (!forwarded) {
|
|
358
|
+
result.missingSourcesCount++;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Check log rotation config
|
|
362
|
+
const logrotateResult = await runCommand("cat", ["/etc/logrotate.d/rsyslog"], 10_000);
|
|
363
|
+
if (logrotateResult.exitCode === 0) {
|
|
364
|
+
result.logRotationConfig = logrotateResult.stdout;
|
|
365
|
+
// Check for sharedscripts and postrotate that restarts rsyslog
|
|
366
|
+
if (!logrotateResult.stdout.includes("sharedscripts") ||
|
|
367
|
+
!logrotateResult.stdout.includes("postrotate")) {
|
|
368
|
+
result.logRotationInterferes = true;
|
|
369
|
+
result.recommendations.push("Log rotation may interfere with forwarding — ensure sharedscripts and postrotate with rsyslog reload are configured");
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// CIS compliance check
|
|
373
|
+
result.cisCompliant = result.rsyslogForwarding || result.filebeatRunning;
|
|
374
|
+
// Recommendations
|
|
375
|
+
if (!result.rsyslogForwarding && !result.filebeatRunning) {
|
|
376
|
+
result.recommendations.push("CRITICAL: No log forwarding configured — logs are not being sent to a remote SIEM");
|
|
377
|
+
result.recommendations.push("Configure rsyslog forwarding or install Filebeat for centralized logging");
|
|
378
|
+
}
|
|
379
|
+
if (result.missingSourcesCount > 0) {
|
|
380
|
+
const missing = result.criticalSourcesCovered
|
|
381
|
+
.filter((s) => !s.forwarded)
|
|
382
|
+
.map((s) => s.source);
|
|
383
|
+
result.recommendations.push(`Missing forwarding for ${result.missingSourcesCount} critical source(s): ${missing.join(", ")}`);
|
|
384
|
+
}
|
|
385
|
+
if (!result.cisCompliant) {
|
|
386
|
+
result.recommendations.push(`Non-compliant with ${result.cisBenchmark}`);
|
|
387
|
+
}
|
|
388
|
+
return result;
|
|
389
|
+
}
|
|
390
|
+
async function testConnectivity(siemHost, siemPort, protocol) {
|
|
391
|
+
const effectivePort = siemPort ?? 514;
|
|
392
|
+
const effectiveProtocol = protocol ?? "tcp";
|
|
393
|
+
const result = {
|
|
394
|
+
siemHost,
|
|
395
|
+
siemPort: effectivePort,
|
|
396
|
+
protocol: effectiveProtocol,
|
|
397
|
+
tcpConnectivity: false,
|
|
398
|
+
tcpMessage: "not tested",
|
|
399
|
+
tlsVerification: false,
|
|
400
|
+
tlsMessage: "not tested",
|
|
401
|
+
dnsResolution: false,
|
|
402
|
+
dnsResult: "not tested",
|
|
403
|
+
firewallStatus: "not checked",
|
|
404
|
+
firewallBlocked: false,
|
|
405
|
+
testMessageSent: false,
|
|
406
|
+
testMessageResult: "not attempted",
|
|
407
|
+
recommendations: [],
|
|
408
|
+
};
|
|
409
|
+
// Test DNS resolution
|
|
410
|
+
const digResult = await runCommand("dig", [siemHost], 15_000);
|
|
411
|
+
if (digResult.exitCode === 0 && digResult.stdout.includes("ANSWER SECTION")) {
|
|
412
|
+
result.dnsResolution = true;
|
|
413
|
+
result.dnsResult = "resolved successfully";
|
|
414
|
+
}
|
|
415
|
+
else if (digResult.exitCode === 0) {
|
|
416
|
+
result.dnsResolution = false;
|
|
417
|
+
result.dnsResult = "DNS query returned no results";
|
|
418
|
+
result.recommendations.push(`DNS resolution failed for ${siemHost} — verify hostname is correct`);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
result.dnsResolution = false;
|
|
422
|
+
result.dnsResult = digResult.stderr || "dig command failed";
|
|
423
|
+
result.recommendations.push("dig command not available — install dnsutils for DNS testing");
|
|
424
|
+
}
|
|
425
|
+
// Test TCP connectivity
|
|
426
|
+
const ncResult = await runCommand("nc", ["-z", "-w", "5", siemHost, String(effectivePort)], 15_000);
|
|
427
|
+
if (ncResult.exitCode === 0) {
|
|
428
|
+
result.tcpConnectivity = true;
|
|
429
|
+
result.tcpMessage = `TCP connection to ${siemHost}:${effectivePort} successful`;
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
result.tcpConnectivity = false;
|
|
433
|
+
result.tcpMessage = `TCP connection to ${siemHost}:${effectivePort} failed`;
|
|
434
|
+
result.recommendations.push(`Cannot reach ${siemHost}:${effectivePort} — check network connectivity and firewall rules`);
|
|
435
|
+
}
|
|
436
|
+
// Test TLS if protocol is TLS
|
|
437
|
+
if (effectiveProtocol === "tls") {
|
|
438
|
+
const opensslResult = await runCommand("openssl", ["s_client", "-connect", `${siemHost}:${effectivePort}`, "-brief"], 15_000);
|
|
439
|
+
if (opensslResult.exitCode === 0 && opensslResult.stdout.includes("Verification")) {
|
|
440
|
+
result.tlsVerification = true;
|
|
441
|
+
result.tlsMessage = "TLS handshake successful";
|
|
442
|
+
}
|
|
443
|
+
else if (opensslResult.exitCode === 0) {
|
|
444
|
+
result.tlsVerification = false;
|
|
445
|
+
result.tlsMessage = opensslResult.stderr || "TLS handshake completed with warnings";
|
|
446
|
+
result.recommendations.push("TLS certificate verification may have issues — check CA certificates");
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
result.tlsVerification = false;
|
|
450
|
+
result.tlsMessage = opensslResult.stderr || "TLS handshake failed";
|
|
451
|
+
result.recommendations.push("TLS connection failed — verify the SIEM supports TLS on this port");
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// Check firewall rules for the port
|
|
455
|
+
const iptablesResult = await runCommand("iptables", ["-L", "-n"], 10_000);
|
|
456
|
+
if (iptablesResult.exitCode === 0) {
|
|
457
|
+
const lines = iptablesResult.stdout.split("\n");
|
|
458
|
+
const portStr = String(effectivePort);
|
|
459
|
+
for (const line of lines) {
|
|
460
|
+
if (line.includes(portStr)) {
|
|
461
|
+
result.firewallStatus = line.trim();
|
|
462
|
+
if (line.includes("DROP") || line.includes("REJECT")) {
|
|
463
|
+
result.firewallBlocked = true;
|
|
464
|
+
result.recommendations.push(`Firewall rule blocking port ${effectivePort} detected — update iptables to allow SIEM traffic`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (result.firewallStatus === "not checked") {
|
|
469
|
+
result.firewallStatus = `No specific rules found for port ${effectivePort}`;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
result.firewallStatus = "iptables not accessible (may need root)";
|
|
474
|
+
}
|
|
475
|
+
// Send test syslog message
|
|
476
|
+
const loggerResult = await runCommand("logger", ["-n", siemHost, "-P", String(effectivePort), "--tcp", "kali-defense SIEM connectivity test"], 15_000);
|
|
477
|
+
if (loggerResult.exitCode === 0) {
|
|
478
|
+
result.testMessageSent = true;
|
|
479
|
+
result.testMessageResult = "Test syslog message sent successfully";
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
result.testMessageSent = false;
|
|
483
|
+
result.testMessageResult = loggerResult.stderr || "Failed to send test message";
|
|
484
|
+
if (loggerResult.stderr?.includes("not found")) {
|
|
485
|
+
result.recommendations.push("logger command not available — install bsdutils for syslog testing");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// Overall connectivity assessment
|
|
489
|
+
if (!result.tcpConnectivity && !result.dnsResolution) {
|
|
490
|
+
result.recommendations.push("CRITICAL: SIEM endpoint is unreachable — verify host, port, and network path");
|
|
491
|
+
}
|
|
492
|
+
else if (!result.tcpConnectivity) {
|
|
493
|
+
result.recommendations.push("DNS resolves but TCP connection fails — check if SIEM is listening on the specified port");
|
|
494
|
+
}
|
|
495
|
+
return result;
|
|
496
|
+
}
|
|
497
|
+
// ── Registration entry point ───────────────────────────────────────────────
|
|
498
|
+
export function registerSiemIntegrationTools(server) {
|
|
499
|
+
server.tool("siem_export", "SIEM integration: configure syslog forwarding, audit Filebeat, comprehensive log forwarding audit with CIS benchmarks, and test SIEM endpoint connectivity.", {
|
|
500
|
+
action: z
|
|
501
|
+
.enum(["configure_syslog_forward", "configure_filebeat", "audit_forwarding", "test_connectivity"])
|
|
502
|
+
.describe("Action: configure_syslog_forward=audit/configure rsyslog remote forwarding, configure_filebeat=audit Filebeat configuration, audit_forwarding=comprehensive log forwarding audit, test_connectivity=test SIEM endpoint connectivity"),
|
|
503
|
+
siem_host: z
|
|
504
|
+
.string()
|
|
505
|
+
.optional()
|
|
506
|
+
.describe("SIEM server hostname or IP address"),
|
|
507
|
+
siem_port: z
|
|
508
|
+
.number()
|
|
509
|
+
.optional()
|
|
510
|
+
.describe("SIEM server port (default 514 for syslog, 5044 for filebeat)"),
|
|
511
|
+
protocol: z
|
|
512
|
+
.enum(["tcp", "udp", "tls"])
|
|
513
|
+
.optional()
|
|
514
|
+
.default("tcp")
|
|
515
|
+
.describe("Transport protocol (default tcp)"),
|
|
516
|
+
log_sources: z
|
|
517
|
+
.array(z.string())
|
|
518
|
+
.optional()
|
|
519
|
+
.describe("Log sources to forward (e.g., auth, syslog, kern, audit)"),
|
|
520
|
+
output_format: z
|
|
521
|
+
.enum(["text", "json"])
|
|
522
|
+
.optional()
|
|
523
|
+
.default("text")
|
|
524
|
+
.describe("Output format (default text)"),
|
|
525
|
+
}, async (params) => {
|
|
526
|
+
const { action } = params;
|
|
527
|
+
const outputFormat = params.output_format ?? "text";
|
|
528
|
+
switch (action) {
|
|
529
|
+
// ── configure_syslog_forward ─────────────────────────────────────
|
|
530
|
+
case "configure_syslog_forward": {
|
|
531
|
+
try {
|
|
532
|
+
const syslog = await configureSyslogForward(params.siem_host, params.siem_port, params.protocol, params.log_sources);
|
|
533
|
+
const output = {
|
|
534
|
+
action: "configure_syslog_forward",
|
|
535
|
+
syslogDaemon: syslog.syslogDaemon,
|
|
536
|
+
daemonInstalled: syslog.daemonInstalled,
|
|
537
|
+
existingForwardingRules: syslog.existingForwardingRules,
|
|
538
|
+
rsyslogModules: syslog.rsyslogModules,
|
|
539
|
+
tlsSupport: syslog.tlsSupport,
|
|
540
|
+
recommendedConfig: syslog.recommendedConfig,
|
|
541
|
+
recommendations: syslog.recommendations,
|
|
542
|
+
};
|
|
543
|
+
if (outputFormat === "json") {
|
|
544
|
+
return { content: [formatToolOutput(output)] };
|
|
545
|
+
}
|
|
546
|
+
let text = "SIEM Integration — Syslog Forwarding Configuration\n\n";
|
|
547
|
+
text += `Syslog Daemon: ${syslog.syslogDaemon}\n`;
|
|
548
|
+
text += `Daemon Installed: ${syslog.daemonInstalled ? "yes" : "no"}\n`;
|
|
549
|
+
text += `TLS Support: ${syslog.tlsSupport ? "available" : "not available"}\n`;
|
|
550
|
+
text += `Modules — imtcp: ${syslog.rsyslogModules.imtcp ? "loaded" : "not loaded"}, imudp: ${syslog.rsyslogModules.imudp ? "loaded" : "not loaded"}\n`;
|
|
551
|
+
if (syslog.existingForwardingRules.length > 0) {
|
|
552
|
+
text += `\nExisting Forwarding Rules (${syslog.existingForwardingRules.length}):\n`;
|
|
553
|
+
for (const rule of syslog.existingForwardingRules) {
|
|
554
|
+
text += ` • ${rule}\n`;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
text += "\nExisting Forwarding Rules: none\n";
|
|
559
|
+
}
|
|
560
|
+
if (syslog.recommendedConfig) {
|
|
561
|
+
text += `\nRecommended Configuration:\n${syslog.recommendedConfig}\n`;
|
|
562
|
+
}
|
|
563
|
+
if (syslog.recommendations.length > 0) {
|
|
564
|
+
text += "\nRecommendations:\n";
|
|
565
|
+
for (const rec of syslog.recommendations) {
|
|
566
|
+
text += ` • ${rec}\n`;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return { content: [createTextContent(text)] };
|
|
570
|
+
}
|
|
571
|
+
catch (err) {
|
|
572
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
573
|
+
return { content: [createErrorContent(`configure_syslog_forward failed: ${msg}`)], isError: true };
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
// ── configure_filebeat ───────────────────────────────────────────
|
|
577
|
+
case "configure_filebeat": {
|
|
578
|
+
try {
|
|
579
|
+
const filebeat = await configureFilebeat(params.siem_host, params.siem_port);
|
|
580
|
+
const output = {
|
|
581
|
+
action: "configure_filebeat",
|
|
582
|
+
installed: filebeat.installed,
|
|
583
|
+
version: filebeat.version,
|
|
584
|
+
enabledModules: filebeat.enabledModules,
|
|
585
|
+
disabledModules: filebeat.disabledModules,
|
|
586
|
+
serviceRunning: filebeat.serviceRunning,
|
|
587
|
+
serviceStatus: filebeat.serviceStatus,
|
|
588
|
+
configPath: filebeat.configPath,
|
|
589
|
+
recommendedConfig: filebeat.recommendedConfig,
|
|
590
|
+
recommendations: filebeat.recommendations,
|
|
591
|
+
};
|
|
592
|
+
if (outputFormat === "json") {
|
|
593
|
+
return { content: [formatToolOutput(output)] };
|
|
594
|
+
}
|
|
595
|
+
let text = "SIEM Integration — Filebeat Configuration\n\n";
|
|
596
|
+
text += `Installed: ${filebeat.installed ? "yes" : "no"}\n`;
|
|
597
|
+
if (!filebeat.installed) {
|
|
598
|
+
text += "\nFilebeat is not installed.\n";
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
text += `Version: ${filebeat.version}\n`;
|
|
602
|
+
text += `Service Running: ${filebeat.serviceRunning ? "yes ✓" : "no ⚠"}\n`;
|
|
603
|
+
text += `Config Path: ${filebeat.configPath}\n`;
|
|
604
|
+
if (filebeat.enabledModules.length > 0) {
|
|
605
|
+
text += `\nEnabled Modules (${filebeat.enabledModules.length}):\n`;
|
|
606
|
+
for (const mod of filebeat.enabledModules) {
|
|
607
|
+
text += ` • ${mod}\n`;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
text += "\nEnabled Modules: none\n";
|
|
612
|
+
}
|
|
613
|
+
if (filebeat.disabledModules.length > 0) {
|
|
614
|
+
text += `\nDisabled Modules (${filebeat.disabledModules.length}):\n`;
|
|
615
|
+
for (const mod of filebeat.disabledModules.slice(0, 10)) {
|
|
616
|
+
text += ` • ${mod}\n`;
|
|
617
|
+
}
|
|
618
|
+
if (filebeat.disabledModules.length > 10) {
|
|
619
|
+
text += ` ... and ${filebeat.disabledModules.length - 10} more\n`;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (filebeat.recommendedConfig) {
|
|
624
|
+
text += `\nRecommended Output Configuration:\n${filebeat.recommendedConfig}\n`;
|
|
625
|
+
}
|
|
626
|
+
if (filebeat.recommendations.length > 0) {
|
|
627
|
+
text += "\nRecommendations:\n";
|
|
628
|
+
for (const rec of filebeat.recommendations) {
|
|
629
|
+
text += ` • ${rec}\n`;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return { content: [createTextContent(text)] };
|
|
633
|
+
}
|
|
634
|
+
catch (err) {
|
|
635
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
636
|
+
return { content: [createErrorContent(`configure_filebeat failed: ${msg}`)], isError: true };
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
// ── audit_forwarding ─────────────────────────────────────────────
|
|
640
|
+
case "audit_forwarding": {
|
|
641
|
+
try {
|
|
642
|
+
const audit = await auditForwarding(params.log_sources);
|
|
643
|
+
const output = {
|
|
644
|
+
action: "audit_forwarding",
|
|
645
|
+
rsyslogForwarding: audit.rsyslogForwarding,
|
|
646
|
+
rsyslogRules: audit.rsyslogRules,
|
|
647
|
+
filebeatRunning: audit.filebeatRunning,
|
|
648
|
+
criticalSourcesCovered: audit.criticalSourcesCovered,
|
|
649
|
+
missingSourcesCount: audit.missingSourcesCount,
|
|
650
|
+
logRotationInterferes: audit.logRotationInterferes,
|
|
651
|
+
cisBenchmark: audit.cisBenchmark,
|
|
652
|
+
cisCompliant: audit.cisCompliant,
|
|
653
|
+
recommendations: audit.recommendations,
|
|
654
|
+
};
|
|
655
|
+
if (outputFormat === "json") {
|
|
656
|
+
return { content: [formatToolOutput(output)] };
|
|
657
|
+
}
|
|
658
|
+
let text = "SIEM Integration — Log Forwarding Audit\n\n";
|
|
659
|
+
text += `CIS Reference: ${audit.cisBenchmark}\n`;
|
|
660
|
+
text += `CIS Compliant: ${audit.cisCompliant ? "YES ✓" : "NO ⚠"}\n\n`;
|
|
661
|
+
text += `Rsyslog Forwarding: ${audit.rsyslogForwarding ? "configured" : "not configured"}\n`;
|
|
662
|
+
text += `Filebeat Running: ${audit.filebeatRunning ? "yes" : "no"}\n`;
|
|
663
|
+
if (audit.rsyslogRules.length > 0) {
|
|
664
|
+
text += `\nRsyslog Forwarding Rules (${audit.rsyslogRules.length}):\n`;
|
|
665
|
+
for (const rule of audit.rsyslogRules) {
|
|
666
|
+
text += ` • ${rule}\n`;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
text += `\nCritical Log Source Coverage:\n`;
|
|
670
|
+
for (const source of audit.criticalSourcesCovered) {
|
|
671
|
+
text += ` • ${source.source} (${source.path}): ${source.forwarded ? "forwarded ✓" : "NOT forwarded ⚠"}\n`;
|
|
672
|
+
}
|
|
673
|
+
if (audit.missingSourcesCount > 0) {
|
|
674
|
+
text += `\n⚠ Missing Sources: ${audit.missingSourcesCount}\n`;
|
|
675
|
+
}
|
|
676
|
+
if (audit.logRotationInterferes) {
|
|
677
|
+
text += "\n⚠ Log rotation may interfere with forwarding\n";
|
|
678
|
+
}
|
|
679
|
+
if (audit.recommendations.length > 0) {
|
|
680
|
+
text += "\nRecommendations:\n";
|
|
681
|
+
for (const rec of audit.recommendations) {
|
|
682
|
+
text += ` • ${rec}\n`;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return { content: [createTextContent(text)] };
|
|
686
|
+
}
|
|
687
|
+
catch (err) {
|
|
688
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
689
|
+
return { content: [createErrorContent(`audit_forwarding failed: ${msg}`)], isError: true };
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
// ── test_connectivity ────────────────────────────────────────────
|
|
693
|
+
case "test_connectivity": {
|
|
694
|
+
try {
|
|
695
|
+
if (!params.siem_host) {
|
|
696
|
+
return {
|
|
697
|
+
content: [createErrorContent("test_connectivity requires siem_host parameter")],
|
|
698
|
+
isError: true,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
if (!validateSiemHost(params.siem_host)) {
|
|
702
|
+
return {
|
|
703
|
+
content: [createErrorContent(`Invalid siem_host format: ${params.siem_host}`)],
|
|
704
|
+
isError: true,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
const connectivity = await testConnectivity(params.siem_host, params.siem_port, params.protocol);
|
|
708
|
+
const output = {
|
|
709
|
+
action: "test_connectivity",
|
|
710
|
+
siemHost: connectivity.siemHost,
|
|
711
|
+
siemPort: connectivity.siemPort,
|
|
712
|
+
protocol: connectivity.protocol,
|
|
713
|
+
tcpConnectivity: connectivity.tcpConnectivity,
|
|
714
|
+
tcpMessage: connectivity.tcpMessage,
|
|
715
|
+
tlsVerification: connectivity.tlsVerification,
|
|
716
|
+
tlsMessage: connectivity.tlsMessage,
|
|
717
|
+
dnsResolution: connectivity.dnsResolution,
|
|
718
|
+
dnsResult: connectivity.dnsResult,
|
|
719
|
+
firewallStatus: connectivity.firewallStatus,
|
|
720
|
+
firewallBlocked: connectivity.firewallBlocked,
|
|
721
|
+
testMessageSent: connectivity.testMessageSent,
|
|
722
|
+
testMessageResult: connectivity.testMessageResult,
|
|
723
|
+
recommendations: connectivity.recommendations,
|
|
724
|
+
};
|
|
725
|
+
if (outputFormat === "json") {
|
|
726
|
+
return { content: [formatToolOutput(output)] };
|
|
727
|
+
}
|
|
728
|
+
let text = "SIEM Integration — Connectivity Test\n\n";
|
|
729
|
+
text += `Target: ${connectivity.siemHost}:${connectivity.siemPort} (${connectivity.protocol})\n\n`;
|
|
730
|
+
text += `DNS Resolution: ${connectivity.dnsResolution ? "✓ " : "✗ "}${connectivity.dnsResult}\n`;
|
|
731
|
+
text += `TCP Connectivity: ${connectivity.tcpConnectivity ? "✓ " : "✗ "}${connectivity.tcpMessage}\n`;
|
|
732
|
+
if (connectivity.protocol === "tls") {
|
|
733
|
+
text += `TLS Verification: ${connectivity.tlsVerification ? "✓ " : "✗ "}${connectivity.tlsMessage}\n`;
|
|
734
|
+
}
|
|
735
|
+
text += `Firewall: ${connectivity.firewallBlocked ? "⚠ BLOCKED" : connectivity.firewallStatus}\n`;
|
|
736
|
+
text += `Test Message: ${connectivity.testMessageSent ? "✓ " : "✗ "}${connectivity.testMessageResult}\n`;
|
|
737
|
+
if (connectivity.recommendations.length > 0) {
|
|
738
|
+
text += "\nRecommendations:\n";
|
|
739
|
+
for (const rec of connectivity.recommendations) {
|
|
740
|
+
text += ` • ${rec}\n`;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return { content: [createTextContent(text)] };
|
|
744
|
+
}
|
|
745
|
+
catch (err) {
|
|
746
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
747
|
+
return { content: [createErrorContent(`test_connectivity failed: ${msg}`)], isError: true };
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
default:
|
|
751
|
+
return { content: [createErrorContent(`Unknown action: ${action}`)], isError: true };
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
}
|