feat: Setup new network log analysis tool with CSV parsing and PDF generation.
This commit is contained in:
250
analyzer.js
Normal file
250
analyzer.js
Normal file
@@ -0,0 +1,250 @@
|
||||
const fs = require('fs');
|
||||
const csv = require('csv-parser');
|
||||
const PDFDocument = require('pdfkit');
|
||||
|
||||
const inputFile = process.argv[2];
|
||||
|
||||
if (!inputFile) {
|
||||
console.error('Please provide a log file path as an argument.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const stats = {
|
||||
totalEvents: 0,
|
||||
severityCounts: {},
|
||||
dropEvents: 0,
|
||||
dropBySrcIP: {},
|
||||
thermalWarnings: 0,
|
||||
possibleAttacks: 0,
|
||||
attackDetails: [],
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
timeline: {} // Format: "YYYY-MM-DD HH:00": { Info: 0, Error: 0, ... }
|
||||
};
|
||||
|
||||
const THREAT_KEYWORDS = ['attack', 'exploit', 'malware', 'bad password', 'auth fail', 'intrusion'];
|
||||
|
||||
console.log(`Starting analysis of ${inputFile}...`);
|
||||
|
||||
// Helper to update timeline
|
||||
function updateTimeline(dateStr, timeStr, severity) {
|
||||
try {
|
||||
// Assuming Date format MM/DD/YYYY and Time HH:MM:SS
|
||||
const hourKey = `${dateStr} ${timeStr.split(':')[0]}:00`;
|
||||
if (!stats.timeline[hourKey]) stats.timeline[hourKey] = {};
|
||||
stats.timeline[hourKey][severity] = (stats.timeline[hourKey][severity] || 0) + 1;
|
||||
|
||||
// Track start/end time
|
||||
if (!stats.startTime) stats.startTime = `${dateStr} ${timeStr}`;
|
||||
stats.endTime = `${dateStr} ${timeStr}`;
|
||||
} catch (e) {
|
||||
// Ignore parsing errors for timeline
|
||||
}
|
||||
}
|
||||
|
||||
fs.createReadStream(inputFile)
|
||||
.pipe(csv({ skipLines: 1 }))
|
||||
.on('data', (row) => {
|
||||
stats.totalEvents++;
|
||||
const level = (row['Level'] || 'Unknown').trim();
|
||||
const message = (row['Messages'] || '').toString();
|
||||
const date = row['Date'];
|
||||
const time = row['Time'];
|
||||
|
||||
// Stats
|
||||
stats.severityCounts[level] = (stats.severityCounts[level] || 0) + 1;
|
||||
updateTimeline(date, time, level);
|
||||
|
||||
// Security Rules
|
||||
if (message.includes('DROP')) {
|
||||
stats.dropEvents++;
|
||||
const srcMatch = message.match(/SRC=([\d\.]+)/);
|
||||
if (srcMatch && srcMatch[1]) {
|
||||
const ip = srcMatch[1];
|
||||
stats.dropBySrcIP[ip] = (stats.dropBySrcIP[ip] || 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (level === 'Emergency' && message.includes('THERMAL')) {
|
||||
stats.thermalWarnings++;
|
||||
}
|
||||
|
||||
const lowerMsg = message.toLowerCase();
|
||||
for (const word of THREAT_KEYWORDS) {
|
||||
if (lowerMsg.includes(word)) {
|
||||
stats.possibleAttacks++;
|
||||
// Increase capture limit for HTML report
|
||||
if (stats.possibleAttacks <= 100) {
|
||||
stats.attackDetails.push({ date, time, level, msg: message });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.on('end', () => {
|
||||
generateReports();
|
||||
});
|
||||
|
||||
function generateReports() {
|
||||
const reportBase = inputFile.replace(/\.csv$/i, '');
|
||||
generatePDFReport(`${reportBase}.report.pdf`);
|
||||
generateHTMLReport(`${reportBase}.report.html`);
|
||||
console.log('\nAnalysis Complete.');
|
||||
console.log(`PDF Report: ${reportBase}.report.pdf`);
|
||||
console.log(`HTML Report: ${reportBase}.report.html`);
|
||||
}
|
||||
|
||||
function generatePDFReport(filename) {
|
||||
const doc = new PDFDocument();
|
||||
doc.pipe(fs.createWriteStream(filename));
|
||||
|
||||
// Title
|
||||
doc.fontSize(20).text('ASUS Log Security Analysis', { align: 'center' });
|
||||
doc.moveDown();
|
||||
|
||||
// Summary
|
||||
doc.fontSize(12).text(`Analyzed File: ${inputFile}`);
|
||||
doc.text(`Time Period: ${stats.startTime} to ${stats.endTime}`);
|
||||
doc.text(`Total Log Lines: ${stats.totalEvents.toLocaleString()}`);
|
||||
doc.moveDown();
|
||||
|
||||
// Key Stats
|
||||
doc.fontSize(14).text('Security Summary', { underline: true });
|
||||
doc.fontSize(12).text(`Total Firewall Drops: ${stats.dropEvents.toLocaleString()}`);
|
||||
doc.text(`Potential Threats: ${stats.possibleAttacks.toLocaleString()}`);
|
||||
doc.text(`Thermal Warnings: ${stats.thermalWarnings}`);
|
||||
doc.moveDown();
|
||||
|
||||
// Chart: Severity Breakdown (Simple Bar Chart)
|
||||
doc.addPage();
|
||||
doc.fontSize(14).text('Severity Breakdown', { align: 'center' });
|
||||
doc.moveDown();
|
||||
|
||||
const severities = Object.entries(stats.severityCounts).sort(([,a], [,b]) => b - a);
|
||||
const maxVal = Math.max(...Object.values(stats.severityCounts));
|
||||
const startX = 50;
|
||||
let startY = 150;
|
||||
const barHeight = 20;
|
||||
const maxBarWidth = 400;
|
||||
|
||||
severities.forEach(([label, count]) => {
|
||||
const width = (count / maxVal) * maxBarWidth;
|
||||
doc.rect(startX + 100, startY, width, barHeight).fill('blue');
|
||||
doc.fillColor('black').text(label, startX, startY + 5);
|
||||
doc.text(count.toLocaleString(), startX + 100 + width + 5, startY + 5);
|
||||
startY += 30;
|
||||
});
|
||||
|
||||
// Table: Top Blocked IPs
|
||||
doc.addPage();
|
||||
doc.fontSize(14).text('Top 10 Blocked Source IPs', { underline: true });
|
||||
doc.moveDown();
|
||||
|
||||
const topIPs = Object.entries(stats.dropBySrcIP).sort(([,a], [,b]) => b - a).slice(0, 10);
|
||||
topIPs.forEach(([ip, count], i) => {
|
||||
doc.fontSize(12).text(`${i+1}. ${ip}: ${count.toLocaleString()} drops`);
|
||||
});
|
||||
|
||||
doc.end();
|
||||
}
|
||||
|
||||
function generateHTMLReport(filename) {
|
||||
// Aggregating timeline for Chart.js
|
||||
const labels = Object.keys(stats.timeline).sort();
|
||||
const datasets = [];
|
||||
|
||||
// Check which severities exist
|
||||
const allSeverities = new Set();
|
||||
labels.forEach(t => Object.keys(stats.timeline[t]).forEach(s => allSeverities.add(s)));
|
||||
|
||||
const colors = { 'Info': '#36a2eb', 'Warning': '#ffcd56', 'Error': '#ff6384', 'Emergency': '#c45850' };
|
||||
|
||||
allSeverities.forEach(sev => {
|
||||
const data = labels.map(t => stats.timeline[t][sev] || 0);
|
||||
datasets.push({
|
||||
label: sev,
|
||||
data: data,
|
||||
borderColor: colors[sev] || '#999',
|
||||
fill: false
|
||||
});
|
||||
});
|
||||
|
||||
const htmlContent = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Log Analysis Report</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<style>
|
||||
body { font-family: sans-serif; margin: 20px; }
|
||||
.card { border: 1px solid #ccc; padding: 15px; margin-bottom: 20px; border-radius: 5px; }
|
||||
.chart-container { position: relative; height: 400px; width: 100%; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||||
th { background-color: #f2f2f2; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Log Analysis Report</h1>
|
||||
<div class="card">
|
||||
<h2>Summary</h2>
|
||||
<p><strong>File:</strong> ${inputFile}</p>
|
||||
<p><strong>Period:</strong> ${stats.startTime} - ${stats.endTime}</p>
|
||||
<p><strong>Total Events:</strong> ${stats.totalEvents.toLocaleString()}</p>
|
||||
<p><strong>Firewall Drops:</strong> ${stats.dropEvents.toLocaleString()}</p>
|
||||
<p><strong>Threats:</strong> ${stats.possibleAttacks}</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Event Timeline</h2>
|
||||
<div class="chart-container">
|
||||
<canvas id="timelineChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Recent Threat Details (Sample)</h2>
|
||||
<input type="text" id="filterInput" onkeyup="filterTable()" placeholder="Search threats...">
|
||||
<table id="threatTable">
|
||||
<thead><tr><th>Date</th><th>Time</th><th>Level</th><th>Message</th></tr></thead>
|
||||
<tbody>
|
||||
${stats.attackDetails.map(d => `<tr><td>${d.date}</td><td>${d.time}</td><td>${d.level}</td><td>${d.msg}</td></tr>`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const ctx = document.getElementById('timelineChart').getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ${JSON.stringify(labels)},
|
||||
datasets: ${JSON.stringify(datasets)}
|
||||
},
|
||||
options: { responsive: true, maintainAspectRatio: false }
|
||||
});
|
||||
|
||||
function filterTable() {
|
||||
var input, filter, table, tr, td, i, txtValue;
|
||||
input = document.getElementById("filterInput");
|
||||
filter = input.value.toUpperCase();
|
||||
table = document.getElementById("threatTable");
|
||||
tr = table.getElementsByTagName("tr");
|
||||
for (i = 0; i < tr.length; i++) {
|
||||
td = tr[i].getElementsByTagName("td")[3]; // Message column
|
||||
if (td) {
|
||||
txtValue = td.textContent || td.innerText;
|
||||
if (txtValue.toUpperCase().indexOf(filter) > -1) {
|
||||
tr[i].style.display = "";
|
||||
} else {
|
||||
tr[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
fs.writeFileSync(filename, htmlContent);
|
||||
}
|
||||
Reference in New Issue
Block a user