feat: initial setup for PVE security scanner VM

Scripts for deploying a hardened internal network security scanner on Proxmox VE:
- PVE-level firewall and VM creation
- System hardening (sysctl, auditd, AIDE)
- nftables firewall with dynamic IP blocking
- SSH hardening with fail2ban
- Security tools (OpenVAS, Nmap, Nuclei, httpx, Nikto, testssl, NetExec)
- Monitoring, logging, and Docker autostart
This commit is contained in:
Yaojia Wang
2026-03-08 20:21:29 +01:00
commit 5e49b977ab
10 changed files with 1511 additions and 0 deletions

476
vm/04-install-tools.sh Normal file
View File

@@ -0,0 +1,476 @@
#!/bin/bash
# =============================================================================
# Security Scanning Tools Installation
# Run this inside the VM as root
# =============================================================================
set -euo pipefail
echo "============================================"
echo " Security Tools - Installation"
echo "============================================"
# --- 1. Base dependencies ---
echo "[+] Installing base dependencies..."
apt install -y \
curl wget git \
net-tools iproute2 \
dnsutils whois \
python3 python3-pip python3-venv \
jq tmux htop \
ca-certificates gnupg
# --- 2. Docker (for Greenbone OpenVAS) ---
if ! command -v docker &>/dev/null; then
echo "[+] Installing Docker..."
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod 644 /etc/apt/keyrings/docker.asc
# Detect distro (debian or ubuntu)
. /etc/os-release
DOCKER_DISTRO="${ID}" # "debian" or "ubuntu"
DOCKER_CODENAME="${VERSION_CODENAME}"
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/${DOCKER_DISTRO} \
${DOCKER_CODENAME} stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable docker
fi
# --- 3. Nmap (network scanner) ---
echo "[+] Installing Nmap..."
apt install -y nmap
# --- Helper: download and verify binary from GitHub ---
install_github_binary() {
local REPO="$1" # e.g., projectdiscovery/nuclei
local NAME="$2" # e.g., nuclei
local ARCH="linux_amd64"
echo "[+] Installing ${NAME}..."
local VERSION
VERSION=$(curl -s "https://api.github.com/repos/${REPO}/releases/latest" | jq -r '.tag_name' | sed 's/^v//')
local ZIP_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${NAME}_${VERSION}_${ARCH}.zip"
local CHECKSUM_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${NAME}_${VERSION}_checksums.txt"
wget -q "${ZIP_URL}" -O "/tmp/${NAME}.zip"
wget -q "${CHECKSUM_URL}" -O "/tmp/${NAME}_checksums.txt"
# Verify checksum
local EXPECTED_SHA
EXPECTED_SHA=$(grep "${ARCH}.zip" "/tmp/${NAME}_checksums.txt" | awk '{print $1}')
local ACTUAL_SHA
ACTUAL_SHA=$(sha256sum "/tmp/${NAME}.zip" | awk '{print $1}')
if [[ "${EXPECTED_SHA}" != "${ACTUAL_SHA}" ]]; then
echo "[ERROR] Checksum verification failed for ${NAME}!" >&2
echo " Expected: ${EXPECTED_SHA}" >&2
echo " Actual: ${ACTUAL_SHA}" >&2
rm -f "/tmp/${NAME}.zip" "/tmp/${NAME}_checksums.txt"
return 1
fi
echo " Checksum verified for ${NAME} v${VERSION}"
unzip -o "/tmp/${NAME}.zip" -d "/tmp/${NAME}-bin"
mv "/tmp/${NAME}-bin/${NAME}" "/usr/local/bin/${NAME}"
chmod +x "/usr/local/bin/${NAME}"
rm -rf "/tmp/${NAME}.zip" "/tmp/${NAME}-bin" "/tmp/${NAME}_checksums.txt"
}
# --- 4. Nuclei (fast vulnerability scanner) ---
install_github_binary "projectdiscovery/nuclei" "nuclei"
# Update Nuclei templates
nuclei -update-templates 2>&1 | logger -t nuclei-setup || true
# --- 5. httpx (HTTP toolkit) ---
install_github_binary "projectdiscovery/httpx" "httpx"
# --- 6. Nikto (web server scanner) ---
echo "[+] Installing Nikto..."
apt install -y nikto
# --- 7. testssl.sh (TLS/SSL testing) ---
echo "[+] Installing testssl.sh..."
git clone --depth 1 https://github.com/drwetter/testssl.sh.git /opt/testssl
ln -sf /opt/testssl/testssl.sh /usr/local/bin/testssl
# --- 8. CrackMapExec / NetExec (network assessment) ---
echo "[+] Installing NetExec..."
python3 -m pip install --break-system-packages netexec 2>/dev/null || \
pipx install netexec 2>/dev/null || \
echo "[!] NetExec install failed - install manually if needed"
# --- 9. Greenbone OpenVAS (Docker Compose) ---
echo "[+] Setting up Greenbone OpenVAS..."
mkdir -p /opt/greenbone
cat > /opt/greenbone/docker-compose.yml << 'COMPEOF'
name: greenbone-community-edition
services:
vulnerability-tests:
image: registry.community.greenbone.net/community/vulnerability-tests
environment:
FEED_RELEASE: "24.10"
KEEP_ALIVE: 1
volumes:
- vt_data_vol:/mnt
notus-data:
image: registry.community.greenbone.net/community/notus-data
environment:
KEEP_ALIVE: 1
volumes:
- notus_data_vol:/mnt
scap-data:
image: registry.community.greenbone.net/community/scap-data
environment:
KEEP_ALIVE: 1
volumes:
- scap_data_vol:/mnt
cert-bund-data:
image: registry.community.greenbone.net/community/cert-bund-data
environment:
KEEP_ALIVE: 1
volumes:
- cert_data_vol:/mnt
dfn-cert-data:
image: registry.community.greenbone.net/community/dfn-cert-data
environment:
KEEP_ALIVE: 1
volumes:
- cert_data_vol:/mnt
depends_on:
cert-bund-data:
condition: service_healthy
data-objects:
image: registry.community.greenbone.net/community/data-objects
environment:
FEED_RELEASE: "24.10"
KEEP_ALIVE: 1
volumes:
- data_objects_vol:/mnt
report-formats:
image: registry.community.greenbone.net/community/report-formats
environment:
FEED_RELEASE: "24.10"
KEEP_ALIVE: 1
volumes:
- data_objects_vol:/mnt
depends_on:
data-objects:
condition: service_healthy
gpg-data:
image: registry.community.greenbone.net/community/gpg-data
volumes:
- gpg_data_vol:/mnt
redis-server:
image: registry.community.greenbone.net/community/redis-server
restart: on-failure
volumes:
- redis_socket_vol:/run/redis/
pg-gvm-migrator:
image: registry.community.greenbone.net/community/pg-gvm-migrator:stable
restart: "no"
volumes:
- psql_data_vol:/var/lib/postgresql
- psql_socket_vol:/var/run/postgresql
pg-gvm:
image: registry.community.greenbone.net/community/pg-gvm:stable
restart: on-failure:10
volumes:
- psql_data_vol:/var/lib/postgresql
- psql_socket_vol:/var/run/postgresql
depends_on:
pg-gvm-migrator:
condition: service_completed_successfully
gvmd:
image: registry.community.greenbone.net/community/gvmd:stable
restart: on-failure
volumes:
- gvmd_data_vol:/var/lib/gvm
- scap_data_vol:/var/lib/gvm/scap-data/
- cert_data_vol:/var/lib/gvm/cert-data
- data_objects_vol:/var/lib/gvm/data-objects/gvmd
- vt_data_vol:/var/lib/openvas/plugins
- psql_data_vol:/var/lib/postgresql
- gvmd_socket_vol:/run/gvmd
- ospd_openvas_socket_vol:/run/ospd
- psql_socket_vol:/var/run/postgresql
depends_on:
pg-gvm:
condition: service_started
scap-data:
condition: service_healthy
cert-bund-data:
condition: service_healthy
dfn-cert-data:
condition: service_healthy
data-objects:
condition: service_healthy
report-formats:
condition: service_healthy
gsa:
image: registry.community.greenbone.net/community/gsa:stable-slim
environment:
MOUNT_PATH: "/mnt/web"
KEEP_ALIVE: 1
healthcheck:
test: ["CMD-SHELL", "test -e /run/gsa/copying.done"]
start_period: 5s
volumes:
- gsa_data_vol:/mnt/web
gsad:
image: registry.community.greenbone.net/community/gsad:stable
restart: on-failure
environment:
GSAD_ARGS: "--listen=0.0.0.0 --http-only --api-only -f"
volumes:
- gvmd_socket_vol:/run/gvmd
depends_on:
gvmd:
condition: service_started
gvm-config:
image: registry.community.greenbone.net/community/gvm-config:latest
environment:
ENABLE_NGINX_CONFIG: "true"
ENABLE_TLS_GENERATION: "true"
volumes:
- nginx_config_vol:/mnt/nginx/configs
- nginx_certificates_vol:/mnt/nginx/certs
nginx:
image: registry.community.greenbone.net/community/nginx:latest
ports:
- "0.0.0.0:9392:9392"
volumes:
- nginx_config_vol:/etc/nginx/conf.d:ro
- nginx_certificates_vol:/etc/nginx/certs:ro
- gsa_data_vol:/usr/share/nginx/html:ro
depends_on:
gvm-config:
condition: service_completed_successfully
gsa:
condition: service_healthy
gsad:
condition: service_started
configure-openvas:
image: registry.community.greenbone.net/community/openvas-scanner:stable
volumes:
- openvas_data_vol:/mnt
- openvas_log_data_vol:/var/log/openvas
command:
- /bin/sh
- -c
- |
printf "table_driven_lsc = yes\nopenvasd_server = http://openvasd:80\n" > /mnt/openvas.conf
sed "s/127/128/" /etc/openvas/openvas_log.conf | sed 's/gvm/openvas/' > /mnt/openvas_log.conf
chmod 644 /mnt/openvas.conf
chmod 644 /mnt/openvas_log.conf
touch /var/log/openvas/openvas.log
chmod 666 /var/log/openvas/openvas.log
openvas:
image: registry.community.greenbone.net/community/openvas-scanner:stable
restart: on-failure
volumes:
- openvas_data_vol:/etc/openvas
- openvas_log_data_vol:/var/log/openvas
command:
- /bin/sh
- -c
- |
cat /etc/openvas/openvas.conf
tail -f /var/log/openvas/openvas.log
depends_on:
configure-openvas:
condition: service_completed_successfully
openvasd:
image: registry.community.greenbone.net/community/openvas-scanner:stable
restart: on-failure
environment:
OPENVASD_MODE: service_notus
GNUPGHOME: /etc/openvas/gnupg
LISTENING: 0.0.0.0:80
volumes:
- openvas_data_vol:/etc/openvas
- openvas_log_data_vol:/var/log/openvas
- gpg_data_vol:/etc/openvas/gnupg
- notus_data_vol:/var/lib/notus
depends_on:
vulnerability-tests:
condition: service_healthy
notus-data:
condition: service_healthy
configure-openvas:
condition: service_completed_successfully
gpg-data:
condition: service_completed_successfully
networks:
default:
aliases:
- openvasd
ospd-openvas:
image: registry.community.greenbone.net/community/ospd-openvas:stable
restart: on-failure
hostname: ospd-openvas.local
# SECURITY NOTE: NET_ADMIN, NET_RAW, and seccomp=unconfined are required
# for raw packet scanning (SYN scans, OS detection). Risk is mitigated by
# host-level nftables, PVE firewall, and VM-level isolation.
cap_add:
- NET_ADMIN
- NET_RAW
security_opt:
- seccomp=unconfined
- apparmor=unconfined
command:
[
"ospd-openvas",
"-f",
"--config",
"/etc/gvm/ospd-openvas.conf",
"--notus-feed-dir",
"/var/lib/notus/advisories",
"-m",
"666",
]
volumes:
- gpg_data_vol:/etc/openvas/gnupg
- vt_data_vol:/var/lib/openvas/plugins
- notus_data_vol:/var/lib/notus
- ospd_openvas_socket_vol:/run/ospd
- redis_socket_vol:/run/redis/
- openvas_data_vol:/etc/openvas/
- openvas_log_data_vol:/var/log/openvas
depends_on:
redis-server:
condition: service_started
gpg-data:
condition: service_completed_successfully
configure-openvas:
condition: service_completed_successfully
vulnerability-tests:
condition: service_healthy
notus-data:
condition: service_healthy
gvm-tools:
image: registry.community.greenbone.net/community/gvm-tools
volumes:
- gvmd_socket_vol:/run/gvmd
- ospd_openvas_socket_vol:/run/ospd
depends_on:
- gvmd
- ospd-openvas
volumes:
gpg_data_vol:
scap_data_vol:
cert_data_vol:
data_objects_vol:
gvmd_data_vol:
psql_data_vol:
vt_data_vol:
notus_data_vol:
psql_socket_vol:
gvmd_socket_vol:
ospd_openvas_socket_vol:
redis_socket_vol:
openvas_data_vol:
openvas_log_data_vol:
gsa_data_vol:
nginx_config_vol:
nginx_certificates_vol:
COMPEOF
# --- 10. Create scan scripts directory ---
mkdir -p /opt/scans/{results,scripts}
# Quick scan helper script
cat > /opt/scans/scripts/quick-scan.sh << 'SCANEOF'
#!/bin/bash
# Quick internal network scan
# Usage: ./quick-scan.sh 192.168.1.0/24
set -euo pipefail
TARGET="${1:?Usage: $0 <target-subnet>}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RESULTS_DIR="/opt/scans/results/${TIMESTAMP}"
mkdir -p "${RESULTS_DIR}"
echo "[+] Starting quick scan of ${TARGET}"
echo "[+] Results will be saved to ${RESULTS_DIR}"
# Step 1: Host discovery
echo "[+] Step 1/4: Host discovery..."
nmap -sn "${TARGET}" -oG "${RESULTS_DIR}/hosts-alive.gnmap"
HOSTS=$(grep "Up" "${RESULTS_DIR}/hosts-alive.gnmap" | awk '{print $2}')
echo "${HOSTS}" > "${RESULTS_DIR}/hosts-alive.txt"
HOST_COUNT=$(wc -l < "${RESULTS_DIR}/hosts-alive.txt")
echo " Found ${HOST_COUNT} alive hosts"
# Step 2: Port scan top 1000
echo "[+] Step 2/4: Port scanning (top 1000 ports)..."
nmap -sV --script=safe -T4 --top-ports 1000 -iL "${RESULTS_DIR}/hosts-alive.txt" \
-oA "${RESULTS_DIR}/port-scan"
# Step 3: HTTP service detection
echo "[+] Step 3/4: HTTP service detection..."
grep -oP '\d+\.\d+\.\d+\.\d+' "${RESULTS_DIR}/hosts-alive.txt" | \
httpx -silent -ports 80,443,8080,8443,9090 -o "${RESULTS_DIR}/http-services.txt" 2>/dev/null || true
# Step 4: Nuclei scan on HTTP services
if [ -s "${RESULTS_DIR}/http-services.txt" ]; then
echo "[+] Step 4/4: Vulnerability scanning with Nuclei..."
nuclei -l "${RESULTS_DIR}/http-services.txt" \
-severity medium,high,critical \
-o "${RESULTS_DIR}/nuclei-findings.txt" 2>/dev/null || true
else
echo "[+] Step 4/4: No HTTP services found, skipping Nuclei."
fi
echo ""
echo "[+] Scan complete. Results in: ${RESULTS_DIR}"
echo " - hosts-alive.txt: Live hosts"
echo " - port-scan.*: Port scan results"
echo " - http-services.txt: HTTP endpoints"
echo " - nuclei-findings.txt: Vulnerabilities found"
SCANEOF
chmod +x /opt/scans/scripts/quick-scan.sh
echo ""
echo "[+] All security tools installed."
echo ""
echo "Tool summary:"
echo " nmap - Network discovery and port scanning"
echo " nuclei - Fast vulnerability scanner"
echo " httpx - HTTP probing"
echo " nikto - Web server scanner"
echo " testssl - TLS/SSL testing"
echo " netexec - Network assessment (SMB, RDP, etc.)"
echo " OpenVAS (Docker) - Full vulnerability management"
echo ""
echo "Quick start:"
echo " cd /opt/greenbone && docker compose up -d # Start OpenVAS"
echo " /opt/scans/scripts/quick-scan.sh 192.168.1.0/24 # Quick scan"