diff --git a/.gitignore b/.gitignore index 15f43b3..f84670e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ # Network Scanner - Git Ignore # Scan results and output -*.json +# Ignore archived results in results/ folder +results/ + +# Keep config.json.example !config.json.example -*.svg # Python __pycache__/ diff --git a/EXAMPLES.sh b/EXAMPLES.sh new file mode 100644 index 0000000..db8a9c8 --- /dev/null +++ b/EXAMPLES.sh @@ -0,0 +1,260 @@ +#!/bin/bash +# Example usage scenarios for the network scanner + +echo "==========================================" +echo "Network Scanner - Usage Examples" +echo "==========================================" +echo "" + +cat << 'EOF' +# SCENARIO 1: Quick Network Overview +# ----------------------------------- +# Scan your local network and get a basic overview + +./network_scanner.py -v -o quick_scan.json + + +# SCENARIO 2: Complete Network Documentation +# ------------------------------------------- +# Full scan with pfSense integration + +./integrated_scanner.py -c config.json -o full_network.json -v + + +# SCENARIO 3: pfSense Deep Dive +# ------------------------------ +# Detailed scan of a specific pfSense firewall + +./pfsense_scanner.py 192.168.1.1 -u root -k ~/.ssh/id_rsa -o pfsense_main.json + +# View the results: +cat pfsense_main.json | jq '.vpn' # Show VPN info +cat pfsense_main.json | jq '.routes' # Show routing table + + +# SCENARIO 4: Multi-Network Scan with VPN +# ---------------------------------------- +# Create a config for multiple networks + +cat > my_network_config.json << 'CONFIG' +{ + "ssh_user": "root", + "ssh_key_path": "/home/user/.ssh/id_rsa", + "timeout": 3, + "additional_networks": [ + "192.168.1.0/24", # Main network + "192.168.2.0/24", # Guest network + "10.8.0.0/24", # OpenVPN network + "10.0.0.0/24" # WireGuard VPN + ], + "special_devices": { + "192.168.1.1": { + "name": "Main pfSense Firewall", + "type": "firewall", + "os": "pfSense" + }, + "192.168.2.1": { + "name": "Guest Network Router", + "type": "router" + } + } +} +CONFIG + +./integrated_scanner.py -c my_network_config.json -o multi_network.json + + +# SCENARIO 5: Scheduled Network Monitoring +# ----------------------------------------- +# Add to crontab for daily network documentation + +# Create wrapper script +cat > /usr/local/bin/network-scan-daily.sh << 'SCRIPT' +#!/bin/bash +DATE=$(date +%Y%m%d) +OUTPUT_DIR="/var/log/network-scans" +mkdir -p "$OUTPUT_DIR" + +cd /path/to/network_scanner +./integrated_scanner.py \ + -o "$OUTPUT_DIR/scan_$DATE.json" + +# Keep only last 30 days +find "$OUTPUT_DIR" -name "scan_*.json" -mtime +30 -delete +SCRIPT + +chmod +x /usr/local/bin/network-scan-daily.sh + +# Add to crontab (run at 2 AM daily): +# 0 2 * * * /usr/local/bin/network-scan-daily.sh + + +# SCENARIO 6: Compare Network Changes +# ------------------------------------ +# Scan and compare with previous results + +# Initial scan +./integrated_scanner.py -o baseline.json + +# After changes +./integrated_scanner.py -o current.json + +# Compare device counts +echo "Baseline devices:" +cat baseline.json | jq '[.segments[].devices[].ip] | length' +echo "Current devices:" +cat current.json | jq '[.segments[].devices[].ip] | length' + +# Find new devices +comm -13 \ + <(cat baseline.json | jq -r '.segments[].devices[].ip' | sort) \ + <(cat current.json | jq -r '.segments[].devices[].ip' | sort) \ + | sed 's/^/NEW: /' + +# Find removed devices +comm -23 \ + <(cat baseline.json | jq -r '.segments[].devices[].ip' | sort) \ + <(cat current.json | jq -r '.segments[].devices[].ip' | sort) \ + | sed 's/^/REMOVED: /' + + +# SCENARIO 7: Extract Specific Information +# ----------------------------------------- +# Use jq to extract specific data from scan results + +# List all SSH-accessible devices +cat network_scan.json | jq -r '.segments[].devices[] | select(.ssh_accessible==true) | .ip' + +# List all routers/firewalls +cat network_scan.json | jq -r '.segments[].devices[] | select(.device_type=="router" or .device_type=="firewall") | "\(.ip) - \(.hostname // "unknown")"' + +# List all devices with their OS +cat network_scan.json | jq -r '.segments[].devices[] | "\(.ip)\t\(.os_type // "unknown")\t\(.hostname // "unknown")"' + +# Export to CSV +echo "IP,Hostname,Type,OS" > devices.csv +cat network_scan.json | jq -r '.segments[].devices[] | "\(.ip),\(.hostname // ""),\(.device_type // ""),\(.os_type // "")"' >> devices.csv + + +# SCENARIO 8: Integration with Documentation +# ------------------------------------------- +# Generate markdown documentation from scan + +cat > generate_docs.py << 'PYTHON' +#!/usr/bin/env python3 +import json +import sys + +with open(sys.argv[1]) as f: + data = json.load(f) + +print("# Network Documentation") +print(f"\nGenerated: {data.get('scan_timestamp', 'N/A')}") +print("\n## Network Segments\n") + +for segment in data['segments']: + print(f"### {segment['name']}") + print(f"- CIDR: `{segment['cidr']}`") + print(f"- Devices: {len(segment['devices'])}") + if segment.get('is_vpn'): + print("- Type: VPN Network") + print("\n#### Devices\n") + print("| IP | Hostname | Type | OS |") + print("|---|---|---|---|") + + for device in segment['devices']: + ip = device['ip'] + hostname = device.get('hostname', '-') + dtype = device.get('device_type', '-') + os = device.get('os_type', '-') + print(f"| {ip} | {hostname} | {dtype} | {os} |") + + print() +PYTHON + +chmod +x generate_docs.py +./generate_docs.py network_scan.json > NETWORK_DOCS.md + + +# SCENARIO 9: Security Audit +# --------------------------- +# Check for common security issues + +# Find devices with Telnet open +cat network_scan.json | jq -r '.segments[].devices[] | select(.open_ports[]? == 23) | "⚠️ Telnet open on \(.ip) (\(.hostname // "unknown"))"' + +# Find devices without SSH access +cat network_scan.json | jq -r '.segments[].devices[] | select(.device_type=="router" or .device_type=="firewall") | select(.ssh_accessible==false) | "⚠️ No SSH access to \(.ip) (\(.hostname // "unknown"))"' + +# List devices with many open ports +cat network_scan.json | jq -r '.segments[].devices[] | select((.open_ports | length) > 5) | "ℹ️ \(.ip) has \(.open_ports | length) open ports"' + + +# SCENARIO 10: WireGuard Topology Mapping +# ---------------------------------------- +# Extract WireGuard tunnel information from pfSense + +./pfsense_scanner.py 192.168.1.1 -o pfsense.json + +# List all WireGuard peers +cat pfsense.json | jq -r '.vpn.wireguard[] | "Peer: \(.peer // "N/A") -> \(.allowed_ips // "N/A")"' + +# Check tunnel status +cat pfsense.json | jq -r '.vpn.wireguard[] | select(.latest_handshake) | "Active tunnel to \(.endpoint) (handshake: \(.latest_handshake)s ago)"' + + +# SCENARIO 11: Network Capacity Planning +# --------------------------------------- +# Analyze network usage and plan capacity + +# Count devices per segment +cat network_scan.json | jq -r '.segments[] | "\(.cidr): \(.devices | length) devices"' + +# Calculate subnet utilization +cat network_scan.json | jq -r '.segments[] | + if .cidr | contains("/24") then + "\(.cidr): \(.devices | length)/254 = \((.devices | length) * 100 / 254 | floor)% utilized" + else + "\(.cidr): \(.devices | length) devices" + end' + + +# SCENARIO 12: Quick Health Check +# -------------------------------- +# Create a health check script + +cat > health_check.sh << 'HEALTH' +#!/bin/bash +SCAN_FILE="latest_scan.json" + +echo "Network Health Check" +echo "====================" +echo "" + +# Total devices +TOTAL=$(cat $SCAN_FILE | jq '[.segments[].devices[]] | length') +echo "Total devices: $TOTAL" + +# SSH accessible +SSH_OK=$(cat $SCAN_FILE | jq '[.segments[].devices[] | select(.ssh_accessible==true)] | length') +echo "SSH accessible: $SSH_OK" + +# By type +echo "" +echo "Device Types:" +cat $SCAN_FILE | jq -r '.segments[].devices[].device_type' | sort | uniq -c | sort -rn + +# Segments +echo "" +echo "Network Segments:" +cat $SCAN_FILE | jq -r '.segments[] | " \(.name): \(.devices | length) devices"' +HEALTH + +chmod +x health_check.sh +./integrated_scanner.py -o latest_scan.json +./health_check.sh + +EOF + +echo "" +echo "For more examples, see README.md" diff --git a/complete_workflow.sh b/complete_workflow.sh index 93b409f..cdbc5a0 100755 --- a/complete_workflow.sh +++ b/complete_workflow.sh @@ -32,6 +32,14 @@ log_error() { echo -e "${RED}[ERROR]${NC} $1" } +# Move old results to results folder +log_info "Moving old results to results folder..." +mkdir -p results +mv network_scan_*.json server_details_*.json network_summary_*.md *_failed_ssh.json results/ 2>/dev/null || true +if [ $? -eq 0 ] && [ "$(ls results/ 2>/dev/null | wc -l)" -gt 0 ]; then + log_info "Moved old result files to results/ folder" +fi + # Check if we're in the right directory if [ ! -f "src/integrated_scanner.py" ]; then log_error "src/integrated_scanner.py not found. Please run this script from the network scanner directory." @@ -60,24 +68,30 @@ log_info "Step 2: Running integrated network scan..." SCAN_OUTPUT="network_scan_$(date +%Y%m%d_%H%M%S).json" if python3 src/integrated_scanner.py -o "$SCAN_OUTPUT" -v; then log_success "Network scan completed: $SCAN_OUTPUT" + + # Check for failed SSH hosts file + FAILED_SSH_OUTPUT="${SCAN_OUTPUT%.json}_failed_ssh.json" + if [ -f "$FAILED_SSH_OUTPUT" ]; then + FAILED_COUNT=$(jq '.total_failed' "$FAILED_SSH_OUTPUT" 2>/dev/null || echo "unknown") + log_warning "Found $FAILED_COUNT hosts with SSH port open but failed authentication: $FAILED_SSH_OUTPUT" + fi else log_error "Network scan failed" exit 1 fi -# Step 3: Generate SVG diagram -log_info "Step 3: Generating network diagram..." -SVG_OUTPUT="${SCAN_OUTPUT%.json}.svg" -if python3 src/svg_generator.py "$SCAN_OUTPUT" -o "$SVG_OUTPUT"; then - log_success "SVG diagram generated: $SVG_OUTPUT" +# Step 3: Collect server information from hypervisors +log_info "Step 3: Collecting server information from hypervisors..." +SERVER_OUTPUT="server_details_$(date +%Y%m%d_%H%M%S).json" +if python3 src/server_info_collector.py -o "$SERVER_OUTPUT"; then + log_success "Server information collected: $SERVER_OUTPUT" else - log_error "SVG generation failed" - exit 1 + log_warning "Server information collection failed" fi -# Step 4: Generate pfSense summary if XML files exist +# Step 5: Generate pfSense summary if XML files exist if [ "$XML_FILES" -gt 0 ]; then - log_info "Step 4: Generating pfSense network summary..." + log_info "Step 5: Generating pfSense network summary..." SUMMARY_OUTPUT="network_summary_$(date +%Y%m%d_%H%M%S).md" if python3 src/pfsense_integrator.py *.xml --summary "$SUMMARY_OUTPUT"; then log_success "Network summary generated: $SUMMARY_OUTPUT" @@ -86,7 +100,7 @@ if [ "$XML_FILES" -gt 0 ]; then fi fi -# Step 5: Show results summary +# Step 6: Show results summary echo "" echo "==========================================" log_success "Network Discovery Complete!" @@ -94,7 +108,12 @@ echo "==========================================" echo "" echo "Generated files:" echo " 📊 Network Scan: $SCAN_OUTPUT" -echo " 🎨 Network Diagram: $SVG_OUTPUT" +if [ -f "$SERVER_OUTPUT" ]; then + echo " 🖥️ Server Details: $SERVER_OUTPUT" +fi +if [ -f "$FAILED_SSH_OUTPUT" ]; then + echo " 🔐 Failed SSH Hosts: $FAILED_SSH_OUTPUT" +fi if [ "$XML_FILES" -gt 0 ]; then echo " 📋 Network Summary: $SUMMARY_OUTPUT" fi @@ -114,11 +133,21 @@ if command -v jq >/dev/null 2>&1; then fi echo "Next steps:" -echo " 1. Open $SVG_OUTPUT in your web browser to view the network diagram" -if [ "$XML_FILES" -gt 0 ]; then - echo " 2. Review $SUMMARY_OUTPUT for detailed pfSense configuration" +if [ -f "$SERVER_OUTPUT" ]; then + echo " 1. Review $SERVER_OUTPUT for detailed server and VM information" + STEP_NUM=2 +else + STEP_NUM=1 fi -echo " 3. Examine $SCAN_OUTPUT for complete network data (use jq for querying)" +if [ -f "$FAILED_SSH_OUTPUT" ]; then + echo " $STEP_NUM. Review $FAILED_SSH_OUTPUT for hosts needing SSH credential fixes" + STEP_NUM=$((STEP_NUM + 1)) +fi +if [ "$XML_FILES" -gt 0 ]; then + echo " $STEP_NUM. Review $SUMMARY_OUTPUT for detailed pfSense configuration" + STEP_NUM=$((STEP_NUM + 1)) +fi +echo " $STEP_NUM. Examine $SCAN_OUTPUT for complete network data (use jq for querying)" echo "" log_success "Workflow completed successfully! 🎉" \ No newline at end of file diff --git a/config.json.example b/config.json.example index 51f1e0b..8d0d7c6 100644 --- a/config.json.example +++ b/config.json.example @@ -17,5 +17,18 @@ "max_workers": 10, "ping_timeout": 2, "port_scan_timeout": 1 - } + }, + "hypervisors": [ + { + "host": "srvhost04.egonetix.de", + "port": 2222, + "user": "root" + }, + { + "host": "srv-wmw-host01", + "port": 22, + "user": "root" + } + ], + "probe_ssh_on_discovered": true } diff --git a/quickstart.sh b/quickstart.sh new file mode 100644 index 0000000..670466a --- /dev/null +++ b/quickstart.sh @@ -0,0 +1,165 @@ +#!/bin/bash +# Quick Start Script for Network Scanner +# This script helps you get started quickly + +set -e + +echo "================================" +echo "Network Scanner - Quick Start" +echo "================================" +echo "" + +# Check for Python +if ! command -v python3 &> /dev/null; then + echo "❌ Error: Python 3 is not installed" + exit 1 +fi + +echo "✓ Python 3 found" + +# Create config if it doesn't exist +if [ ! -f config.json ]; then + echo "" + echo "📝 Creating configuration file..." + + # Try to detect default SSH key + SSH_KEY="" + if [ -f ~/.ssh/id_rsa ]; then + SSH_KEY="$HOME/.ssh/id_rsa" + elif [ -f ~/.ssh/id_ed25519 ]; then + SSH_KEY="$HOME/.ssh/id_ed25519" + fi + + # Get current user + CURRENT_USER=$(whoami) + + # Try to detect local network + LOCAL_NET=$(ip route | grep -oP 'src \K[\d.]+' | head -1) + if [ -n "$LOCAL_NET" ]; then + # Convert to /24 network + NET_PREFIX=$(echo $LOCAL_NET | cut -d. -f1-3) + LOCAL_NET="${NET_PREFIX}.0/24" + else + LOCAL_NET="192.168.1.0/24" + fi + + cat > config.json << EOF +{ + "ssh_user": "$CURRENT_USER", + "ssh_key_path": "$SSH_KEY", + "timeout": 2, + "additional_networks": [ + "$LOCAL_NET" + ], + "special_devices": { + }, + "scan_options": { + "max_workers": 10, + "ping_timeout": 2, + "port_scan_timeout": 1 + } +} +EOF + + echo "✓ Created config.json" + echo " Local network detected: $LOCAL_NET" + [ -n "$SSH_KEY" ] && echo " SSH key detected: $SSH_KEY" + echo "" + echo " Please edit config.json to customize for your network!" + echo "" +else + echo "✓ config.json already exists" +fi + +# Ask what to do +echo "" +echo "What would you like to do?" +echo "" +echo "1) Run a quick scan (current network only)" +echo "2) Run a full scan with pfSense integration" +echo "3) Scan specific pfSense device" +echo "4) Show help" +echo "5) Exit" +echo "" +read -p "Choose an option (1-6): " choice + +case $choice in + 1) + echo "" + echo "🔍 Running quick network scan..." + ./network_scanner.py -o quick_scan.json -v + echo "" + echo "✓ Done! Results saved to: quick_scan.json" + ;; + 2) + echo "" + echo "🔍 Running full integrated scan..." + ./integrated_scanner.py -o full_scan.json -v + echo "" + echo "✓ Done! Results saved to: full_scan.json" + ;; + 3) + echo "" + read -p "Enter pfSense IP address: " pfsense_ip + echo "🔍 Scanning pfSense at $pfsense_ip..." + ./pfsense_scanner.py "$pfsense_ip" -o "pfsense_${pfsense_ip}.json" + echo "" + echo "✓ Done! Results saved to: pfsense_${pfsense_ip}.json" + ;; + 4) + echo "" + cat << 'HELP' +Network Scanner - Help +====================== + +Available Scripts: +----------------- + +1. network_scanner.py + Basic network scanner that discovers devices and gathers info + Usage: ./network_scanner.py [-c config.json] [-o output.json] [-v] + +2. pfsense_scanner.py + Specialized scanner for pfSense firewalls + Usage: ./pfsense_scanner.py [-u user] [-k keyfile] [-o output.json] + +3. integrated_scanner.py + Complete scanner with pfSense integration + Usage: ./integrated_scanner.py [-c config.json] [-o output.json] [-v] + +Configuration: +------------- +Edit config.json to customize: +- SSH credentials +- Network ranges to scan +- Special device definitions +- Scan timeouts + +Examples: +-------- +# Quick scan of current network +./network_scanner.py -v + +# Full scan with diagram +./integrated_scanner.py --generate-svg + +# Scan pfSense +./pfsense_scanner.py 192.168.1.1 -u root -k ~/.ssh/id_rsa + +For more information, see README.md +HELP + ;; + 5) + echo "Goodbye!" + exit 0 + ;; + *) + echo "Invalid option" + exit 1 + ;; +esac + +echo "" +echo "================================" +echo "Thanks for using Network Scanner!" +echo "================================" diff --git a/src/comprehensive_mapper.py b/src/comprehensive_mapper.py deleted file mode 100755 index 0e4917f..0000000 --- a/src/comprehensive_mapper.py +++ /dev/null @@ -1,355 +0,0 @@ -#!/usr/bin/env python3 -""" -Comprehensive Network Diagram Generator -Combines network scanning with pfSense XML parsing for complete network topology -""" - -import json -import argparse -import logging -from pathlib import Path -from typing import Dict, List, Any -from network_scanner import NetworkScanner -from pfsense_xml_parser import PfSenseXMLParser -from svg_generator import NetworkDiagramGenerator - -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') -logger = logging.getLogger(__name__) - - -class ComprehensiveNetworkMapper: - """Complete network mapping solution combining multiple data sources""" - - def __init__(self, config: Dict): - self.config = config - self.network_data = {} - self.pfsense_data = {} - self.combined_data = { - 'scan_timestamp': None, - 'data_sources': [], - 'segments': [], - 'pfsense_firewalls': [], - 'wireguard_networks': [], - 'openvpn_networks': [], - 'routing_table': [], - 'dhcp_leases': [], - 'static_mappings': [] - } - - def load_network_scan(self, scan_file: str): - """Load network scan data""" - logger.info(f"Loading network scan data from {scan_file}") - try: - with open(scan_file, 'r') as f: - self.network_data = json.load(f) - self.combined_data['data_sources'].append('network_scan') - logger.info("Network scan data loaded successfully") - except Exception as e: - logger.error(f"Error loading network scan: {e}") - - def load_pfsense_configs(self, xml_files: List[str]): - """Load and parse pfSense XML configuration files""" - for xml_file in xml_files: - if Path(xml_file).exists(): - logger.info(f"Parsing pfSense config: {xml_file}") - parser = PfSenseXMLParser(xml_file) - data = parser.parse_all() - - if data: - hostname = data.get('hostname', f'pfsense_{len(self.pfsense_data)}') - self.pfsense_data[hostname] = data - self.combined_data['data_sources'].append(f'pfsense_{hostname}') - self.combined_data['pfsense_firewalls'].append(data) - logger.info(f"pfSense config {hostname} loaded successfully") - else: - logger.error(f"Failed to parse pfSense config: {xml_file}") - else: - logger.warning(f"pfSense config file not found: {xml_file}") - - def merge_network_data(self): - """Merge all data sources into comprehensive network map""" - logger.info("Merging network data sources...") - - # Start with network scan data if available - if self.network_data: - self.combined_data.update({ - 'scan_timestamp': self.network_data.get('scan_timestamp'), - 'segments': self.network_data.get('segments', []) - }) - - # Process pfSense data - self._process_pfsense_data() - - # Extract additional network information - self._extract_network_topology() - - logger.info("Network data merging complete") - - def _process_pfsense_data(self): - """Process and integrate pfSense configuration data""" - for hostname, pfsense in self.pfsense_data.items(): - logger.info(f"Processing pfSense data for {hostname}") - - # Add interfaces as network segments - interfaces = pfsense.get('interfaces', {}) - for iface_name, iface_data in interfaces.items(): - if iface_data.get('ipaddr') and iface_data.get('ipaddr') != 'dhcp': - try: - # Create network segment from interface - ip = iface_data['ipaddr'] - subnet = iface_data.get('subnet', '24') - network_cidr = f"{ip.rsplit('.', 1)[0]}.0/{subnet}" - - segment = { - 'name': f"{hostname}_{iface_name}", - 'cidr': network_cidr, - 'gateway': iface_data.get('gateway', ip), - 'interface': iface_data.get('interface'), - 'pfsense_host': hostname, - 'is_vpn': iface_data.get('type') in ['wireguard', 'openvpn'], - 'devices': [] - } - - # Add gateway device - gateway_device = { - 'ip': ip, - 'hostname': hostname, - 'device_type': 'firewall', - 'os_type': 'pfSense', - 'interface': iface_name, - 'pfsense_config': True - } - segment['devices'].append(gateway_device) - - self.combined_data['segments'].append(segment) - - # Track VPN networks - if iface_data.get('type') == 'wireguard': - self.combined_data['wireguard_networks'].append({ - 'network': network_cidr, - 'interface': iface_data.get('interface'), - 'pfsense': hostname, - 'peers': pfsense.get('wireguard', {}).get('peers', []) - }) - elif iface_data.get('type') == 'openvpn': - self.combined_data['openvpn_networks'].append({ - 'network': network_cidr, - 'interface': iface_data.get('interface'), - 'pfsense': hostname - }) - - except Exception as e: - logger.debug(f"Error processing interface {iface_name}: {e}") - - # Add static routes - static_routes = pfsense.get('static_routes', []) - for route in static_routes: - route_info = { - 'network': route.get('network'), - 'gateway': route.get('gateway'), - 'description': route.get('description'), - 'pfsense': hostname - } - self.combined_data['routing_table'].append(route_info) - - # Add DHCP information - dhcp_config = pfsense.get('dhcp', {}) - for iface_name, dhcp_data in dhcp_config.items(): - if dhcp_data.get('static_mappings'): - for mapping in dhcp_data['static_mappings']: - mapping_info = { - 'ip': mapping.get('ipaddr'), - 'mac': mapping.get('mac'), - 'hostname': mapping.get('hostname'), - 'description': mapping.get('description'), - 'interface': iface_name, - 'pfsense': hostname - } - self.combined_data['static_mappings'].append(mapping_info) - - def _extract_network_topology(self): - """Extract network topology information from all sources""" - logger.info("Extracting network topology...") - - # Process WireGuard peer information - for wg_net in self.combined_data['wireguard_networks']: - for peer in wg_net.get('peers', []): - for allowed_ip in peer.get('allowed_ips', []): - # Create segment for remote networks - if allowed_ip.get('mask') and allowed_ip['mask'] != '32': - remote_segment = { - 'name': f"WG_Remote_{allowed_ip['address']}_{allowed_ip['mask']}", - 'cidr': f"{allowed_ip['address']}/{allowed_ip['mask']}", - 'gateway': wg_net.get('network', '').split('/')[0].rsplit('.', 1)[0] + '.1', - 'is_vpn': True, - 'vpn_type': 'wireguard', - 'pfsense_host': wg_net.get('pfsense'), - 'devices': [] - } - self.combined_data['segments'].append(remote_segment) - - def generate_svg_diagram(self, output_file: str): - """Generate SVG network diagram""" - logger.info(f"Generating SVG diagram: {output_file}") - - # Use the existing SVG generator - generator = NetworkDiagramGenerator(self.combined_data) - generator.generate_svg(output_file) - - logger.info(f"SVG diagram generated: {output_file}") - - def export_comprehensive_json(self, output_file: str): - """Export comprehensive network data""" - logger.info(f"Exporting comprehensive data to {output_file}") - - with open(output_file, 'w', encoding='utf-8') as f: - json.dump(self.combined_data, f, indent=2, ensure_ascii=False) - - logger.info(f"Comprehensive network data exported to {output_file}") - - def print_summary(self): - """Print comprehensive network summary""" - print("\n" + "="*80) - print("COMPREHENSIVE NETWORK MAPPING SUMMARY") - print("="*80) - - print(f"\nData Sources: {', '.join(self.combined_data['data_sources'])}") - print(f"Network Segments: {len(self.combined_data['segments'])}") - print(f"pfSense Firewalls: {len(self.combined_data['pfsense_firewalls'])}") - print(f"WireGuard Networks: {len(self.combined_data['wireguard_networks'])}") - print(f"OpenVPN Networks: {len(self.combined_data['openvpn_networks'])}") - print(f"Static Routes: {len(self.combined_data['routing_table'])}") - print(f"DHCP Static Mappings: {len(self.combined_data['static_mappings'])}") - - # Network segments summary - print(f"\n{'='*80}") - print("NETWORK SEGMENTS") - print(f"{'='*80}") - - for segment in self.combined_data['segments']: - vpn_indicator = " (VPN)" if segment.get('is_vpn') else "" - pfsense_indicator = f" [{segment.get('pfsense_host')}]" if segment.get('pfsense_host') else "" - print(f"\n📡 {segment['name']}{vpn_indicator}{pfsense_indicator}") - print(f" CIDR: {segment['cidr']}") - print(f" Gateway: {segment.get('gateway', 'N/A')}") - print(f" Devices: {len(segment.get('devices', []))}") - - # Show devices - for device in segment.get('devices', [])[:5]: # Show first 5 - print(f" • {device.get('ip', 'N/A')} - {device.get('hostname', 'N/A')} ({device.get('device_type', 'N/A')})") - - if len(segment.get('devices', [])) > 5: - print(f" ... and {len(segment.get('devices', [])) - 5} more devices") - - # pfSense summary - if self.combined_data['pfsense_firewalls']: - print(f"\n{'='*80}") - print("PFSENSE FIREWALLS") - print(f"{'='*80}") - - for pfsense in self.combined_data['pfsense_firewalls']: - hostname = pfsense.get('hostname', 'Unknown') - print(f"\n🛡️ {hostname}") - print(f" Interfaces: {len(pfsense.get('interfaces', {}))}") - print(f" Static Routes: {len(pfsense.get('static_routes', []))}") - - wg_config = pfsense.get('wireguard', {}) - if wg_config.get('enabled'): - print(f" WireGuard: {len(wg_config.get('tunnels', []))} tunnels, {len(wg_config.get('peers', []))} peers") - - dhcp_config = pfsense.get('dhcp', {}) - total_mappings = sum(len(dhcp.get('static_mappings', [])) for dhcp in dhcp_config.values()) - print(f" DHCP Static Mappings: {total_mappings}") - - # VPN Networks - if self.combined_data['wireguard_networks'] or self.combined_data['openvpn_networks']: - print(f"\n{'='*80}") - print("VPN NETWORKS") - print(f"{'='*80}") - - for wg_net in self.combined_data['wireguard_networks']: - print(f"\n🔐 WireGuard: {wg_net['network']} via {wg_net['pfsense']}") - print(f" Interface: {wg_net['interface']}") - print(f" Peers: {len(wg_net.get('peers', []))}") - - for ovpn_net in self.combined_data['openvpn_networks']: - print(f"\n🔐 OpenVPN: {ovpn_net['network']} via {ovpn_net['pfsense']}") - print(f" Interface: {ovpn_net['interface']}") - - print(f"\n{'='*80}") - print("NETWORK TOPOLOGY ANALYSIS COMPLETE") - print(f"{'='*80}") - - -def main(): - """Command line interface""" - parser = argparse.ArgumentParser( - description='Comprehensive Network Diagram Generator with pfSense Integration' - ) - parser.add_argument('-c', '--config', default='config.json', - help='Network scanner configuration file') - parser.add_argument('-s', '--scan-data', help='Existing network scan JSON file') - parser.add_argument('-p', '--pfsense-xml', nargs='+', - help='pfSense XML configuration files') - parser.add_argument('-o', '--output', default='comprehensive_network.json', - help='Output comprehensive JSON file') - parser.add_argument('--svg', help='Generate SVG diagram') - parser.add_argument('-v', '--verbose', action='store_true', - help='Verbose output') - - args = parser.parse_args() - - if args.verbose: - logging.getLogger().setLevel(logging.DEBUG) - - # Load configuration - config = {} - if Path(args.config).exists(): - try: - with open(args.config, 'r') as f: - config = json.load(f) - except Exception as e: - logger.warning(f"Could not load config {args.config}: {e}") - - # Initialize comprehensive mapper - mapper = ComprehensiveNetworkMapper(config) - - # Load network scan data if provided - if args.scan_data and Path(args.scan_data).exists(): - mapper.load_network_scan(args.scan_data) - else: - logger.info("No network scan data provided - will use pfSense data only") - - # Load pfSense configurations - pfsense_files = args.pfsense_xml or [] - # Auto-detect pfSense XML files if none specified - if not pfsense_files: - xml_files = list(Path('.').glob('config-*.xml')) - if xml_files: - pfsense_files = [str(f) for f in xml_files] - logger.info(f"Auto-detected pfSense XML files: {pfsense_files}") - - if pfsense_files: - mapper.load_pfsense_configs(pfsense_files) - else: - logger.warning("No pfSense XML files found") - - # Merge all data - mapper.merge_network_data() - - # Print summary - mapper.print_summary() - - # Export comprehensive data - mapper.export_comprehensive_json(args.output) - print(f"\n✅ Comprehensive network data saved to: {args.output}") - - # Generate SVG if requested - if args.svg: - svg_file = args.svg - mapper.generate_svg_diagram(svg_file) - print(f"✅ SVG network diagram generated: {svg_file}") - - -if __name__ == '__main__': - main() diff --git a/src/integrated_scanner.py b/src/integrated_scanner.py index c58e116..fa46758 100755 --- a/src/integrated_scanner.py +++ b/src/integrated_scanner.py @@ -447,8 +447,6 @@ def main(): help='Output JSON file (default: network_scan.json)') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') - parser.add_argument('--generate-svg', action='store_true', - help='Automatically generate SVG diagram after scan') args = parser.parse_args() @@ -474,23 +472,15 @@ def main(): # Export results scanner.export_json(args.output) + # Save failed SSH hosts if any + if scanner.base_scanner.failed_ssh_hosts: + failed_ssh_file = args.output.replace('.json', '_failed_ssh.json') + scanner.base_scanner.save_failed_ssh_hosts(failed_ssh_file) + print(f"\n⚠️ {len(scanner.base_scanner.failed_ssh_hosts)} hosts have SSH port open but failed authentication.") + print(f" See {failed_ssh_file} for details.") + print(f"\n✓ Scan complete! Results saved to {args.output}") - # Generate SVG if requested - if args.generate_svg: - try: - from svg_generator import NetworkDiagramGenerator - - with open(args.output, 'r') as f: - scan_data = json.load(f) - - svg_file = args.output.replace('.json', '.svg') - generator = NetworkDiagramGenerator(scan_data) - generator.generate_svg(svg_file) - - print(f"✓ SVG diagram generated: {svg_file}") - except Exception as e: - logger.error(f"Error generating SVG: {e}") if __name__ == '__main__': diff --git a/src/network_scanner.py b/src/network_scanner.py index 6466339..77e5781 100755 --- a/src/network_scanner.py +++ b/src/network_scanner.py @@ -16,6 +16,7 @@ from typing import Dict, List, Optional, Tuple from dataclasses import dataclass, asdict, field from concurrent.futures import ThreadPoolExecutor, as_completed import logging +from datetime import datetime # Configure logging logging.basicConfig( @@ -40,6 +41,7 @@ class Device: services: List[str] = field(default_factory=list) routes: List[Dict] = field(default_factory=list) interfaces: List[Dict] = field(default_factory=list) + ssh_info: Optional[Dict] = None @dataclass @@ -62,6 +64,7 @@ class NetworkScanner: self.ssh_user = config.get('ssh_user', 'root') self.ssh_key = config.get('ssh_key_path') self.timeout = config.get('timeout', 2) + self.failed_ssh_hosts: List[Dict] = [] def discover_networks(self) -> List[str]: """Discover all network segments from local routing table""" @@ -198,6 +201,13 @@ class NetworkScanner: if device.ssh_accessible: # Gather detailed info via SSH self._gather_ssh_info(device) + else: + # Track hosts with SSH port open but failed SSH connection + self.failed_ssh_hosts.append({ + 'ip': ip, + 'hostname': device.hostname, + 'reason': 'SSH port open but authentication failed or connection refused' + }) # Identify device type based on ports and services device.device_type = self._identify_device_type(device) @@ -290,17 +300,41 @@ class NetworkScanner: """Gather detailed information via SSH""" logger.info(f"Gathering SSH info from {device.ip}") + ssh_info = {} + # Get OS info - device.os_type, device.os_version = self._get_os_info(device.ip) + os_type, os_version = self._get_os_info(device.ip) + if os_type: + ssh_info['os_type'] = os_type + device.os_type = os_type + if os_version: + ssh_info['os_version'] = os_version + device.os_version = os_version # Get network interfaces - device.interfaces = self._get_interfaces(device.ip) + interfaces = self._get_interfaces(device.ip) + if interfaces: + ssh_info['interfaces'] = interfaces + device.interfaces = interfaces # Get routing table - device.routes = self._get_routes(device.ip) + routes = self._get_routes(device.ip) + if routes: + ssh_info['routes'] = routes + device.routes = routes # Get running services - device.services = self._get_services(device.ip) + services = self._get_services(device.ip) + if services: + ssh_info['services'] = services + device.services = services + + # Get system info + system_info = self._get_system_info(device.ip) + if system_info: + ssh_info['system'] = system_info + + device.ssh_info = ssh_info def _ssh_exec(self, ip: str, command: str) -> Optional[str]: """Execute command via SSH""" @@ -409,6 +443,40 @@ class NetworkScanner: return services[:20] # Limit to top 20 + def _get_system_info(self, ip: str) -> Optional[Dict]: + """Get basic system information""" + system_info = {} + + # Get hostname + hostname = self._ssh_exec(ip, 'hostname') + if hostname: + system_info['hostname'] = hostname.strip() + + # Get uptime + uptime = self._ssh_exec(ip, 'uptime -p 2>/dev/null || uptime') + if uptime: + system_info['uptime'] = uptime.strip() + + # Get CPU info + cpu_info = self._ssh_exec(ip, 'nproc && cat /proc/cpuinfo | grep "model name" | head -1') + if cpu_info: + lines = cpu_info.strip().split('\n') + if len(lines) >= 2: + system_info['cpu_cores'] = int(lines[0]) + system_info['cpu_model'] = lines[1].split(':')[1].strip() + + # Get memory info + mem_info = self._ssh_exec(ip, 'free -h | grep Mem') + if mem_info: + system_info['memory'] = mem_info.strip() + + # Get disk info + disk_info = self._ssh_exec(ip, 'df -h / | tail -1') + if disk_info: + system_info['disk'] = disk_info.strip() + + return system_info if system_info else None + def _identify_device_type(self, device: Device) -> str: """Identify device type based on available info""" if device.routes and len(device.routes) > 5: @@ -505,6 +573,17 @@ class NetworkScanner: logger.info(f"Exported results to {filename}") + def save_failed_ssh_hosts(self, filename: str): + """Save list of hosts with SSH port open but failed authentication""" + if self.failed_ssh_hosts: + with open(filename, 'w') as f: + json.dump({ + 'failed_ssh_hosts': self.failed_ssh_hosts, + 'total_failed': len(self.failed_ssh_hosts), + 'scan_timestamp': str(datetime.now()) + }, f, indent=2) + logger.info(f"Saved {len(self.failed_ssh_hosts)} failed SSH hosts to {filename}") + def print_summary(self): """Print a human-readable summary""" print("\n" + "="*80) @@ -567,6 +646,7 @@ def main(): # Export results from datetime import datetime scanner.export_json(args.output) + scanner.save_failed_ssh_hosts('failed_ssh_hosts.json') print(f"\n✓ Scan complete! Results saved to {args.output}") diff --git a/src/server_info_collector.py b/src/server_info_collector.py new file mode 100644 index 0000000..640868c --- /dev/null +++ b/src/server_info_collector.py @@ -0,0 +1,385 @@ +#!/usr/bin/env python3 +""" +Server Information Collector + +This module collects detailed information about servers and VMs from hypervisors +via SSH. It connects to specified hypervisors and gathers system information, +VM details, resource usage, and network configurations. +""" + +import json +import logging +import subprocess +import sys +from dataclasses import dataclass, asdict +from typing import Dict, List, Optional, Any +from concurrent.futures import ThreadPoolExecutor, as_completed + +@dataclass +class VMInfo: + """Information about a virtual machine.""" + vmid: str + name: str + status: str + cpu: float + memory_used: int + memory_total: int + disk_used: int + disk_total: int + uptime: str + ip_addresses: List[str] + vm_type: str = 'vm' # 'vm' or 'container' + +@dataclass +class ServerInfo: + """Information about a physical server/hypervisor.""" + hostname: str + os: str + kernel: str + uptime: str + cpu_model: str + cpu_cores: int + memory_total: int + memory_free: int + disk_total: int + disk_free: int + load_average: str + network_interfaces: Dict[str, str] + vms: List[VMInfo] + containers: Optional[List[VMInfo]] = None + +@dataclass +class HypervisorConfig: + """Configuration for a hypervisor connection.""" + host: str + port: int + user: str + +class ServerInfoCollector: + """Collects server and VM information from hypervisors via SSH.""" + + def __init__(self, config: Dict[str, Any]): + self.config = config + self.logger = logging.getLogger(__name__) + self.hypervisors = [ + HypervisorConfig(**hv) for hv in config.get('hypervisors', []) + ] + self.ssh_user = config.get('ssh_user', 'root') + self.ssh_key_path = config.get('ssh_key_path') + self.timeout = config.get('timeout', 10) + + def run_ssh_command(self, host: str, port: int, user: str, command: str) -> Optional[str]: + """Run a command via SSH on a remote host.""" + ssh_cmd = [ + 'ssh', + '-o', 'ConnectTimeout=5', + '-o', 'StrictHostKeyChecking=no', + '-o', 'UserKnownHostsFile=/dev/null', + '-p', str(port), + f'{user}@{host}', + command + ] + + try: + result = subprocess.run( + ssh_cmd, + capture_output=True, + text=True, + timeout=self.timeout + ) + if result.returncode == 0: + return result.stdout.strip() + else: + self.logger.warning(f"SSH command failed on {host}:{port}: {result.stderr}") + return None + except subprocess.TimeoutExpired: + self.logger.warning(f"SSH command timed out on {host}:{port}") + return None + except Exception as e: + self.logger.error(f"SSH error on {host}:{port}: {e}") + return None + + def detect_hypervisor_type(self, host: str, port: int, user: str) -> str: + """Detect the type of hypervisor (Proxmox, VMware, etc.).""" + # Check for Proxmox + if self.run_ssh_command(host, port, user, 'which pvesh'): + return 'proxmox' + + # Check for VMware + if self.run_ssh_command(host, port, user, 'which vmware'): + return 'vmware' + + # Check for KVM/libvirt + if self.run_ssh_command(host, port, user, 'which virsh'): + return 'kvm' + + return 'unknown' + + def collect_proxmox_info(self, host: str, port: int, user: str) -> ServerInfo: + """Collect information from a Proxmox hypervisor.""" + hostname = self.run_ssh_command(host, port, user, 'hostname') or host + + # System info + os_info = self.run_ssh_command(host, port, user, 'cat /etc/os-release | grep PRETTY_NAME | cut -d\'"\' -f2') or 'Unknown' + kernel = self.run_ssh_command(host, port, user, 'uname -r') or 'Unknown' + uptime = self.run_ssh_command(host, port, user, 'uptime -p') or 'Unknown' + + # CPU info + cpu_model = self.run_ssh_command(host, port, user, 'cat /proc/cpuinfo | grep "model name" | head -1 | cut -d: -f2 | xargs') or 'Unknown' + cpu_cores = int(self.run_ssh_command(host, port, user, 'nproc') or '0') + + # Memory info + mem_info = self.run_ssh_command(host, port, user, 'free -b | grep Mem') + if mem_info: + parts = mem_info.split() + memory_total = int(parts[1]) + memory_free = int(parts[3]) + else: + memory_total = memory_free = 0 + + # Disk info + disk_info = self.run_ssh_command(host, port, user, 'df -B1 / | tail -1') + if disk_info: + parts = disk_info.split() + disk_total = int(parts[1]) + disk_free = int(parts[3]) + else: + disk_total = disk_free = 0 + + # Load average + load_average = self.run_ssh_command(host, port, user, 'uptime | cut -d: -f5') or 'Unknown' + + # Network interfaces + network_interfaces = {} + net_info = self.run_ssh_command(host, port, user, 'ip -o addr show | grep -v lo') + if net_info: + for line in net_info.split('\n'): + if line.strip(): + parts = line.split() + if len(parts) >= 4: + iface = parts[1] + ip = parts[3].split('/')[0] + network_interfaces[iface] = ip + + # VMs + vms = [] + vm_list = self.run_ssh_command(host, port, user, 'qm list') + if vm_list: + for line in vm_list.split('\n')[1:]: # Skip header + if line.strip(): + parts = line.split() + if len(parts) >= 3: + vmid = parts[0] + name = parts[1] + status = parts[2] + + # Get VM resource usage + cpu = memory_used = memory_total = disk_used = disk_total = 0 + uptime = 'Unknown' + + # Try to get current status + status_output = self.run_ssh_command(host, port, user, f'qm status {vmid} --verbose') + if status_output: + # Parse status output for resource usage using grep-like extraction + if 'mem:' in status_output: + try: + mem_line = [line for line in status_output.split('\n') if line.strip().startswith('mem:')][0] + memory_used = int(mem_line.split(':')[1].strip()) + except: + pass + + if 'maxmem:' in status_output: + try: + maxmem_line = [line for line in status_output.split('\n') if line.strip().startswith('maxmem:')][0] + memory_total = int(maxmem_line.split(':')[1].strip()) + except: + pass + + if 'maxdisk:' in status_output: + try: + maxdisk_line = [line for line in status_output.split('\n') if line.strip().startswith('maxdisk:')][0] + disk_total = int(maxdisk_line.split(':')[1].strip()) + except: + pass + + if 'uptime:' in status_output: + try: + uptime_line = [line for line in status_output.split('\n') if line.strip().startswith('uptime:')][0] + uptime = uptime_line.split(':')[1].strip() + except: + pass + + # Get VM config for IP addresses + vm_config = self.run_ssh_command(host, port, user, f'qm config {vmid}') + ip_addresses = [] + if vm_config: + for config_line in vm_config.split('\n'): + if 'ip=' in config_line: + ip = config_line.split('=')[1].strip() + ip_addresses.append(ip) + + vm_info = VMInfo( + vmid=vmid, + name=name, + status=status, + cpu=cpu, + memory_used=memory_used, + memory_total=memory_total, + disk_used=disk_used, + disk_total=disk_total, + uptime=uptime, + ip_addresses=ip_addresses, + vm_type='vm' + ) + vms.append(vm_info) + + # Containers + containers = [] + ct_list = self.run_ssh_command(host, port, user, 'pct list') + if ct_list: + for line in ct_list.split('\n')[1:]: # Skip header + if line.strip(): + parts = line.split() + if len(parts) >= 3: + vmid = parts[0] + status = parts[1] + # Handle variable number of columns (Lock column may be empty) + if len(parts) == 4: + name = parts[3] + else: + name = parts[2] + + # Get container resource usage + cpu = memory_used = memory_total = disk_used = disk_total = 0 + uptime = 'Unknown' + + # Try to get current status + status_output = self.run_ssh_command(host, port, user, f'pct status {vmid}') + if status_output: + # Parse status output for resource usage + lines = status_output.split('\n') + for line in lines: + line = line.strip() + if ':' in line: + key, value = line.split(':', 1) + key = key.strip() + value = value.strip() + + if key == 'status': + # Status is already from pct list + pass + # Container resource info might be limited + # Could add more parsing here if available + + # Get container config for IP addresses + ct_config = self.run_ssh_command(host, port, user, f'pct config {vmid}') + ip_addresses = [] + if ct_config: + for config_line in ct_config.split('\n'): + if 'ip=' in config_line: + ip = config_line.split('=')[1].strip() + ip_addresses.append(ip) + + container_info = VMInfo( + vmid=vmid, + name=name, + status=status, + cpu=cpu, + memory_used=memory_used, + memory_total=memory_total, + disk_used=disk_used, + disk_total=disk_total, + uptime=uptime, + ip_addresses=ip_addresses, + vm_type='container' + ) + containers.append(container_info) + + return ServerInfo( + hostname=hostname, + os=os_info, + kernel=kernel, + uptime=uptime, + cpu_model=cpu_model, + cpu_cores=cpu_cores, + memory_total=memory_total, + memory_free=memory_free, + disk_total=disk_total, + disk_free=disk_free, + load_average=load_average, + network_interfaces=network_interfaces, + vms=vms, + containers=containers + ) + + def collect_server_info(self, hypervisor: HypervisorConfig) -> Optional[ServerInfo]: + """Collect information from a single hypervisor.""" + self.logger.info(f"Collecting info from {hypervisor.host}:{hypervisor.port}") + + hv_type = self.detect_hypervisor_type(hypervisor.host, hypervisor.port, hypervisor.user) + + if hv_type == 'proxmox': + return self.collect_proxmox_info(hypervisor.host, hypervisor.port, hypervisor.user) + else: + self.logger.warning(f"Unsupported hypervisor type: {hv_type} on {hypervisor.host}") + return None + + def collect_all_server_info(self) -> Dict[str, ServerInfo]: + """Collect information from all configured hypervisors.""" + server_info = {} + + with ThreadPoolExecutor(max_workers=len(self.hypervisors)) as executor: + future_to_hv = { + executor.submit(self.collect_server_info, hv): hv + for hv in self.hypervisors + } + + for future in as_completed(future_to_hv): + hv = future_to_hv[future] + try: + info = future.result() + if info: + server_info[f"{hv.host}:{hv.port}"] = info + except Exception as e: + self.logger.error(f"Failed to collect info from {hv.host}:{hv.port}: {e}") + + return server_info + + def save_to_file(self, server_info: Dict[str, ServerInfo], filename: str): + """Save collected server information to a JSON file.""" + data = {host: asdict(info) for host, info in server_info.items()} + + with open(filename, 'w') as f: + json.dump(data, f, indent=2) + + self.logger.info(f"Server information saved to {filename}") + +def main(): + """Main function for standalone execution.""" + import argparse + + parser = argparse.ArgumentParser(description='Collect server information from hypervisors') + parser.add_argument('-o', '--output', default='server_details.json', help='Output JSON file') + args = parser.parse_args() + + logging.basicConfig(level=logging.INFO) + + # Load config + try: + with open('config.json', 'r') as f: + config = json.load(f) + except FileNotFoundError: + print("config.json not found. Please create it from config.json.example") + sys.exit(1) + + collector = ServerInfoCollector(config) + server_info = collector.collect_all_server_info() + + if server_info: + collector.save_to_file(server_info, args.output) + print(f"Collected information from {len(server_info)} hypervisors") + else: + print("No server information collected") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/svg_generator.py b/src/svg_generator.py deleted file mode 100755 index 2d8596a..0000000 --- a/src/svg_generator.py +++ /dev/null @@ -1,353 +0,0 @@ -#!/usr/bin/env python3 -""" -SVG Network Diagram Generator -Converts network scan results into an SVG network diagram -""" - -import json -import math -from typing import Dict, List, Tuple -from xml.etree.ElementTree import Element, SubElement, tostring -from xml.dom import minidom - - -class NetworkDiagramGenerator: - """Generate SVG diagrams from network scan data""" - - # Device type styling - DEVICE_STYLES = { - 'router': {'color': '#FF6B6B', 'icon': '🔀', 'shape': 'rect'}, - 'firewall': {'color': '#FF0000', 'icon': '🛡️', 'shape': 'rect'}, - 'switch': {'color': '#4ECDC4', 'icon': '🔌', 'shape': 'rect'}, - 'server': {'color': '#45B7D1', 'icon': '🖥️', 'shape': 'rect'}, - 'linux_server': {'color': '#45B7D1', 'icon': '🐧', 'shape': 'rect'}, - 'windows_client': {'color': '#95E1D3', 'icon': '💻', 'shape': 'rect'}, - 'client': {'color': '#A8E6CF', 'icon': '💻', 'shape': 'rect'}, - 'default': {'color': '#CCCCCC', 'icon': '❓', 'shape': 'rect'} - } - - # Network segment colors - SEGMENT_COLORS = [ - '#E3F2FD', '#F3E5F5', '#E8F5E9', '#FFF3E0', '#FCE4EC', - '#E0F2F1', '#F1F8E9', '#FFF9C4', '#FFE0B2', '#F8BBD0' - ] - - def __init__(self, scan_data: Dict): - self.scan_data = scan_data - self.width = 1600 - self.height = 1200 - self.margin = 50 - self.device_width = 120 - self.device_height = 80 - self.segment_spacing = 200 - - def generate_svg(self, output_file: str): - """Generate SVG diagram""" - svg = Element('svg', { - 'width': str(self.width), - 'height': str(self.height), - 'xmlns': 'http://www.w3.org/2000/svg', - 'version': '1.1' - }) - - # Add styles - self._add_styles(svg) - - # Add title - self._add_title(svg) - - # Calculate layout - segments = self.scan_data.get('segments', []) - layout = self._calculate_layout(segments) - - # Draw network segments - for i, (segment, positions) in enumerate(zip(segments, layout)): - self._draw_segment(svg, segment, positions, i) - - # Draw connections - self._draw_connections(svg, segments, layout) - - # Add legend - self._add_legend(svg) - - # Save to file - self._save_svg(svg, output_file) - - def _add_styles(self, svg: Element): - """Add CSS styles""" - style = SubElement(svg, 'style') - style.text = """ - .device-box { stroke: #333; stroke-width: 2; } - .device-label { font-family: Arial, sans-serif; font-size: 12px; fill: #000; } - .device-icon { font-size: 24px; } - .segment-box { stroke: #666; stroke-width: 2; fill-opacity: 0.1; } - .segment-label { font-family: Arial, sans-serif; font-size: 14px; font-weight: bold; fill: #333; } - .connection { stroke: #999; stroke-width: 2; stroke-dasharray: 5,5; } - .title { font-family: Arial, sans-serif; font-size: 24px; font-weight: bold; fill: #333; } - .info-text { font-family: Arial, sans-serif; font-size: 10px; fill: #666; } - .legend-text { font-family: Arial, sans-serif; font-size: 11px; fill: #333; } - """ - - def _add_title(self, svg: Element): - """Add diagram title""" - title = SubElement(svg, 'text', { - 'x': str(self.width // 2), - 'y': '30', - 'text-anchor': 'middle', - 'class': 'title' - }) - title.text = 'Network Topology Diagram' - - def _calculate_layout(self, segments: List[Dict]) -> List[List[Tuple]]: - """Calculate positions for all devices""" - layout = [] - - # Arrange segments horizontally - segment_width = (self.width - 2 * self.margin) / len(segments) if segments else self.width - - for seg_idx, segment in enumerate(segments): - devices = segment.get('devices', []) - positions = [] - - # Calculate segment area - seg_x = self.margin + seg_idx * segment_width - seg_y = self.margin + 60 - seg_w = segment_width - 20 - seg_h = self.height - seg_y - self.margin - - # Arrange devices in a grid within segment - cols = math.ceil(math.sqrt(len(devices))) - rows = math.ceil(len(devices) / cols) if cols > 0 else 1 - - device_spacing_x = seg_w / (cols + 1) if cols > 0 else seg_w / 2 - device_spacing_y = seg_h / (rows + 1) if rows > 0 else seg_h / 2 - - for dev_idx, device in enumerate(devices): - col = dev_idx % cols - row = dev_idx // cols - - x = seg_x + (col + 1) * device_spacing_x - y = seg_y + (row + 1) * device_spacing_y - - positions.append((x, y, device)) - - layout.append(positions) - - return layout - - def _draw_segment(self, svg: Element, segment: Dict, positions: List[Tuple], seg_idx: int): - """Draw a network segment and its devices""" - if not positions: - return - - # Calculate segment bounds - xs = [pos[0] for pos in positions] - ys = [pos[1] for pos in positions] - - min_x = min(xs) - self.device_width - max_x = max(xs) + self.device_width - min_y = min(ys) - self.device_height - max_y = max(ys) + self.device_height - - # Draw segment background - color = self.SEGMENT_COLORS[seg_idx % len(self.SEGMENT_COLORS)] - SubElement(svg, 'rect', { - 'x': str(min_x - 20), - 'y': str(min_y - 40), - 'width': str(max_x - min_x + 40), - 'height': str(max_y - min_y + 60), - 'fill': color, - 'class': 'segment-box', - 'rx': '10' - }) - - # Draw segment label - label = SubElement(svg, 'text', { - 'x': str(min_x), - 'y': str(min_y - 20), - 'class': 'segment-label' - }) - vpn_indicator = ' (VPN)' if segment.get('is_vpn') else '' - label.text = f"{segment.get('name', 'Unknown')} - {segment.get('cidr', '')}{vpn_indicator}" - - # Draw devices - for x, y, device in positions: - self._draw_device(svg, device, x, y) - - def _draw_device(self, svg: Element, device: Dict, x: float, y: float): - """Draw a single device""" - device_type = device.get('device_type', 'default') - style = self.DEVICE_STYLES.get(device_type, self.DEVICE_STYLES['default']) - - # Draw device box - box_x = x - self.device_width / 2 - box_y = y - self.device_height / 2 - - SubElement(svg, 'rect', { - 'x': str(box_x), - 'y': str(box_y), - 'width': str(self.device_width), - 'height': str(self.device_height), - 'fill': style['color'], - 'class': 'device-box', - 'rx': '5' - }) - - # Add icon - icon_text = SubElement(svg, 'text', { - 'x': str(x), - 'y': str(y - 15), - 'text-anchor': 'middle', - 'class': 'device-icon' - }) - icon_text.text = style['icon'] - - # Add IP address - ip_text = SubElement(svg, 'text', { - 'x': str(x), - 'y': str(y + 5), - 'text-anchor': 'middle', - 'class': 'device-label', - 'font-weight': 'bold' - }) - ip_text.text = device.get('ip', 'Unknown') - - # Add hostname - hostname = device.get('hostname', '') - if hostname: - hostname_text = SubElement(svg, 'text', { - 'x': str(x), - 'y': str(y + 20), - 'text-anchor': 'middle', - 'class': 'info-text' - }) - # Truncate long hostnames - if len(hostname) > 20: - hostname = hostname[:17] + '...' - hostname_text.text = hostname - - # Add additional info - info_y = y + 32 - if device.get('ssh_accessible'): - ssh_text = SubElement(svg, 'text', { - 'x': str(x), - 'y': str(info_y), - 'text-anchor': 'middle', - 'class': 'info-text', - 'fill': 'green' - }) - ssh_text.text = '🔓 SSH' - - def _draw_connections(self, svg: Element, segments: List[Dict], layout: List[List[Tuple]]): - """Draw connections between devices based on routing info""" - # This is simplified - you'd analyze routing tables to determine actual connections - - # For now, connect routers between segments - routers = [] - for positions in layout: - for x, y, device in positions: - if device.get('device_type') in ['router', 'firewall']: - routers.append((x, y, device)) - - # Connect consecutive routers - for i in range(len(routers) - 1): - x1, y1, _ = routers[i] - x2, y2, _ = routers[i + 1] - - SubElement(svg, 'line', { - 'x1': str(x1), - 'y1': str(y1), - 'x2': str(x2), - 'y2': str(y2), - 'class': 'connection' - }) - - def _add_legend(self, svg: Element): - """Add legend explaining device types""" - legend_x = self.width - 200 - legend_y = self.height - 200 - - # Legend box - SubElement(svg, 'rect', { - 'x': str(legend_x - 10), - 'y': str(legend_y - 10), - 'width': '190', - 'height': str(len(self.DEVICE_STYLES) * 25 + 30), - 'fill': 'white', - 'stroke': '#333', - 'stroke-width': '1', - 'rx': '5' - }) - - # Legend title - title = SubElement(svg, 'text', { - 'x': str(legend_x), - 'y': str(legend_y + 5), - 'class': 'legend-text', - 'font-weight': 'bold' - }) - title.text = 'Device Types' - - # Legend items - y_offset = 25 - for device_type, style in self.DEVICE_STYLES.items(): - if device_type == 'default': - continue - - # Color box - SubElement(svg, 'rect', { - 'x': str(legend_x), - 'y': str(legend_y + y_offset - 8), - 'width': '15', - 'height': '15', - 'fill': style['color'], - 'stroke': '#333' - }) - - # Label - label = SubElement(svg, 'text', { - 'x': str(legend_x + 20), - 'y': str(legend_y + y_offset + 4), - 'class': 'legend-text' - }) - label.text = f"{style['icon']} {device_type.replace('_', ' ').title()}" - - y_offset += 25 - - def _save_svg(self, svg: Element, output_file: str): - """Save SVG to file with pretty formatting""" - rough_string = tostring(svg, encoding='unicode') - reparsed = minidom.parseString(rough_string) - pretty_svg = reparsed.toprettyxml(indent=' ') - - # Remove extra blank lines - pretty_svg = '\n'.join([line for line in pretty_svg.split('\n') if line.strip()]) - - with open(output_file, 'w', encoding='utf-8') as f: - f.write(pretty_svg) - - -def main(): - """Command line interface""" - import argparse - - parser = argparse.ArgumentParser(description='Generate SVG network diagram from scan data') - parser.add_argument('input', help='Input JSON file from network scan') - parser.add_argument('-o', '--output', default='network_diagram.svg', - help='Output SVG file (default: network_diagram.svg)') - - args = parser.parse_args() - - # Load scan data - with open(args.input, 'r') as f: - scan_data = json.load(f) - - # Generate diagram - generator = NetworkDiagramGenerator(scan_data) - generator.generate_svg(args.output) - - print(f"✓ Network diagram generated: {args.output}") - - -if __name__ == '__main__': - main() diff --git a/src/test_system.py b/src/test_system.py index eb211bd..d6b518c 100755 --- a/src/test_system.py +++ b/src/test_system.py @@ -58,7 +58,6 @@ def test_scripts_exist(): scripts = [ 'src/network_scanner.py', 'src/pfsense_scanner.py', - 'src/svg_generator.py', 'src/integrated_scanner.py', 'scripts/quickstart.sh' ] @@ -79,7 +78,6 @@ def test_script_syntax(): scripts = [ 'src/network_scanner.py', 'src/pfsense_scanner.py', - 'src/svg_generator.py', 'src/integrated_scanner.py' ] @@ -230,7 +228,6 @@ def main(): print("Next steps:") print("1. Edit config.json with your network details") print("2. Run: ./quickstart.sh") - print(" or: ./integrated_scanner.py --generate-svg") else: print("⚠️ Some tests failed. Please check the errors above.") print() diff --git a/test_config.json b/test_config.json new file mode 100644 index 0000000..ac596ac --- /dev/null +++ b/test_config.json @@ -0,0 +1,12 @@ +{ + "ssh_user": "root", + "ssh_key_path": null, + "timeout": 2, + "additional_networks": [], + "special_devices": {}, + "scan_options": { + "max_workers": 10, + "ping_timeout": 2, + "port_scan_timeout": 1 + } +} \ No newline at end of file diff --git a/tests/test_config.json b/tests/test_config.json new file mode 100644 index 0000000..ac596ac --- /dev/null +++ b/tests/test_config.json @@ -0,0 +1,12 @@ +{ + "ssh_user": "root", + "ssh_key_path": null, + "timeout": 2, + "additional_networks": [], + "special_devices": {}, + "scan_options": { + "max_workers": 10, + "ping_timeout": 2, + "port_scan_timeout": 1 + } +} \ No newline at end of file diff --git a/tests/test_output.json b/tests/test_output.json new file mode 100644 index 0000000..407c99d --- /dev/null +++ b/tests/test_output.json @@ -0,0 +1,1429 @@ +{ + "hostname": "gw-nue01", + "system": { + "hostname": "gw-nue01", + "domain": "egonetix.lan", + "timezone": "Europe/Berlin", + "language": "en_US", + "version": "24.0" + }, + "interfaces": { + "wan": { + "name": "wan", + "description": "WAN", + "enabled": true, + "interface": "vtnet0", + "ipaddr": "dhcp", + "subnet": "", + "gateway": "", + "mtu": "", + "mss": "", + "spoofmac": "", + "type": "physical" + }, + "lan": { + "name": "lan", + "description": "LAN", + "enabled": true, + "interface": "vtnet1", + "ipaddr": "10.0.0.1", + "subnet": "24", + "gateway": "", + "mtu": "", + "mss": "", + "spoofmac": "", + "type": "physical" + }, + "opt1": { + "name": "opt1", + "description": "wireguardnachhause", + "enabled": true, + "interface": "tun_wg0", + "ipaddr": "10.69.69.1", + "subnet": "30", + "gateway": "WirusguardusGW", + "mtu": "1300", + "mss": "1260", + "spoofmac": "", + "type": "wireguard" + } + }, + "static_routes": [ + { + "network": "172.20.0.0/16", + "gateway": "WirusguardusGW", + "description": "heyme" + } + ], + "gateways": {}, + "dhcp": { + "lan": { + "enabled": true, + "range": { + "from": "10.0.0.100", + "to": "10.0.0.150" + }, + "static_mappings": [ + { + "mac": "fe:0a:ea:4d:6c:78", + "ipaddr": "10.0.0.23", + "hostname": "srvmail01", + "description": "" + }, + { + "mac": "32:38:39:37:39:39", + "ipaddr": "10.0.0.24", + "hostname": "srvmonitor01", + "description": "" + }, + { + "mac": "96:63:70:a2:bd:6b", + "ipaddr": "10.0.0.29", + "hostname": "srvrevproxy02", + "description": "" + }, + { + "mac": "7e:9b:28:bb:d7:df", + "ipaddr": "10.0.0.37", + "hostname": "srvmailgw02", + "description": "" + }, + { + "mac": "bc:24:11:54:de:9d", + "ipaddr": "10.0.0.37", + "hostname": "srvmailgw03", + "description": "" + }, + { + "mac": "0a:e9:b9:ac:59:8f", + "ipaddr": "10.0.0.39", + "hostname": "srvnextcloud02", + "description": "" + }, + { + "mac": "a6:91:f4:10:2e:47", + "ipaddr": "10.0.0.41", + "hostname": "srvrmgw01", + "description": "" + }, + { + "mac": "da:42:70:74:c0:13", + "ipaddr": "10.0.0.42", + "hostname": "srvwiki01", + "description": "" + }, + { + "mac": "f6:49:5d:6b:6c:57", + "ipaddr": "10.0.0.43", + "hostname": "srvwordpress01", + "description": "" + }, + { + "mac": "96:87:94:11:3d:23", + "ipaddr": "10.0.0.44", + "hostname": "srvnextcloud03", + "description": "" + }, + { + "mac": "be:0e:83:ec:fd:01", + "ipaddr": "10.0.0.45", + "hostname": "srvdms01", + "description": "" + }, + { + "mac": "5e:d7:34:84:2e:5f", + "ipaddr": "10.0.0.46", + "hostname": "srvdocker04", + "description": "" + }, + { + "mac": "b2:81:4e:c2:8a:47", + "ipaddr": "10.0.0.47", + "hostname": "srvdocker01", + "description": "" + }, + { + "mac": "be:b1:4b:77:28:a5", + "ipaddr": "10.0.0.48", + "hostname": "srvdocker02", + "description": "" + }, + { + "mac": "e6:f6:41:0e:1f:d9", + "ipaddr": "10.0.0.49", + "hostname": "srvdocker03", + "description": "" + }, + { + "mac": "2a:43:da:a9:b8:32", + "ipaddr": "10.0.0.50", + "hostname": "srvfs02", + "description": "" + }, + { + "mac": "2a:78:fa:54:85:8e", + "ipaddr": "10.0.0.51", + "hostname": "srvhome01", + "description": "" + }, + { + "mac": "26:9f:33:83:31:d9", + "ipaddr": "10.0.0.52", + "hostname": "srvplex", + "description": "" + }, + { + "mac": "d2:d6:43:a8:5c:60", + "ipaddr": "10.0.0.53", + "hostname": "srvgrass01", + "description": "" + }, + { + "mac": "ce:7c:2b:3e:16:91", + "ipaddr": "10.0.0.54", + "hostname": "srvgrass02", + "description": "" + }, + { + "mac": "b6:ee:e8:89:88:41", + "ipaddr": "10.0.0.55", + "hostname": "srvmail02", + "description": "" + }, + { + "mac": "92:f5:8c:d2:06:19", + "ipaddr": "10.0.0.56", + "hostname": "srvnextcloud01", + "description": "" + }, + { + "mac": "82:72:5d:a2:0d:73", + "ipaddr": "10.0.0.57", + "hostname": "srvgpt01", + "description": "" + }, + { + "mac": "bc:24:11:b7:be:31", + "ipaddr": "10.0.0.58", + "hostname": "srvai01", + "description": "" + }, + { + "mac": "bc:24:11:98:40:27", + "ipaddr": "10.0.0.59", + "hostname": "srvts01", + "description": "" + }, + { + "mac": "bc:24:11:29:a0:c6", + "ipaddr": "10.0.0.60", + "hostname": "srvcode01", + "description": "" + } + ], + "defaultleasetime": "7200", + "maxleasetime": "", + "gateway": "10.0.0.1", + "domain": "egonetix.lan", + "domainsearchlist": "egonetix.lan", + "ddnsdomain": "egonetix.lan", + "dns1": "10.0.0.21", + "dns2": "10.0.0.1", + "ntpserver": "192.53.103.108" + } + }, + "wireguard": { + "enabled": true, + "tunnels": [ + { + "name": "tun_wg0", + "enabled": true, + "description": "heyme", + "listenport": "51820", + "publickey": "dwQ/ZByIZdFVawJ31WEH9AKEoc6sjYmw7AFG5LYLygE=", + "mtu": "1420" + } + ], + "peers": [ + { + "enabled": true, + "tunnel": "tun_wg0", + "description": "wireguardheyme", + "publickey": "pQr6y8pJLiVII+SbuZVaPT6tTMF+bYnQ9g5aBK1AaBw=", + "persistentkeepalive": "", + "allowed_ips": [ + { + "address": "172.20.0.0", + "mask": "16", + "description": "" + }, + { + "address": "10.69.69.2", + "mask": "32", + "description": "" + } + ] + } + ] + }, + "openvpn": { + "servers": [ + { + "configured": true, + "description": "OpenVPN Server configured" + } + ], + "clients": [] + }, + "firewall_rules": [ + { + "id": "", + "tracker": "1770008075", + "type": "reject", + "interface": "lan", + "ipprotocol": "inet", + "protocol": "", + "description": "pfB_PRI1_v4", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "pfB_PRI1_v4", + "port": "", + "any": false + }, + "log": true + }, + { + "id": "", + "tracker": "1426500457", + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp", + "description": "Mailbox.org", + "enabled": true, + "source": { + "address": "80.241.60.212", + "port": "", + "any": false + }, + "destination": { + "address": "", + "port": "25", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1422395664", + "type": "block", + "interface": "wan", + "ipprotocol": "", + "protocol": "", + "description": "pfBlockerTopSpammers auto rule", + "enabled": true, + "source": { + "address": "pfBlockerTopSpammers", + "port": "", + "any": false + }, + "destination": { + "address": "", + "port": "", + "any": true + }, + "log": true + }, + { + "id": "", + "tracker": "1422395665", + "type": "pass", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp", + "description": "", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "443", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1422395666", + "type": "pass", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp", + "description": "", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "80", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1422395667", + "type": "pass", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp", + "description": "", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "22", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1422395668", + "type": "pass", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp", + "description": "", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "443", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1422395669", + "type": "pass", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp/udp", + "description": "", + "enabled": true, + "source": { + "address": "", + "port": "4500", + "any": true + }, + "destination": { + "address": "", + "port": "4500", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1422395670", + "type": "pass", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp", + "description": "", + "enabled": true, + "source": { + "address": "", + "port": "500", + "any": true + }, + "destination": { + "address": "", + "port": "500", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1422395671", + "type": "pass", + "interface": "wan", + "ipprotocol": "", + "protocol": "esp", + "description": "", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1422395672", + "type": "pass", + "interface": "wan", + "ipprotocol": "", + "protocol": "ah", + "description": "", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1422395674", + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp", + "description": "NAT srvrevproxy01", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.29", + "port": "443", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1422395675", + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp", + "description": "NAT srvrevproxy01", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.29", + "port": "80", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1422395678", + "type": "", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp/udp", + "description": "NAT email", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.37", + "port": "25", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1422395684", + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "description": "OpenVPN egonetix robert wizard", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "1195", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1422395698", + "type": "pass", + "interface": "wan", + "ipprotocol": "", + "protocol": "icmp", + "description": "Easy Rule: Passed from Firewall Log View", + "enabled": true, + "source": { + "address": "213.133.113.84", + "port": "", + "any": false + }, + "destination": { + "address": "176.9.95.82", + "port": "", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1480950946", + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp", + "description": "Terminal Server", + "enabled": true, + "source": { + "address": "", + "port": "30331", + "any": true + }, + "destination": { + "address": "10.0.0.20", + "port": "3389", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1531912851", + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "", + "description": "NAT TURN Server Nextcloud", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.34", + "port": "", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1562761341", + "type": "", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp/udp", + "description": "NAT IMAPS", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.21", + "port": "993", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1562761387", + "type": "", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp/udp", + "description": "NAT SMTPS", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.21", + "port": "465", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1562836606", + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp", + "description": "Easy Rule: Passed from Firewall Log View", + "enabled": true, + "source": { + "address": "92.79.82.210", + "port": "", + "any": false + }, + "destination": { + "address": "95.216.21.72", + "port": "110", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1562837421", + "type": "", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp/udp", + "description": "NAT SMTP Submission", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.21", + "port": "587", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1562837449", + "type": "", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp/udp", + "description": "NAT IMAP", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.21", + "port": "143", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1562841192", + "type": "", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp", + "description": "NAT Icals", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.21", + "port": "8443", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1564125887", + "type": "", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp", + "description": "NAT Ical", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.21", + "port": "8080", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1564743419", + "type": "", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp/udp", + "description": "NAT Plex", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.48", + "port": "32400", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1566751935", + "type": "", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp/udp", + "description": "NAT Jellyfin for Kodi", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.48", + "port": "8096", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1586675434", + "type": "", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "description": "NAT Nextcloud Stun Coturn", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.13", + "port": "3478", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1609177457", + "type": "", + "interface": "wan", + "ipprotocol": "", + "protocol": "tcp", + "description": "NAT srvrevproxy01", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.29", + "port": "8448", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1748421225", + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "description": "wireguard", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "51820", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1748864974", + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp", + "description": "", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "853", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1750939411", + "type": "", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "description": "NAT srvdocker02", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.48", + "port": "3478", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1752051663", + "type": "", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "description": "NAT srvdocker02 gitea access", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.48", + "port": "222", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1755687343", + "type": "", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "description": "NAT syncthing", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "10.0.0.48", + "port": "22000", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1750255444", + "type": "pass", + "interface": "lan", + "ipprotocol": "inet", + "protocol": "", + "description": "Passed via EasyRule", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": false + }, + "destination": { + "address": "10.69.69.2", + "port": "", + "any": false + }, + "log": false + }, + { + "id": "", + "tracker": "1486659007", + "type": "pass", + "interface": "lan", + "ipprotocol": "inet", + "protocol": "", + "description": "", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1750250633", + "type": "pass", + "interface": "WireGuard", + "ipprotocol": "inet46", + "protocol": "", + "description": "", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1422395712", + "type": "pass", + "interface": "enc0", + "ipprotocol": "", + "protocol": "", + "description": "", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1422395713", + "type": "pass", + "interface": "enc0", + "ipprotocol": "", + "protocol": "icmp", + "description": "icmp echo reply", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1422395714", + "type": "pass", + "interface": "enc0", + "ipprotocol": "", + "protocol": "icmp", + "description": "icmp echo request", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1422395715", + "type": "pass", + "interface": "openvpn", + "ipprotocol": "inet", + "protocol": "", + "description": "OpenVPN OpenVPNMobile wizard", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "", + "any": true + }, + "log": false + }, + { + "id": "", + "tracker": "1748422740", + "type": "pass", + "interface": "opt1", + "ipprotocol": "inet", + "protocol": "", + "description": "", + "enabled": true, + "source": { + "address": "", + "port": "", + "any": true + }, + "destination": { + "address": "", + "port": "", + "any": true + }, + "log": false + } + ], + "nat_rules": [ + { + "description": "Nextcloud Stun Coturn", + "interface": "wan", + "protocol": "tcp/udp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "3478" + }, + "target": "10.0.0.13", + "local_port": "3478", + "enabled": true + }, + { + "description": "IMAPS", + "interface": "wan", + "protocol": "tcp/udp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "993" + }, + "target": "10.0.0.21", + "local_port": "993", + "enabled": true + }, + { + "description": "IMAP", + "interface": "wan", + "protocol": "tcp/udp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "143" + }, + "target": "10.0.0.21", + "local_port": "143", + "enabled": true + }, + { + "description": "SMTPS", + "interface": "wan", + "protocol": "tcp/udp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "465" + }, + "target": "10.0.0.21", + "local_port": "465", + "enabled": true + }, + { + "description": "SMTP Submission", + "interface": "wan", + "protocol": "tcp/udp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "587" + }, + "target": "10.0.0.21", + "local_port": "587", + "enabled": true + }, + { + "description": "srvrevproxy01", + "interface": "wan", + "protocol": "tcp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "443" + }, + "target": "10.0.0.29", + "local_port": "443", + "enabled": true + }, + { + "description": "srvrevproxy01", + "interface": "wan", + "protocol": "tcp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "8448" + }, + "target": "10.0.0.29", + "local_port": "8448", + "enabled": true + }, + { + "description": "srvdocker02", + "interface": "wan", + "protocol": "tcp/udp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "3478" + }, + "target": "10.0.0.48", + "local_port": "3478", + "enabled": true + }, + { + "description": "srvdocker02 gitea access", + "interface": "wan", + "protocol": "tcp/udp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "222" + }, + "target": "10.0.0.48", + "local_port": "222", + "enabled": true + }, + { + "description": "Plex", + "interface": "wan", + "protocol": "tcp/udp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "32400" + }, + "target": "10.0.0.48", + "local_port": "32400", + "enabled": true + }, + { + "description": "Jellyfin for Kodi", + "interface": "wan", + "protocol": "tcp/udp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "8096" + }, + "target": "10.0.0.48", + "local_port": "8096", + "enabled": true + }, + { + "description": "syncthing", + "interface": "wan", + "protocol": "tcp/udp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "22000" + }, + "target": "10.0.0.48", + "local_port": "22000", + "enabled": true + }, + { + "description": "Ical", + "interface": "wan", + "protocol": "tcp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "8080" + }, + "target": "10.0.0.21", + "local_port": "8080", + "enabled": true + }, + { + "description": "Icals", + "interface": "wan", + "protocol": "tcp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "8443" + }, + "target": "10.0.0.21", + "local_port": "8443", + "enabled": true + }, + { + "description": "srvrevproxy01", + "interface": "wan", + "protocol": "tcp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "80" + }, + "target": "10.0.0.29", + "local_port": "80", + "enabled": true + }, + { + "description": "email", + "interface": "wan", + "protocol": "tcp/udp", + "source": { + "address": "", + "port": "" + }, + "destination": { + "address": "", + "port": "25" + }, + "target": "10.0.0.37", + "local_port": "25", + "enabled": true + } + ], + "dns": { + "servers": [ + "194.242.2.2", + "193.19.108.2", + "91.239.100.100" + ], + "domain": "egonetix.lan", + "search_domains": [] + } +} \ No newline at end of file