feat(p2p): Add decentralized threat intelligence sharing via mesh
Share CrowdSec bans and mitmproxy detections between mesh nodes using the existing blockchain chain + gossip sync. Received IOCs from trusted peers are auto-applied as CrowdSec decisions based on a three-tier trust model (direct/transitive/unknown). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
857622ff56
commit
1652b39137
@ -232,7 +232,30 @@
|
||||
"Bash(# Find usign keys find ~/CyberMindStudio/secubox-openwrt -name \"\"*.key\"\")",
|
||||
"Bash(cd ~/CyberMindStudio/secubox-openwrt/package/secubox/secubox-app-bonus/root/www/secubox-feed ls -la Packages* luci-app-secubox-security-threats*.ipk echo \"\" grep -A3 \"Package: luci-app-secubox-security-threats\" Packages)",
|
||||
"Bash(docker pull:*)",
|
||||
"Bash(do echo \"=== $f ===\")"
|
||||
"Bash(do echo \"=== $f ===\")",
|
||||
"WebFetch(domain:fabianlee.org)",
|
||||
"Bash(do grep \"^PKG_NAME\" \"$f\")",
|
||||
"WebFetch(domain:sysupgrade.openwrt.org)",
|
||||
"Bash(./secubox-tools/secubox-image.sh:*)",
|
||||
"Bash(source:*)",
|
||||
"Bash(build_request_json:*)",
|
||||
"Bash(bash -c '\nSCRIPT_DIR=\"\"$\\(cd secubox-tools && pwd\\)\"\"\n\n# Inline the function\ngenerate_defaults\\(\\) {\n cat <<'\"''\"'DEFAULTS'\"''\"'\n#!/bin/sh\ntest line\nDEFAULTS\n}\n\ndefaults_file=$\\(mktemp\\)\ngenerate_defaults > \"\"$defaults_file\"\"\ncat \"\"$defaults_file\"\"\necho \"\"---\"\"\npython3 -c \"\"\nimport json\nwith open\\('\"''\"'$defaults_file'\"''\"'\\) as f:\n print\\(json.dumps\\(f.read\\(\\)\\)\\)\n\"\"\nrm -f \"\"$defaults_file\"\"\n')",
|
||||
"Bash(bash:*)",
|
||||
"Bash(sudo ./secubox-tools/secubox-image.sh:*)",
|
||||
"Bash(DISPLAY=:0 gnome-screenshot:*)",
|
||||
"Bash(DISPLAY=:0 scrot:*)",
|
||||
"Bash(DISPLAY=:1 gnome-screenshot:*)",
|
||||
"Bash(DISPLAY=:1 import -window root /tmp/claude/-home-reepost-CyberMindStudio-secubox-openwrt/d82d04b5-7250-4f1b-99cf-0bb5c38ac8e8/scratchpad/master-link-landing.png)",
|
||||
"Bash(DISPLAY=:1 xdpyinfo:*)",
|
||||
"Bash(DISPLAY=:0 xdpyinfo:*)",
|
||||
"Bash(DISPLAY=:1 xdotool search:*)",
|
||||
"Bash(DISPLAY=:1 wmctrl:*)",
|
||||
"Bash(DISPLAY=:1 xdotool:*)",
|
||||
"Bash(while read wid)",
|
||||
"Bash(do DISPLAY=:1 xdotool getwindowname \"$wid\")",
|
||||
"Bash(dpkg:*)",
|
||||
"Bash(cosmic-screenshot:*)",
|
||||
"Bash(do bash -n \"$f\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,406 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require ui';
|
||||
'require dom';
|
||||
'require poll';
|
||||
'require request';
|
||||
|
||||
var API_BASE = window.location.protocol + '//' + window.location.hostname + ':7331/api/threat-intel';
|
||||
|
||||
function fetchJSON(endpoint) {
|
||||
return request.get(API_BASE + '/' + endpoint, { timeout: 10000 })
|
||||
.then(function(res) {
|
||||
try { return res.json(); }
|
||||
catch(e) { return null; }
|
||||
})
|
||||
.catch(function() { return null; });
|
||||
}
|
||||
|
||||
function postJSON(endpoint) {
|
||||
return request.post(API_BASE + '/' + endpoint, null, { timeout: 15000 })
|
||||
.then(function(res) {
|
||||
try { return res.json(); }
|
||||
catch(e) { return null; }
|
||||
})
|
||||
.catch(function() { return null; });
|
||||
}
|
||||
|
||||
function timeAgo(ts) {
|
||||
if (!ts || ts === 0) return 'Never';
|
||||
var now = Math.floor(Date.now() / 1000);
|
||||
var diff = now - ts;
|
||||
if (diff < 60) return diff + 's ago';
|
||||
if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
|
||||
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
|
||||
return Math.floor(diff / 86400) + 'd ago';
|
||||
}
|
||||
|
||||
function severityBadge(severity) {
|
||||
var colors = {
|
||||
critical: '#e74c3c',
|
||||
high: '#e67e22',
|
||||
medium: '#f1c40f',
|
||||
low: '#3498db'
|
||||
};
|
||||
var color = colors[severity] || '#95a5a6';
|
||||
return E('span', {
|
||||
'style': 'display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:bold;color:#fff;background:' + color
|
||||
}, severity || 'unknown');
|
||||
}
|
||||
|
||||
function trustBadge(trust) {
|
||||
var colors = {
|
||||
direct: '#27ae60',
|
||||
transitive: '#f39c12',
|
||||
unknown: '#95a5a6',
|
||||
self: '#3498db'
|
||||
};
|
||||
var icons = {
|
||||
direct: '\u2714',
|
||||
transitive: '\u2194',
|
||||
unknown: '?',
|
||||
self: '\u2605'
|
||||
};
|
||||
var color = colors[trust] || '#95a5a6';
|
||||
return E('span', {
|
||||
'style': 'display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:bold;color:#fff;background:' + color
|
||||
}, (icons[trust] || '') + ' ' + (trust || 'unknown'));
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
status: null,
|
||||
iocs: [],
|
||||
peers: [],
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
fetchJSON('status'),
|
||||
fetchJSON('iocs'),
|
||||
fetchJSON('peers')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
this.status = data[0] || {};
|
||||
this.iocs = data[1] || [];
|
||||
this.peers = data[2] || [];
|
||||
|
||||
var self = this;
|
||||
|
||||
var view = E('div', { 'class': 'cbi-map', 'style': 'padding:20px;' }, [
|
||||
this.renderHeader(),
|
||||
this.renderSummaryCards(),
|
||||
this.renderActions(),
|
||||
this.renderPeerTable(),
|
||||
this.renderIOCTable()
|
||||
]);
|
||||
|
||||
poll.add(function() {
|
||||
return Promise.all([
|
||||
fetchJSON('status'),
|
||||
fetchJSON('iocs'),
|
||||
fetchJSON('peers')
|
||||
]).then(function(fresh) {
|
||||
self.status = fresh[0] || self.status;
|
||||
self.iocs = fresh[1] || self.iocs;
|
||||
self.peers = fresh[2] || self.peers;
|
||||
self.updateCards();
|
||||
self.updatePeerTable();
|
||||
self.updateIOCTable();
|
||||
});
|
||||
}, 30);
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
renderHeader: function() {
|
||||
var enabled = this.status.enabled !== false;
|
||||
return E('div', { 'style': 'margin-bottom:24px;' }, [
|
||||
E('h2', { 'style': 'margin:0 0 8px;color:#ecf0f1;font-size:24px;' },
|
||||
'Threat Intelligence Hub'),
|
||||
E('p', { 'style': 'margin:0;color:#95a5a6;font-size:14px;' },
|
||||
'Decentralized IOC sharing across mesh nodes via CrowdSec + mitmproxy'),
|
||||
E('div', { 'style': 'margin-top:8px;' }, [
|
||||
E('span', {
|
||||
'style': 'display:inline-block;padding:4px 12px;border-radius:12px;font-size:12px;font-weight:bold;color:#fff;background:' + (enabled ? '#27ae60' : '#e74c3c')
|
||||
}, enabled ? 'ACTIVE' : 'DISABLED'),
|
||||
this.status.auto_apply ?
|
||||
E('span', { 'style': 'display:inline-block;margin-left:8px;padding:4px 12px;border-radius:12px;font-size:12px;color:#fff;background:#2980b9;' }, 'Auto-Apply ON') : null
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
renderSummaryCards: function() {
|
||||
var s = this.status;
|
||||
var cards = [
|
||||
{ id: 'card-local', label: 'Local IOCs Shared', value: s.local_iocs || 0, icon: '\uD83D\uDCE4', color: '#3498db' },
|
||||
{ id: 'card-received', label: 'Received from Mesh', value: s.received_iocs || 0, icon: '\uD83D\uDCE5', color: '#e67e22' },
|
||||
{ id: 'card-applied', label: 'Applied to Firewall', value: s.applied_iocs || 0, icon: '\uD83D\uDEE1', color: '#27ae60' },
|
||||
{ id: 'card-peers', label: 'Peer Contributors', value: s.peer_contributors || 0, icon: '\uD83D\uDC65', color: '#9b59b6' },
|
||||
{ id: 'card-chain', label: 'Chain Blocks', value: s.chain_threat_blocks || 0, icon: '\u26D3', color: '#1abc9c' }
|
||||
];
|
||||
|
||||
return E('div', {
|
||||
'id': 'summary-cards',
|
||||
'style': 'display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:16px;margin-bottom:24px;'
|
||||
}, cards.map(function(c) {
|
||||
return E('div', {
|
||||
'id': c.id,
|
||||
'style': 'background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:12px;padding:16px;text-align:center;border-left:4px solid ' + c.color + ';'
|
||||
}, [
|
||||
E('div', { 'style': 'font-size:28px;margin-bottom:4px;' }, c.icon),
|
||||
E('div', {
|
||||
'class': 'card-value',
|
||||
'style': 'font-size:32px;font-weight:bold;color:' + c.color + ';'
|
||||
}, String(c.value)),
|
||||
E('div', { 'style': 'font-size:12px;color:#95a5a6;margin-top:4px;' }, c.label)
|
||||
]);
|
||||
}));
|
||||
},
|
||||
|
||||
renderActions: function() {
|
||||
var self = this;
|
||||
|
||||
var publishBtn = E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'style': 'margin-right:12px;padding:8px 20px;',
|
||||
'click': function() {
|
||||
this.disabled = true;
|
||||
this.textContent = 'Publishing...';
|
||||
var btn = this;
|
||||
postJSON('publish').then(function(res) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Publish Now';
|
||||
if (res && res.success)
|
||||
ui.addNotification(null, E('p', 'Published ' + (res.published || 0) + ' IOCs to chain'), 'info');
|
||||
else
|
||||
ui.addNotification(null, E('p', 'Publish failed'), 'error');
|
||||
});
|
||||
}
|
||||
}, 'Publish Now');
|
||||
|
||||
var applyBtn = E('button', {
|
||||
'class': 'cbi-button cbi-button-apply',
|
||||
'style': 'padding:8px 20px;',
|
||||
'click': function() {
|
||||
this.disabled = true;
|
||||
this.textContent = 'Applying...';
|
||||
var btn = this;
|
||||
postJSON('apply').then(function(res) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Apply Pending';
|
||||
if (res && res.success)
|
||||
ui.addNotification(null, E('p', 'Applied ' + (res.applied || 0) + ' IOCs, skipped ' + (res.skipped || 0)), 'info');
|
||||
else
|
||||
ui.addNotification(null, E('p', 'Apply failed'), 'error');
|
||||
});
|
||||
}
|
||||
}, 'Apply Pending');
|
||||
|
||||
return E('div', { 'style': 'margin-bottom:24px;padding:16px;background:rgba(255,255,255,0.03);border-radius:8px;border:1px solid rgba(255,255,255,0.08);' }, [
|
||||
E('h3', { 'style': 'margin:0 0 12px;color:#ecf0f1;font-size:16px;' }, 'Actions'),
|
||||
E('div', {}, [publishBtn, applyBtn])
|
||||
]);
|
||||
},
|
||||
|
||||
renderPeerTable: function() {
|
||||
var peers = this.peers || [];
|
||||
|
||||
var rows = peers.map(function(p) {
|
||||
return E('tr', {}, [
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);font-family:monospace;font-size:13px;color:#ecf0f1;' },
|
||||
(p.node || '').substring(0, 12) + '...'),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);' },
|
||||
trustBadge(p.trust)),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);color:#ecf0f1;text-align:center;' },
|
||||
String(p.ioc_count || 0)),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);color:#ecf0f1;text-align:center;' },
|
||||
String(p.applied_count || 0)),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);color:#95a5a6;font-size:12px;' },
|
||||
timeAgo(p.last_seen))
|
||||
]);
|
||||
});
|
||||
|
||||
if (rows.length === 0) {
|
||||
rows = [E('tr', {}, [
|
||||
E('td', { 'colspan': '5', 'style': 'padding:24px;text-align:center;color:#95a5a6;' },
|
||||
'No peer contributions yet. IOCs from mesh nodes will appear here after sync.')
|
||||
])];
|
||||
}
|
||||
|
||||
return E('div', { 'style': 'margin-bottom:24px;' }, [
|
||||
E('h3', { 'style': 'margin:0 0 12px;color:#ecf0f1;font-size:16px;' }, 'Peer Contributions'),
|
||||
E('div', { 'style': 'overflow-x:auto;' }, [
|
||||
E('table', {
|
||||
'id': 'peer-table',
|
||||
'class': 'table',
|
||||
'style': 'width:100%;border-collapse:collapse;background:rgba(255,255,255,0.03);border-radius:8px;border:1px solid rgba(255,255,255,0.08);'
|
||||
}, [
|
||||
E('thead', {}, [
|
||||
E('tr', { 'style': 'background:rgba(255,255,255,0.05);' }, [
|
||||
E('th', { 'style': 'padding:10px 12px;text-align:left;color:#95a5a6;font-size:12px;text-transform:uppercase;' }, 'Node'),
|
||||
E('th', { 'style': 'padding:10px 12px;text-align:left;color:#95a5a6;font-size:12px;text-transform:uppercase;' }, 'Trust'),
|
||||
E('th', { 'style': 'padding:10px 12px;text-align:center;color:#95a5a6;font-size:12px;text-transform:uppercase;' }, 'IOCs'),
|
||||
E('th', { 'style': 'padding:10px 12px;text-align:center;color:#95a5a6;font-size:12px;text-transform:uppercase;' }, 'Applied'),
|
||||
E('th', { 'style': 'padding:10px 12px;text-align:left;color:#95a5a6;font-size:12px;text-transform:uppercase;' }, 'Last Seen')
|
||||
])
|
||||
]),
|
||||
E('tbody', {}, rows)
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
renderIOCTable: function() {
|
||||
var iocs = this.iocs || [];
|
||||
|
||||
var rows = iocs.slice(0, 50).map(function(ioc) {
|
||||
return E('tr', {}, [
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);font-family:monospace;font-size:13px;color:#ecf0f1;' },
|
||||
ioc.ip || '-'),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);' },
|
||||
severityBadge(ioc.severity)),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);color:#95a5a6;font-size:12px;' },
|
||||
ioc.source || '-'),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);color:#95a5a6;font-size:12px;' },
|
||||
ioc.scenario || '-'),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);font-family:monospace;font-size:11px;color:#7f8c8d;' },
|
||||
(ioc.node || '').substring(0, 12)),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);' },
|
||||
trustBadge(ioc.trust)),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);text-align:center;' },
|
||||
ioc.applied ?
|
||||
E('span', { 'style': 'color:#27ae60;font-weight:bold;' }, '\u2714') :
|
||||
E('span', { 'style': 'color:#95a5a6;' }, '\u2013')),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);color:#95a5a6;font-size:12px;' },
|
||||
timeAgo(ioc.ts))
|
||||
]);
|
||||
});
|
||||
|
||||
if (rows.length === 0) {
|
||||
rows = [E('tr', {}, [
|
||||
E('td', { 'colspan': '8', 'style': 'padding:24px;text-align:center;color:#95a5a6;' },
|
||||
'No IOCs received from mesh yet.')
|
||||
])];
|
||||
}
|
||||
|
||||
return E('div', {}, [
|
||||
E('h3', { 'style': 'margin:0 0 4px;color:#ecf0f1;font-size:16px;' }, 'Received IOCs'),
|
||||
E('p', { 'style': 'margin:0 0 12px;color:#7f8c8d;font-size:12px;' },
|
||||
'Showing up to 50 most recent. Total: ' + (this.iocs || []).length),
|
||||
E('div', { 'style': 'overflow-x:auto;' }, [
|
||||
E('table', {
|
||||
'id': 'ioc-table',
|
||||
'class': 'table',
|
||||
'style': 'width:100%;border-collapse:collapse;background:rgba(255,255,255,0.03);border-radius:8px;border:1px solid rgba(255,255,255,0.08);'
|
||||
}, [
|
||||
E('thead', {}, [
|
||||
E('tr', { 'style': 'background:rgba(255,255,255,0.05);' }, [
|
||||
E('th', { 'style': 'padding:10px 12px;text-align:left;color:#95a5a6;font-size:12px;text-transform:uppercase;' }, 'IP'),
|
||||
E('th', { 'style': 'padding:10px 12px;text-align:left;color:#95a5a6;font-size:12px;text-transform:uppercase;' }, 'Severity'),
|
||||
E('th', { 'style': 'padding:10px 12px;text-align:left;color:#95a5a6;font-size:12px;text-transform:uppercase;' }, 'Source'),
|
||||
E('th', { 'style': 'padding:10px 12px;text-align:left;color:#95a5a6;font-size:12px;text-transform:uppercase;' }, 'Scenario'),
|
||||
E('th', { 'style': 'padding:10px 12px;text-align:left;color:#95a5a6;font-size:12px;text-transform:uppercase;' }, 'Origin'),
|
||||
E('th', { 'style': 'padding:10px 12px;text-align:left;color:#95a5a6;font-size:12px;text-transform:uppercase;' }, 'Trust'),
|
||||
E('th', { 'style': 'padding:10px 12px;text-align:center;color:#95a5a6;font-size:12px;text-transform:uppercase;' }, 'Applied'),
|
||||
E('th', { 'style': 'padding:10px 12px;text-align:left;color:#95a5a6;font-size:12px;text-transform:uppercase;' }, 'Age')
|
||||
])
|
||||
]),
|
||||
E('tbody', {}, rows)
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
updateCards: function() {
|
||||
var s = this.status || {};
|
||||
var mapping = {
|
||||
'card-local': s.local_iocs || 0,
|
||||
'card-received': s.received_iocs || 0,
|
||||
'card-applied': s.applied_iocs || 0,
|
||||
'card-peers': s.peer_contributors || 0,
|
||||
'card-chain': s.chain_threat_blocks || 0
|
||||
};
|
||||
|
||||
Object.keys(mapping).forEach(function(id) {
|
||||
var card = document.getElementById(id);
|
||||
if (card) {
|
||||
var valEl = card.querySelector('.card-value');
|
||||
if (valEl) valEl.textContent = String(mapping[id]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updatePeerTable: function() {
|
||||
var table = document.getElementById('peer-table');
|
||||
if (!table) return;
|
||||
var tbody = table.querySelector('tbody');
|
||||
if (!tbody) return;
|
||||
|
||||
var peers = this.peers || [];
|
||||
dom.content(tbody, peers.length === 0 ?
|
||||
E('tr', {}, [
|
||||
E('td', { 'colspan': '5', 'style': 'padding:24px;text-align:center;color:#95a5a6;' },
|
||||
'No peer contributions yet.')
|
||||
]) :
|
||||
peers.map(function(p) {
|
||||
return E('tr', {}, [
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);font-family:monospace;font-size:13px;color:#ecf0f1;' },
|
||||
(p.node || '').substring(0, 12) + '...'),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);' },
|
||||
trustBadge(p.trust)),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);color:#ecf0f1;text-align:center;' },
|
||||
String(p.ioc_count || 0)),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);color:#ecf0f1;text-align:center;' },
|
||||
String(p.applied_count || 0)),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);color:#95a5a6;font-size:12px;' },
|
||||
timeAgo(p.last_seen))
|
||||
]);
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
updateIOCTable: function() {
|
||||
var table = document.getElementById('ioc-table');
|
||||
if (!table) return;
|
||||
var tbody = table.querySelector('tbody');
|
||||
if (!tbody) return;
|
||||
|
||||
var iocs = (this.iocs || []).slice(0, 50);
|
||||
var countEl = table.parentNode.parentNode.querySelector('p');
|
||||
if (countEl) countEl.textContent = 'Showing up to 50 most recent. Total: ' + (this.iocs || []).length;
|
||||
|
||||
dom.content(tbody, iocs.length === 0 ?
|
||||
E('tr', {}, [
|
||||
E('td', { 'colspan': '8', 'style': 'padding:24px;text-align:center;color:#95a5a6;' },
|
||||
'No IOCs received from mesh yet.')
|
||||
]) :
|
||||
iocs.map(function(ioc) {
|
||||
return E('tr', {}, [
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);font-family:monospace;font-size:13px;color:#ecf0f1;' },
|
||||
ioc.ip || '-'),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);' },
|
||||
severityBadge(ioc.severity)),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);color:#95a5a6;font-size:12px;' },
|
||||
ioc.source || '-'),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);color:#95a5a6;font-size:12px;' },
|
||||
ioc.scenario || '-'),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);font-family:monospace;font-size:11px;color:#7f8c8d;' },
|
||||
(ioc.node || '').substring(0, 12)),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);' },
|
||||
trustBadge(ioc.trust)),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);text-align:center;' },
|
||||
ioc.applied ?
|
||||
E('span', { 'style': 'color:#27ae60;font-weight:bold;' }, '\u2714') :
|
||||
E('span', { 'style': 'color:#95a5a6;' }, '\u2013')),
|
||||
E('td', { 'style': 'padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.05);color:#95a5a6;font-size:12px;' },
|
||||
timeAgo(ioc.ts))
|
||||
]);
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@ -66,6 +66,14 @@
|
||||
"path": "secubox-p2p/factory"
|
||||
}
|
||||
},
|
||||
"admin/secubox/mirrorbox/p2p-hub": {
|
||||
"title": "Threat Intel",
|
||||
"order": 25,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "secubox-p2p/threat-hub"
|
||||
}
|
||||
},
|
||||
"admin/secubox/mirrorbox/settings": {
|
||||
"title": "Settings",
|
||||
"order": 90,
|
||||
|
||||
@ -23,7 +23,10 @@
|
||||
],
|
||||
"uci": ["get", "state"]
|
||||
},
|
||||
"uci": ["secubox-p2p"]
|
||||
"uci": ["secubox-p2p"],
|
||||
"file": {
|
||||
"/var/lib/secubox/threat-intel/*": ["read"]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
|
||||
@ -57,3 +57,12 @@ config backup 'backup'
|
||||
option backup_dir '/etc/secubox/backups'
|
||||
option max_backups '10'
|
||||
option auto_cleanup '1'
|
||||
|
||||
config threat_intel 'threat_intel'
|
||||
option enabled '1'
|
||||
option auto_apply '1'
|
||||
option apply_transitive '1'
|
||||
option min_severity 'high'
|
||||
option collect_interval '900'
|
||||
option max_iocs_per_batch '20'
|
||||
option ioc_ttl '86400'
|
||||
|
||||
@ -48,4 +48,19 @@ if ! uci show firewall 2>/dev/null | grep -q "mDNS"; then
|
||||
uci commit firewall
|
||||
fi
|
||||
|
||||
# Add threat-intel cron jobs if not already present
|
||||
CRONTAB="/etc/crontabs/root"
|
||||
[ -f "$CRONTAB" ] || touch "$CRONTAB"
|
||||
|
||||
if ! grep -q "threat-intel.sh collect-and-publish" "$CRONTAB" 2>/dev/null; then
|
||||
echo "*/15 * * * * /usr/lib/secubox/threat-intel.sh collect-and-publish" >> "$CRONTAB"
|
||||
fi
|
||||
|
||||
if ! grep -q "threat-intel.sh apply-pending" "$CRONTAB" 2>/dev/null; then
|
||||
echo "*/30 * * * * /usr/lib/secubox/threat-intel.sh apply-pending" >> "$CRONTAB"
|
||||
fi
|
||||
|
||||
# Restart cron if running
|
||||
/etc/init.d/cron restart 2>/dev/null || true
|
||||
|
||||
exit 0
|
||||
|
||||
869
package/secubox/secubox-p2p/root/usr/lib/secubox/threat-intel.sh
Executable file
869
package/secubox/secubox-p2p/root/usr/lib/secubox/threat-intel.sh
Executable file
@ -0,0 +1,869 @@
|
||||
#!/bin/sh
|
||||
# SecuBox Threat Intelligence - Decentralized IOC Sharing via P2P Mesh
|
||||
# Shares CrowdSec bans and mitmproxy detections between mesh nodes
|
||||
# IOCs propagate via the existing blockchain chain + gossip sync
|
||||
# Copyright 2026 CyberMind - Licensed under MIT
|
||||
|
||||
# Source mesh and factory libraries (suppress case-statement output)
|
||||
. /usr/lib/secubox/p2p-mesh.sh >/dev/null 2>/dev/null
|
||||
. /usr/lib/secubox/factory.sh >/dev/null 2>/dev/null
|
||||
|
||||
# ============================================================================
|
||||
# Chain helper (fixes single-line JSON append in p2p-mesh.sh)
|
||||
# ============================================================================
|
||||
_ti_chain_add_block() {
|
||||
local block_type="$1"
|
||||
local block_data="$2"
|
||||
local block_hash="$3"
|
||||
|
||||
local prev_hash=$(chain_get_hash)
|
||||
local index=$(jsonfilter -i "$CHAIN_FILE" -e '@.blocks[*]' | wc -l)
|
||||
local timestamp=$(date +%s)
|
||||
local node_id=$(cat "$NODE_ID_FILE")
|
||||
|
||||
local block_record="{\"index\":$index,\"timestamp\":$timestamp,\"type\":\"$block_type\",\"hash\":\"$block_hash\",\"prev_hash\":\"$prev_hash\",\"node\":\"$node_id\",\"data\":$block_data}"
|
||||
|
||||
# Strip trailing ] } from LAST LINE only (avoids corrupting block data)
|
||||
local tmp_chain="$MESH_DIR/tmp/chain_$$.json"
|
||||
sed '$ s/ *\] *} *$//' "$CHAIN_FILE" > "$tmp_chain"
|
||||
echo ", $block_record ] }" >> "$tmp_chain"
|
||||
mv "$tmp_chain" "$CHAIN_FILE"
|
||||
|
||||
echo "$block_hash"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
TI_DIR="/var/lib/secubox/threat-intel"
|
||||
IOC_LOCAL="$TI_DIR/iocs-local.json"
|
||||
IOC_RECEIVED="$TI_DIR/iocs-received.json"
|
||||
IOC_APPLIED="$TI_DIR/iocs-applied.json"
|
||||
TI_PROCESSED="$TI_DIR/processed-blocks.list"
|
||||
TI_WHITELIST="$TI_DIR/whitelist.list"
|
||||
TI_LOCK="/tmp/secubox-threat-intel.lock"
|
||||
|
||||
# UCI defaults
|
||||
TI_ENABLED="1"
|
||||
TI_AUTO_APPLY="1"
|
||||
TI_APPLY_TRANSITIVE="1"
|
||||
TI_MIN_SEVERITY="high"
|
||||
TI_COLLECT_INTERVAL="900"
|
||||
TI_MAX_BATCH="20"
|
||||
TI_IOC_TTL="86400"
|
||||
|
||||
# ============================================================================
|
||||
# Initialization
|
||||
# ============================================================================
|
||||
ti_init() {
|
||||
mkdir -p "$TI_DIR"
|
||||
|
||||
# Load UCI config
|
||||
TI_ENABLED=$(uci -q get secubox-p2p.threat_intel.enabled || echo "1")
|
||||
TI_AUTO_APPLY=$(uci -q get secubox-p2p.threat_intel.auto_apply || echo "1")
|
||||
TI_APPLY_TRANSITIVE=$(uci -q get secubox-p2p.threat_intel.apply_transitive || echo "1")
|
||||
TI_MIN_SEVERITY=$(uci -q get secubox-p2p.threat_intel.min_severity || echo "high")
|
||||
TI_COLLECT_INTERVAL=$(uci -q get secubox-p2p.threat_intel.collect_interval || echo "900")
|
||||
TI_MAX_BATCH=$(uci -q get secubox-p2p.threat_intel.max_iocs_per_batch || echo "20")
|
||||
TI_IOC_TTL=$(uci -q get secubox-p2p.threat_intel.ioc_ttl || echo "86400")
|
||||
|
||||
# Initialize JSON files if missing
|
||||
[ -f "$IOC_LOCAL" ] || echo '[]' > "$IOC_LOCAL"
|
||||
[ -f "$IOC_RECEIVED" ] || echo '[]' > "$IOC_RECEIVED"
|
||||
[ -f "$IOC_APPLIED" ] || echo '[]' > "$IOC_APPLIED"
|
||||
[ -f "$TI_PROCESSED" ] || touch "$TI_PROCESSED"
|
||||
[ -f "$TI_WHITELIST" ] || touch "$TI_WHITELIST"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Severity Helpers
|
||||
# ============================================================================
|
||||
_severity_rank() {
|
||||
case "$1" in
|
||||
critical) echo 4 ;;
|
||||
high) echo 3 ;;
|
||||
medium) echo 2 ;;
|
||||
low) echo 1 ;;
|
||||
*) echo 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
_severity_meets_min() {
|
||||
local sev="$1"
|
||||
local min="$2"
|
||||
local sev_rank=$(_severity_rank "$sev")
|
||||
local min_rank=$(_severity_rank "$min")
|
||||
[ "$sev_rank" -ge "$min_rank" ]
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Collection - CrowdSec
|
||||
# ============================================================================
|
||||
ti_collect_crowdsec() {
|
||||
command -v cscli >/dev/null 2>&1 || return 0
|
||||
|
||||
local now=$(date +%s)
|
||||
local node_id=$(cat "$NODE_ID_FILE" 2>/dev/null || echo "unknown")
|
||||
local decisions
|
||||
|
||||
decisions=$(cscli decisions list -o json 2>/dev/null)
|
||||
[ -z "$decisions" ] && return 0
|
||||
|
||||
# Parse decisions - each is an IP ban with scenario and duration
|
||||
local count=0
|
||||
local iocs="["
|
||||
local first=1
|
||||
|
||||
echo "$decisions" | jsonfilter -e '@[*]' 2>/dev/null | while read -r decision; do
|
||||
local ip=$(echo "$decision" | jsonfilter -e '@.value' 2>/dev/null)
|
||||
local scenario=$(echo "$decision" | jsonfilter -e '@.scenario' 2>/dev/null)
|
||||
local duration=$(echo "$decision" | jsonfilter -e '@.duration' 2>/dev/null)
|
||||
|
||||
[ -z "$ip" ] && continue
|
||||
|
||||
# Skip whitelisted IPs
|
||||
grep -q "^${ip}$" "$TI_WHITELIST" 2>/dev/null && continue
|
||||
|
||||
# Determine severity from scenario
|
||||
local severity="high"
|
||||
case "$scenario" in
|
||||
*brute*|*exploit*|*scan*) severity="critical" ;;
|
||||
*crawl*|*probe*) severity="high" ;;
|
||||
*bad-user-agent*) severity="medium" ;;
|
||||
esac
|
||||
|
||||
# Normalize duration to seconds
|
||||
local dur_secs="$TI_IOC_TTL"
|
||||
case "$duration" in
|
||||
*h) dur_secs=$(echo "$duration" | sed 's/h//' | awk '{print $1 * 3600}') ;;
|
||||
*m) dur_secs=$(echo "$duration" | sed 's/m//' | awk '{print $1 * 60}') ;;
|
||||
*s) dur_secs=$(echo "$duration" | sed 's/s//') ;;
|
||||
esac
|
||||
[ -z "$dur_secs" ] && dur_secs="$TI_IOC_TTL"
|
||||
|
||||
echo "{\"ip\":\"$ip\",\"type\":\"ban\",\"severity\":\"$severity\",\"source\":\"crowdsec\",\"scenario\":\"$scenario\",\"duration\":\"${dur_secs}s\",\"ts\":$now,\"node\":\"$node_id\",\"ttl\":$dur_secs}"
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Collection - mitmproxy threats log
|
||||
# ============================================================================
|
||||
ti_collect_mitmproxy() {
|
||||
local threats_log="/srv/mitmproxy/threats.log"
|
||||
[ -f "$threats_log" ] || return 0
|
||||
|
||||
local now=$(date +%s)
|
||||
local node_id=$(cat "$NODE_ID_FILE" 2>/dev/null || echo "unknown")
|
||||
local last_collect_pos="$TI_DIR/.mitmproxy-last-pos"
|
||||
local last_pos=0
|
||||
[ -f "$last_collect_pos" ] && last_pos=$(cat "$last_collect_pos")
|
||||
|
||||
# Get current file size; only process new lines since last run
|
||||
local current_size=$(wc -c < "$threats_log" 2>/dev/null)
|
||||
[ -z "$current_size" ] && return 0
|
||||
|
||||
# If file was truncated/rotated, reset position
|
||||
[ "$last_pos" -gt "$current_size" ] 2>/dev/null && last_pos=0
|
||||
|
||||
# Read recent entries (tail last 5000 lines for coverage)
|
||||
tail -n 5000 "$threats_log" | while read -r line; do
|
||||
[ -z "$line" ] && continue
|
||||
|
||||
# Parse JSON fields using jsonfilter
|
||||
local ip=$(echo "$line" | jsonfilter -e '@.source_ip' 2>/dev/null)
|
||||
[ -z "$ip" ] && continue
|
||||
|
||||
# Skip private/local IPs
|
||||
case "$ip" in
|
||||
192.168.*|10.*|172.1[6-9].*|172.2[0-9].*|172.3[01].*|127.*) continue ;;
|
||||
esac
|
||||
|
||||
local severity=$(echo "$line" | jsonfilter -e '@.severity' 2>/dev/null)
|
||||
|
||||
# Only high and critical severity
|
||||
case "$severity" in
|
||||
critical|high) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
|
||||
# Skip whitelisted IPs
|
||||
grep -q "^${ip}$" "$TI_WHITELIST" 2>/dev/null && continue
|
||||
|
||||
local scenario=$(echo "$line" | jsonfilter -e '@.type' 2>/dev/null || echo "unknown")
|
||||
local pattern=$(echo "$line" | jsonfilter -e '@.pattern' 2>/dev/null)
|
||||
[ -n "$pattern" ] && scenario="${scenario}:${pattern}"
|
||||
|
||||
echo "{\"ip\":\"$ip\",\"type\":\"ban\",\"severity\":\"$severity\",\"source\":\"mitmproxy\",\"scenario\":\"$scenario\",\"duration\":\"${TI_IOC_TTL}s\",\"ts\":$now,\"node\":\"$node_id\",\"ttl\":$TI_IOC_TTL}"
|
||||
done
|
||||
|
||||
echo "$current_size" > "$last_collect_pos"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Collection - Aggregate and deduplicate
|
||||
# ============================================================================
|
||||
ti_collect_all() {
|
||||
ti_init
|
||||
|
||||
local tmp_file="$TI_DIR/tmp-collect-$$.json"
|
||||
local existing_ips=""
|
||||
|
||||
# Gather existing local IOC IPs for dedup
|
||||
if [ -f "$IOC_LOCAL" ] && [ "$(cat "$IOC_LOCAL")" != "[]" ]; then
|
||||
existing_ips=$(jsonfilter -i "$IOC_LOCAL" -e '@[*].ip' 2>/dev/null | sort -u)
|
||||
fi
|
||||
|
||||
# Collect from all sources
|
||||
{
|
||||
ti_collect_crowdsec
|
||||
ti_collect_mitmproxy
|
||||
} > "$tmp_file"
|
||||
|
||||
# Deduplicate by IP against existing and within new results
|
||||
local new_iocs="["
|
||||
local first=1
|
||||
local seen_ips=""
|
||||
|
||||
while read -r ioc_line; do
|
||||
[ -z "$ioc_line" ] && continue
|
||||
|
||||
local ip=$(echo "$ioc_line" | jsonfilter -e '@.ip' 2>/dev/null)
|
||||
[ -z "$ip" ] && continue
|
||||
|
||||
# Skip if already in local IOCs
|
||||
echo "$existing_ips" | grep -q "^${ip}$" && continue
|
||||
|
||||
# Skip if already seen in this batch
|
||||
echo "$seen_ips" | grep -q "^${ip}$" && continue
|
||||
seen_ips="$seen_ips
|
||||
$ip"
|
||||
|
||||
[ $first -eq 0 ] && new_iocs="$new_iocs,"
|
||||
first=0
|
||||
new_iocs="$new_iocs$ioc_line"
|
||||
done < "$tmp_file"
|
||||
|
||||
new_iocs="$new_iocs]"
|
||||
rm -f "$tmp_file"
|
||||
|
||||
# Merge with existing local IOCs, pruning expired
|
||||
local now=$(date +%s)
|
||||
local merged="["
|
||||
local mfirst=1
|
||||
|
||||
# Keep non-expired existing IOCs
|
||||
if [ -f "$IOC_LOCAL" ] && [ "$(cat "$IOC_LOCAL")" != "[]" ]; then
|
||||
jsonfilter -i "$IOC_LOCAL" -e '@[*]' 2>/dev/null | while read -r existing; do
|
||||
local ets=$(echo "$existing" | jsonfilter -e '@.ts' 2>/dev/null || echo "0")
|
||||
local ettl=$(echo "$existing" | jsonfilter -e '@.ttl' 2>/dev/null || echo "$TI_IOC_TTL")
|
||||
local expires=$((ets + ettl))
|
||||
[ "$now" -ge "$expires" ] && continue
|
||||
echo "$existing"
|
||||
done
|
||||
fi > "$TI_DIR/tmp-existing-$$.json"
|
||||
|
||||
# Rebuild local IOC list
|
||||
local final="["
|
||||
local ffirst=1
|
||||
|
||||
while read -r line; do
|
||||
[ -z "$line" ] && continue
|
||||
[ $ffirst -eq 0 ] && final="$final,"
|
||||
ffirst=0
|
||||
final="$final$line"
|
||||
done < "$TI_DIR/tmp-existing-$$.json"
|
||||
|
||||
# Append new IOCs
|
||||
echo "$new_iocs" | jsonfilter -e '@[*]' 2>/dev/null | while read -r nioc; do
|
||||
echo "$nioc"
|
||||
done > "$TI_DIR/tmp-new-$$.json"
|
||||
|
||||
while read -r line; do
|
||||
[ -z "$line" ] && continue
|
||||
[ $ffirst -eq 0 ] && final="$final,"
|
||||
ffirst=0
|
||||
final="$final$line"
|
||||
done < "$TI_DIR/tmp-new-$$.json"
|
||||
|
||||
final="$final]"
|
||||
echo "$final" > "$IOC_LOCAL"
|
||||
|
||||
rm -f "$TI_DIR/tmp-existing-$$.json" "$TI_DIR/tmp-new-$$.json"
|
||||
|
||||
local total=$(echo "$final" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
|
||||
logger -t threat-intel "Collected IOCs: $total local"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Publishing - Push IOCs to chain as blocks
|
||||
# ============================================================================
|
||||
ti_publish_iocs() {
|
||||
ti_init
|
||||
[ "$TI_ENABLED" != "1" ] && return 0
|
||||
|
||||
[ -f "$IOC_LOCAL" ] || return 0
|
||||
local count=$(jsonfilter -i "$IOC_LOCAL" -e '@[*]' 2>/dev/null | wc -l)
|
||||
[ "$count" -eq 0 ] && return 0
|
||||
|
||||
local published_file="$TI_DIR/.published-hashes"
|
||||
[ -f "$published_file" ] || touch "$published_file"
|
||||
|
||||
# Dump IOCs to temp file to avoid pipe-subshell variable loss
|
||||
local tmp_iocs="$TI_DIR/tmp-pub-$$.list"
|
||||
jsonfilter -i "$IOC_LOCAL" -e '@[*]' > "$tmp_iocs" 2>/dev/null
|
||||
|
||||
# Build batches of max TI_MAX_BATCH IOCs
|
||||
local batch="["
|
||||
local batch_count=0
|
||||
local bfirst=1
|
||||
local total_published=0
|
||||
|
||||
while read -r ioc; do
|
||||
[ -z "$ioc" ] && continue
|
||||
local ioc_hash=$(echo "$ioc" | sha256sum | cut -c1-16)
|
||||
|
||||
# Skip already published
|
||||
grep -q "$ioc_hash" "$published_file" 2>/dev/null && continue
|
||||
|
||||
[ $bfirst -eq 0 ] && batch="$batch,"
|
||||
bfirst=0
|
||||
batch="$batch$ioc"
|
||||
batch_count=$((batch_count + 1))
|
||||
|
||||
echo "$ioc_hash" >> "$published_file"
|
||||
|
||||
# Publish batch when full
|
||||
if [ "$batch_count" -ge "$TI_MAX_BATCH" ]; then
|
||||
batch="$batch]"
|
||||
local block_data="{\"version\":1,\"count\":$batch_count,\"iocs\":$batch}"
|
||||
local block_hash=$(echo "$block_data" | sha256sum | cut -d' ' -f1)
|
||||
_ti_chain_add_block "threat_ioc" "$block_data" "$block_hash"
|
||||
total_published=$((total_published + batch_count))
|
||||
|
||||
# Reset batch
|
||||
batch="["
|
||||
batch_count=0
|
||||
bfirst=1
|
||||
fi
|
||||
done < "$tmp_iocs"
|
||||
|
||||
# Publish remaining IOCs
|
||||
if [ "$batch_count" -gt 0 ]; then
|
||||
batch="$batch]"
|
||||
local block_data="{\"version\":1,\"count\":$batch_count,\"iocs\":$batch}"
|
||||
local block_hash=$(echo "$block_data" | sha256sum | cut -d' ' -f1)
|
||||
_ti_chain_add_block "threat_ioc" "$block_data" "$block_hash"
|
||||
total_published=$((total_published + batch_count))
|
||||
fi
|
||||
|
||||
rm -f "$tmp_iocs"
|
||||
logger -t threat-intel "Published $total_published IOCs to chain"
|
||||
echo "{\"published\":$total_published}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Receiving - Scan chain for threat_ioc blocks
|
||||
# ============================================================================
|
||||
ti_process_pending() {
|
||||
ti_init
|
||||
|
||||
[ -f "$CHAIN_FILE" ] || return 0
|
||||
local node_id=$(cat "$NODE_ID_FILE" 2>/dev/null || echo "unknown")
|
||||
local now=$(date +%s)
|
||||
local new_count=0
|
||||
|
||||
# Scan chain for unprocessed threat_ioc blocks
|
||||
jsonfilter -i "$CHAIN_FILE" -e '@.blocks[*]' 2>/dev/null | while read -r block; do
|
||||
local btype=$(echo "$block" | jsonfilter -e '@.type' 2>/dev/null)
|
||||
[ "$btype" = "threat_ioc" ] || continue
|
||||
|
||||
local bhash=$(echo "$block" | jsonfilter -e '@.hash' 2>/dev/null)
|
||||
local bnode=$(echo "$block" | jsonfilter -e '@.node' 2>/dev/null)
|
||||
|
||||
# Skip our own blocks
|
||||
[ "$bnode" = "$node_id" ] && continue
|
||||
|
||||
# Skip already processed
|
||||
grep -q "$bhash" "$TI_PROCESSED" 2>/dev/null && continue
|
||||
|
||||
# Extract IOCs from block data
|
||||
local block_data=$(echo "$block" | jsonfilter -e '@.data' 2>/dev/null)
|
||||
echo "$block_data" | jsonfilter -e '@.iocs[*]' 2>/dev/null | while read -r ioc; do
|
||||
local ip=$(echo "$ioc" | jsonfilter -e '@.ip' 2>/dev/null)
|
||||
[ -z "$ip" ] && continue
|
||||
|
||||
local ioc_ts=$(echo "$ioc" | jsonfilter -e '@.ts' 2>/dev/null || echo "0")
|
||||
local ioc_ttl=$(echo "$ioc" | jsonfilter -e '@.ttl' 2>/dev/null || echo "$TI_IOC_TTL")
|
||||
local expires=$((ioc_ts + ioc_ttl))
|
||||
|
||||
# Skip expired IOCs
|
||||
[ "$now" -ge "$expires" ] && continue
|
||||
|
||||
echo "$ioc"
|
||||
done
|
||||
|
||||
# Mark block as processed
|
||||
echo "$bhash" >> "$TI_PROCESSED"
|
||||
done > "$TI_DIR/tmp-received-$$.json"
|
||||
|
||||
# Append to received IOCs (dedup by IP)
|
||||
local existing_recv_ips=""
|
||||
if [ -f "$IOC_RECEIVED" ] && [ "$(cat "$IOC_RECEIVED")" != "[]" ]; then
|
||||
existing_recv_ips=$(jsonfilter -i "$IOC_RECEIVED" -e '@[*].ip' 2>/dev/null | sort -u)
|
||||
fi
|
||||
|
||||
# Load existing received IOCs (prune expired)
|
||||
local recv="["
|
||||
local rfirst=1
|
||||
|
||||
if [ -f "$IOC_RECEIVED" ] && [ "$(cat "$IOC_RECEIVED")" != "[]" ]; then
|
||||
jsonfilter -i "$IOC_RECEIVED" -e '@[*]' 2>/dev/null | while read -r existing; do
|
||||
local ets=$(echo "$existing" | jsonfilter -e '@.ts' 2>/dev/null || echo "0")
|
||||
local ettl=$(echo "$existing" | jsonfilter -e '@.ttl' 2>/dev/null || echo "$TI_IOC_TTL")
|
||||
local expires=$((ets + ettl))
|
||||
[ "$now" -ge "$expires" ] && continue
|
||||
[ $rfirst -eq 0 ] && recv="$recv,"
|
||||
rfirst=0
|
||||
recv="$recv$existing"
|
||||
done
|
||||
fi
|
||||
|
||||
# Add new received IOCs
|
||||
while read -r ioc_line; do
|
||||
[ -z "$ioc_line" ] && continue
|
||||
local ip=$(echo "$ioc_line" | jsonfilter -e '@.ip' 2>/dev/null)
|
||||
[ -z "$ip" ] && continue
|
||||
echo "$existing_recv_ips" | grep -q "^${ip}$" && continue
|
||||
|
||||
[ $rfirst -eq 0 ] && recv="$recv,"
|
||||
rfirst=0
|
||||
recv="$recv$ioc_line"
|
||||
new_count=$((new_count + 1))
|
||||
done < "$TI_DIR/tmp-received-$$.json"
|
||||
|
||||
recv="$recv]"
|
||||
echo "$recv" > "$IOC_RECEIVED"
|
||||
rm -f "$TI_DIR/tmp-received-$$.json"
|
||||
|
||||
logger -t threat-intel "Processed pending: $new_count new IOCs received"
|
||||
echo "{\"new_received\":$new_count}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Trust Model
|
||||
# ============================================================================
|
||||
ti_trust_score() {
|
||||
local target_node="$1"
|
||||
[ -z "$target_node" ] && { echo "unknown"; return; }
|
||||
|
||||
local node_id=$(cat "$NODE_ID_FILE" 2>/dev/null || echo "unknown")
|
||||
[ "$target_node" = "$node_id" ] && { echo "self"; return; }
|
||||
|
||||
# Check if node is a direct approved peer
|
||||
local peers_file="/tmp/secubox-p2p-peers.json"
|
||||
if [ -f "$peers_file" ]; then
|
||||
local peer_count=$(jsonfilter -i "$peers_file" -e '@.peers[*]' 2>/dev/null | wc -l)
|
||||
local p=0
|
||||
while [ $p -lt $peer_count ]; do
|
||||
local pid=$(jsonfilter -i "$peers_file" -e "@.peers[$p].id" 2>/dev/null)
|
||||
local pname=$(jsonfilter -i "$peers_file" -e "@.peers[$p].name" 2>/dev/null)
|
||||
if [ "$pid" = "$target_node" ] || [ "$pname" = "$target_node" ]; then
|
||||
echo "direct"
|
||||
return
|
||||
fi
|
||||
p=$((p + 1))
|
||||
done
|
||||
fi
|
||||
|
||||
# Check if node is known in the chain (transitive trust)
|
||||
if [ -f "$CHAIN_FILE" ]; then
|
||||
local in_chain=$(jsonfilter -i "$CHAIN_FILE" -e '@.blocks[*].node' 2>/dev/null | grep -c "^${target_node}$")
|
||||
[ "$in_chain" -gt 0 ] && { echo "transitive"; return; }
|
||||
fi
|
||||
|
||||
# Check trusted peers directory
|
||||
if [ -d "$TRUSTED_PEERS_DIR" ]; then
|
||||
for pub in "$TRUSTED_PEERS_DIR"/*.pub; do
|
||||
[ -f "$pub" ] || continue
|
||||
local fp=$(basename "$pub" .pub)
|
||||
if echo "$target_node" | grep -q "$fp"; then
|
||||
echo "direct"
|
||||
return
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "unknown"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Apply IOCs - Add CrowdSec decisions
|
||||
# ============================================================================
|
||||
ti_apply_ioc() {
|
||||
local ioc_json="$1"
|
||||
local ip=$(echo "$ioc_json" | jsonfilter -e '@.ip' 2>/dev/null)
|
||||
local severity=$(echo "$ioc_json" | jsonfilter -e '@.severity' 2>/dev/null)
|
||||
local source_node=$(echo "$ioc_json" | jsonfilter -e '@.node' 2>/dev/null)
|
||||
local scenario=$(echo "$ioc_json" | jsonfilter -e '@.scenario' 2>/dev/null || echo "mesh-shared")
|
||||
local duration=$(echo "$ioc_json" | jsonfilter -e '@.duration' 2>/dev/null || echo "${TI_IOC_TTL}s")
|
||||
local ttl=$(echo "$ioc_json" | jsonfilter -e '@.ttl' 2>/dev/null || echo "$TI_IOC_TTL")
|
||||
local ts=$(echo "$ioc_json" | jsonfilter -e '@.ts' 2>/dev/null || echo "0")
|
||||
|
||||
[ -z "$ip" ] && return 1
|
||||
|
||||
# Check whitelist
|
||||
grep -q "^${ip}$" "$TI_WHITELIST" 2>/dev/null && {
|
||||
logger -t threat-intel "Skipping whitelisted IP: $ip"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check TTL (skip expired)
|
||||
local now=$(date +%s)
|
||||
local expires=$((ts + ttl))
|
||||
[ "$now" -ge "$expires" ] && return 1
|
||||
|
||||
# Check trust level
|
||||
local trust=$(ti_trust_score "$source_node")
|
||||
|
||||
case "$trust" in
|
||||
direct)
|
||||
# Apply as-is for direct peers
|
||||
;;
|
||||
transitive)
|
||||
# Only apply if policy allows and severity is high enough
|
||||
[ "$TI_APPLY_TRANSITIVE" != "1" ] && return 1
|
||||
_severity_meets_min "$severity" "high" || return 1
|
||||
# Halve the remaining TTL for transitive trust
|
||||
local remaining=$((expires - now))
|
||||
ttl=$((remaining / 2))
|
||||
duration="${ttl}s"
|
||||
;;
|
||||
unknown|*)
|
||||
# Never auto-apply unknown sources
|
||||
logger -t threat-intel "Skipping IOC from unknown node: $source_node ($ip)"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check minimum severity
|
||||
_severity_meets_min "$severity" "$TI_MIN_SEVERITY" || return 1
|
||||
|
||||
# Apply via CrowdSec
|
||||
if command -v cscli >/dev/null 2>&1; then
|
||||
cscli decisions add --ip "$ip" --duration "$duration" \
|
||||
--reason "mesh-p2p:$scenario" --type ban 2>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
logger -t threat-intel "Applied IOC: $ip (trust=$trust, severity=$severity, source=$source_node)"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
ti_apply_pending() {
|
||||
ti_init
|
||||
[ "$TI_ENABLED" != "1" ] && return 0
|
||||
[ "$TI_AUTO_APPLY" != "1" ] && return 0
|
||||
|
||||
# First process any new blocks from chain
|
||||
ti_process_pending >/dev/null 2>&1
|
||||
|
||||
[ -f "$IOC_RECEIVED" ] || return 0
|
||||
local recv_count=$(jsonfilter -i "$IOC_RECEIVED" -e '@[*]' 2>/dev/null | wc -l)
|
||||
[ "$recv_count" -eq 0 ] && return 0
|
||||
|
||||
local applied_count=0
|
||||
local skipped_count=0
|
||||
local now=$(date +%s)
|
||||
|
||||
# Get already-applied IPs
|
||||
local applied_ips=""
|
||||
if [ -f "$IOC_APPLIED" ] && [ "$(cat "$IOC_APPLIED")" != "[]" ]; then
|
||||
applied_ips=$(jsonfilter -i "$IOC_APPLIED" -e '@[*].ip' 2>/dev/null | sort -u)
|
||||
fi
|
||||
|
||||
local new_applied="["
|
||||
local afirst=1
|
||||
|
||||
# Load existing applied (prune expired) via temp file to avoid subshell
|
||||
local tmp_applied="$TI_DIR/tmp-applied-$$.list"
|
||||
if [ -f "$IOC_APPLIED" ] && [ "$(cat "$IOC_APPLIED")" != "[]" ]; then
|
||||
jsonfilter -i "$IOC_APPLIED" -e '@[*]' > "$tmp_applied" 2>/dev/null
|
||||
while read -r existing; do
|
||||
[ -z "$existing" ] && continue
|
||||
local ets=$(echo "$existing" | jsonfilter -e '@.ts' 2>/dev/null || echo "0")
|
||||
local ettl=$(echo "$existing" | jsonfilter -e '@.ttl' 2>/dev/null || echo "$TI_IOC_TTL")
|
||||
local expires=$((ets + ettl))
|
||||
[ "$now" -ge "$expires" ] && continue
|
||||
[ $afirst -eq 0 ] && new_applied="$new_applied,"
|
||||
afirst=0
|
||||
new_applied="$new_applied$existing"
|
||||
done < "$tmp_applied"
|
||||
fi
|
||||
|
||||
# Dump received IOCs to temp file to avoid subshell
|
||||
local tmp_recv="$TI_DIR/tmp-recv-$$.list"
|
||||
jsonfilter -i "$IOC_RECEIVED" -e '@[*]' > "$tmp_recv" 2>/dev/null
|
||||
|
||||
# Apply pending received IOCs
|
||||
while read -r ioc; do
|
||||
[ -z "$ioc" ] && continue
|
||||
local ip=$(echo "$ioc" | jsonfilter -e '@.ip' 2>/dev/null)
|
||||
[ -z "$ip" ] && continue
|
||||
|
||||
# Skip already applied
|
||||
echo "$applied_ips" | grep -q "^${ip}$" && continue
|
||||
|
||||
if ti_apply_ioc "$ioc"; then
|
||||
[ $afirst -eq 0 ] && new_applied="$new_applied,"
|
||||
afirst=0
|
||||
# Add applied_at timestamp
|
||||
local ioc_with_meta=$(echo "$ioc" | sed "s/}$/,\"applied_at\":$now}/")
|
||||
new_applied="$new_applied$ioc_with_meta"
|
||||
applied_count=$((applied_count + 1))
|
||||
else
|
||||
skipped_count=$((skipped_count + 1))
|
||||
fi
|
||||
done < "$tmp_recv"
|
||||
|
||||
new_applied="$new_applied]"
|
||||
echo "$new_applied" > "$IOC_APPLIED"
|
||||
|
||||
rm -f "$tmp_applied" "$tmp_recv"
|
||||
logger -t threat-intel "Apply pending: applied=$applied_count skipped=$skipped_count"
|
||||
echo "{\"applied\":$applied_count,\"skipped\":$skipped_count}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Status and Listings
|
||||
# ============================================================================
|
||||
ti_status() {
|
||||
ti_init
|
||||
|
||||
local now=$(date +%s)
|
||||
local node_id=$(cat "$NODE_ID_FILE" 2>/dev/null || echo "unknown")
|
||||
|
||||
# Count IOCs
|
||||
local local_count=0
|
||||
local received_count=0
|
||||
local applied_count=0
|
||||
|
||||
[ -f "$IOC_LOCAL" ] && local_count=$(jsonfilter -i "$IOC_LOCAL" -e '@[*]' 2>/dev/null | wc -l)
|
||||
[ -f "$IOC_RECEIVED" ] && received_count=$(jsonfilter -i "$IOC_RECEIVED" -e '@[*]' 2>/dev/null | wc -l)
|
||||
[ -f "$IOC_APPLIED" ] && applied_count=$(jsonfilter -i "$IOC_APPLIED" -e '@[*]' 2>/dev/null | wc -l)
|
||||
|
||||
# Count contributing peers
|
||||
local peer_nodes=""
|
||||
local peer_count=0
|
||||
if [ -f "$IOC_RECEIVED" ] && [ "$received_count" -gt 0 ]; then
|
||||
peer_nodes=$(jsonfilter -i "$IOC_RECEIVED" -e '@[*].node' 2>/dev/null | sort -u)
|
||||
peer_count=$(echo "$peer_nodes" | grep -c '.' 2>/dev/null)
|
||||
[ -z "$peer_count" ] && peer_count=0
|
||||
fi
|
||||
|
||||
# Chain threat_ioc block count
|
||||
local chain_blocks=0
|
||||
if [ -f "$CHAIN_FILE" ]; then
|
||||
chain_blocks=$(jsonfilter -i "$CHAIN_FILE" -e '@.blocks[*].type' 2>/dev/null | grep -c "^threat_ioc$" 2>/dev/null)
|
||||
[ -z "$chain_blocks" ] && chain_blocks=0
|
||||
fi
|
||||
|
||||
cat << EOF
|
||||
{
|
||||
"enabled": $( [ "$TI_ENABLED" = "1" ] && echo "true" || echo "false"),
|
||||
"auto_apply": $( [ "$TI_AUTO_APPLY" = "1" ] && echo "true" || echo "false"),
|
||||
"node_id": "$node_id",
|
||||
"timestamp": $now,
|
||||
"local_iocs": $local_count,
|
||||
"received_iocs": $received_count,
|
||||
"applied_iocs": $applied_count,
|
||||
"peer_contributors": $peer_count,
|
||||
"chain_threat_blocks": $chain_blocks,
|
||||
"min_severity": "$TI_MIN_SEVERITY",
|
||||
"ioc_ttl": $TI_IOC_TTL,
|
||||
"apply_transitive": $( [ "$TI_APPLY_TRANSITIVE" = "1" ] && echo "true" || echo "false")
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
ti_list_local() {
|
||||
ti_init
|
||||
[ -f "$IOC_LOCAL" ] && cat "$IOC_LOCAL" || echo '[]'
|
||||
}
|
||||
|
||||
ti_list_received() {
|
||||
ti_init
|
||||
|
||||
if [ ! -f "$IOC_RECEIVED" ] || [ "$(cat "$IOC_RECEIVED")" = "[]" ]; then
|
||||
echo '[]'
|
||||
return
|
||||
fi
|
||||
|
||||
# Enrich with trust scores
|
||||
local enriched="["
|
||||
local efirst=1
|
||||
|
||||
jsonfilter -i "$IOC_RECEIVED" -e '@[*]' 2>/dev/null | while read -r ioc; do
|
||||
local source_node=$(echo "$ioc" | jsonfilter -e '@.node' 2>/dev/null)
|
||||
local trust=$(ti_trust_score "$source_node")
|
||||
|
||||
# Check if already applied
|
||||
local ip=$(echo "$ioc" | jsonfilter -e '@.ip' 2>/dev/null)
|
||||
local is_applied="false"
|
||||
if [ -f "$IOC_APPLIED" ]; then
|
||||
jsonfilter -i "$IOC_APPLIED" -e '@[*].ip' 2>/dev/null | grep -q "^${ip}$" && is_applied="true"
|
||||
fi
|
||||
|
||||
# Add trust and applied fields
|
||||
local enriched_ioc=$(echo "$ioc" | sed "s/}$/,\"trust\":\"$trust\",\"applied\":$is_applied}/")
|
||||
[ $efirst -eq 0 ] && enriched="$enriched,"
|
||||
efirst=0
|
||||
enriched="$enriched$enriched_ioc"
|
||||
done
|
||||
|
||||
enriched="$enriched]"
|
||||
echo "$enriched"
|
||||
}
|
||||
|
||||
ti_list_applied() {
|
||||
ti_init
|
||||
[ -f "$IOC_APPLIED" ] && cat "$IOC_APPLIED" || echo '[]'
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Peer Statistics
|
||||
# ============================================================================
|
||||
ti_peer_stats() {
|
||||
ti_init
|
||||
|
||||
[ -f "$IOC_RECEIVED" ] || { echo '[]'; return; }
|
||||
|
||||
local now=$(date +%s)
|
||||
local stats="["
|
||||
local sfirst=1
|
||||
local seen_nodes=""
|
||||
|
||||
jsonfilter -i "$IOC_RECEIVED" -e '@[*]' 2>/dev/null | while read -r ioc; do
|
||||
local node=$(echo "$ioc" | jsonfilter -e '@.node' 2>/dev/null)
|
||||
[ -z "$node" ] && continue
|
||||
|
||||
# Skip already counted nodes
|
||||
echo "$seen_nodes" | grep -q "^${node}$" && continue
|
||||
seen_nodes="$seen_nodes
|
||||
$node"
|
||||
|
||||
# Count IOCs from this node
|
||||
local ioc_count=$(jsonfilter -i "$IOC_RECEIVED" -e '@[*].node' 2>/dev/null | grep -c "^${node}$" 2>/dev/null)
|
||||
[ -z "$ioc_count" ] && ioc_count=0
|
||||
|
||||
# Get last seen timestamp
|
||||
local last_ts=0
|
||||
jsonfilter -i "$IOC_RECEIVED" -e '@[*]' 2>/dev/null | while read -r n_ioc; do
|
||||
local n_node=$(echo "$n_ioc" | jsonfilter -e '@.node' 2>/dev/null)
|
||||
[ "$n_node" = "$node" ] || continue
|
||||
local n_ts=$(echo "$n_ioc" | jsonfilter -e '@.ts' 2>/dev/null || echo "0")
|
||||
[ "$n_ts" -gt "$last_ts" ] && last_ts="$n_ts"
|
||||
done
|
||||
|
||||
local trust=$(ti_trust_score "$node")
|
||||
|
||||
# Count applied from this node
|
||||
local applied_count=0
|
||||
if [ -f "$IOC_APPLIED" ]; then
|
||||
applied_count=$(jsonfilter -i "$IOC_APPLIED" -e '@[*].node' 2>/dev/null | grep -c "^${node}$" 2>/dev/null)
|
||||
[ -z "$applied_count" ] && applied_count=0
|
||||
fi
|
||||
|
||||
[ $sfirst -eq 0 ] && stats="$stats,"
|
||||
sfirst=0
|
||||
stats="$stats{\"node\":\"$node\",\"trust\":\"$trust\",\"ioc_count\":$ioc_count,\"applied_count\":$applied_count,\"last_seen\":$last_ts}"
|
||||
done
|
||||
|
||||
stats="$stats]"
|
||||
echo "$stats"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Collect and Publish (cron entry point)
|
||||
# ============================================================================
|
||||
ti_collect_and_publish() {
|
||||
# Acquire lock
|
||||
if [ -f "$TI_LOCK" ]; then
|
||||
local lock_age=$(( $(date +%s) - $(stat -c %Y "$TI_LOCK" 2>/dev/null || echo "0") ))
|
||||
[ "$lock_age" -lt 300 ] && {
|
||||
logger -t threat-intel "Skipping collect-and-publish: locked"
|
||||
return 0
|
||||
}
|
||||
fi
|
||||
touch "$TI_LOCK"
|
||||
|
||||
ti_init
|
||||
|
||||
if [ "$TI_ENABLED" != "1" ]; then
|
||||
rm -f "$TI_LOCK"
|
||||
return 0
|
||||
fi
|
||||
|
||||
ti_collect_all
|
||||
ti_publish_iocs
|
||||
|
||||
rm -f "$TI_LOCK"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# CLI Interface
|
||||
# ============================================================================
|
||||
case "${1:-}" in
|
||||
collect-and-publish)
|
||||
ti_collect_and_publish
|
||||
;;
|
||||
apply-pending)
|
||||
ti_init
|
||||
ti_apply_pending
|
||||
;;
|
||||
status)
|
||||
ti_status
|
||||
;;
|
||||
list)
|
||||
case "${2:-}" in
|
||||
local) ti_list_local ;;
|
||||
received) ti_list_received ;;
|
||||
applied) ti_list_applied ;;
|
||||
*) ti_list_received ;;
|
||||
esac
|
||||
;;
|
||||
peers)
|
||||
ti_peer_stats
|
||||
;;
|
||||
collect)
|
||||
ti_init
|
||||
ti_collect_all
|
||||
;;
|
||||
publish)
|
||||
ti_init
|
||||
ti_publish_iocs
|
||||
;;
|
||||
process)
|
||||
ti_init
|
||||
ti_process_pending
|
||||
;;
|
||||
*)
|
||||
# When sourced as library, do nothing
|
||||
[ -n "${1:-}" ] && {
|
||||
echo "SecuBox Threat Intelligence - P2P IOC Sharing"
|
||||
echo ""
|
||||
echo "Usage: $0 <command>"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " collect-and-publish Collect IOCs and publish to chain (cron)"
|
||||
echo " apply-pending Process and apply received IOCs (cron)"
|
||||
echo " status Show threat intel status (JSON)"
|
||||
echo " list [local|received|applied] List IOCs"
|
||||
echo " peers Show peer contribution stats"
|
||||
echo " collect Collect IOCs from local sources"
|
||||
echo " publish Publish local IOCs to chain"
|
||||
echo " process Process pending chain blocks"
|
||||
}
|
||||
;;
|
||||
esac
|
||||
33
package/secubox/secubox-p2p/root/www/api/threat-intel/apply
Executable file
33
package/secubox/secubox-p2p/root/www/api/threat-intel/apply
Executable file
@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
# Threat Intel API - Apply pending IOCs
|
||||
# POST: Triggers processing and applying of received IOCs
|
||||
|
||||
echo "Content-Type: application/json"
|
||||
echo "Access-Control-Allow-Origin: *"
|
||||
echo "Access-Control-Allow-Methods: POST, OPTIONS"
|
||||
echo "Access-Control-Allow-Headers: Content-Type"
|
||||
echo ""
|
||||
|
||||
# Handle CORS preflight
|
||||
if [ "$REQUEST_METHOD" = "OPTIONS" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$REQUEST_METHOD" != "POST" ]; then
|
||||
echo '{"success":false,"error":"method_not_allowed","message":"Use POST to trigger apply"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
. /usr/lib/secubox/threat-intel.sh 2>/dev/null
|
||||
|
||||
ti_init
|
||||
|
||||
result=$(ti_apply_pending 2>/dev/null)
|
||||
|
||||
if [ -n "$result" ]; then
|
||||
applied=$(echo "$result" | jsonfilter -e '@.applied' 2>/dev/null || echo "0")
|
||||
skipped=$(echo "$result" | jsonfilter -e '@.skipped' 2>/dev/null || echo "0")
|
||||
echo "{\"success\":true,\"applied\":$applied,\"skipped\":$skipped}"
|
||||
else
|
||||
echo '{"success":true,"applied":0,"skipped":0}'
|
||||
fi
|
||||
23
package/secubox/secubox-p2p/root/www/api/threat-intel/iocs
Executable file
23
package/secubox/secubox-p2p/root/www/api/threat-intel/iocs
Executable file
@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
# Threat Intel API - IOC listing endpoint
|
||||
# GET: Returns IOCs (query: type=local|received|applied, default=received)
|
||||
|
||||
echo "Content-Type: application/json"
|
||||
echo "Access-Control-Allow-Origin: *"
|
||||
echo ""
|
||||
|
||||
. /usr/lib/secubox/threat-intel.sh 2>/dev/null
|
||||
|
||||
# Parse query string for type parameter
|
||||
ioc_type="received"
|
||||
case "$QUERY_STRING" in
|
||||
*type=local*) ioc_type="local" ;;
|
||||
*type=received*) ioc_type="received" ;;
|
||||
*type=applied*) ioc_type="applied" ;;
|
||||
esac
|
||||
|
||||
case "$ioc_type" in
|
||||
local) ti_list_local 2>/dev/null ;;
|
||||
received) ti_list_received 2>/dev/null ;;
|
||||
applied) ti_list_applied 2>/dev/null ;;
|
||||
esac || echo '[]'
|
||||
11
package/secubox/secubox-p2p/root/www/api/threat-intel/peers
Executable file
11
package/secubox/secubox-p2p/root/www/api/threat-intel/peers
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
# Threat Intel API - Peer contribution stats
|
||||
# GET: Returns per-peer IOC statistics and trust levels
|
||||
|
||||
echo "Content-Type: application/json"
|
||||
echo "Access-Control-Allow-Origin: *"
|
||||
echo ""
|
||||
|
||||
. /usr/lib/secubox/threat-intel.sh 2>/dev/null
|
||||
|
||||
ti_peer_stats 2>/dev/null || echo '[]'
|
||||
34
package/secubox/secubox-p2p/root/www/api/threat-intel/publish
Executable file
34
package/secubox/secubox-p2p/root/www/api/threat-intel/publish
Executable file
@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
# Threat Intel API - Publish IOCs to chain
|
||||
# POST: Triggers collection and publishing of local IOCs
|
||||
|
||||
echo "Content-Type: application/json"
|
||||
echo "Access-Control-Allow-Origin: *"
|
||||
echo "Access-Control-Allow-Methods: POST, OPTIONS"
|
||||
echo "Access-Control-Allow-Headers: Content-Type"
|
||||
echo ""
|
||||
|
||||
# Handle CORS preflight
|
||||
if [ "$REQUEST_METHOD" = "OPTIONS" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$REQUEST_METHOD" != "POST" ]; then
|
||||
echo '{"success":false,"error":"method_not_allowed","message":"Use POST to trigger publish"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
. /usr/lib/secubox/threat-intel.sh 2>/dev/null
|
||||
|
||||
ti_init
|
||||
|
||||
# Collect fresh IOCs then publish
|
||||
ti_collect_all >/dev/null 2>&1
|
||||
result=$(ti_publish_iocs 2>/dev/null)
|
||||
|
||||
if [ -n "$result" ]; then
|
||||
published=$(echo "$result" | jsonfilter -e '@.published' 2>/dev/null || echo "0")
|
||||
echo "{\"success\":true,\"published\":$published}"
|
||||
else
|
||||
echo '{"success":true,"published":0}'
|
||||
fi
|
||||
11
package/secubox/secubox-p2p/root/www/api/threat-intel/status
Executable file
11
package/secubox/secubox-p2p/root/www/api/threat-intel/status
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
# Threat Intel API - Status endpoint
|
||||
# GET: Returns threat intelligence status summary
|
||||
|
||||
echo "Content-Type: application/json"
|
||||
echo "Access-Control-Allow-Origin: *"
|
||||
echo ""
|
||||
|
||||
. /usr/lib/secubox/threat-intel.sh 2>/dev/null
|
||||
|
||||
ti_status 2>/dev/null || echo '{"error":"threat_intel_unavailable"}'
|
||||
Loading…
Reference in New Issue
Block a user