- Fixed TypeScript errors and build issues - Added comprehensive README.md with documentation - Created .env.example with all configuration options - Improved .gitignore with CSF-specific entries - Added VS Code configuration for development - Fixed next.config.mjs configuration - Corrected API route type issues - Added CHANGELOG.md with version history - All components now compile without errors - Ready for production deployment Features included: - Modern web interface for CSF firewall management - Real-time monitoring with WebSockets - JWT authentication system - Complete API for CSF control - Responsive UI with Tailwind CSS - TypeScript support throughout - Docker-ready configuration Signed-off-by: ale <ale@manalejandro.com>
328 líneas
9.1 KiB
TypeScript
328 líneas
9.1 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { exec } from 'child_process'
|
|
import { promisify } from 'util'
|
|
import fs from 'fs/promises'
|
|
|
|
const execAsync = promisify(exec)
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const { searchParams } = new URL(request.url)
|
|
const type = searchParams.get('type')
|
|
const limit = parseInt(searchParams.get('limit') || '100')
|
|
const since = searchParams.get('since') || undefined // timestamp
|
|
|
|
try {
|
|
switch (type) {
|
|
case 'firewall':
|
|
return await getFirewallLogs(limit, since)
|
|
|
|
case 'lfd':
|
|
return await getLfdLogs(limit, since)
|
|
|
|
case 'system':
|
|
return await getSystemLogs(limit, since)
|
|
|
|
case 'blocked':
|
|
return await getBlockedIPs(limit)
|
|
|
|
case 'connections':
|
|
return await getActiveConnections()
|
|
|
|
default:
|
|
return await getAllLogs(limit, since)
|
|
}
|
|
|
|
} catch (error: any) {
|
|
console.error('Logs API error:', error)
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: `Failed to retrieve logs: ${error.message}`
|
|
}, { status: 500 })
|
|
}
|
|
}
|
|
|
|
async function getFirewallLogs(limit: number, since?: string) {
|
|
try {
|
|
// Get iptables logs from syslog
|
|
let command = `grep "CSF\\|iptables" /var/log/syslog | tail -${limit}`
|
|
if (since) {
|
|
const sinceDate = new Date(since).toISOString().split('T')[0]
|
|
command = `grep "CSF\\|iptables" /var/log/syslog | grep "${sinceDate}" | tail -${limit}`
|
|
}
|
|
|
|
const { stdout } = await execAsync(command)
|
|
const logs = parseFirewallLogs(stdout)
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
data: {
|
|
logs,
|
|
total: logs.length,
|
|
type: 'firewall'
|
|
}
|
|
})
|
|
} catch (error: any) {
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: `Failed to get firewall logs: ${error.message}`
|
|
}, { status: 500 })
|
|
}
|
|
}
|
|
|
|
async function getLfdLogs(limit: number, since?: string) {
|
|
try {
|
|
let command = `grep "lfd" /var/log/lfd.log 2>/dev/null | tail -${limit} || echo ""`
|
|
if (since) {
|
|
const sinceDate = new Date(since).toISOString().split('T')[0]
|
|
command = `grep "lfd" /var/log/lfd.log 2>/dev/null | grep "${sinceDate}" | tail -${limit} || echo ""`
|
|
}
|
|
|
|
const { stdout } = await execAsync(command)
|
|
const logs = parseLfdLogs(stdout)
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
data: {
|
|
logs,
|
|
total: logs.length,
|
|
type: 'lfd'
|
|
}
|
|
})
|
|
} catch (error: any) {
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: `Failed to get LFD logs: ${error.message}`
|
|
}, { status: 500 })
|
|
}
|
|
}
|
|
|
|
async function getSystemLogs(limit: number, since?: string) {
|
|
try {
|
|
let command = `grep "kernel\\|iptables" /var/log/syslog | tail -${limit}`
|
|
if (since) {
|
|
const sinceDate = new Date(since).toISOString().split('T')[0]
|
|
command = `grep "kernel\\|iptables" /var/log/syslog | grep "${sinceDate}" | tail -${limit}`
|
|
}
|
|
|
|
const { stdout } = await execAsync(command)
|
|
const logs = parseSystemLogs(stdout)
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
data: {
|
|
logs,
|
|
total: logs.length,
|
|
type: 'system'
|
|
}
|
|
})
|
|
} catch (error: any) {
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: `Failed to get system logs: ${error.message}`
|
|
}, { status: 500 })
|
|
}
|
|
}
|
|
|
|
async function getBlockedIPs(limit: number) {
|
|
try {
|
|
// Get current blocked IPs from iptables
|
|
const { stdout } = await execAsync('/usr/local/csf/bin/csf --status | grep DROP | head -' + limit)
|
|
const blockedIPs = parseBlockedIPs(stdout)
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
data: {
|
|
blocked_ips: blockedIPs,
|
|
total: blockedIPs.length,
|
|
type: 'blocked'
|
|
}
|
|
})
|
|
} catch (error: any) {
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: `Failed to get blocked IPs: ${error.message}`
|
|
}, { status: 500 })
|
|
}
|
|
}
|
|
|
|
async function getActiveConnections() {
|
|
try {
|
|
// Get active network connections
|
|
const { stdout } = await execAsync('netstat -tn | grep ESTABLISHED | wc -l')
|
|
const activeConnections = parseInt(stdout.trim()) || 0
|
|
|
|
// Get connection details
|
|
const { stdout: connections } = await execAsync('netstat -tn | grep ESTABLISHED | head -20')
|
|
const connectionList = parseConnections(connections)
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
data: {
|
|
active_connections: activeConnections,
|
|
connections: connectionList,
|
|
type: 'connections'
|
|
}
|
|
})
|
|
} catch (error: any) {
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: `Failed to get active connections: ${error.message}`
|
|
}, { status: 500 })
|
|
}
|
|
}
|
|
|
|
async function getAllLogs(limit: number, since?: string) {
|
|
try {
|
|
const [firewallResult, lfdResult, systemResult] = await Promise.allSettled([
|
|
getFirewallLogs(Math.floor(limit / 3), since),
|
|
getLfdLogs(Math.floor(limit / 3), since),
|
|
getSystemLogs(Math.floor(limit / 3), since)
|
|
])
|
|
|
|
const allLogs = []
|
|
|
|
if (firewallResult.status === 'fulfilled') {
|
|
const response = await firewallResult.value.json()
|
|
if (response.success) allLogs.push(...response.data.logs)
|
|
}
|
|
|
|
if (lfdResult.status === 'fulfilled') {
|
|
const response = await lfdResult.value.json()
|
|
if (response.success) allLogs.push(...response.data.logs)
|
|
}
|
|
|
|
if (systemResult.status === 'fulfilled') {
|
|
const response = await systemResult.value.json()
|
|
if (response.success) allLogs.push(...response.data.logs)
|
|
}
|
|
|
|
// Sort by timestamp
|
|
allLogs.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
data: {
|
|
logs: allLogs.slice(0, limit),
|
|
total: allLogs.length,
|
|
type: 'all'
|
|
}
|
|
})
|
|
} catch (error: any) {
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: `Failed to get all logs: ${error.message}`
|
|
}, { status: 500 })
|
|
}
|
|
}
|
|
|
|
// Parsing functions
|
|
function parseFirewallLogs(output: string) {
|
|
const logs = []
|
|
const lines = output.split('\\n').filter(line => line.trim())
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i]
|
|
const logEntry = parseLogLine(line, 'firewall')
|
|
if (logEntry) logs.push(logEntry)
|
|
}
|
|
|
|
return logs
|
|
}
|
|
|
|
function parseLfdLogs(output: string) {
|
|
const logs = []
|
|
const lines = output.split('\\n').filter(line => line.trim())
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i]
|
|
const logEntry = parseLogLine(line, 'lfd')
|
|
if (logEntry) logs.push(logEntry)
|
|
}
|
|
|
|
return logs
|
|
}
|
|
|
|
function parseSystemLogs(output: string) {
|
|
const logs = []
|
|
const lines = output.split('\\n').filter(line => line.trim())
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i]
|
|
const logEntry = parseLogLine(line, 'system')
|
|
if (logEntry) logs.push(logEntry)
|
|
}
|
|
|
|
return logs
|
|
}
|
|
|
|
function parseLogLine(line: string, type: string) {
|
|
// Basic log parsing - adjust regex patterns based on actual log format
|
|
const patterns = {
|
|
timestamp: /^(\\w{3}\\s+\\d{1,2}\\s+\\d{2}:\\d{2}:\\d{2})/,
|
|
ip: /(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})/,
|
|
port: /DPT=(\\d+)/,
|
|
protocol: /(TCP|UDP|ICMP)/i,
|
|
action: /(BLOCK|DROP|ACCEPT|ALLOW|DENY)/i
|
|
}
|
|
|
|
const timestampMatch = line.match(patterns.timestamp)
|
|
const ipMatch = line.match(patterns.ip)
|
|
const portMatch = line.match(patterns.port)
|
|
const protocolMatch = line.match(patterns.protocol)
|
|
const actionMatch = line.match(patterns.action)
|
|
|
|
if (!timestampMatch) return null
|
|
|
|
const currentYear = new Date().getFullYear()
|
|
const timestamp = new Date(`${currentYear} ${timestampMatch[1]}`).toISOString()
|
|
|
|
return {
|
|
id: `${type}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
timestamp,
|
|
level: actionMatch && (actionMatch[1].toLowerCase() === 'block' || actionMatch[1].toLowerCase() === 'drop') ? 'block' : 'info',
|
|
message: line,
|
|
ip: ipMatch ? ipMatch[1] : undefined,
|
|
port: portMatch ? portMatch[1] : undefined,
|
|
protocol: protocolMatch ? protocolMatch[1].toUpperCase() : undefined,
|
|
action: actionMatch ? actionMatch[1].toUpperCase() : undefined,
|
|
type
|
|
}
|
|
}
|
|
|
|
function parseBlockedIPs(output: string) {
|
|
const blockedIPs = []
|
|
const lines = output.split('\\n').filter(line => line.trim())
|
|
|
|
for (const line of lines) {
|
|
const ipMatch = line.match(/(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})/)
|
|
if (ipMatch) {
|
|
blockedIPs.push({
|
|
ip: ipMatch[1],
|
|
reason: 'Firewall rule',
|
|
timestamp: new Date().toISOString()
|
|
})
|
|
}
|
|
}
|
|
|
|
return blockedIPs
|
|
}
|
|
|
|
function parseConnections(output: string) {
|
|
const connections = []
|
|
const lines = output.split('\\n').filter(line => line.trim())
|
|
|
|
for (const line of lines) {
|
|
const parts = line.trim().split(/\\s+/)
|
|
if (parts.length >= 4) {
|
|
const [protocol, , , localAddress, foreignAddress] = parts
|
|
connections.push({
|
|
protocol,
|
|
local_address: localAddress,
|
|
foreign_address: foreignAddress,
|
|
state: 'ESTABLISHED'
|
|
})
|
|
}
|
|
}
|
|
|
|
return connections
|
|
} |