298 lines
7.8 KiB
JavaScript
298 lines
7.8 KiB
JavaScript
/**
|
|
* Log Management API Routes (CWE-53 Compliance)
|
|
* Admin-only endpoints for log retention, archival, and integrity
|
|
*/
|
|
|
|
const express = require('express');
|
|
const router = express.Router();
|
|
const { authenticate } = require('../middleware/auth');
|
|
const { requirePermission } = require('../middleware/rbac');
|
|
const { readLimiter, modifyLimiter } = require('../middleware/rateLimiter');
|
|
const logManagement = require('../jobs/logManagement');
|
|
const SecurityAuditLogger = require('../utils/securityAudit');
|
|
const logger = require('../utils/logger');
|
|
const path = require('path');
|
|
const fs = require('fs').promises;
|
|
|
|
/**
|
|
* GET /api/log-management/statistics
|
|
* Get log management statistics
|
|
*/
|
|
router.get('/statistics',
|
|
authenticate,
|
|
requirePermission('security.view_audit'),
|
|
readLimiter,
|
|
async (req, res) => {
|
|
try {
|
|
const stats = await logManagement.getStatistics();
|
|
|
|
await SecurityAuditLogger.logSensitiveDataAccess(req.user.userId, 'log_statistics', {
|
|
ip: req.ip,
|
|
userAgent: req.headers['user-agent']
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: stats
|
|
});
|
|
} catch (error) {
|
|
logger.error('[LogManagement API] Error getting statistics:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to get log statistics'
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
/**
|
|
* GET /api/log-management/archives
|
|
* List available log archives
|
|
*/
|
|
router.get('/archives',
|
|
authenticate,
|
|
requirePermission('security.view_audit'),
|
|
readLimiter,
|
|
async (req, res) => {
|
|
try {
|
|
const archives = await logManagement.listArchives();
|
|
|
|
await SecurityAuditLogger.logSensitiveDataAccess(req.user.userId, 'log_archives_list', {
|
|
ip: req.ip,
|
|
userAgent: req.headers['user-agent'],
|
|
recordCount: archives.length
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: archives
|
|
});
|
|
} catch (error) {
|
|
logger.error('[LogManagement API] Error listing archives:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to list archives'
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
/**
|
|
* POST /api/log-management/cleanup
|
|
* Manual trigger for log cleanup
|
|
* Admin only
|
|
*/
|
|
router.post('/cleanup',
|
|
authenticate,
|
|
requirePermission('security.manage'),
|
|
modifyLimiter,
|
|
async (req, res) => {
|
|
try {
|
|
const { retentionDays } = req.body;
|
|
const days = parseInt(retentionDays) || 90;
|
|
|
|
if (days < 7) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Retention days must be at least 7'
|
|
});
|
|
}
|
|
|
|
const result = await logManagement.manualCleanup(days);
|
|
|
|
await SecurityAuditLogger.logAdminActivity(req.user.userId, 'log_cleanup_manual', {
|
|
ip: req.ip,
|
|
userAgent: req.headers['user-agent'],
|
|
retentionDays: days,
|
|
...result
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `Deleted ${result.auditDeleted + result.aggregatedDeleted} old log entries`,
|
|
data: result
|
|
});
|
|
} catch (error) {
|
|
logger.error('[LogManagement API] Error during manual cleanup:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to perform log cleanup'
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
/**
|
|
* POST /api/log-management/verify-integrity
|
|
* Manual trigger for integrity verification
|
|
* Admin only
|
|
*/
|
|
router.post('/verify-integrity',
|
|
authenticate,
|
|
requirePermission('security.view_audit'),
|
|
modifyLimiter,
|
|
async (req, res) => {
|
|
try {
|
|
const result = await logManagement.manualIntegrityCheck();
|
|
|
|
if (!result) {
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: 'Integrity verification failed'
|
|
});
|
|
}
|
|
|
|
await SecurityAuditLogger.logAdminActivity(req.user.userId, 'log_integrity_check', {
|
|
ip: req.ip,
|
|
userAgent: req.headers['user-agent'],
|
|
verified: result.verified,
|
|
tampered: result.tampered
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: result.tampered > 0
|
|
? `⚠️ WARNING: ${result.tampered} tampered logs detected!`
|
|
: `All ${result.verified} logs verified successfully`,
|
|
data: result,
|
|
alert: result.tampered > 0
|
|
});
|
|
} catch (error) {
|
|
logger.error('[LogManagement API] Error during integrity check:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to verify log integrity'
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
/**
|
|
* GET /api/log-management/archives/download/:filename
|
|
* Download a log archive
|
|
* Admin only
|
|
*/
|
|
router.get('/archives/download/:filename',
|
|
authenticate,
|
|
requirePermission('security.view_audit'),
|
|
readLimiter,
|
|
async (req, res) => {
|
|
try {
|
|
const { filename } = req.params;
|
|
|
|
// Security: prevent path traversal
|
|
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Invalid filename'
|
|
});
|
|
}
|
|
|
|
// Security: only allow .json.gz files
|
|
if (!filename.endsWith('.json.gz')) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Invalid file type'
|
|
});
|
|
}
|
|
|
|
const archiveDir = path.join(__dirname, '../../data/log-archives');
|
|
const filePath = path.join(archiveDir, filename);
|
|
|
|
// Check if file exists
|
|
try {
|
|
await fs.access(filePath);
|
|
} catch (error) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Archive not found'
|
|
});
|
|
}
|
|
|
|
// Log the access
|
|
await SecurityAuditLogger.logSensitiveDataAccess(req.user.userId, 'log_archive_download', {
|
|
ip: req.ip,
|
|
userAgent: req.headers['user-agent'],
|
|
filename,
|
|
accessMethod: 'download'
|
|
});
|
|
|
|
// Send file
|
|
res.download(filePath, filename);
|
|
} catch (error) {
|
|
logger.error('[LogManagement API] Error downloading archive:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to download archive'
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
/**
|
|
* DELETE /api/log-management/archives/:filename
|
|
* Delete a log archive
|
|
* Admin only
|
|
*/
|
|
router.delete('/archives/:filename',
|
|
authenticate,
|
|
requirePermission('security.manage'),
|
|
modifyLimiter,
|
|
async (req, res) => {
|
|
try {
|
|
const { filename } = req.params;
|
|
|
|
// Security: prevent path traversal
|
|
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Invalid filename'
|
|
});
|
|
}
|
|
|
|
// Security: only allow .json.gz files
|
|
if (!filename.endsWith('.json.gz')) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Invalid file type'
|
|
});
|
|
}
|
|
|
|
const archiveDir = path.join(__dirname, '../../data/log-archives');
|
|
const filePath = path.join(archiveDir, filename);
|
|
|
|
// Check if file exists
|
|
try {
|
|
await fs.access(filePath);
|
|
} catch (error) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Archive not found'
|
|
});
|
|
}
|
|
|
|
// Delete file
|
|
await fs.unlink(filePath);
|
|
|
|
// Log the deletion
|
|
await SecurityAuditLogger.logAdminActivity(req.user.userId, 'log_archive_deleted', {
|
|
ip: req.ip,
|
|
userAgent: req.headers['user-agent'],
|
|
filename
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Archive deleted successfully'
|
|
});
|
|
} catch (error) {
|
|
logger.error('[LogManagement API] Error deleting archive:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to delete archive'
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
module.exports = router;
|