Files
vps-management-bot/scripts/mac-guard.js

113 lines
3.3 KiB
JavaScript
Raw Normal View History

2026-03-21 01:10:53 +08:00
/**
* Mac 登录失败监控 & 自动封禁脚本
* 检测 SSH/登录失败同一 IP 失败 5 次自动封禁
*/
const { spawn } = require('child_process');
const https = require('https');
const fs = require('fs');
// ========== 配置 ==========
const CONFIG = {
maxFailures: 5,
banDurationMin: 60,
tgBotToken: '8300905342:AAH3Q78FuR5Exrw2zWYJHFRyVeLlws3xnww',
tgChatId: '165067365',
dataFile: '/Users/jianzhang/.openclaw/workspace/scripts/ban-data.json',
whitelistIPs: ['127.0.0.1', '192.168.1.1']
};
let failedAttempts = {};
let bannedIPs = {};
// ========== Telegram 通知 ==========
function sendTG(message) {
const data = JSON.stringify({ chat_id: CONFIG.tgChatId, text: message, parse_mode: 'HTML' });
const req = https.request({
hostname: 'api.telegram.org',
path: `/bot${CONFIG.tgBotToken}/sendMessage`,
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
req.write(data);
req.end();
}
// ========== 数据持久化 ==========
function saveData() {
fs.writeFileSync(CONFIG.dataFile, JSON.stringify({ failedAttempts, bannedIPs }, null, 2));
}
function loadData() {
try {
if (fs.existsSync(CONFIG.dataFile)) {
const d = JSON.parse(fs.readFileSync(CONFIG.dataFile, 'utf8'));
failedAttempts = d.failedAttempts || {};
bannedIPs = d.bannedIPs || {};
}
} catch (e) {}
}
// ========== 封禁/解封 IP ==========
function banIP(ip) {
if (CONFIG.whitelistIPs.includes(ip) || bannedIPs[ip]) return;
console.log(`[BAN] ${ip}`);
bannedIPs[ip] = Date.now();
spawn('sudo', ['pfctl', '-t', 'bruteforce', '-T', 'add', ip]).on('close', (code) => {
if (code === 0) {
sendTG(`🚫 <b>IP 已封禁</b>\n\nIP: <code>${ip}</code>\n原因: 登录失败 ${CONFIG.maxFailures}\n时长: ${CONFIG.banDurationMin} 分钟`);
}
});
setTimeout(() => unbanIP(ip), CONFIG.banDurationMin * 60 * 1000);
saveData();
}
function unbanIP(ip) {
if (!bannedIPs[ip]) return;
console.log(`[UNBAN] ${ip}`);
delete bannedIPs[ip];
delete failedAttempts[ip];
spawn('sudo', ['pfctl', '-t', 'bruteforce', '-T', 'delete', ip]);
saveData();
}
// ========== 记录失败 ==========
function recordFailure(ip) {
if (CONFIG.whitelistIPs.includes(ip) || bannedIPs[ip]) return;
if (!failedAttempts[ip]) failedAttempts[ip] = { count: 0, firstTime: Date.now() };
failedAttempts[ip].count++;
console.log(`[FAIL] ${ip} x${failedAttempts[ip].count}`);
if (failedAttempts[ip].count >= CONFIG.maxFailures) banIP(ip);
saveData();
}
// ========== 解析日志 ==========
function parseLine(line) {
let m = line.match(/Failed password for .* from ([\d.]+)/);
if (m) return m[1];
m = line.match(/Invalid user .* from ([\d.]+)/);
if (m) return m[1];
m = line.match(/authentication failure.*rhost=([\d.]+)/);
if (m) return m[1];
return null;
}
// ========== 启动监控 ==========
function start() {
console.log('[START] Mac Guard 启动');
loadData();
const log = spawn('log', ['stream', '--predicate', 'process == "sshd"', '--style', 'syslog']);
log.stdout.on('data', (d) => {
d.toString().split('\n').forEach(line => {
const ip = parseLine(line);
if (ip) recordFailure(ip);
});
});
log.on('close', () => { console.log('[RESTART]'); setTimeout(start, 5000); });
}
start();