324 lines
10 KiB
Bash
324 lines
10 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
REPORTS_DIR="/var/lib/saikyo-av/reports"
|
|
LOG_DIR="/var/log/saikyo-av"
|
|
QUAR_DIR="/var/lib/saikyo-av/quarantine"
|
|
|
|
mkdir -p "${REPORTS_DIR}" "${LOG_DIR}" "${QUAR_DIR}" || true
|
|
chmod 0755 /var/lib/saikyo-av "${REPORTS_DIR}" "${LOG_DIR}" 2>/dev/null || true
|
|
|
|
cmd="${1:-}"
|
|
shift || true
|
|
|
|
utc_ts() {
|
|
date -u +%Y-%m-%dT%H:%M:%SZ
|
|
}
|
|
|
|
write_report() {
|
|
local id="$1"
|
|
local summary="$2"
|
|
local severity="$3"
|
|
local outfile="${REPORTS_DIR}/${id}.json"
|
|
local created
|
|
created="$(utc_ts)"
|
|
|
|
# shellcheck disable=SC2129
|
|
{
|
|
echo "{"
|
|
echo " \"created_utc\": \"${created}\","
|
|
echo " \"severity\": \"${severity}\","
|
|
echo " \"summary\": \"${summary}\","
|
|
echo " \"details\": {"
|
|
echo " \"action\": \"${cmd}\","
|
|
echo " \"argv\": \"$*\""
|
|
echo " },"
|
|
echo " \"artifacts\": {},"
|
|
echo " \"suggested_fixes\": []"
|
|
echo "}"
|
|
} > "${outfile}"
|
|
|
|
echo "${outfile}"
|
|
}
|
|
|
|
run_to_file() {
|
|
local out="$1"
|
|
shift
|
|
("$@" 2>&1 || true) | sed -e 's/\r$//' > "${out}"
|
|
}
|
|
|
|
json_escape() {
|
|
# Minimal JSON string escape (no unicode handling needed for our file paths)
|
|
sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\t/\\t/g' -e 's/\r/\\r/g' -e 's/\n/\\n/g'
|
|
}
|
|
|
|
write_scan_report() {
|
|
local id="$1"
|
|
local summary="$2"
|
|
local severity="$3"
|
|
local log_file="$4"
|
|
local detections_tsv="$5"
|
|
local outfile="${REPORTS_DIR}/${id}.json"
|
|
local created
|
|
created="$(utc_ts)"
|
|
|
|
local scanned_count infected_count
|
|
scanned_count="$(grep -E '^Scanned files:' "${log_file}" 2>/dev/null | awk -F': ' '{print $2}' | tail -n 1 || true)"
|
|
infected_count="$(grep -E '^Infected files:' "${log_file}" 2>/dev/null | awk -F': ' '{print $2}' | tail -n 1 || true)"
|
|
scanned_count="${scanned_count:-0}"
|
|
infected_count="${infected_count:-0}"
|
|
|
|
{
|
|
echo "{"
|
|
echo " \"created_utc\": \"${created}\","
|
|
echo " \"severity\": \"${severity}\","
|
|
echo " \"summary\": \"${summary}\","
|
|
echo " \"details\": {"
|
|
echo " \"action\": \"${cmd}\","
|
|
echo " \"argv\": \"$*\","
|
|
echo " \"scanned_files\": ${scanned_count},"
|
|
echo " \"infected_files\": ${infected_count},"
|
|
echo " \"detections\": ["
|
|
|
|
if [[ -s "${detections_tsv}" ]]; then
|
|
first=1
|
|
while IFS=$'\t' read -r pth sig; do
|
|
[[ -n "${pth}" ]] || continue
|
|
pth_esc="$(printf '%s' "${pth}" | json_escape)"
|
|
sig_esc="$(printf '%s' "${sig}" | json_escape)"
|
|
if [[ "${first}" -eq 1 ]]; then
|
|
first=0
|
|
else
|
|
echo ","
|
|
fi
|
|
printf ' {"path":"%s","name":"%s"}' "${pth_esc}" "${sig_esc}"
|
|
done < "${detections_tsv}"
|
|
echo
|
|
fi
|
|
|
|
echo " ]"
|
|
echo " },"
|
|
echo " \"artifacts\": {"
|
|
echo " \"clamav_log\": \"${log_file}\","
|
|
echo " \"detections_tsv\": \"${detections_tsv}\""
|
|
echo " },"
|
|
echo " \"suggested_fixes\": ["
|
|
echo " {\"id\":\"quarantine\",\"title\":\"Quarantine infected file\",\"description\":\"Move selected file to local quarantine.\",\"requires_consent\":true},"
|
|
echo " {\"id\":\"delete\",\"title\":\"Delete infected file\",\"description\":\"Delete selected infected file (dangerous).\",\"requires_consent\":true}"
|
|
echo " ]"
|
|
echo "}"
|
|
} > "${outfile}"
|
|
|
|
echo "${outfile}"
|
|
}
|
|
|
|
svc_is_enabled() {
|
|
systemctl is-enabled saikyo-avd.timer 2>/dev/null || true
|
|
}
|
|
|
|
svc_is_active() {
|
|
systemctl is-active saikyo-avd.timer 2>/dev/null || true
|
|
}
|
|
|
|
case "${cmd}" in
|
|
health)
|
|
echo "ok"
|
|
;;
|
|
|
|
status-protection)
|
|
echo "enabled=$(svc_is_enabled) active=$(svc_is_active)"
|
|
;;
|
|
|
|
run-evidence)
|
|
outdir="${REPORTS_DIR}/artifacts"
|
|
mkdir -p "${outdir}" || true
|
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
evidence_out="${outdir}/saikyo-evidence-${ts}.log"
|
|
|
|
if command -v saikyo-evidence >/dev/null 2>&1; then
|
|
run_to_file "${evidence_out}" saikyo-evidence
|
|
elif [ -x /usr/bin/saikyo-evidence ]; then
|
|
run_to_file "${evidence_out}" /usr/bin/saikyo-evidence
|
|
else
|
|
echo "saikyo-evidence not found" > "${evidence_out}"
|
|
fi
|
|
|
|
rpt="$(write_report "evidence-${ts}" "Evidence report generated" "info" "${cmd}")"
|
|
# Append artifacts into report JSON (minimal, without jq dependency).
|
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"evidence_log\": \"${evidence_out}\"}#" "${rpt}" || true
|
|
echo "${rpt}"
|
|
;;
|
|
|
|
scan)
|
|
outdir="${REPORTS_DIR}/artifacts"
|
|
mkdir -p "${outdir}" || true
|
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
log_file="${outdir}/clamav-scan-${ts}.log"
|
|
det_tsv="${outdir}/clamav-detections-${ts}.tsv"
|
|
|
|
if ! command -v clamscan >/dev/null 2>&1; then
|
|
echo "clamscan not found (install clamav)" > "${log_file}"
|
|
rpt="$(write_report "scan-${ts}" "ClamAV scan failed: clamscan not found" "warn" "${cmd}")"
|
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"clamav_log\": \"${log_file}\"}#" "${rpt}" || true
|
|
echo "${rpt}"
|
|
exit 1
|
|
fi
|
|
|
|
# Full scan of / with exclusions.
|
|
# - exclude hidden paths (/.*/)
|
|
# - exclude virtual/system dirs
|
|
# - keep a log we can parse
|
|
(
|
|
clamscan \
|
|
-r \
|
|
--infected \
|
|
--no-summary \
|
|
--exclude-dir='^/proc/' \
|
|
--exclude-dir='^/sys/' \
|
|
--exclude-dir='^/dev/' \
|
|
--exclude-dir='^/run/' \
|
|
--exclude-dir='^/tmp/' \
|
|
--exclude-dir='/.*/' \
|
|
/ \
|
|
2>&1
|
|
echo
|
|
clamscan -r --infected --exclude-dir='^/proc/' --exclude-dir='^/sys/' --exclude-dir='^/dev/' --exclude-dir='^/run/' --exclude-dir='^/tmp/' --exclude-dir='/.*/' / --summary 2>/dev/null || true
|
|
) | sed -e 's/\r$//' > "${log_file}"
|
|
|
|
: > "${det_tsv}"
|
|
# Parse "PATH: SIGNATURE FOUND" lines.
|
|
grep -E ' FOUND$' "${log_file}" | sed -E 's/: (.+) FOUND$//;t;d' >/dev/null 2>&1 || true
|
|
while IFS= read -r line; do
|
|
# Example: /path/file: Eicar-Test-Signature FOUND
|
|
pth="${line%%:*}"
|
|
rest="${line#*: }"
|
|
sig="${rest% FOUND}"
|
|
printf '%s\t%s\n' "${pth}" "${sig}" >> "${det_tsv}"
|
|
done < <(grep -E ' FOUND$' "${log_file}" || true)
|
|
|
|
infected_count="$(wc -l < "${det_tsv}" 2>/dev/null || echo 0)"
|
|
severity="info"
|
|
summary="ClamAV scan completed: no threats"
|
|
if [[ "${infected_count}" -gt 0 ]]; then
|
|
severity="bad"
|
|
summary="ClamAV scan completed: ${infected_count} threat(s) found"
|
|
fi
|
|
|
|
rpt="$(write_scan_report "scan-${ts}" "${summary}" "${severity}" "${log_file}" "${det_tsv}" "${cmd}")"
|
|
echo "${rpt}"
|
|
;;
|
|
|
|
quarantine)
|
|
target="${1:-}"
|
|
if [[ -z "${target}" ]]; then
|
|
echo "missing file path" >&2
|
|
exit 2
|
|
fi
|
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
bn="$(basename -- "${target}" 2>/dev/null || echo file)"
|
|
dest="${QUAR_DIR}/${ts}-${bn}"
|
|
if [[ ! -f "${target}" ]]; then
|
|
rpt="$(write_report "quarantine-${ts}" "Quarantine failed: file not found" "warn" "${cmd}")"
|
|
echo "${rpt}"
|
|
exit 1
|
|
fi
|
|
mv -f -- "${target}" "${dest}" 2>>"${LOG_DIR}/saikyo-av-admin.log" || {
|
|
rpt="$(write_report "quarantine-${ts}" "Quarantine failed: move error" "warn" "${cmd}")"
|
|
echo "${rpt}"
|
|
exit 1
|
|
}
|
|
rpt="$(write_report "quarantine-${ts}" "File quarantined" "warn" "${cmd}")"
|
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"from\": \"${target}\", \"to\": \"${dest}\"}#" "${rpt}" || true
|
|
echo "${rpt}"
|
|
;;
|
|
|
|
delete)
|
|
target="${1:-}"
|
|
if [[ -z "${target}" ]]; then
|
|
echo "missing file path" >&2
|
|
exit 2
|
|
fi
|
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
if [[ ! -e "${target}" ]]; then
|
|
rpt="$(write_report "delete-${ts}" "Delete failed: path not found" "warn" "${cmd}")"
|
|
echo "${rpt}"
|
|
exit 1
|
|
fi
|
|
rm -rf -- "${target}" 2>>"${LOG_DIR}/saikyo-av-admin.log" || {
|
|
rpt="$(write_report "delete-${ts}" "Delete failed" "warn" "${cmd}")"
|
|
echo "${rpt}"
|
|
exit 1
|
|
}
|
|
rpt="$(write_report "delete-${ts}" "File deleted" "warn" "${cmd}")"
|
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"path\": \"${target}\"}#" "${rpt}" || true
|
|
echo "${rpt}"
|
|
;;
|
|
|
|
run-audit)
|
|
outdir="${REPORTS_DIR}/artifacts"
|
|
mkdir -p "${outdir}" || true
|
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
audit_out="${outdir}/saikyo-audit-report-${ts}.txt"
|
|
|
|
if command -v saikyo-audit-report >/dev/null 2>&1; then
|
|
run_to_file "${audit_out}" saikyo-audit-report
|
|
else
|
|
echo "saikyo-audit-report not installed" > "${audit_out}"
|
|
fi
|
|
|
|
rpt="$(write_report "audit-${ts}" "Audit report executed" "info" "${cmd}")"
|
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"audit_stdout\": \"${audit_out}\"}#" "${rpt}" || true
|
|
echo "${rpt}"
|
|
;;
|
|
|
|
collect-artifacts)
|
|
outdir="${REPORTS_DIR}/artifacts"
|
|
mkdir -p "${outdir}" || true
|
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
collect_out="${outdir}/collect-artifacts-${ts}.log"
|
|
|
|
if [ -x /usr/share/saikyo-os/forensics/collect-artifacts.sh ]; then
|
|
run_to_file "${collect_out}" /usr/share/saikyo-os/forensics/collect-artifacts.sh
|
|
else
|
|
echo "collect-artifacts.sh not found" > "${collect_out}"
|
|
fi
|
|
|
|
rpt="$(write_report "collect-${ts}" "Artifacts collection executed" "info" "${cmd}")"
|
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"collector_log\": \"${collect_out}\"}#" "${rpt}" || true
|
|
echo "${rpt}"
|
|
;;
|
|
|
|
enable-protection)
|
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
(systemctl daemon-reload 2>&1 || true) >> "${LOG_DIR}/saikyo-av-admin.log" || true
|
|
if systemctl enable --now saikyo-avd.timer >/dev/null 2>&1; then
|
|
rpt="$(write_report "enable-${ts}" "Protection enabled" "info" "${cmd}")"
|
|
echo "${rpt}"
|
|
exit 0
|
|
else
|
|
rpt="$(write_report "enable-${ts}" "Protection enable failed" "warn" "${cmd}")"
|
|
echo "${rpt}"
|
|
exit 1
|
|
fi
|
|
;;
|
|
|
|
disable-protection)
|
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
(systemctl daemon-reload 2>&1 || true) >> "${LOG_DIR}/saikyo-av-admin.log" || true
|
|
if systemctl disable --now saikyo-avd.timer >/dev/null 2>&1; then
|
|
rpt="$(write_report "disable-${ts}" "Protection disabled" "warn" "${cmd}")"
|
|
echo "${rpt}"
|
|
exit 0
|
|
else
|
|
rpt="$(write_report "disable-${ts}" "Protection disable failed" "warn" "${cmd}")"
|
|
echo "${rpt}"
|
|
exit 1
|
|
fi
|
|
;;
|
|
|
|
*)
|
|
echo "Usage: saikyo-av-admin {health|status-protection|run-evidence|run-audit|collect-artifacts|scan|quarantine <file>|delete <file>|enable-protection|disable-protection}" >&2
|
|
exit 2
|
|
;;
|
|
esac
|