Skip to content

luukverhoeven/ld-host-scanner

Repository files navigation

LD Host Scanner

Tests codecov

This codebase is a FastAPI web app + APScheduler service that periodically scans a configured TARGET_HOST for open TCP/UDP ports (Rustscan for fast TCP discovery, nmap for UDP and optional service/version enrichment), stores results and diffs in an SQLite database, exposes a dashboard + REST API to view scan history/progress, and sends notifications (email/webhook) when ports change or expected ports go missing. It also runs lightweight host online checks between full scans and includes special handling for “silent” UDP services like WireGuard (stealth/probe verification).

Screen1.png

Features

  • Full spectrum port scanning: TCP (1-65535) and UDP (top 1000 ports)
  • Online/offline detection: Quick checks every 15 minutes
  • Automated scheduling: Configurable scan intervals (default: 2 hours)
  • Expected ports monitoring: Alert when critical services go down
  • Stealth service detection: Track services like WireGuard that don't respond to probes
  • WireGuard verification: Optional handshake probe to verify VPN is running
  • Email notifications: SMTP alerts with HTML reports
  • Webhook notifications: Discord and Slack compatible
  • Web dashboard: Real-time status and scan history
  • REST API: Programmatic access to scan data
  • Docker deployment: Easy setup with docker-compose

Quick Start

1. Clone and configure

# Clone the repository
git clone git@github.com:luukverhoeven/ld-host-scanner.git
cd ld-host-scanner

# Copy example environment file
cp .env.example .env

# Edit configuration
nano .env

2. Configure notifications (optional)

Edit .env to add your notification settings:

# Email (SMTP)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-app-password
SMTP_FROM=LD Host Scanner <your-email@gmail.com>
SMTP_TO=alerts@example.com

# Discord webhook
WEBHOOK_URL=https://discord.com/api/webhooks/xxx/yyy

# Or Slack webhook
WEBHOOK_URL=https://hooks.slack.com/services/xxx/yyy/zzz

3. Build and run

# Build and start the container
docker-compose build && docker-compose up -d

# View logs
docker-compose logs -f

# Stop the container
docker-compose down

Build with version info (optional)

# Build with version metadata
docker build -f docker/Dockerfile \
  --build-arg VERSION=1.1.0 \
  --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
  --build-arg GIT_COMMIT=$(git rev-parse --short HEAD) \
  -t ld-host-scanner:1.1.0 .

4. Access the dashboard

Open your browser to: http://localhost:8080

Configuration

Environment Variables

Variable Default Description
TARGET_HOST example.com Host to scan (must be configured)
SCAN_INTERVAL_HOURS 2 Full scan frequency
HOST_CHECK_INTERVAL_MINUTES 15 Host online check frequency
SMTP_HOST - SMTP server hostname
SMTP_PORT 587 SMTP server port
SMTP_USER - SMTP username
SMTP_PASSWORD - SMTP password
SMTP_FROM - Email sender address
SMTP_TO - Email recipient address
WEBHOOK_URL - Discord/Slack webhook URL
TZ Europe/Amsterdam Timezone
LOG_LEVEL INFO Logging level
TCP_SERVICE_ENRICHMENT true Run targeted TCP nmap -sV on Rustscan hits
TCP_SERVICE_ENRICHMENT_INTENSITY light TCP version detection intensity (`light
TCP_SERVICE_ENRICHMENT_PORTS_LIMIT 200 Max TCP ports to version-scan per run
UDP_TOP_PORTS 1000 UDP nmap --top-ports count when no range is set
UDP_VERSION_DETECTION true Run UDP version detection on prioritized ports
UDP_VERSION_DETECTION_INTENSITY light UDP version detection intensity (`light
UDP_VERSION_DETECTION_PORTS_LIMIT 50 Max UDP ports to version-scan per run
EXPECTED_PORTS - Ports that should be open (e.g., 80/tcp,443/tcp,448/udp)
WIREGUARD_PUBLIC_KEY - WireGuard server public key (base64) for probe verification
WIREGUARD_PROBE_PORTS - Ports to probe for WireGuard (e.g., 448,51820)
WIREGUARD_SCANNER_PRIVATE_KEY - Scanner's private key (base64) for authenticated probes

Scan Intervals

  • Full scan: Every 2 hours (configurable via SCAN_INTERVAL_HOURS)
  • Host check: Every 15 minutes (quick online/offline check)
  • Initial scan: Runs immediately on container startup

Expected Ports Monitoring

Monitor critical services and get alerts when they go down:

# In .env
EXPECTED_PORTS=80/tcp,443/tcp,22/tcp,448/udp

Features:

  • UDP ports are explicitly scanned even if not in nmap's top-N (e.g., port 448)
  • Alerts only on state change - notifies when a port goes from open to closed
  • Dashboard shows status - expected ports card with open/missing indicators

Stealth Services & WireGuard

Some services like WireGuard are designed to be "silent" - they don't respond to port scans. The scanner handles these with special detection:

Stealth Detection:

  • Ports that return "open|filtered" AND are in your expected list are marked as "stealth"
  • Dashboard shows a yellow "stealth" badge with tooltip
  • This means: "We expect this service to be running, but can't verify it"

WireGuard Verification (Recommended):

For reliable WireGuard monitoring, set up authenticated probes. This requires:

  1. The WireGuard server's public key
  2. A scanner keypair (the scanner must be added as a peer on the server)

Step 1: Generate scanner keypair

docker-compose run --rm --no-deps ld-host-scanner python3 -c "
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
import base64
private_key = X25519PrivateKey.generate()
print('Private (for .env):', base64.b64encode(private_key.private_bytes_raw()).decode())
print('Public (for server):', base64.b64encode(private_key.public_key().public_bytes_raw()).decode())
"

Step 2: Add scanner as peer on WireGuard server

# On your WireGuard server
sudo wg set wg0 peer <SCANNER_PUBLIC_KEY> allowed-ips 10.255.255.254/32

Step 3: Configure scanner .env

# WireGuard monitoring
EXPECTED_PORTS=446/udp
WIREGUARD_PROBE_PORTS=446
WIREGUARD_PUBLIC_KEY="<server-public-key-base64>"
WIREGUARD_SCANNER_PRIVATE_KEY="<scanner-private-key-base64>"

How it works:

  • Scanner sends a cryptographically valid WireGuard handshake initiation
  • Server recognizes the scanner as a known peer and responds
  • Response confirms WireGuard is running (marked as "verified")
  • If no response, port is marked "stealth" and alerts are sent

Without scanner keypair: The scanner can only detect "no ICMP rejection" which indicates something is listening, but can't confirm it's actually WireGuard responding. Firewalls that drop packets silently will cause false positives.

API Endpoints

Endpoint Method Description
/ GET Dashboard
/history GET Scan history
/health GET Health check (includes version)
/version GET Version information
/api/status GET Current target status
/api/scans GET List recent scans
/api/scans/{id} GET Get scan details
/api/scans/trigger POST Trigger manual scan
/api/changes GET Port change history
/api/jobs GET Scheduled jobs info
/api/host-status-history GET Host uptime history (for chart)
/api/port-history GET Open port count history (for chart)
/docs GET API documentation (Swagger)

Check Version

curl http://localhost:8080/version
# {"version":"1.1.0","build_date":null,"git_commit":null}

# Or via health endpoint
curl http://localhost:8080/health
# {"status":"healthy","scheduler_running":true,"version":"1.1.0","message":"OK"}

Manual Scan Trigger

curl -X POST http://localhost:8080/api/scans/trigger

Get Current Status

curl http://localhost:8080/api/status

Docker Capabilities

The container requires specific Linux capabilities for nmap to function:

  • NET_RAW: TCP SYN scans, ICMP ping
  • NET_ADMIN: OS detection, advanced scans

These are configured in docker-compose.yml.

Testing

Tests run inside Docker to ensure the correct environment with all dependencies:

# Run all tests
docker-compose run --rm --no-deps -v "$(pwd)/tests:/app/tests" ld-host-scanner \
  sh -c "pip install -q pytest pytest-asyncio && python -m pytest tests/ -v"

# Run with coverage
docker-compose run --rm --no-deps -v "$(pwd)/tests:/app/tests" ld-host-scanner \
  sh -c "pip install -q pytest pytest-asyncio pytest-cov && python -m pytest tests/ --cov=src --cov-report=term-missing"

# Run a single test file
docker-compose run --rm --no-deps -v "$(pwd)/tests:/app/tests" ld-host-scanner \
  sh -c "pip install -q pytest pytest-asyncio && python -m pytest tests/test_port_scanner.py -v"

Project Structure

ld-host-scanner/
├── docker/
│   └── Dockerfile
├── docker-compose.yml
├── src/
│   ├── main.py              # Entry point
│   ├── config.py            # Configuration
│   ├── scanner/             # Port scanning logic
│   ├── storage/             # Database layer
│   ├── notifications/       # Email & webhook alerts
│   ├── scheduler/           # Job scheduling
│   └── web/                 # FastAPI dashboard
├── tests/                   # Test suite (pytest)
├── data/                    # Persistent data (SQLite)
├── requirements.txt
├── .env.example
└── README.md

Notifications

Email Alerts

HTML-formatted emails include:

  • Host status (online/offline)
  • List of open ports with service detection
  • Port changes (newly opened/closed)

Webhook Alerts (Discord/Slack)

Rich embeds showing:

  • Host status
  • Open port count
  • Port changes with color coding (red = opened, green = closed)
  • Port details with service names

Troubleshooting

Container won't start

Check logs:

docker-compose logs ld-host-scanner

Scans not running

Verify the scheduler is running:

curl http://localhost:8080/api/jobs

Email not sending

  1. Check SMTP settings in .env
  2. For Gmail, use an App Password
  3. Check logs for SMTP errors

Permission denied errors

Ensure the container has proper capabilities:

cap_add:
  - NET_RAW
  - NET_ADMIN

Security Notes

  • Container runs as non-root user with limited capabilities
  • Credentials stored in environment variables (not in code)
  • SQLite database persisted in mounted volume
  • No scanning of private/internal networks by default

License

MIT

About

A Docker-based FastAPI web app + APScheduler service that periodically scans a configured TARGET_HOST for open TCP/UDP ports (Rustscan for fast TCP discovery, nmap for UDP and optional service/version enrichment), stores results and diffs in an SQLite database, exposes a dashboard + REST API to view scan history/progress

Topics

Resources

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

Contributors