Tools · Python and Bash for security automation · 10 / 20
A Python TCP service check for your own hosts: expected ports versus reality
Write a small script that connects to a list of your hosts and ports, compares results with an expected-state file, and reports unexpected open or closed services.
Expected state
Network exposure drifts: a debug port left open, a database bound to all interfaces, a service that stopped. A simple check that compares reality with an expected-state file catches drift between full scans. Run it only against systems you own or are authorized to test.
The script
A TCP connect with a short timeout tells you whether a port accepts connections. ThreadPoolExecutor keeps it fast for dozens of checks.
#!/usr/bin/env python3
"""Compare open TCP ports on owned hosts with an expected state file."""
import json, socket, sys
from concurrent.futures import ThreadPoolExecutor
expected = json.load(open(sys.argv[1])) # {"10.0.0.5": {"open": [22, 443], "closed": [3306, 6379]}}
def is_open(host, port):
try:
with socket.create_connection((host, port), timeout=1.5):
return True
except OSError:
return False
jobs = [(h, p, want) for h, spec in expected.items() for want in ('open', 'closed') for p in spec.get(want, [])]
with ThreadPoolExecutor(max_workers=32) as ex:
results = list(ex.map(lambda j: (*j, is_open(j[0], j[1])), jobs))
drift = 0
for host, port, want, actual in results:
ok = actual == (want == 'open')
drift += not ok
print(f'{"OK " if ok else "DRIFT"} {host}:{port} expected {want}, actually {"open" if actual else "closed"}')
sys.exit(1 if drift else 0)Lab: catch a drift
Write an expected-state file for two lab VMs: SSH open, a web port open, and database and cache ports closed. Run the check. Then start a test service on a closed port, such as python3 -m http.server 6379 on one VM, and rerun to see a DRIFT line and exit code 1. Stop the service and confirm the check is clean again.
cat > expected.json <<'EOF'
{"192.168.56.10": {"open": [22, 443], "closed": [3306, 6379, 9200]},
"192.168.56.11": {"open": [22], "closed": [5432, 8080]}}
EOF
python3 portcheck.py expected.json; echo exit=$?When to use a real scanner
This check only tests ports you list. For discovering unknown services, use Nmap against your own ranges on a schedule and diff results, as covered in the Nmap hub. Cloud security groups and firewall rules are another source of truth worth comparing against.
Diagram
Expected versus actual
Drift is any mismatch.
192.168.56.10:22
expected open
actually open
192.168.56.10:6379
expected closed
actually open
192.168.56.11:22
expected open
actually closed
Check yourself
1. Which systems may this script check?
2. A port expected closed is open. What does the script report?
3. Why use a thread pool?