- Add pfsense_integrator.py for automatic XML parsing and integration - Add complete_workflow.sh for one-command network discovery - Enhance integrated_scanner.py to auto-integrate pfSense XML files - Update README with pfSense XML features and workflow - Generate comprehensive network summaries from XML configs - Support for WireGuard, OpenVPN, IPsec, routing, DHCP, firewall rules
367 lines
14 KiB
Python
Executable File
367 lines
14 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Complete Network Scanner with pfSense Integration
|
|
Combines network scanning with pfSense-specific features
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import argparse
|
|
from datetime import datetime
|
|
from network_scanner import NetworkScanner, NetworkSegment
|
|
from pfsense_scanner import PfSenseScanner
|
|
from dataclasses import asdict
|
|
from network_scanner import Device
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class IntegratedNetworkScanner:
|
|
"""Enhanced network scanner with pfSense integration"""
|
|
|
|
def __init__(self, config: dict):
|
|
self.config = config
|
|
self.base_scanner = NetworkScanner(config)
|
|
self.pfsense_devices = []
|
|
|
|
def scan_all(self):
|
|
"""Perform complete network scan including pfSense devices"""
|
|
logger.info("Starting integrated network scan...")
|
|
|
|
# Run base network scan
|
|
self.base_scanner.scan_all()
|
|
|
|
# Check for pfSense XML files and integrate them
|
|
self._integrate_pfsense_xml()
|
|
|
|
# Identify and enhance pfSense devices
|
|
self._scan_pfsense_devices()
|
|
|
|
logger.info("Integrated scan complete")
|
|
|
|
def _integrate_pfsense_xml(self):
|
|
"""Automatically integrate pfSense XML files if present"""
|
|
import glob
|
|
from pfsense_integrator import PfSenseIntegrator
|
|
|
|
# Look for XML files in current directory
|
|
xml_files = glob.glob("*.xml")
|
|
if not xml_files:
|
|
logger.info("No pfSense XML files found, skipping XML integration")
|
|
return
|
|
|
|
logger.info(f"Found {len(xml_files)} pfSense XML files, integrating...")
|
|
|
|
try:
|
|
integrator = PfSenseIntegrator(xml_files)
|
|
integrator.load_pfsense_configs()
|
|
|
|
# Create temporary scan file for integration
|
|
import tempfile
|
|
import os
|
|
|
|
temp_scan = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
|
|
try:
|
|
# Export current scan data
|
|
temp_data = {
|
|
'scan_timestamp': None,
|
|
'segments': [
|
|
{
|
|
'name': seg.name,
|
|
'cidr': seg.cidr,
|
|
'gateway': seg.gateway,
|
|
'is_vpn': seg.is_vpn,
|
|
'devices': [asdict(dev) for dev in seg.devices]
|
|
}
|
|
for seg in self.base_scanner.segments
|
|
]
|
|
}
|
|
json.dump(temp_data, temp_scan)
|
|
temp_scan.close()
|
|
|
|
# Integrate pfSense data
|
|
integrator.integrate_with_scan(temp_scan.name, temp_scan.name + '_enhanced')
|
|
|
|
# Load enhanced data back
|
|
with open(temp_scan.name + '_enhanced', 'r') as f:
|
|
enhanced_data = json.load(f)
|
|
|
|
# Update segments
|
|
self.base_scanner.segments = []
|
|
for seg_data in enhanced_data.get('segments', []):
|
|
segment = NetworkSegment(
|
|
name=seg_data['name'],
|
|
cidr=seg_data['cidr'],
|
|
gateway=seg_data['gateway'],
|
|
is_vpn=seg_data['is_vpn'],
|
|
devices=[]
|
|
)
|
|
|
|
for dev_data in seg_data['devices']:
|
|
device = Device(**dev_data)
|
|
segment.devices.append(device)
|
|
|
|
self.base_scanner.segments.append(segment)
|
|
|
|
logger.info(f"Integrated {len(integrator.pfsense_configs)} pfSense configurations")
|
|
|
|
finally:
|
|
# Clean up temp files
|
|
try:
|
|
os.unlink(temp_scan.name)
|
|
os.unlink(temp_scan.name + '_enhanced')
|
|
except:
|
|
pass
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error integrating pfSense XML: {e}")
|
|
|
|
def _scan_pfsense_devices(self):
|
|
"""Find and deeply scan pfSense devices"""
|
|
logger.info("Looking for pfSense devices...")
|
|
|
|
# Check configured special devices
|
|
special_devices = self.config.get('special_devices', {})
|
|
|
|
for segment in self.base_scanner.segments:
|
|
for device in segment.devices:
|
|
is_pfsense = False
|
|
|
|
# Check if device is marked as pfSense in config
|
|
if device.ip in special_devices:
|
|
if special_devices[device.ip].get('type') == 'firewall' or \
|
|
special_devices[device.ip].get('os') == 'pfSense':
|
|
is_pfsense = True
|
|
|
|
# Check if hostname contains pfsense
|
|
if device.hostname and 'pfsense' in device.hostname.lower():
|
|
is_pfsense = True
|
|
|
|
# Check if device looks like a pfSense (has many routes and ports 80/443)
|
|
if device.routes and len(device.routes) > 3 and \
|
|
80 in device.open_ports and 443 in device.open_ports:
|
|
is_pfsense = True
|
|
|
|
if is_pfsense and device.ssh_accessible:
|
|
logger.info(f"Enhanced scanning pfSense device: {device.ip}")
|
|
self._enhance_pfsense_device(device)
|
|
|
|
def _enhance_pfsense_device(self, device):
|
|
"""Enhance device info with pfSense-specific data"""
|
|
try:
|
|
scanner = PfSenseScanner(
|
|
device.ip,
|
|
self.config.get('ssh_user', 'root'),
|
|
self.config.get('ssh_key_path')
|
|
)
|
|
|
|
pfsense_info = scanner.get_full_info()
|
|
|
|
# Merge pfSense-specific info into device
|
|
device.device_type = 'firewall'
|
|
device.os_type = 'pfSense (FreeBSD)'
|
|
|
|
# Store additional pfSense data
|
|
if not hasattr(device, 'pfsense_info'):
|
|
device.__dict__['pfsense_info'] = pfsense_info
|
|
|
|
# Add DHCP leases to known devices
|
|
dhcp_leases = pfsense_info.get('dhcp_leases', [])
|
|
if dhcp_leases:
|
|
logger.info(f"Found {len(dhcp_leases)} DHCP leases on {device.ip}")
|
|
|
|
# Add VPN info
|
|
vpn_info = pfsense_info.get('vpn', {})
|
|
wireguard = vpn_info.get('wireguard', [])
|
|
if wireguard:
|
|
logger.info(f"Found {len(wireguard)} WireGuard tunnels on {device.ip}")
|
|
|
|
self.pfsense_devices.append(device)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error enhancing pfSense device {device.ip}: {e}")
|
|
|
|
def export_json(self, filename: str):
|
|
"""Export enhanced results to JSON"""
|
|
data = {
|
|
'scan_timestamp': datetime.now().isoformat(),
|
|
'segments': []
|
|
}
|
|
|
|
for segment in self.base_scanner.segments:
|
|
segment_data = {
|
|
'name': segment.name,
|
|
'cidr': segment.cidr,
|
|
'gateway': segment.gateway,
|
|
'is_vpn': segment.is_vpn,
|
|
'devices': []
|
|
}
|
|
|
|
for device in segment.devices:
|
|
device_data = {
|
|
'ip': device.ip,
|
|
'hostname': device.hostname,
|
|
'mac': device.mac,
|
|
'manufacturer': device.manufacturer,
|
|
'os_type': device.os_type,
|
|
'os_version': device.os_version,
|
|
'device_type': device.device_type,
|
|
'open_ports': device.open_ports,
|
|
'ssh_accessible': device.ssh_accessible,
|
|
'services': device.services,
|
|
'routes': device.routes,
|
|
'interfaces': device.interfaces
|
|
}
|
|
|
|
# Add pfSense-specific info if available
|
|
if hasattr(device, 'pfsense_info'):
|
|
device_data['pfsense_info'] = device.__dict__['pfsense_info']
|
|
|
|
segment_data['devices'].append(device_data)
|
|
|
|
data['segments'].append(segment_data)
|
|
|
|
with open(filename, 'w') as f:
|
|
json.dump(data, f, indent=2)
|
|
|
|
logger.info(f"Exported results to {filename}")
|
|
|
|
def print_summary(self):
|
|
"""Print enhanced summary"""
|
|
print("\n" + "="*80)
|
|
print("INTEGRATED NETWORK SCAN SUMMARY")
|
|
print("="*80)
|
|
|
|
total_devices = sum(len(seg.devices) for seg in self.base_scanner.segments)
|
|
print(f"\nTotal Segments: {len(self.base_scanner.segments)}")
|
|
print(f"Total Devices: {total_devices}")
|
|
print(f"pfSense Devices: {len(self.pfsense_devices)}")
|
|
|
|
for segment in self.base_scanner.segments:
|
|
print(f"\n{'='*80}")
|
|
print(f"📡 Network: {segment.name} ({segment.cidr})")
|
|
if segment.is_vpn:
|
|
print(" 🔐 VPN Network")
|
|
print(f" Devices: {len(segment.devices)}")
|
|
|
|
for device in segment.devices:
|
|
icon = self._get_device_icon(device.device_type)
|
|
print(f"\n {icon} {device.ip}")
|
|
|
|
if device.hostname:
|
|
print(f" Hostname: {device.hostname}")
|
|
if device.mac:
|
|
print(f" MAC: {device.mac}")
|
|
if device.device_type:
|
|
print(f" Type: {device.device_type}")
|
|
if device.os_type:
|
|
print(f" OS: {device.os_type} {device.os_version or ''}")
|
|
if device.open_ports:
|
|
print(f" Ports: {', '.join(map(str, device.open_ports))}")
|
|
if device.ssh_accessible:
|
|
print(f" ✓ SSH Accessible")
|
|
|
|
# Show pfSense-specific info
|
|
if hasattr(device, 'pfsense_info'):
|
|
pfsense = device.__dict__['pfsense_info']
|
|
print(f" 🛡️ pfSense Firewall:")
|
|
print(f" Interfaces: {len(pfsense.get('interfaces', []))}")
|
|
print(f" Routes: {len(pfsense.get('routes', []))}")
|
|
print(f" DHCP Leases: {len(pfsense.get('dhcp_leases', []))}")
|
|
|
|
vpn = pfsense.get('vpn', {})
|
|
wg_count = len(vpn.get('wireguard', []))
|
|
ovpn_count = len(vpn.get('openvpn', []))
|
|
if wg_count:
|
|
print(f" WireGuard Tunnels: {wg_count}")
|
|
if ovpn_count:
|
|
print(f" OpenVPN Connections: {ovpn_count}")
|
|
|
|
if device.routes and len(device.routes) > 0:
|
|
print(f" 📋 Routing Table ({len(device.routes)} routes):")
|
|
for route in device.routes[:3]: # Show first 3
|
|
dest = route.get('destination', 'unknown')
|
|
gw = route.get('gateway', 'direct')
|
|
print(f" → {dest} via {gw}")
|
|
if len(device.routes) > 3:
|
|
print(f" ... and {len(device.routes) - 3} more")
|
|
|
|
print("\n" + "="*80)
|
|
|
|
def _get_device_icon(self, device_type):
|
|
"""Get emoji icon for device type"""
|
|
icons = {
|
|
'router': '🔀',
|
|
'firewall': '🛡️',
|
|
'switch': '🔌',
|
|
'server': '🖥️',
|
|
'linux_server': '🐧',
|
|
'windows_client': '💻',
|
|
'client': '💻'
|
|
}
|
|
return icons.get(device_type, '❓')
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='Integrated Network Scanner with pfSense Support'
|
|
)
|
|
parser.add_argument('-c', '--config', default='config.json',
|
|
help='Configuration file (default: config.json)')
|
|
parser.add_argument('-o', '--output', default='network_scan.json',
|
|
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()
|
|
|
|
if args.verbose:
|
|
logging.getLogger().setLevel(logging.DEBUG)
|
|
|
|
# Load configuration
|
|
try:
|
|
with open(args.config, 'r') as f:
|
|
config = json.load(f)
|
|
logger.info(f"Loaded configuration from {args.config}")
|
|
except FileNotFoundError:
|
|
logger.warning(f"Config file {args.config} not found, using defaults")
|
|
config = {}
|
|
|
|
# Run integrated scanner
|
|
scanner = IntegratedNetworkScanner(config)
|
|
scanner.scan_all()
|
|
|
|
# Print summary
|
|
scanner.print_summary()
|
|
|
|
# Export results
|
|
scanner.export_json(args.output)
|
|
|
|
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__':
|
|
main()
|