313 lines
8.9 KiB
Bash
Executable File
313 lines
8.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ENV_FILE="${ENV_FILE:-$SCRIPT_DIR/.env}"
|
|
SRC_DIR="$SCRIPT_DIR/src"
|
|
|
|
log(){ printf '[malware-watch] %s\n' "$*"; }
|
|
die(){ printf '[malware-watch] ERROR: %s\n' "$*" >&2; exit 1; }
|
|
|
|
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
|
|
exec sudo -E bash "$0" "$@"
|
|
fi
|
|
|
|
[[ -r "$ENV_FILE" ]] || die "Missing $ENV_FILE"
|
|
set -a
|
|
. "$ENV_FILE"
|
|
set +a
|
|
|
|
: "${DISCORD_WEBHOOK:?DISCORD_WEBHOOK is required in .env}"
|
|
: "${ABUSECH_AUTH_KEY:?ABUSECH_AUTH_KEY is required in .env}"
|
|
[[ "$DISCORD_WEBHOOK" != "디스코드웹훅URL" ]] || die "Replace DISCORD_WEBHOOK placeholder in .env"
|
|
[[ "$ABUSECH_AUTH_KEY" != "abuse.ch api 키" ]] || die "Replace ABUSECH_AUTH_KEY placeholder in .env"
|
|
|
|
CPU_THRESHOLD="${CPU_THRESHOLD:-150}"
|
|
ALERT_SCORE="${ALERT_SCORE:-60}"
|
|
CRITICAL_SCORE="${CRITICAL_SCORE:-100}"
|
|
COOLDOWN_SECONDS="${COOLDOWN_SECONDS:-1800}"
|
|
CLAMAV_VERSION="${CLAMAV_VERSION:-1.4.6}"
|
|
CLAMAV_URL="${CLAMAV_URL:-}"
|
|
INSTALL_CLAMAV="${INSTALL_CLAMAV:-1}"
|
|
RUN_FRESHCLAM_NOW="${RUN_FRESHCLAM_NOW:-0}"
|
|
SEND_INSTALL_TEST="${SEND_INSTALL_TEST:-0}"
|
|
|
|
case "$DISCORD_WEBHOOK$ABUSECH_AUTH_KEY" in
|
|
*"'"*) die "Secrets containing a single quote are not supported by this installer" ;;
|
|
esac
|
|
|
|
command -v systemctl >/dev/null || die "systemd is required"
|
|
command -v apt-get >/dev/null || die "This installer currently supports Debian/Ubuntu (apt)"
|
|
|
|
for f in malware-watch.py malware-quarantine.py malware-feed-update.py clamav-watch-scan.sh block-malware-iocs; do
|
|
[[ -f "$SRC_DIR/$f" ]] || die "Missing $SRC_DIR/$f"
|
|
done
|
|
|
|
log "Installing base dependencies"
|
|
apt-get update -y
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
|
ca-certificates curl python3 iproute2 procps util-linux iptables coreutils passwd
|
|
|
|
install -d -m 700 /etc/malware-watch /var/lib/malware-watch /var/lib/malware-watch/feeds
|
|
install -d -m 700 /root/security-evidence/malware-watch /root/security-evidence/quarantine
|
|
|
|
BACKUP="/root/security-evidence/malware-watch/install-backup-$(date +%Y%m%d-%H%M%S)"
|
|
install -d -m 700 "$BACKUP"
|
|
for f in \
|
|
/etc/malware-watch/config \
|
|
/usr/local/sbin/malware-watch.py \
|
|
/usr/local/sbin/malware-quarantine.py \
|
|
/usr/local/sbin/malware-feed-update.py \
|
|
/usr/local/sbin/clamav-watch-scan.sh \
|
|
/usr/local/sbin/block-malware-iocs; do
|
|
[[ -e "$f" ]] && cp -a "$f" "$BACKUP/" || true
|
|
done
|
|
|
|
umask 077
|
|
{
|
|
printf "CPU_THRESHOLD='%s'\n" "$CPU_THRESHOLD"
|
|
printf "ALERT_SCORE='%s'\n" "$ALERT_SCORE"
|
|
printf "CRITICAL_SCORE='%s'\n" "$CRITICAL_SCORE"
|
|
printf "COOLDOWN_SECONDS='%s'\n" "$COOLDOWN_SECONDS"
|
|
printf "ABUSECH_AUTH_KEY='%s'\n" "$ABUSECH_AUTH_KEY"
|
|
printf "DISCORD_WEBHOOK='%s'\n" "$DISCORD_WEBHOOK"
|
|
} > /etc/malware-watch/config
|
|
chmod 600 /etc/malware-watch/config
|
|
|
|
for f in malware-watch.py malware-quarantine.py malware-feed-update.py clamav-watch-scan.sh block-malware-iocs; do
|
|
install -o root -g root -m 700 "$SRC_DIR/$f" "/usr/local/sbin/$f"
|
|
done
|
|
|
|
if [[ "$INSTALL_CLAMAV" == "1" ]]; then
|
|
arch="$(uname -m)"
|
|
case "$arch" in
|
|
aarch64|arm64) clam_arch="aarch64" ;;
|
|
x86_64|amd64) clam_arch="x86_64" ;;
|
|
*) die "Unsupported ClamAV architecture: $arch" ;;
|
|
esac
|
|
|
|
if ! /usr/local/bin/clamscan --version 2>/dev/null | grep -q "ClamAV $CLAMAV_VERSION"; then
|
|
log "Installing official ClamAV $CLAMAV_VERSION for $clam_arch"
|
|
deb="$(mktemp /tmp/clamav.XXXXXX.deb)"
|
|
url="${CLAMAV_URL:-https://www.clamav.net/downloads/production/clamav-${CLAMAV_VERSION}.linux.${clam_arch}.deb}"
|
|
curl -fL --retry 3 --retry-delay 2 -o "$deb" "$url"
|
|
systemctl disable --now clamav-freshclam 2>/dev/null || true
|
|
DEBIAN_FRONTEND=noninteractive apt-get remove -y \
|
|
clamav clamav-freshclam clamav-base libclamav9 libclamav11 2>/dev/null || true
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y "$deb"
|
|
rm -f "$deb"
|
|
fi
|
|
|
|
[[ -x /usr/local/bin/clamscan ]] || die "Official ClamAV clamscan not found after installation"
|
|
[[ -f /usr/local/etc/freshclam.conf.sample ]] || die "freshclam.conf.sample not found"
|
|
|
|
if ! getent group clamav >/dev/null 2>&1; then
|
|
groupadd --system clamav
|
|
fi
|
|
if ! id -u clamav >/dev/null 2>&1; then
|
|
useradd --system --gid clamav --home-dir /var/lib/clamav --shell /usr/sbin/nologin clamav
|
|
fi
|
|
install -d -o clamav -g clamav -m 755 /var/lib/clamav
|
|
|
|
cp /usr/local/etc/freshclam.conf.sample /usr/local/etc/freshclam.conf
|
|
sed -i 's/^Example/#Example/' /usr/local/etc/freshclam.conf
|
|
cat >> /usr/local/etc/freshclam.conf <<'EOF_FRESH'
|
|
DatabaseDirectory /var/lib/clamav
|
|
DatabaseOwner clamav
|
|
Checks 12
|
|
EOF_FRESH
|
|
chmod 644 /usr/local/etc/freshclam.conf
|
|
fi
|
|
|
|
cat > /etc/systemd/system/malware-watch.service <<'EOF_UNIT'
|
|
[Unit]
|
|
Description=Malware Watch realtime detector
|
|
After=network-online.target docker.service
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/local/sbin/malware-watch.py
|
|
User=root
|
|
Nice=10
|
|
IOSchedulingClass=best-effort
|
|
IOSchedulingPriority=7
|
|
EOF_UNIT
|
|
|
|
cat > /etc/systemd/system/malware-watch.timer <<'EOF_UNIT'
|
|
[Unit]
|
|
Description=Run Malware Watch every minute
|
|
|
|
[Timer]
|
|
OnBootSec=2min
|
|
OnUnitActiveSec=1min
|
|
AccuracySec=5s
|
|
Persistent=true
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
EOF_UNIT
|
|
|
|
cat > /etc/systemd/system/malware-feed-update.service <<'EOF_UNIT'
|
|
[Unit]
|
|
Description=Malware Watch abuse.ch feed updater
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
EnvironmentFile=/etc/malware-watch/config
|
|
ExecStart=/usr/local/sbin/malware-feed-update.py
|
|
User=root
|
|
EOF_UNIT
|
|
|
|
cat > /etc/systemd/system/malware-feed-update.timer <<'EOF_UNIT'
|
|
[Unit]
|
|
Description=Update malware threat feeds every 6 hours
|
|
|
|
[Timer]
|
|
OnBootSec=5min
|
|
OnUnitActiveSec=6h
|
|
RandomizedDelaySec=5min
|
|
Persistent=true
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
EOF_UNIT
|
|
|
|
cat > /etc/systemd/system/block-malware-iocs.service <<'EOF_UNIT'
|
|
[Unit]
|
|
Description=Restore malware IOC firewall blocks
|
|
After=network-online.target docker.service
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/local/sbin/block-malware-iocs
|
|
RemainAfterExit=yes
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF_UNIT
|
|
|
|
if [[ "$INSTALL_CLAMAV" == "1" ]]; then
|
|
cat > /etc/systemd/system/clamav-freshclam-local.service <<EOF_UNIT
|
|
[Unit]
|
|
Description=ClamAV ${CLAMAV_VERSION} signature database update
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/local/bin/freshclam --config-file=/usr/local/etc/freshclam.conf
|
|
ExecStartPost=-/bin/systemctl start clamav-risk-scan.service
|
|
User=root
|
|
Nice=10
|
|
IOSchedulingClass=best-effort
|
|
IOSchedulingPriority=7
|
|
EOF_UNIT
|
|
|
|
cat > /etc/systemd/system/clamav-freshclam-local.timer <<'EOF_UNIT'
|
|
[Unit]
|
|
Description=Update ClamAV signatures every 2 hours
|
|
|
|
[Timer]
|
|
OnBootSec=15min
|
|
OnUnitActiveSec=2h
|
|
RandomizedDelaySec=5min
|
|
Persistent=true
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
EOF_UNIT
|
|
|
|
cat > /etc/systemd/system/clamav-risk-scan.service <<'EOF_UNIT'
|
|
[Unit]
|
|
Description=ClamAV high-risk path scan
|
|
After=local-fs.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/local/sbin/clamav-watch-scan.sh quick
|
|
User=root
|
|
Nice=15
|
|
IOSchedulingClass=idle
|
|
SuccessExitStatus=1
|
|
EOF_UNIT
|
|
|
|
cat > /etc/systemd/system/clamav-risk-scan.timer <<'EOF_UNIT'
|
|
[Unit]
|
|
Description=Hourly ClamAV scan of high-risk temporary paths
|
|
|
|
[Timer]
|
|
OnBootSec=20min
|
|
OnUnitActiveSec=1h
|
|
RandomizedDelaySec=5min
|
|
Persistent=true
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
EOF_UNIT
|
|
|
|
cat > /etc/systemd/system/clamav-deep-scan.service <<'EOF_UNIT'
|
|
[Unit]
|
|
Description=ClamAV daily low-priority server scan
|
|
After=local-fs.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/local/sbin/clamav-watch-scan.sh deep
|
|
User=root
|
|
Nice=19
|
|
IOSchedulingClass=idle
|
|
SuccessExitStatus=1
|
|
EOF_UNIT
|
|
|
|
cat > /etc/systemd/system/clamav-deep-scan.timer <<'EOF_UNIT'
|
|
[Unit]
|
|
Description=Daily ClamAV low-priority server scan
|
|
|
|
[Timer]
|
|
OnCalendar=*-*-* 04:30:00
|
|
RandomizedDelaySec=15min
|
|
Persistent=true
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
EOF_UNIT
|
|
fi
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable --now malware-watch.timer malware-feed-update.timer block-malware-iocs.service
|
|
if [[ "$INSTALL_CLAMAV" == "1" ]]; then
|
|
systemctl enable --now clamav-freshclam-local.timer clamav-risk-scan.timer clamav-deep-scan.timer
|
|
fi
|
|
|
|
log "Updating abuse.ch feeds"
|
|
if ! systemctl start malware-feed-update.service; then
|
|
log "WARNING: abuse.ch feed update failed; timer will retry"
|
|
fi
|
|
|
|
if [[ "$INSTALL_CLAMAV" == "1" && "$RUN_FRESHCLAM_NOW" == "1" ]]; then
|
|
log "Trying initial ClamAV signature update"
|
|
systemctl start clamav-freshclam-local.service || log "WARNING: freshclam failed; timer will retry"
|
|
fi
|
|
|
|
systemctl start malware-watch.service || true
|
|
|
|
if [[ "$SEND_INSTALL_TEST" == "1" ]]; then
|
|
payload="$(python3 - <<'PYMSG'
|
|
import json,socket
|
|
print(json.dumps({'content':f'malware-watch install OK | server: {socket.gethostname()}'},ensure_ascii=False))
|
|
PYMSG
|
|
)"
|
|
curl -sS -o /dev/null -A 'Mozilla/5.0 (compatible; malware-watch/2.0)' \
|
|
-H 'Content-Type: application/json' --data "$payload" "$DISCORD_WEBHOOK" || true
|
|
fi
|
|
|
|
log "Installation complete"
|
|
/usr/local/sbin/malware-watch.py || true
|
|
if [[ -x "$SCRIPT_DIR/verify.sh" ]]; then
|
|
"$SCRIPT_DIR/verify.sh" || true
|
|
fi
|