858 lines
27 KiB
JavaScript
858 lines
27 KiB
JavaScript
/**
|
|
* Response Protocol Manager
|
|
* Automated response protocols for security incident handling
|
|
* CWE-778 Compliance: Logs all automated responses and protocol executions
|
|
*/
|
|
|
|
const logger = require('./logger');
|
|
const logAggregator = require('./logAggregator');
|
|
const { db } = require('../database/db');
|
|
const EventEmitter = require('events');
|
|
|
|
class ResponseProtocolManager extends EventEmitter {
|
|
constructor() {
|
|
super();
|
|
this.protocols = new Map();
|
|
this.executionHistory = new Map();
|
|
this.initialize();
|
|
}
|
|
|
|
/**
|
|
* Initialize response protocol manager
|
|
*/
|
|
async initialize() {
|
|
await this.createProtocolsTable();
|
|
await this.createExecutionHistoryTable();
|
|
await this.loadProtocols();
|
|
|
|
logger.info('[ResponseProtocolManager] Initialized with automated response protocols');
|
|
|
|
// Log initialization (CWE-778)
|
|
logAggregator.aggregate('response_protocol_manager', 'info', 'security', 'Response protocol manager initialized', {
|
|
totalProtocols: this.protocols.size
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create protocols table
|
|
*/
|
|
async createProtocolsTable() {
|
|
return new Promise((resolve, reject) => {
|
|
db.run(`
|
|
CREATE TABLE IF NOT EXISTS response_protocols (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
protocol_id TEXT UNIQUE NOT NULL,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
trigger_type TEXT NOT NULL,
|
|
trigger_condition TEXT NOT NULL,
|
|
actions TEXT NOT NULL,
|
|
severity TEXT NOT NULL,
|
|
enabled INTEGER DEFAULT 1,
|
|
auto_execute INTEGER DEFAULT 0,
|
|
cooldown_minutes INTEGER DEFAULT 60,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`, async (err) => {
|
|
if (err) reject(err);
|
|
else {
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_protocols_trigger ON response_protocols(trigger_type, enabled)`);
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_protocols_severity ON response_protocols(severity, enabled)`);
|
|
await this.createDefaultProtocols();
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create execution history table
|
|
*/
|
|
async createExecutionHistoryTable() {
|
|
return new Promise((resolve, reject) => {
|
|
db.run(`
|
|
CREATE TABLE IF NOT EXISTS protocol_executions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
execution_id TEXT UNIQUE NOT NULL,
|
|
protocol_id TEXT NOT NULL,
|
|
trigger_event TEXT NOT NULL,
|
|
actions_executed TEXT NOT NULL,
|
|
execution_status TEXT NOT NULL,
|
|
execution_result TEXT,
|
|
executed_by TEXT DEFAULT 'system',
|
|
executed_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`, (err) => {
|
|
if (err) reject(err);
|
|
else {
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_executions_protocol ON protocol_executions(protocol_id)`);
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_executions_status ON protocol_executions(execution_status)`);
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create default response protocols
|
|
*/
|
|
async createDefaultProtocols() {
|
|
const defaultProtocols = [
|
|
{
|
|
protocol_id: 'PROTOCOL-BRUTE-FORCE-RESPONSE',
|
|
name: 'Brute Force Attack Response',
|
|
description: 'Automated response to brute force attacks',
|
|
trigger_type: 'anomaly',
|
|
trigger_condition: JSON.stringify({ anomaly_type: 'brute_force_attack', severity: 'critical' }),
|
|
actions: JSON.stringify([
|
|
{ action: 'block_ip', duration_minutes: 60, reason: 'brute_force_attack' },
|
|
{ action: 'notify_admin', channel: 'email', priority: 'high' },
|
|
{ action: 'log_incident', category: 'security_breach' }
|
|
]),
|
|
severity: 'critical',
|
|
auto_execute: 1,
|
|
cooldown_minutes: 30
|
|
},
|
|
{
|
|
protocol_id: 'PROTOCOL-CREDENTIAL-STUFFING-RESPONSE',
|
|
name: 'Credential Stuffing Response',
|
|
description: 'Automated response to credential stuffing attacks',
|
|
trigger_type: 'anomaly',
|
|
trigger_condition: JSON.stringify({ anomaly_type: 'credential_stuffing', severity: 'critical' }),
|
|
actions: JSON.stringify([
|
|
{ action: 'block_ip', duration_minutes: 120, reason: 'credential_stuffing' },
|
|
{ action: 'require_2fa', target: 'affected_accounts' },
|
|
{ action: 'notify_admin', channel: 'email', priority: 'high' },
|
|
{ action: 'log_incident', category: 'account_compromise' }
|
|
]),
|
|
severity: 'critical',
|
|
auto_execute: 1,
|
|
cooldown_minutes: 60
|
|
},
|
|
{
|
|
protocol_id: 'PROTOCOL-PRIVILEGE-ESC-RESPONSE',
|
|
name: 'Privilege Escalation Response',
|
|
description: 'Automated response to privilege escalation attempts',
|
|
trigger_type: 'anomaly',
|
|
trigger_condition: JSON.stringify({ anomaly_type: 'privilege_escalation', severity: 'critical' }),
|
|
actions: JSON.stringify([
|
|
{ action: 'lock_account', target: 'attacker', duration_minutes: 240 },
|
|
{ action: 'revoke_sessions', target: 'attacker' },
|
|
{ action: 'notify_admin', channel: 'email', priority: 'critical' },
|
|
{ action: 'escalate_incident', level: 'security_team' },
|
|
{ action: 'log_incident', category: 'privilege_violation' }
|
|
]),
|
|
severity: 'critical',
|
|
auto_execute: 1,
|
|
cooldown_minutes: 15
|
|
},
|
|
{
|
|
protocol_id: 'PROTOCOL-SUSPICIOUS-IP-RESPONSE',
|
|
name: 'Suspicious IP Response',
|
|
description: 'Automated response to suspicious IP activity',
|
|
trigger_type: 'anomaly',
|
|
trigger_condition: JSON.stringify({ anomaly_type: 'suspicious_ip', severity: 'high' }),
|
|
actions: JSON.stringify([
|
|
{ action: 'rate_limit_ip', limit: 10, window_minutes: 10 },
|
|
{ action: 'notify_admin', channel: 'in_app', priority: 'medium' },
|
|
{ action: 'log_incident', category: 'suspicious_activity' }
|
|
]),
|
|
severity: 'high',
|
|
auto_execute: 1,
|
|
cooldown_minutes: 60
|
|
},
|
|
{
|
|
protocol_id: 'PROTOCOL-DATA-EXFIL-RESPONSE',
|
|
name: 'Data Exfiltration Response',
|
|
description: 'Automated response to data exfiltration attempts',
|
|
trigger_type: 'anomaly',
|
|
trigger_condition: JSON.stringify({ anomaly_type: 'data_exfiltration', severity: 'high' }),
|
|
actions: JSON.stringify([
|
|
{ action: 'block_ip', duration_minutes: 180, reason: 'data_exfiltration' },
|
|
{ action: 'lock_account', target: 'attacker', duration_minutes: 360 },
|
|
{ action: 'notify_admin', channel: 'email', priority: 'critical' },
|
|
{ action: 'escalate_incident', level: 'data_protection_team' },
|
|
{ action: 'log_incident', category: 'data_breach' }
|
|
]),
|
|
severity: 'high',
|
|
auto_execute: 0,
|
|
cooldown_minutes: 120
|
|
},
|
|
{
|
|
protocol_id: 'PROTOCOL-MALICIOUS-SIGNATURE-RESPONSE',
|
|
name: 'Malicious Signature Response',
|
|
description: 'Automated response to malicious signature matches',
|
|
trigger_type: 'signature',
|
|
trigger_condition: JSON.stringify({ signature_type: 'attack_pattern', threat_level: 'critical', auto_block: true }),
|
|
actions: JSON.stringify([
|
|
{ action: 'block_ip', duration_minutes: 240, reason: 'malicious_signature' },
|
|
{ action: 'notify_admin', channel: 'email', priority: 'high' },
|
|
{ action: 'log_incident', category: 'attack_detected' }
|
|
]),
|
|
severity: 'critical',
|
|
auto_execute: 1,
|
|
cooldown_minutes: 30
|
|
},
|
|
{
|
|
protocol_id: 'PROTOCOL-THREAT-SCORE-CRITICAL',
|
|
name: 'Critical Threat Score Response',
|
|
description: 'Automated response when threat score reaches critical level',
|
|
trigger_type: 'threshold',
|
|
trigger_condition: JSON.stringify({ metric: 'threat_score', operator: '>=', value: 80 }),
|
|
actions: JSON.stringify([
|
|
{ action: 'notify_admin', channel: 'email', priority: 'critical' },
|
|
{ action: 'escalate_incident', level: 'security_team' },
|
|
{ action: 'enable_enhanced_monitoring', duration_minutes: 120 },
|
|
{ action: 'log_incident', category: 'threat_escalation' }
|
|
]),
|
|
severity: 'critical',
|
|
auto_execute: 1,
|
|
cooldown_minutes: 60
|
|
}
|
|
];
|
|
|
|
for (const protocol of defaultProtocols) {
|
|
await new Promise((resolve, reject) => {
|
|
db.run(
|
|
`INSERT OR IGNORE INTO response_protocols
|
|
(protocol_id, name, description, trigger_type, trigger_condition, actions, severity, auto_execute, cooldown_minutes)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
protocol.protocol_id,
|
|
protocol.name,
|
|
protocol.description,
|
|
protocol.trigger_type,
|
|
protocol.trigger_condition,
|
|
protocol.actions,
|
|
protocol.severity,
|
|
protocol.auto_execute,
|
|
protocol.cooldown_minutes
|
|
],
|
|
(err) => {
|
|
if (err) reject(err);
|
|
else resolve();
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
logger.info(`[ResponseProtocolManager] Created ${defaultProtocols.length} default protocols`);
|
|
}
|
|
|
|
/**
|
|
* Load protocols from database into memory
|
|
*/
|
|
async loadProtocols() {
|
|
return new Promise((resolve, reject) => {
|
|
db.all(
|
|
`SELECT * FROM response_protocols WHERE enabled = 1`,
|
|
[],
|
|
(err, rows) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
this.protocols.clear();
|
|
rows.forEach(row => {
|
|
row.trigger_condition = JSON.parse(row.trigger_condition);
|
|
row.actions = JSON.parse(row.actions);
|
|
this.protocols.set(row.protocol_id, row);
|
|
});
|
|
logger.info(`[ResponseProtocolManager] Loaded ${rows.length} active protocols`);
|
|
resolve();
|
|
}
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Execute protocols based on trigger event
|
|
* CWE-778: Logs all protocol executions
|
|
*/
|
|
async executeProtocols(triggerType, triggerEvent, context = {}) {
|
|
const matchingProtocols = Array.from(this.protocols.values()).filter(
|
|
p => p.trigger_type === triggerType && this.matchesTriggerCondition(p.trigger_condition, triggerEvent)
|
|
);
|
|
|
|
if (matchingProtocols.length === 0) {
|
|
return { executed: false, protocols: [] };
|
|
}
|
|
|
|
const executedProtocols = [];
|
|
|
|
for (const protocol of matchingProtocols) {
|
|
// Check cooldown
|
|
if (this.isInCooldown(protocol.protocol_id)) {
|
|
logger.info(`[ResponseProtocolManager] Protocol ${protocol.protocol_id} in cooldown, skipping`);
|
|
continue;
|
|
}
|
|
|
|
// Check if auto-execute is enabled
|
|
if (!protocol.auto_execute) {
|
|
logger.info(`[ResponseProtocolManager] Protocol ${protocol.protocol_id} requires manual execution, skipping`);
|
|
continue;
|
|
}
|
|
|
|
// Execute protocol
|
|
const executionResult = await this.executeProtocolActions(protocol, triggerEvent, context);
|
|
executedProtocols.push(executionResult);
|
|
|
|
// Set cooldown
|
|
this.setCooldown(protocol.protocol_id, protocol.cooldown_minutes);
|
|
|
|
// Log protocol execution (CWE-778)
|
|
logAggregator.aggregate('response_protocol_manager', 'warn', 'security', 'Response protocol executed', {
|
|
protocolId: protocol.protocol_id,
|
|
protocolName: protocol.name,
|
|
triggerType,
|
|
triggerEvent: JSON.stringify(triggerEvent).substring(0, 200),
|
|
actionsExecuted: executionResult.actionsExecuted.length,
|
|
executionStatus: executionResult.status,
|
|
context
|
|
});
|
|
|
|
logger.warn(`[ResponseProtocolManager] Protocol executed: ${protocol.name} (${executionResult.status})`);
|
|
}
|
|
|
|
return {
|
|
executed: executedProtocols.length > 0,
|
|
protocols: executedProtocols
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Execute protocol actions
|
|
*/
|
|
async executeProtocolActions(protocol, triggerEvent, context) {
|
|
const executionId = `EXEC-${Date.now()}-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;
|
|
const actionsExecuted = [];
|
|
const actionResults = [];
|
|
|
|
for (const action of protocol.actions) {
|
|
try {
|
|
const result = await this.executeAction(action, triggerEvent, context);
|
|
actionsExecuted.push(action.action);
|
|
actionResults.push({ action: action.action, status: 'success', result });
|
|
|
|
// Emit event for action execution
|
|
this.emit('action_executed', {
|
|
protocolId: protocol.protocol_id,
|
|
action: action.action,
|
|
result
|
|
});
|
|
} catch (error) {
|
|
logger.error(`[ResponseProtocolManager] Action execution failed: ${action.action}`, error);
|
|
actionResults.push({ action: action.action, status: 'failed', error: error.message });
|
|
}
|
|
}
|
|
|
|
const executionStatus = actionResults.every(r => r.status === 'success') ? 'success' : 'partial';
|
|
|
|
// Save execution history
|
|
await this.saveExecutionHistory({
|
|
executionId,
|
|
protocolId: protocol.protocol_id,
|
|
triggerEvent: JSON.stringify(triggerEvent),
|
|
actionsExecuted: JSON.stringify(actionsExecuted),
|
|
executionStatus,
|
|
executionResult: JSON.stringify(actionResults)
|
|
});
|
|
|
|
return {
|
|
executionId,
|
|
protocolId: protocol.protocol_id,
|
|
protocolName: protocol.name,
|
|
actionsExecuted,
|
|
actionResults,
|
|
status: executionStatus
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Execute individual action
|
|
*/
|
|
async executeAction(action, triggerEvent, context) {
|
|
switch (action.action) {
|
|
case 'block_ip':
|
|
return await this.actionBlockIP(action, triggerEvent, context);
|
|
case 'lock_account':
|
|
return await this.actionLockAccount(action, triggerEvent, context);
|
|
case 'revoke_sessions':
|
|
return await this.actionRevokeSessions(action, triggerEvent, context);
|
|
case 'require_2fa':
|
|
return await this.actionRequire2FA(action, triggerEvent, context);
|
|
case 'rate_limit_ip':
|
|
return await this.actionRateLimitIP(action, triggerEvent, context);
|
|
case 'notify_admin':
|
|
return await this.actionNotifyAdmin(action, triggerEvent, context);
|
|
case 'escalate_incident':
|
|
return await this.actionEscalateIncident(action, triggerEvent, context);
|
|
case 'log_incident':
|
|
return await this.actionLogIncident(action, triggerEvent, context);
|
|
case 'enable_enhanced_monitoring':
|
|
return await this.actionEnableEnhancedMonitoring(action, triggerEvent, context);
|
|
default:
|
|
throw new Error(`Unknown action: ${action.action}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Action: Block IP address
|
|
*/
|
|
async actionBlockIP(action, triggerEvent, context) {
|
|
const ipAddress = context.ip_address || triggerEvent.ip_address;
|
|
const duration = action.duration_minutes || 60;
|
|
const reason = action.reason || 'security_violation';
|
|
|
|
if (!ipAddress) {
|
|
throw new Error('No IP address provided for blocking');
|
|
}
|
|
|
|
// TODO: Implement actual IP blocking (firewall rules, rate limiter, etc.)
|
|
logger.warn(`[ResponseProtocolManager] Action: Block IP ${ipAddress} for ${duration} minutes (reason: ${reason})`);
|
|
|
|
return {
|
|
action: 'block_ip',
|
|
ipAddress,
|
|
duration,
|
|
reason,
|
|
expiresAt: new Date(Date.now() + duration * 60 * 1000).toISOString()
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action: Lock user account
|
|
*/
|
|
async actionLockAccount(action, triggerEvent, context) {
|
|
const userId = context.user_id || triggerEvent.user_id;
|
|
const duration = action.duration_minutes || 120;
|
|
|
|
if (!userId) {
|
|
throw new Error('No user ID provided for account locking');
|
|
}
|
|
|
|
// TODO: Implement actual account locking
|
|
logger.warn(`[ResponseProtocolManager] Action: Lock account ${userId} for ${duration} minutes`);
|
|
|
|
return {
|
|
action: 'lock_account',
|
|
userId,
|
|
duration,
|
|
lockedUntil: new Date(Date.now() + duration * 60 * 1000).toISOString()
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Action: Revoke all sessions
|
|
*/
|
|
async actionRevokeSessions(action, triggerEvent, context) {
|
|
const userId = context.user_id || triggerEvent.user_id;
|
|
|
|
if (!userId) {
|
|
throw new Error('No user ID provided for session revocation');
|
|
}
|
|
|
|
// TODO: Implement actual session revocation
|
|
logger.warn(`[ResponseProtocolManager] Action: Revoke all sessions for user ${userId}`);
|
|
|
|
return { action: 'revoke_sessions', userId, revokedAt: new Date().toISOString() };
|
|
}
|
|
|
|
/**
|
|
* Action: Require 2FA for affected accounts
|
|
*/
|
|
async actionRequire2FA(action, triggerEvent, context) {
|
|
const target = action.target || 'affected_accounts';
|
|
|
|
// TODO: Implement 2FA requirement
|
|
logger.warn(`[ResponseProtocolManager] Action: Require 2FA for ${target}`);
|
|
|
|
return { action: 'require_2fa', target, enabledAt: new Date().toISOString() };
|
|
}
|
|
|
|
/**
|
|
* Action: Rate limit IP
|
|
*/
|
|
async actionRateLimitIP(action, triggerEvent, context) {
|
|
const ipAddress = context.ip_address || triggerEvent.ip_address;
|
|
const limit = action.limit || 10;
|
|
const windowMinutes = action.window_minutes || 10;
|
|
|
|
if (!ipAddress) {
|
|
throw new Error('No IP address provided for rate limiting');
|
|
}
|
|
|
|
// TODO: Implement actual rate limiting
|
|
logger.warn(`[ResponseProtocolManager] Action: Rate limit IP ${ipAddress} to ${limit} requests per ${windowMinutes} minutes`);
|
|
|
|
return { action: 'rate_limit_ip', ipAddress, limit, windowMinutes };
|
|
}
|
|
|
|
/**
|
|
* Action: Notify administrator
|
|
*/
|
|
async actionNotifyAdmin(action, triggerEvent, context) {
|
|
const channel = action.channel || 'in_app';
|
|
const priority = action.priority || 'medium';
|
|
|
|
// TODO: Implement actual admin notification (email, SMS, webhook)
|
|
logger.warn(`[ResponseProtocolManager] Action: Notify admin via ${channel} (priority: ${priority})`);
|
|
|
|
return { action: 'notify_admin', channel, priority, notifiedAt: new Date().toISOString() };
|
|
}
|
|
|
|
/**
|
|
* Action: Escalate incident
|
|
*/
|
|
async actionEscalateIncident(action, triggerEvent, context) {
|
|
const level = action.level || 'security_team';
|
|
|
|
// TODO: Implement actual incident escalation
|
|
logger.warn(`[ResponseProtocolManager] Action: Escalate incident to ${level}`);
|
|
|
|
return { action: 'escalate_incident', level, escalatedAt: new Date().toISOString() };
|
|
}
|
|
|
|
/**
|
|
* Action: Log incident
|
|
*/
|
|
async actionLogIncident(action, triggerEvent, context) {
|
|
const category = action.category || 'security_incident';
|
|
|
|
logAggregator.aggregate('response_protocol_manager', 'error', category, 'Security incident logged', {
|
|
triggerEvent,
|
|
context
|
|
});
|
|
|
|
return { action: 'log_incident', category, loggedAt: new Date().toISOString() };
|
|
}
|
|
|
|
/**
|
|
* Action: Enable enhanced monitoring
|
|
*/
|
|
async actionEnableEnhancedMonitoring(action, triggerEvent, context) {
|
|
const duration = action.duration_minutes || 120;
|
|
|
|
// TODO: Implement enhanced monitoring mode
|
|
logger.warn(`[ResponseProtocolManager] Action: Enable enhanced monitoring for ${duration} minutes`);
|
|
|
|
return {
|
|
action: 'enable_enhanced_monitoring',
|
|
duration,
|
|
expiresAt: new Date(Date.now() + duration * 60 * 1000).toISOString()
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if trigger condition matches event
|
|
*/
|
|
matchesTriggerCondition(condition, event) {
|
|
for (const [key, value] of Object.entries(condition)) {
|
|
if (event[key] !== value) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if protocol is in cooldown
|
|
*/
|
|
isInCooldown(protocolId) {
|
|
const lastExecution = this.executionHistory.get(protocolId);
|
|
if (!lastExecution) return false;
|
|
|
|
const cooldownEnd = new Date(lastExecution.cooldownUntil);
|
|
return Date.now() < cooldownEnd.getTime();
|
|
}
|
|
|
|
/**
|
|
* Set cooldown for protocol
|
|
*/
|
|
setCooldown(protocolId, cooldownMinutes) {
|
|
this.executionHistory.set(protocolId, {
|
|
lastExecuted: new Date().toISOString(),
|
|
cooldownUntil: new Date(Date.now() + cooldownMinutes * 60 * 1000).toISOString()
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Save execution history to database
|
|
*/
|
|
async saveExecutionHistory(data) {
|
|
return new Promise((resolve, reject) => {
|
|
db.run(
|
|
`INSERT INTO protocol_executions
|
|
(execution_id, protocol_id, trigger_event, actions_executed, execution_status, execution_result)
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
data.executionId,
|
|
data.protocolId,
|
|
data.triggerEvent,
|
|
data.actionsExecuted,
|
|
data.executionStatus,
|
|
data.executionResult
|
|
],
|
|
(err) => {
|
|
if (err) reject(err);
|
|
else resolve();
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get all protocols
|
|
*/
|
|
async getProtocols(filters = {}) {
|
|
const { triggerType, severity, enabled, limit = 100 } = filters;
|
|
|
|
let whereClause = [];
|
|
let params = [];
|
|
|
|
if (triggerType) {
|
|
whereClause.push('trigger_type = ?');
|
|
params.push(triggerType);
|
|
}
|
|
|
|
if (severity) {
|
|
whereClause.push('severity = ?');
|
|
params.push(severity);
|
|
}
|
|
|
|
if (enabled !== undefined) {
|
|
whereClause.push('enabled = ?');
|
|
params.push(enabled ? 1 : 0);
|
|
}
|
|
|
|
const where = whereClause.length > 0 ? `WHERE ${whereClause.join(' AND ')}` : '';
|
|
params.push(limit);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
db.all(
|
|
`SELECT * FROM response_protocols ${where}
|
|
ORDER BY severity DESC, trigger_type
|
|
LIMIT ?`,
|
|
params,
|
|
(err, rows) => {
|
|
if (err) reject(err);
|
|
else resolve(rows);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get protocol by ID
|
|
*/
|
|
async getProtocolById(protocolId) {
|
|
return new Promise((resolve, reject) => {
|
|
db.get(
|
|
`SELECT * FROM response_protocols WHERE protocol_id = ?`,
|
|
[protocolId],
|
|
(err, row) => {
|
|
if (err) reject(err);
|
|
else resolve(row);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get execution history
|
|
*/
|
|
async getExecutionHistory(filters = {}) {
|
|
const { protocolId, status, limit = 100 } = filters;
|
|
|
|
let whereClause = [];
|
|
let params = [];
|
|
|
|
if (protocolId) {
|
|
whereClause.push('protocol_id = ?');
|
|
params.push(protocolId);
|
|
}
|
|
|
|
if (status) {
|
|
whereClause.push('execution_status = ?');
|
|
params.push(status);
|
|
}
|
|
|
|
const where = whereClause.length > 0 ? `WHERE ${whereClause.join(' AND ')}` : '';
|
|
params.push(limit);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
db.all(
|
|
`SELECT * FROM protocol_executions ${where}
|
|
ORDER BY executed_at DESC
|
|
LIMIT ?`,
|
|
params,
|
|
(err, rows) => {
|
|
if (err) reject(err);
|
|
else resolve(rows);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create new protocol
|
|
* CWE-778: Logs protocol creation
|
|
*/
|
|
async createProtocol(data, userId) {
|
|
const protocolId = `PROTOCOL-${Date.now()}-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
db.run(
|
|
`INSERT INTO response_protocols
|
|
(protocol_id, name, description, trigger_type, trigger_condition, actions, severity, enabled, auto_execute, cooldown_minutes)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
protocolId,
|
|
data.name,
|
|
data.description || '',
|
|
data.trigger_type,
|
|
JSON.stringify(data.trigger_condition),
|
|
JSON.stringify(data.actions),
|
|
data.severity,
|
|
data.enabled !== undefined ? (data.enabled ? 1 : 0) : 1,
|
|
data.auto_execute !== undefined ? (data.auto_execute ? 1 : 0) : 0,
|
|
data.cooldown_minutes || 60
|
|
],
|
|
async (err) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
await this.loadProtocols();
|
|
|
|
// Log protocol creation (CWE-778)
|
|
logAggregator.aggregate('response_protocol_manager', 'info', 'security', 'Response protocol created', {
|
|
protocolId,
|
|
userId,
|
|
name: data.name,
|
|
triggerType: data.trigger_type,
|
|
severity: data.severity,
|
|
autoExecute: data.auto_execute === 1
|
|
});
|
|
|
|
logger.info(`[ResponseProtocolManager] Protocol created: ${protocolId} by user ${userId}`);
|
|
resolve({ protocolId });
|
|
}
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update protocol
|
|
* CWE-778: Logs protocol modifications
|
|
*/
|
|
async updateProtocol(protocolId, updates, userId) {
|
|
const allowedFields = ['name', 'description', 'trigger_condition', 'actions', 'severity', 'enabled', 'auto_execute', 'cooldown_minutes'];
|
|
const setClause = [];
|
|
const params = [];
|
|
|
|
for (const [key, value] of Object.entries(updates)) {
|
|
if (allowedFields.includes(key)) {
|
|
setClause.push(`${key} = ?`);
|
|
if (key === 'trigger_condition' || key === 'actions') {
|
|
params.push(JSON.stringify(value));
|
|
} else if (key === 'enabled' || key === 'auto_execute') {
|
|
params.push(value ? 1 : 0);
|
|
} else {
|
|
params.push(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (setClause.length === 0) {
|
|
throw new Error('No valid fields to update');
|
|
}
|
|
|
|
setClause.push('updated_at = CURRENT_TIMESTAMP');
|
|
params.push(protocolId);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
db.run(
|
|
`UPDATE response_protocols
|
|
SET ${setClause.join(', ')}
|
|
WHERE protocol_id = ?`,
|
|
params,
|
|
async (err) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
await this.loadProtocols();
|
|
|
|
// Log protocol update (CWE-778)
|
|
logAggregator.aggregate('response_protocol_manager', 'info', 'security', 'Response protocol updated', {
|
|
protocolId,
|
|
userId,
|
|
updates
|
|
});
|
|
|
|
logger.info(`[ResponseProtocolManager] Protocol updated: ${protocolId} by user ${userId}`);
|
|
resolve({ success: true });
|
|
}
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete protocol
|
|
* CWE-778: Logs protocol deletion
|
|
*/
|
|
async deleteProtocol(protocolId, userId) {
|
|
return new Promise((resolve, reject) => {
|
|
db.run(
|
|
`DELETE FROM response_protocols WHERE protocol_id = ?`,
|
|
[protocolId],
|
|
async (err) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
await this.loadProtocols();
|
|
|
|
// Log protocol deletion (CWE-778)
|
|
logAggregator.aggregate('response_protocol_manager', 'warn', 'security', 'Response protocol deleted', {
|
|
protocolId,
|
|
userId
|
|
});
|
|
|
|
logger.info(`[ResponseProtocolManager] Protocol deleted: ${protocolId} by user ${userId}`);
|
|
resolve({ success: true });
|
|
}
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get protocol statistics
|
|
*/
|
|
async getStatistics() {
|
|
return new Promise((resolve, reject) => {
|
|
db.get(
|
|
`SELECT
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN enabled = 1 THEN 1 ELSE 0 END) as enabled,
|
|
SUM(CASE WHEN enabled = 0 THEN 1 ELSE 0 END) as disabled,
|
|
SUM(CASE WHEN auto_execute = 1 THEN 1 ELSE 0 END) as auto_execute_enabled,
|
|
COUNT(DISTINCT trigger_type) as unique_triggers,
|
|
COUNT(DISTINCT severity) as unique_severities
|
|
FROM response_protocols`,
|
|
[],
|
|
(err, row) => {
|
|
if (err) reject(err);
|
|
else resolve(row);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Create singleton instance
|
|
const responseProtocolManager = new ResponseProtocolManager();
|
|
|
|
module.exports = responseProtocolManager;
|