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:
32
README.md
Normal file
32
README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# PVE Security Scanner
|
||||
|
||||
Internal network security scanning VM for Proxmox VE.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
pve-security-scanner/
|
||||
├── pve/ # Proxmox host-level config
|
||||
│ ├── create-vm.sh # One-click VM creation
|
||||
│ └── firewall.sh # PVE firewall rules
|
||||
├── vm/ # VM internal config
|
||||
│ ├── 01-system-harden.sh # OS hardening
|
||||
│ ├── 02-firewall.sh # nftables firewall
|
||||
│ ├── 03-ssh-harden.sh # SSH hardening
|
||||
│ ├── 04-install-tools.sh # Security tools
|
||||
│ └── 05-monitoring.sh # Logging and monitoring
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
1. On PVE host: run `pve/create-vm.sh` to create the VM
|
||||
2. On PVE host: run `pve/firewall.sh` to apply PVE-level firewall
|
||||
3. SSH into VM, run scripts in `vm/` directory in order (01 -> 05)
|
||||
|
||||
## Network Design
|
||||
|
||||
- Scanner VM sits on the management VLAN / main bridge
|
||||
- Allowed to reach all internal subnets for scanning
|
||||
- Outbound internet restricted (only for vuln DB updates)
|
||||
- Inbound restricted to SSH + Web UI from admin IPs only
|
||||
48
pve/create-vm.sh
Normal file
48
pve/create-vm.sh
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# PVE Security Scanner VM - Creation Script
|
||||
# Run this on the Proxmox host
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
# --- Configuration (modify these) ---
|
||||
VMID=200
|
||||
VM_NAME="security-scanner"
|
||||
STORAGE="local-lvm" # PVE storage pool
|
||||
ISO_PATH="local:iso/debian-12-amd64-netinst.iso" # Debian 12 ISO
|
||||
BRIDGE="vmbr0" # Network bridge
|
||||
CORES=4
|
||||
MEMORY=8192 # MB
|
||||
DISK_SIZE="80G"
|
||||
VLAN_TAG="" # Set VLAN tag if needed, e.g., "10"
|
||||
|
||||
# --- Guard: check if VM already exists ---
|
||||
if qm status "${VMID}" &>/dev/null; then
|
||||
echo "[!] VM ${VMID} already exists. Skipping creation."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Create VM ---
|
||||
echo "[+] Creating VM ${VMID} (${VM_NAME})..."
|
||||
|
||||
# VLAN_TAG conditional: appends ,tag=<N> only when VLAN_TAG is set
|
||||
qm create "${VMID}" \
|
||||
--name "${VM_NAME}" \
|
||||
--ostype l26 \
|
||||
--cores "${CORES}" \
|
||||
--memory "${MEMORY}" \
|
||||
--cpu cputype=host \
|
||||
--scsihw virtio-scsi-single \
|
||||
--scsi0 "${STORAGE}:${DISK_SIZE}" \
|
||||
--ide2 "${ISO_PATH},media=cdrom" \
|
||||
--net0 "virtio,bridge=${BRIDGE}${VLAN_TAG:+,tag=${VLAN_TAG}}" \
|
||||
--boot "order=ide2;scsi0" \
|
||||
--agent enabled=1 \
|
||||
--onboot 1 \
|
||||
--protection 0 \
|
||||
--description "Internal network security scanner. Restricted network access."
|
||||
|
||||
echo "[+] VM ${VMID} created successfully."
|
||||
echo "[+] Start the VM and install Debian 12, then run the vm/ scripts."
|
||||
echo ""
|
||||
echo " qm start ${VMID}"
|
||||
135
pve/firewall.sh
Normal file
135
pve/firewall.sh
Normal file
@@ -0,0 +1,135 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# PVE-Level Firewall for Security Scanner VM
|
||||
# Run this on the Proxmox host
|
||||
# Applies firewall rules at the hypervisor level (defense in depth)
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
VMID=200
|
||||
|
||||
# --- Configuration (MUST be changed before running) ---
|
||||
# Admin IPs allowed to access the scanner (comma-separated)
|
||||
ADMIN_IPS="${SCANNER_ADMIN_IPS:-}"
|
||||
# Internal subnets the scanner is allowed to reach
|
||||
INTERNAL_NETS="${SCANNER_INTERNAL_NETS:-10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}"
|
||||
|
||||
# --- Guard: require ADMIN_IPS to be set ---
|
||||
if [[ -z "${ADMIN_IPS}" ]]; then
|
||||
echo "[ERROR] SCANNER_ADMIN_IPS environment variable is not set." >&2
|
||||
echo " Example: export SCANNER_ADMIN_IPS='192.168.68.100,192.168.68.101'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Enable PVE firewall for the VM
|
||||
# =============================================================================
|
||||
echo "[+] Enabling PVE firewall for VM ${VMID}..."
|
||||
|
||||
# Create firewall config directory
|
||||
mkdir -p "/etc/pve/firewall"
|
||||
|
||||
# --- VM-level firewall rules ---
|
||||
cat > "/etc/pve/firewall/${VMID}.fw" << 'FWEOF'
|
||||
[OPTIONS]
|
||||
enable: 1
|
||||
policy_in: DROP
|
||||
policy_out: ACCEPT
|
||||
dhcp: 0
|
||||
macfilter: 0
|
||||
log_level_in: info
|
||||
log_level_out: warning
|
||||
|
||||
[IPSET admin_ips]
|
||||
# Add your admin workstation IPs here
|
||||
192.168.1.100
|
||||
192.168.1.101
|
||||
|
||||
[IPSET internal_nets]
|
||||
# Internal subnets the scanner can reach
|
||||
10.0.0.0/8
|
||||
172.16.0.0/12
|
||||
192.168.0.0/16
|
||||
|
||||
[IPSET update_targets]
|
||||
# External hosts for vulnerability database updates
|
||||
# Greenbone feed servers
|
||||
feed.community.greenbone.net
|
||||
# Debian/Ubuntu repos
|
||||
deb.debian.org
|
||||
security.debian.org
|
||||
# Docker Hub
|
||||
registry-1.docker.io
|
||||
production.cloudflare.docker.com
|
||||
auth.docker.io
|
||||
# GitHub (for Nuclei templates)
|
||||
github.com
|
||||
objects.githubusercontent.com
|
||||
|
||||
# =============================================================================
|
||||
# INBOUND RULES - Only admin access
|
||||
# =============================================================================
|
||||
[RULES]
|
||||
|
||||
# Allow SSH from admin IPs only
|
||||
IN ACCEPT -source +admin_ips -p tcp -dport 22 -log info
|
||||
|
||||
# Allow OpenVAS Web UI (9392) from admin IPs only
|
||||
IN ACCEPT -source +admin_ips -p tcp -dport 9392 -log info
|
||||
|
||||
# Allow ICMP ping from internal networks
|
||||
IN ACCEPT -source +internal_nets -p icmp
|
||||
|
||||
# Allow established/related connections back in
|
||||
IN ACCEPT -match conntrack --ctstate RELATED,ESTABLISHED
|
||||
|
||||
# Log and drop everything else (handled by policy_in: DROP)
|
||||
|
||||
# =============================================================================
|
||||
# OUTBOUND RULES - Restricted
|
||||
# =============================================================================
|
||||
|
||||
# Allow DNS resolution
|
||||
OUT ACCEPT -p udp -dport 53
|
||||
OUT ACCEPT -p tcp -dport 53
|
||||
|
||||
# Allow NTP
|
||||
OUT ACCEPT -p udp -dport 123
|
||||
|
||||
# Allow scanning to internal networks (all ports)
|
||||
OUT ACCEPT -dest +internal_nets
|
||||
|
||||
# Allow HTTPS/HTTP only to update targets (vuln DB, packages, docker)
|
||||
OUT ACCEPT -dest +update_targets -p tcp -dport 443 -log info
|
||||
OUT ACCEPT -dest +update_targets -p tcp -dport 80
|
||||
|
||||
# Allow established connections
|
||||
OUT ACCEPT -match conntrack --ctstate RELATED,ESTABLISHED
|
||||
|
||||
# Drop all other outbound
|
||||
OUT DROP -log warning
|
||||
FWEOF
|
||||
|
||||
echo "[+] PVE firewall rules written to /etc/pve/firewall/${VMID}.fw"
|
||||
|
||||
# --- Enable datacenter-level firewall if not already ---
|
||||
echo "[+] Enabling datacenter-level firewall..."
|
||||
pvesh set /cluster/firewall/options --enable 1 2>/dev/null || {
|
||||
echo "[!] pvesh failed, checking cluster.fw manually..."
|
||||
if ! grep -q "enable: 1" /etc/pve/firewall/cluster.fw 2>/dev/null; then
|
||||
mkdir -p /etc/pve/firewall
|
||||
if [[ -f /etc/pve/firewall/cluster.fw ]]; then
|
||||
sed -i 's/^enable: 0/enable: 1/' /etc/pve/firewall/cluster.fw
|
||||
else
|
||||
cat > /etc/pve/firewall/cluster.fw << 'EOF'
|
||||
[OPTIONS]
|
||||
enable: 1
|
||||
policy_in: ACCEPT
|
||||
policy_out: ACCEPT
|
||||
EOF
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
echo "[+] PVE firewall configured. Verify with: pvesh get /nodes/$(hostname)/qemu/${VMID}/firewall/rules"
|
||||
echo "[!] IMPORTANT: Update admin_ips and internal_nets to match your network."
|
||||
207
vm/01-system-harden.sh
Normal file
207
vm/01-system-harden.sh
Normal file
@@ -0,0 +1,207 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# System Hardening for Security Scanner VM
|
||||
# Run this inside the VM as root
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
echo "============================================"
|
||||
echo " System Hardening - Security Scanner VM"
|
||||
echo "============================================"
|
||||
|
||||
# --- 1. Update system ---
|
||||
echo "[+] Updating system packages..."
|
||||
apt update && apt upgrade -y
|
||||
apt install -y unattended-upgrades apt-listchanges
|
||||
|
||||
# Enable automatic security updates
|
||||
cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
|
||||
Unattended-Upgrade::Allowed-Origins {
|
||||
"${distro_id}:${distro_codename}-security";
|
||||
};
|
||||
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
|
||||
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
||||
Unattended-Upgrade::Automatic-Reboot "false";
|
||||
EOF
|
||||
|
||||
dpkg-reconfigure -f noninteractive unattended-upgrades
|
||||
|
||||
# --- 2. Kernel hardening (sysctl) ---
|
||||
echo "[+] Applying kernel hardening..."
|
||||
cat > /etc/sysctl.d/99-security-scanner.conf << 'EOF'
|
||||
# --- Network hardening ---
|
||||
# Disable IP forwarding (scanner should not route traffic)
|
||||
net.ipv4.ip_forward = 0
|
||||
net.ipv6.conf.all.forwarding = 0
|
||||
|
||||
# Ignore ICMP redirects
|
||||
net.ipv4.conf.all.accept_redirects = 0
|
||||
net.ipv4.conf.default.accept_redirects = 0
|
||||
net.ipv6.conf.all.accept_redirects = 0
|
||||
net.ipv6.conf.default.accept_redirects = 0
|
||||
|
||||
# Don't send ICMP redirects
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv4.conf.default.send_redirects = 0
|
||||
|
||||
# Enable reverse path filtering (anti-spoofing)
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
net.ipv4.conf.default.rp_filter = 1
|
||||
|
||||
# Ignore broadcast pings
|
||||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||
|
||||
# Enable SYN flood protection
|
||||
net.ipv4.tcp_syncookies = 1
|
||||
net.ipv4.tcp_max_syn_backlog = 2048
|
||||
net.ipv4.tcp_synack_retries = 2
|
||||
|
||||
# Log suspicious packets
|
||||
net.ipv4.conf.all.log_martians = 1
|
||||
net.ipv4.conf.default.log_martians = 1
|
||||
|
||||
# Disable source routing
|
||||
net.ipv4.conf.all.accept_source_route = 0
|
||||
net.ipv4.conf.default.accept_source_route = 0
|
||||
|
||||
# --- Memory protection ---
|
||||
# Restrict dmesg access
|
||||
kernel.dmesg_restrict = 1
|
||||
|
||||
# Restrict kernel pointer exposure
|
||||
kernel.kptr_restrict = 2
|
||||
|
||||
# Enable ASLR
|
||||
kernel.randomize_va_space = 2
|
||||
|
||||
# Restrict ptrace
|
||||
kernel.yama.ptrace_scope = 2
|
||||
|
||||
# --- File system ---
|
||||
# Restrict core dumps
|
||||
fs.suid_dumpable = 0
|
||||
EOF
|
||||
|
||||
sysctl -p /etc/sysctl.d/99-security-scanner.conf
|
||||
|
||||
# --- 3. Restrict core dumps ---
|
||||
echo "[+] Disabling core dumps..."
|
||||
cat > /etc/security/limits.d/99-no-core.conf << 'EOF'
|
||||
* hard core 0
|
||||
* soft core 0
|
||||
EOF
|
||||
|
||||
# --- 4. Secure shared memory ---
|
||||
echo "[+] Securing shared memory..."
|
||||
if ! grep -q "tmpfs /dev/shm" /etc/fstab; then
|
||||
echo "tmpfs /dev/shm tmpfs defaults,noexec,nosuid,nodev 0 0" >> /etc/fstab
|
||||
fi
|
||||
|
||||
# --- 5. Set file permissions ---
|
||||
echo "[+] Hardening file permissions..."
|
||||
chmod 700 /root
|
||||
chmod 600 /etc/crontab
|
||||
chmod 700 /etc/cron.d
|
||||
chmod 700 /etc/cron.daily
|
||||
chmod 700 /etc/cron.hourly
|
||||
chmod 700 /etc/cron.weekly
|
||||
chmod 700 /etc/cron.monthly
|
||||
|
||||
# --- 6. Disable unnecessary services ---
|
||||
echo "[+] Disabling unnecessary services..."
|
||||
DISABLE_SERVICES=(
|
||||
"avahi-daemon"
|
||||
"cups"
|
||||
"rpcbind"
|
||||
"bluetooth"
|
||||
)
|
||||
for svc in "${DISABLE_SERVICES[@]}"; do
|
||||
if systemctl is-enabled "${svc}" 2>/dev/null; then
|
||||
systemctl disable --now "${svc}"
|
||||
echo " Disabled: ${svc}"
|
||||
fi
|
||||
done
|
||||
|
||||
# --- 7. Install security tools ---
|
||||
echo "[+] Installing security audit tools..."
|
||||
apt install -y \
|
||||
aide \
|
||||
rkhunter \
|
||||
lynis \
|
||||
auditd \
|
||||
audispd-plugins \
|
||||
fail2ban \
|
||||
logwatch
|
||||
|
||||
# --- 8. Initialize AIDE (file integrity monitoring) ---
|
||||
echo "[!] Initializing AIDE database - this may take 10-20 minutes..."
|
||||
aideinit
|
||||
cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
|
||||
|
||||
# --- 9. Configure auditd ---
|
||||
echo "[+] Configuring audit rules..."
|
||||
cat > /etc/audit/rules.d/scanner-audit.rules << 'EOF'
|
||||
# Delete all existing rules
|
||||
-D
|
||||
|
||||
# Buffer size
|
||||
-b 8192
|
||||
|
||||
# Failure mode (1=printk, 2=panic)
|
||||
-f 1
|
||||
|
||||
# Monitor /etc changes
|
||||
-w /etc/ -p wa -k etc_changes
|
||||
|
||||
# Monitor authentication
|
||||
-w /var/log/auth.log -p wa -k auth_log
|
||||
-w /etc/passwd -p wa -k identity
|
||||
-w /etc/shadow -p wa -k identity
|
||||
-w /etc/group -p wa -k identity
|
||||
-w /etc/gshadow -p wa -k identity
|
||||
|
||||
# Monitor sudo usage
|
||||
-w /etc/sudoers -p wa -k sudoers
|
||||
-w /etc/sudoers.d/ -p wa -k sudoers
|
||||
|
||||
# Monitor network config changes
|
||||
-w /etc/hosts -p wa -k network
|
||||
-w /etc/network/ -p wa -k network
|
||||
-w /etc/nftables.conf -p wa -k firewall
|
||||
|
||||
# Monitor cron changes
|
||||
-w /etc/crontab -p wa -k cron
|
||||
-w /etc/cron.d/ -p wa -k cron
|
||||
|
||||
# Monitor scanner tool configs
|
||||
-w /opt/greenbone/ -p wa -k scanner_config
|
||||
|
||||
# Lock audit rules (requires reboot to change)
|
||||
-e 2
|
||||
EOF
|
||||
|
||||
systemctl restart auditd
|
||||
|
||||
# --- 10. Password policy ---
|
||||
echo "[+] Setting password policy..."
|
||||
apt install -y libpam-pwquality
|
||||
|
||||
sed -i 's/^#\s*minlen.*/minlen = 12/' /etc/security/pwquality.conf
|
||||
sed -i 's/^#\s*minclass.*/minclass = 3/' /etc/security/pwquality.conf
|
||||
sed -i 's/^#\s*maxrepeat.*/maxrepeat = 3/' /etc/security/pwquality.conf
|
||||
|
||||
# Verify settings were applied
|
||||
for setting in minlen minclass maxrepeat; do
|
||||
if ! grep -q "^${setting}" /etc/security/pwquality.conf; then
|
||||
echo "[!] WARNING: ${setting} was not set - appending to config"
|
||||
case "${setting}" in
|
||||
minlen) echo "minlen = 12" >> /etc/security/pwquality.conf ;;
|
||||
minclass) echo "minclass = 3" >> /etc/security/pwquality.conf ;;
|
||||
maxrepeat) echo "maxrepeat = 3" >> /etc/security/pwquality.conf ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "[+] System hardening complete."
|
||||
echo "[!] Reboot recommended: shutdown -r now"
|
||||
201
vm/02-firewall.sh
Normal file
201
vm/02-firewall.sh
Normal file
@@ -0,0 +1,201 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# nftables Firewall for Security Scanner VM
|
||||
# Run this inside the VM as root
|
||||
# Defense in depth: works alongside PVE-level firewall
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
echo "============================================"
|
||||
echo " nftables Firewall - Security Scanner VM"
|
||||
echo "============================================"
|
||||
|
||||
# --- Configuration (MUST be set via environment variables) ---
|
||||
ADMIN_IPS="${SCANNER_ADMIN_IPS:-192.168.68.0/24}"
|
||||
INTERNAL_NETS="${SCANNER_INTERNAL_NETS:-10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16}"
|
||||
DNS_SERVERS="${SCANNER_DNS_SERVERS:-192.168.68.1}"
|
||||
|
||||
# --- Guard: require ADMIN_IPS ---
|
||||
if [[ -z "${ADMIN_IPS}" ]]; then
|
||||
echo "[ERROR] SCANNER_ADMIN_IPS environment variable is not set." >&2
|
||||
echo " Example: export SCANNER_ADMIN_IPS='192.168.68.100, 192.168.68.101'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Install nftables ---
|
||||
apt install -y nftables
|
||||
systemctl enable nftables
|
||||
|
||||
# --- Write nftables config ---
|
||||
cat > /etc/nftables.conf << EOF
|
||||
#!/usr/sbin/nft -f
|
||||
|
||||
flush ruleset
|
||||
|
||||
# =============================================================================
|
||||
# Definitions
|
||||
# =============================================================================
|
||||
define ADMIN_IPS = { ${ADMIN_IPS} }
|
||||
define INTERNAL_NETS = { ${INTERNAL_NETS} }
|
||||
define DNS_SERVERS = { ${DNS_SERVERS} }
|
||||
|
||||
# Ports for scanner services
|
||||
define SCANNER_WEB_PORTS = { 443, 9392 }
|
||||
define SCANNER_SSH_PORT = 22
|
||||
|
||||
# =============================================================================
|
||||
# Main table
|
||||
# =============================================================================
|
||||
table inet firewall {
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Sets for dynamic blocking
|
||||
# -------------------------------------------------------------------------
|
||||
set blocked_ips {
|
||||
type ipv4_addr
|
||||
flags timeout
|
||||
comment "Dynamically blocked IPs"
|
||||
}
|
||||
|
||||
set ssh_bruteforce {
|
||||
type ipv4_addr
|
||||
flags dynamic, timeout
|
||||
timeout 15m
|
||||
comment "SSH brute force tracking"
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# INPUT chain - What can reach the scanner
|
||||
# -------------------------------------------------------------------------
|
||||
chain input {
|
||||
type filter hook input priority 0; policy drop;
|
||||
|
||||
# Drop blocked IPs immediately
|
||||
ip saddr @blocked_ips drop
|
||||
|
||||
# Allow loopback
|
||||
iif "lo" accept
|
||||
|
||||
# Allow established/related connections
|
||||
ct state established,related accept
|
||||
|
||||
# Drop invalid packets
|
||||
ct state invalid counter drop
|
||||
|
||||
# --- ICMP (rate limited) ---
|
||||
ip protocol icmp icmp type {
|
||||
echo-request,
|
||||
echo-reply,
|
||||
destination-unreachable,
|
||||
time-exceeded
|
||||
} limit rate 10/second accept
|
||||
|
||||
# --- SSH from admin IPs only (with brute-force protection) ---
|
||||
tcp dport \$SCANNER_SSH_PORT ip saddr \$ADMIN_IPS \
|
||||
ct state new \
|
||||
limit rate 5/minute \
|
||||
accept
|
||||
|
||||
# Track SSH brute force attempts
|
||||
tcp dport \$SCANNER_SSH_PORT ct state new \
|
||||
add @ssh_bruteforce { ip saddr limit rate 3/minute } \
|
||||
counter drop
|
||||
|
||||
# --- OpenVAS Web UI from admin IPs only ---
|
||||
tcp dport \$SCANNER_WEB_PORTS ip saddr \$ADMIN_IPS accept
|
||||
|
||||
# --- Docker internal communication (use iifname for wildcard) ---
|
||||
iifname "docker*" accept
|
||||
iifname "br-*" accept
|
||||
|
||||
# --- Log dropped packets (rate limited) ---
|
||||
limit rate 10/second \
|
||||
log prefix "[NFT-INPUT-DROP] " level warn
|
||||
|
||||
# Default: drop (set by policy)
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# FORWARD chain - Docker container traffic
|
||||
# -------------------------------------------------------------------------
|
||||
chain forward {
|
||||
type filter hook forward priority 0; policy accept;
|
||||
|
||||
# Allow Docker container communication
|
||||
iifname "docker*" accept
|
||||
oifname "docker*" accept
|
||||
iifname "br-*" accept
|
||||
oifname "br-*" accept
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# OUTPUT chain - What the scanner can reach
|
||||
# -------------------------------------------------------------------------
|
||||
chain output {
|
||||
type filter hook output priority 0; policy drop;
|
||||
|
||||
# Allow loopback
|
||||
oif "lo" accept
|
||||
|
||||
# Allow established/related
|
||||
ct state established,related accept
|
||||
|
||||
# --- DNS ---
|
||||
udp dport 53 accept
|
||||
tcp dport 53 accept
|
||||
|
||||
# --- NTP ---
|
||||
udp dport 123 accept
|
||||
|
||||
# --- Scanning: allow ALL traffic to internal networks ---
|
||||
# This is required for port scanning, service detection, vuln scanning
|
||||
ip daddr \$INTERNAL_NETS accept
|
||||
|
||||
# --- Package updates and vuln DB sync (HTTPS/HTTP) ---
|
||||
# Outbound HTTP/HTTPS is allowed for vuln DB updates, package management,
|
||||
# and Docker image pulls. Restricted at PVE layer to update_targets IPSET.
|
||||
tcp dport { 80, 443 } accept
|
||||
|
||||
# --- ICMP out ---
|
||||
ip protocol icmp accept
|
||||
|
||||
# --- Log dropped outbound (rate limited) ---
|
||||
limit rate 10/second \
|
||||
log prefix "[NFT-OUTPUT-DROP] " level warn
|
||||
|
||||
# Default: drop (set by policy)
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Separate table for connection tracking and logging
|
||||
# =============================================================================
|
||||
table inet logging {
|
||||
|
||||
chain prerouting {
|
||||
type filter hook prerouting priority -200; policy accept;
|
||||
|
||||
# Log new inbound connections (rate limited, for audit trail)
|
||||
ct state new \
|
||||
limit rate 5/second \
|
||||
log prefix "[NFT-NEW-CONN] " level info
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# --- Apply rules ---
|
||||
echo "[+] Applying nftables rules..."
|
||||
nft -f /etc/nftables.conf
|
||||
|
||||
# --- Verify ---
|
||||
echo "[+] Current ruleset:"
|
||||
nft list ruleset
|
||||
|
||||
echo ""
|
||||
echo "[+] nftables firewall configured and active."
|
||||
echo ""
|
||||
echo "Useful commands:"
|
||||
echo " nft list ruleset # Show all rules"
|
||||
echo " nft list set inet firewall blocked_ips # Show blocked IPs"
|
||||
echo " nft add element inet firewall blocked_ips { 1.2.3.4 timeout 1h } # Block IP"
|
||||
echo " systemctl restart nftables # Reload rules"
|
||||
150
vm/03-ssh-harden.sh
Normal file
150
vm/03-ssh-harden.sh
Normal file
@@ -0,0 +1,150 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# SSH Hardening for Security Scanner VM
|
||||
# Run this inside the VM as root
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
echo "============================================"
|
||||
echo " SSH Hardening - Security Scanner VM"
|
||||
echo "============================================"
|
||||
|
||||
SSHD_CONFIG="/etc/ssh/sshd_config"
|
||||
SSHD_CUSTOM="/etc/ssh/sshd_config.d/99-scanner-hardening.conf"
|
||||
SSHD_BACKUP="${SSHD_CONFIG}.bak.$(date +%Y%m%d_%H%M%S)"
|
||||
|
||||
# --- 1. Backup original config ---
|
||||
cp "${SSHD_CONFIG}" "${SSHD_BACKUP}"
|
||||
|
||||
# --- 2. Write hardened SSH config ---
|
||||
echo "[+] Writing hardened SSH configuration..."
|
||||
cat > "${SSHD_CUSTOM}" << 'EOF'
|
||||
# =============================================================================
|
||||
# SSH Hardening - Security Scanner VM
|
||||
# =============================================================================
|
||||
|
||||
# --- Authentication ---
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication no
|
||||
PubkeyAuthentication yes
|
||||
AuthenticationMethods publickey
|
||||
PermitEmptyPasswords no
|
||||
MaxAuthTries 10
|
||||
MaxSessions 3
|
||||
LoginGraceTime 30
|
||||
|
||||
# --- Ciphers ---
|
||||
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org
|
||||
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
|
||||
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
|
||||
|
||||
# --- Network ---
|
||||
Port 22
|
||||
AddressFamily inet
|
||||
ListenAddress 0.0.0.0
|
||||
TCPKeepAlive yes
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
|
||||
# --- Access control ---
|
||||
# Uncomment and set your admin user
|
||||
# AllowUsers scanner-admin
|
||||
# AllowGroups ssh-users
|
||||
|
||||
# --- Restrictions ---
|
||||
# DisableForwarding covers X11, agent, TCP forwarding, and tunneling
|
||||
DisableForwarding yes
|
||||
GatewayPorts no
|
||||
|
||||
# --- Logging ---
|
||||
SyslogFacility AUTH
|
||||
LogLevel VERBOSE
|
||||
|
||||
# --- Misc ---
|
||||
PrintMotd no
|
||||
PrintLastLog yes
|
||||
UseDNS no
|
||||
Compression no
|
||||
StrictModes yes
|
||||
IgnoreRhosts yes
|
||||
HostbasedAuthentication no
|
||||
|
||||
# --- Banner ---
|
||||
Banner /etc/issue.net
|
||||
EOF
|
||||
|
||||
# --- 3. Create scanner admin user ---
|
||||
ADMIN_USER="scanner-admin"
|
||||
# Also allow the cloud-init default user
|
||||
CLOUDINIT_USER="${SCANNER_SSH_USER:-kai}"
|
||||
echo "[+] Creating admin user: ${ADMIN_USER}..."
|
||||
|
||||
if ! id "${ADMIN_USER}" &>/dev/null; then
|
||||
useradd -m -s /bin/bash -G sudo,docker "${ADMIN_USER}"
|
||||
mkdir -p "/home/${ADMIN_USER}/.ssh"
|
||||
chmod 700 "/home/${ADMIN_USER}/.ssh"
|
||||
touch "/home/${ADMIN_USER}/.ssh/authorized_keys"
|
||||
chmod 600 "/home/${ADMIN_USER}/.ssh/authorized_keys"
|
||||
chown -R "${ADMIN_USER}:${ADMIN_USER}" "/home/${ADMIN_USER}/.ssh"
|
||||
echo "[!] Add your SSH public key to: /home/${ADMIN_USER}/.ssh/authorized_keys"
|
||||
else
|
||||
echo " User ${ADMIN_USER} already exists."
|
||||
fi
|
||||
|
||||
# Set AllowUsers (idempotent: replace commented or active line)
|
||||
ALLOW_USERS="${ADMIN_USER} ${CLOUDINIT_USER}"
|
||||
if grep -q "^AllowUsers" "${SSHD_CUSTOM}"; then
|
||||
sed -i "s/^AllowUsers.*/AllowUsers ${ALLOW_USERS}/" "${SSHD_CUSTOM}"
|
||||
elif grep -q "^# AllowUsers" "${SSHD_CUSTOM}"; then
|
||||
sed -i "s/^# AllowUsers.*/AllowUsers ${ALLOW_USERS}/" "${SSHD_CUSTOM}"
|
||||
else
|
||||
echo "AllowUsers ${ALLOW_USERS}" >> "${SSHD_CUSTOM}"
|
||||
fi
|
||||
|
||||
# --- 4. Configure fail2ban for SSH ---
|
||||
echo "[+] Configuring fail2ban for SSH..."
|
||||
cat > /etc/fail2ban/jail.d/sshd.conf << 'EOF'
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = ssh
|
||||
filter = sshd
|
||||
backend = systemd
|
||||
maxretry = 3
|
||||
findtime = 600
|
||||
bantime = 3600
|
||||
banaction = nftables[type=allports]
|
||||
EOF
|
||||
|
||||
systemctl enable fail2ban
|
||||
systemctl restart fail2ban
|
||||
|
||||
# --- 5. Generate new host keys (replace defaults) ---
|
||||
echo "[+] Regenerating SSH host keys..."
|
||||
rm -f /etc/ssh/ssh_host_*
|
||||
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
|
||||
ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""
|
||||
|
||||
# Remove small DH moduli
|
||||
echo "[+] Hardening DH moduli..."
|
||||
awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe
|
||||
mv /etc/ssh/moduli.safe /etc/ssh/moduli
|
||||
|
||||
# --- 6. Validate and restart ---
|
||||
echo "[+] Validating SSH configuration..."
|
||||
if sshd -t; then
|
||||
systemctl restart ssh 2>/dev/null || systemctl restart sshd
|
||||
echo "[+] SSH hardening complete."
|
||||
else
|
||||
echo "[!] SSH config validation failed. Restoring backup..."
|
||||
cp "${SSHD_BACKUP}" "${SSHD_CONFIG}"
|
||||
rm -f "${SSHD_CUSTOM}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[!] IMPORTANT: Before closing this session:"
|
||||
echo " 1. Add your SSH public key to /home/${ADMIN_USER}/.ssh/authorized_keys"
|
||||
echo " 2. Test login with: ssh ${ADMIN_USER}@<this-vm-ip>"
|
||||
echo " 3. Verify you can sudo: sudo whoami"
|
||||
echo ""
|
||||
echo "[!] WARNING: Password login is DISABLED. Ensure key-based access works first!"
|
||||
476
vm/04-install-tools.sh
Normal file
476
vm/04-install-tools.sh
Normal 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"
|
||||
166
vm/05-monitoring.sh
Normal file
166
vm/05-monitoring.sh
Normal file
@@ -0,0 +1,166 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Monitoring and Logging for Security Scanner VM
|
||||
# Run this inside the VM as root
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
echo "============================================"
|
||||
echo " Monitoring & Logging - Security Scanner VM"
|
||||
echo "============================================"
|
||||
|
||||
# --- 1. Configure rsyslog for centralized logging ---
|
||||
echo "[+] Configuring rsyslog..."
|
||||
cat > /etc/rsyslog.d/99-scanner.conf << 'EOF'
|
||||
# Log all scanner-related activity to dedicated file
|
||||
:programname, startswith, "nmap" /var/log/scanner/nmap.log
|
||||
:programname, startswith, "nuclei" /var/log/scanner/nuclei.log
|
||||
:programname, startswith, "nft" /var/log/scanner/firewall.log
|
||||
|
||||
# Log auth separately with more detail
|
||||
auth,authpriv.* /var/log/scanner/auth.log
|
||||
|
||||
# Uncomment to forward to remote syslog server
|
||||
# *.* @@syslog.internal.lan:514
|
||||
EOF
|
||||
|
||||
mkdir -p /var/log/scanner
|
||||
systemctl restart rsyslog
|
||||
|
||||
# --- 2. Log rotation ---
|
||||
echo "[+] Configuring log rotation..."
|
||||
cat > /etc/logrotate.d/scanner << 'EOF'
|
||||
/var/log/scanner/*.log {
|
||||
daily
|
||||
missingok
|
||||
rotate 30
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
create 0640 root adm
|
||||
sharedscripts
|
||||
postrotate
|
||||
systemctl reload rsyslog > /dev/null 2>&1 || true
|
||||
endscript
|
||||
}
|
||||
|
||||
/opt/scans/results/*/*.txt {
|
||||
weekly
|
||||
missingok
|
||||
rotate 12
|
||||
compress
|
||||
notifempty
|
||||
}
|
||||
EOF
|
||||
|
||||
# --- 3. Logwatch (daily summary reports) ---
|
||||
echo "[+] Configuring Logwatch..."
|
||||
cat > /etc/logwatch/conf/logwatch.conf << 'EOF'
|
||||
LogDir = /var/log
|
||||
MailTo = root
|
||||
MailFrom = scanner@localhost
|
||||
Range = yesterday
|
||||
Detail = Med
|
||||
Service = All
|
||||
Format = text
|
||||
EOF
|
||||
|
||||
# --- 4. Disk usage monitoring ---
|
||||
echo "[+] Setting up disk usage monitoring..."
|
||||
cat > /opt/scans/scripts/check-disk.sh << 'DISKEOF'
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
# Alert if disk usage exceeds threshold
|
||||
THRESHOLD=85
|
||||
USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
|
||||
|
||||
if [ "${USAGE}" -ge "${THRESHOLD}" ]; then
|
||||
echo "[ALERT] Disk usage at ${USAGE}% on security scanner" | \
|
||||
logger -t disk-monitor -p user.warning
|
||||
echo "[ALERT] Disk usage at ${USAGE}% - consider cleaning /opt/scans/results/"
|
||||
fi
|
||||
DISKEOF
|
||||
chmod +x /opt/scans/scripts/check-disk.sh
|
||||
|
||||
# --- 5. Docker health check ---
|
||||
cat > /opt/scans/scripts/check-openvas.sh << 'HEALTHEOF'
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
# Check if OpenVAS containers are healthy
|
||||
|
||||
if [[ ! -d /opt/greenbone ]]; then
|
||||
echo "[!] /opt/greenbone not found - skipping health check"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
UNHEALTHY=$(docker compose -f /opt/greenbone/docker-compose.yml ps --format json 2>/dev/null | jq -r 'select(.State != "running") | .Name' 2>/dev/null || true)
|
||||
|
||||
if [ -n "${UNHEALTHY}" ]; then
|
||||
echo "[ALERT] Unhealthy OpenVAS containers: ${UNHEALTHY}" | \
|
||||
logger -t openvas-health -p user.warning
|
||||
echo "[ALERT] Restarting unhealthy containers..."
|
||||
docker compose -f /opt/greenbone/docker-compose.yml up -d
|
||||
fi
|
||||
HEALTHEOF
|
||||
chmod +x /opt/scans/scripts/check-openvas.sh
|
||||
|
||||
# --- 6. Cron jobs ---
|
||||
echo "[+] Setting up monitoring cron jobs..."
|
||||
cat > /etc/cron.d/scanner-monitoring << 'EOF'
|
||||
# Disk check every 6 hours
|
||||
0 */6 * * * root /opt/scans/scripts/check-disk.sh
|
||||
|
||||
# OpenVAS health check every 30 minutes
|
||||
*/30 * * * * root /opt/scans/scripts/check-openvas.sh
|
||||
|
||||
# AIDE integrity check daily at 3am
|
||||
0 3 * * * root /usr/bin/aide --check 2>&1 | logger -t aide-check -p user.info
|
||||
|
||||
# Lynis security audit weekly (Sunday 2am)
|
||||
0 2 * * 0 root /usr/sbin/lynis audit system --quick --no-colors 2>&1 | logger -t lynis-audit -p user.info
|
||||
|
||||
# Clean scan results older than 90 days (maxdepth 1 for safety, log to syslog)
|
||||
0 4 * * 0 root find /opt/scans/results -maxdepth 1 -type d -mtime +90 -print -exec rm -rf {} + 2>&1 | logger -t scan-cleanup
|
||||
|
||||
# Update Nuclei templates weekly
|
||||
0 5 * * 1 root /usr/local/bin/nuclei -update-templates 2>&1 | logger -t nuclei-update -p user.info
|
||||
EOF
|
||||
|
||||
# --- 7. Login banner ---
|
||||
echo "[+] Setting login banner..."
|
||||
cat > /etc/motd << 'EOF'
|
||||
+=====================================================+
|
||||
| SECURITY SCANNER - AUTHORIZED ACCESS ONLY |
|
||||
| |
|
||||
| All activity on this system is logged and audited. |
|
||||
| Unauthorized access is prohibited. |
|
||||
+=====================================================+
|
||||
|
||||
Tools: nmap | nuclei | httpx | nikto | testssl | OpenVAS
|
||||
Scans: /opt/scans/scripts/quick-scan.sh <target>
|
||||
Logs: /var/log/scanner/
|
||||
OpenVAS: http://localhost:9392
|
||||
|
||||
EOF
|
||||
|
||||
cat > /etc/issue.net << 'EOF'
|
||||
*************************************************************
|
||||
* WARNING: This is a restricted system. *
|
||||
* All connections are monitored and recorded. *
|
||||
* Disconnect IMMEDIATELY if you are not authorized. *
|
||||
*************************************************************
|
||||
EOF
|
||||
|
||||
# Banner is configured in /etc/ssh/sshd_config.d/99-scanner-hardening.conf by 03-ssh-harden.sh
|
||||
systemctl reload ssh 2>/dev/null || systemctl reload sshd 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "[+] Monitoring and logging configured."
|
||||
echo ""
|
||||
echo "Summary:"
|
||||
echo " Logs: /var/log/scanner/"
|
||||
echo " Scan results: /opt/scans/results/"
|
||||
echo " Cron jobs: /etc/cron.d/scanner-monitoring"
|
||||
echo " Logwatch: Daily email summary to root"
|
||||
echo " AIDE: File integrity check daily at 3am"
|
||||
echo " Lynis: Security audit weekly (Sunday 2am)"
|
||||
35
vm/06-docker-autostart.sh
Normal file
35
vm/06-docker-autostart.sh
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Docker Compose Autostart for Greenbone OpenVAS
|
||||
# Creates a systemd service so containers start on boot
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
echo "[+] Creating systemd service for Greenbone OpenVAS..."
|
||||
|
||||
cat > /etc/systemd/system/greenbone-openvas.service << 'EOF'
|
||||
[Unit]
|
||||
Description=Greenbone OpenVAS Scanner
|
||||
Requires=docker.service
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
WorkingDirectory=/opt/greenbone
|
||||
ExecStart=/usr/bin/docker compose up -d
|
||||
ExecStop=/usr/bin/docker compose down
|
||||
TimeoutStartSec=300
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable greenbone-openvas.service
|
||||
|
||||
echo "[+] Greenbone OpenVAS will start automatically on boot."
|
||||
echo " Manual control:"
|
||||
echo " systemctl start greenbone-openvas"
|
||||
echo " systemctl stop greenbone-openvas"
|
||||
echo " systemctl status greenbone-openvas"
|
||||
61
vm/setup.sh
Normal file
61
vm/setup.sh
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Security Scanner VM - Full Setup
|
||||
# Run this inside the VM as root to execute all scripts in order
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# --- Pre-flight checks ---
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo "[ERROR] This script must be run as root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${SCANNER_ADMIN_IPS:-}" ]]; then
|
||||
echo "[ERROR] Set SCANNER_ADMIN_IPS before running." >&2
|
||||
echo " Example: export SCANNER_ADMIN_IPS='192.168.68.100, 192.168.68.101'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "============================================"
|
||||
echo " Security Scanner VM - Full Setup"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo " ADMIN_IPS: ${SCANNER_ADMIN_IPS}"
|
||||
echo " INTERNAL_NETS: ${SCANNER_INTERNAL_NETS:-10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16}"
|
||||
echo " DNS_SERVERS: ${SCANNER_DNS_SERVERS:-192.168.68.1}"
|
||||
echo ""
|
||||
|
||||
SCRIPTS=(
|
||||
"01-system-harden.sh"
|
||||
"02-firewall.sh"
|
||||
"03-ssh-harden.sh"
|
||||
"04-install-tools.sh"
|
||||
"05-monitoring.sh"
|
||||
)
|
||||
|
||||
for script in "${SCRIPTS[@]}"; do
|
||||
SCRIPT_PATH="${SCRIPT_DIR}/${script}"
|
||||
if [[ ! -f "${SCRIPT_PATH}" ]]; then
|
||||
echo "[ERROR] Script not found: ${SCRIPT_PATH}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
echo ">>> Running ${script}..."
|
||||
bash "${SCRIPT_PATH}"
|
||||
echo ">>> ${script} completed."
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo " Setup complete!"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo " Next steps:"
|
||||
echo " 1. Add SSH key: /home/scanner-admin/.ssh/authorized_keys"
|
||||
echo " 2. Start OpenVAS: cd /opt/greenbone && docker compose up -d"
|
||||
echo " 3. Set OpenVAS password:"
|
||||
echo " docker compose exec -u gvmd gvmd gvmd --user=admin --new-password=<PASSWORD>"
|
||||
echo " 4. Reboot: shutdown -r now"
|
||||
Reference in New Issue
Block a user