|
75 | 75 | '104.26.7.33', # gitlab.com |
76 | 76 | ], |
77 | 77 | 'connection_db': [ |
78 | | - '10.0.10.50', # Internal DB server |
79 | | - '192.168.100.25', # Internal DB server |
| 78 | + # For internal DB connections, use dedicated DB server IPs in separate subnet |
| 79 | + # This prevents matching workstation IPs (10.0.10.x) which would create |
| 80 | + # same source/destination connections that network sensors can't observe |
| 81 | + '10.0.100.10', # Internal DB server (separate subnet) |
| 82 | + '10.0.100.11', # Internal DB replica |
80 | 83 | ], |
81 | 84 | } |
82 | 85 |
|
83 | 86 |
|
| 87 | +def _is_invalid_network_connection(src_ip: str, dst_ip: str) -> tuple[bool, str]: |
| 88 | + """Validate that a network connection would be observable by network sensors. |
| 89 | +
|
| 90 | + Network-based data sources like Zeek can only observe traffic that actually |
| 91 | + traverses the network. This function checks for connections that would never |
| 92 | + be visible to network sensors. |
| 93 | +
|
| 94 | + Args: |
| 95 | + src_ip: Source IP address |
| 96 | + dst_ip: Destination IP address |
| 97 | +
|
| 98 | + Returns: |
| 99 | + Tuple of (is_invalid, reason). If is_invalid=True, connection should not be generated. |
| 100 | + """ |
| 101 | + # Check if source and destination are the same |
| 102 | + if src_ip == dst_ip: |
| 103 | + return True, f"Source and destination are identical ({src_ip})" |
| 104 | + |
| 105 | + # Check for localhost addresses (127.0.0.0/8) |
| 106 | + # Network sensors cannot observe localhost traffic |
| 107 | + if src_ip.startswith('127.') or dst_ip.startswith('127.'): |
| 108 | + return True, f"Connection involves localhost address (src={src_ip}, dst={dst_ip})" |
| 109 | + |
| 110 | + # Check for link-local addresses (169.254.0.0/16) |
| 111 | + # These are auto-configured and typically not routed |
| 112 | + if src_ip.startswith('169.254.') or dst_ip.startswith('169.254.'): |
| 113 | + return True, f"Connection involves link-local address (src={src_ip}, dst={dst_ip})" |
| 114 | + |
| 115 | + # Check for multicast addresses (224.0.0.0/4) |
| 116 | + # These require special handling and shouldn't appear in typical conn logs |
| 117 | + try: |
| 118 | + src_first_octet = int(src_ip.split('.')[0]) |
| 119 | + dst_first_octet = int(dst_ip.split('.')[0]) |
| 120 | + if src_first_octet >= 224 or dst_first_octet >= 224: |
| 121 | + return True, f"Connection involves multicast/reserved address (src={src_ip}, dst={dst_ip})" |
| 122 | + except (ValueError, IndexError): |
| 123 | + # Invalid IP format - let it pass, will be caught by other validation |
| 124 | + pass |
| 125 | + |
| 126 | + return False, "" |
| 127 | + |
| 128 | + |
84 | 129 | class ActivityGenerator: |
85 | 130 | """Generates specific activity events using StateManager and emitters. |
86 | 131 |
|
@@ -306,6 +351,15 @@ def generate_connection( |
306 | 351 | Returns: |
307 | 352 | Zeek UID (18-character string) |
308 | 353 | """ |
| 354 | + # Validate connection would be observable by network sensors |
| 355 | + is_invalid, reason = _is_invalid_network_connection(src_ip, dst_ip) |
| 356 | + if is_invalid: |
| 357 | + logger.warning( |
| 358 | + f"Skipping invalid network connection: {src_ip} -> {dst_ip}. " |
| 359 | + f"Reason: {reason}. Network sensors would not observe this traffic." |
| 360 | + ) |
| 361 | + return "" # Return empty UID to indicate skipped connection |
| 362 | + |
309 | 363 | src_port = random.randint(49152, 65535) # Ephemeral port |
310 | 364 |
|
311 | 365 | # Create connection in StateManager |
@@ -411,8 +465,21 @@ def execute_baseline_activity( |
411 | 465 |
|
412 | 466 | # Connection activities |
413 | 467 | elif activity_type in EXTERNAL_IPS: |
414 | | - # Choose random destination IP |
415 | | - dst_ip = random.choice(EXTERNAL_IPS[activity_type]) |
| 468 | + # Choose random destination IP (exclude source system's IP) |
| 469 | + available_destinations = [ |
| 470 | + ip for ip in EXTERNAL_IPS[activity_type] |
| 471 | + if ip != system.ip |
| 472 | + ] |
| 473 | + |
| 474 | + if not available_destinations: |
| 475 | + # No valid destinations (all IPs match source) |
| 476 | + logger.debug( |
| 477 | + f"Skipping {activity_type} for {system.hostname}: " |
| 478 | + f"no valid destination IPs (all match source {system.ip})" |
| 479 | + ) |
| 480 | + return |
| 481 | + |
| 482 | + dst_ip = random.choice(available_destinations) |
416 | 483 |
|
417 | 484 | # Set service and port based on activity type |
418 | 485 | if activity_type == 'connection_web': |
|
0 commit comments