123 lines
4.6 KiB
PHP
123 lines
4.6 KiB
PHP
<?php
|
|
// WEVIA Intent Helper : incident-remediation
|
|
// PENDING_APPROVAL wired by Opus 20 Apr - triggered EXCLUSIVELY by Yacine
|
|
// Usage: /api/incident-remediation.php?action=<action>
|
|
// Doctrine #2 zero regression, #3 GOLD backup, #14 zero écrasement sans auth
|
|
// All actions are reversible except kill which is trivially recoverable (systemd would restart)
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
$action = $_GET['action'] ?? '';
|
|
$VAULT = '/opt/wevads/vault/incident-20260420-postgres-cron';
|
|
@mkdir($VAULT, 0755, true);
|
|
$TS = date('Ymd-His');
|
|
$out = ['action'=>$action, 'ts'=>date('c')];
|
|
|
|
switch($action) {
|
|
|
|
case 'backup-only':
|
|
// Pure backup - safest step
|
|
$r = @shell_exec('sudo cat /var/spool/cron/crontabs/postgres 2>&1');
|
|
$path = "$VAULT/pre-action-postgres-crontab-{$TS}.txt";
|
|
@file_put_contents($path, $r);
|
|
$out['backup'] = $path;
|
|
$out['size'] = strlen($r);
|
|
$out['md5'] = md5($r);
|
|
break;
|
|
|
|
case 'remove-postgres-pastebin-cron':
|
|
// Backup first
|
|
$r = @shell_exec('sudo cat /var/spool/cron/crontabs/postgres 2>&1');
|
|
$path = "$VAULT/pre-remove-postgres-crontab-{$TS}.txt";
|
|
@file_put_contents($path, $r);
|
|
$out['backup_path'] = $path;
|
|
// Filter out malicious line
|
|
$lines = explode("\n", $r);
|
|
$new_lines = [];
|
|
$removed = [];
|
|
foreach($lines as $l) {
|
|
if (stripos($l, 'pastebin.com/raw/C0Y31fxq') !== false) {
|
|
$removed[] = $l;
|
|
} else {
|
|
$new_lines[] = $l;
|
|
}
|
|
}
|
|
if (empty($removed)) {
|
|
$out['status'] = 'already_removed_or_not_present';
|
|
break;
|
|
}
|
|
$new_content = implode("\n", $new_lines);
|
|
// Write via postgres user crontab reinstall
|
|
$tmpfile = "/tmp/postgres-crontab-clean-{$TS}";
|
|
@file_put_contents($tmpfile, $new_content);
|
|
@shell_exec("sudo chown postgres:crontab $tmpfile 2>&1");
|
|
@shell_exec("sudo chmod 600 $tmpfile 2>&1");
|
|
$install = @shell_exec("sudo -u postgres crontab $tmpfile 2>&1");
|
|
@unlink($tmpfile);
|
|
// Verify
|
|
$after = @shell_exec('sudo cat /var/spool/cron/crontabs/postgres 2>&1');
|
|
$out['removed_lines'] = $removed;
|
|
$out['install_result'] = trim($install);
|
|
$out['still_present'] = (stripos($after, 'pastebin.com/raw/C0Y31fxq') !== false);
|
|
$out['new_content'] = $after;
|
|
break;
|
|
|
|
case 'chattr-lock-crontab':
|
|
// Make postgres crontab immutable to prevent re-install
|
|
$out['before'] = trim(@shell_exec('sudo lsattr /var/spool/cron/crontabs/postgres 2>&1'));
|
|
@shell_exec('sudo chattr +i /var/spool/cron/crontabs/postgres 2>&1');
|
|
$out['after'] = trim(@shell_exec('sudo lsattr /var/spool/cron/crontabs/postgres 2>&1'));
|
|
break;
|
|
|
|
case 'kill-orphan-paperclip-postgres':
|
|
// Dynamically find orphan node running as postgres with cwd=/opt/paperclip-weval
|
|
$find = @shell_exec(
|
|
"ps -eo pid,user,ppid,cmd 2>/dev/null | awk '\$2==\"postgres\" && \$4 ~ /\\/node$/ && \$3==\"1\" {print \$1}' | head -5"
|
|
);
|
|
$pids = array_filter(array_map('trim', explode("\n", trim($find))));
|
|
$out['found_pids'] = $pids;
|
|
if (empty($pids)) {
|
|
$out['status'] = 'no_orphan_found';
|
|
break;
|
|
}
|
|
$killed = [];
|
|
foreach($pids as $pid) {
|
|
if (!preg_match('/^\d+$/', $pid)) continue;
|
|
// Verify cwd before killing
|
|
$cwd = @shell_exec("sudo readlink /proc/$pid/cwd 2>/dev/null");
|
|
if (strpos((string)$cwd, '/opt/paperclip-weval') === false) {
|
|
$killed[] = ['pid'=>$pid, 'skipped'=>'cwd_not_paperclip', 'cwd'=>trim($cwd ?? '')];
|
|
continue;
|
|
}
|
|
$r = @shell_exec("sudo kill -TERM $pid 2>&1");
|
|
sleep(2);
|
|
$alive = trim(@shell_exec("ps -p $pid -o pid= 2>/dev/null"));
|
|
if ($alive !== '') {
|
|
// Still alive, escalate
|
|
@shell_exec("sudo kill -KILL $pid 2>&1");
|
|
}
|
|
$killed[] = ['pid'=>$pid, 'term_result'=>trim($r), 'alive_after'=>($alive!==''), 'cwd'=>trim($cwd)];
|
|
}
|
|
$out['killed'] = $killed;
|
|
break;
|
|
|
|
case 'full-cleanup':
|
|
// 1) backup, 2) remove, 3) lock, 4) kill orphan
|
|
$sub = [];
|
|
foreach(['backup-only','remove-postgres-pastebin-cron','chattr-lock-crontab','kill-orphan-paperclip-postgres'] as $a) {
|
|
$url = "http://localhost/api/incident-remediation.php?action=$a";
|
|
$sub[$a] = json_decode(@file_get_contents($url), true);
|
|
}
|
|
$out['sub_actions'] = $sub;
|
|
break;
|
|
|
|
default:
|
|
$out['status'] = 'unknown_action';
|
|
$out['valid_actions'] = ['backup-only','remove-postgres-pastebin-cron','chattr-lock-crontab','kill-orphan-paperclip-postgres','full-cleanup'];
|
|
}
|
|
|
|
// Always log
|
|
@file_put_contents("$VAULT/remediation-log-{$TS}.json", json_encode($out, JSON_PRETTY_PRINT));
|
|
|
|
echo json_encode($out, JSON_PRETTY_PRINT);
|