Files
all_ping/scripts/safe_external_inventory.sh
2026-07-01 02:52:14 +05:00

152 lines
4.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
INPUT_FILE="${1:-$ROOT_DIR/public_ipv4_from_master.txt}"
STAMP="$(date +%Y%m%d-%H%M%S)"
OUTPUT_DIR="${2:-$ROOT_DIR/reports/inventory-$STAMP}"
TARGETS_FILE="$OUTPUT_DIR/targets.txt"
RAW_BASENAME="$OUTPUT_DIR/nmap_inventory"
SUMMARY_CSV="$OUTPUT_DIR/summary.csv"
SUMMARY_MD="$OUTPUT_DIR/summary.md"
mkdir -p "$OUTPUT_DIR"
if ! command -v nmap >/dev/null 2>&1; then
echo "nmap is not installed. Install it first, then rerun this script." >&2
echo "Expected input: $INPUT_FILE" >&2
echo "Planned output directory: $OUTPUT_DIR" >&2
exit 1
fi
if [[ ! -f "$INPUT_FILE" ]]; then
echo "Input file not found: $INPUT_FILE" >&2
exit 1
fi
python3 - "$INPUT_FILE" "$TARGETS_FILE" <<'PY'
import ipaddress
import sys
src, dst = sys.argv[1], sys.argv[2]
valid = []
seen = set()
with open(src, "r", encoding="utf-8") as fh:
for raw in fh:
line = raw.strip()
if not line or line.startswith("#"):
continue
try:
ip = ipaddress.IPv4Address(line)
except ipaddress.AddressValueError:
print(f"Skipping invalid IPv4: {line}", file=sys.stderr)
continue
text = str(ip)
if text not in seen:
seen.add(text)
valid.append(text)
with open(dst, "w", encoding="utf-8") as fh:
for ip in valid:
fh.write(f"{ip}\n")
print(len(valid))
PY
TARGET_COUNT="$(wc -l < "$TARGETS_FILE" | tr -d ' ')"
if [[ "$TARGET_COUNT" -eq 0 ]]; then
echo "No valid IPv4 targets found in $INPUT_FILE" >&2
exit 1
fi
echo "Validated $TARGET_COUNT targets"
echo "Running a conservative external inventory scan"
nmap \
-Pn \
-n \
-T3 \
--top-ports 20 \
-sV \
--version-light \
--open \
--max-retries 2 \
--host-timeout 2m \
-iL "$TARGETS_FILE" \
-oA "$RAW_BASENAME"
python3 - "$RAW_BASENAME.gnmap" "$SUMMARY_CSV" "$SUMMARY_MD" "$TARGET_COUNT" <<'PY'
import csv
import sys
from collections import defaultdict
gnmap_path, csv_path, md_path, total_targets = sys.argv[1:5]
rows = []
ports_by_ip = defaultdict(list)
seen_hosts = set()
with open(gnmap_path, "r", encoding="utf-8", errors="replace") as fh:
for raw in fh:
line = raw.strip()
if not line.startswith("Host: "):
continue
if "Ports: " not in line:
continue
host = line.split()[1]
seen_hosts.add(host)
ports_blob = line.split("Ports: ", 1)[1]
for item in ports_blob.split(", "):
parts = item.split("/")
if len(parts) < 7:
continue
port, state, proto, _, service, product, extra = parts[:7]
if state != "open":
continue
product_info = " ".join(x for x in (product, extra) if x).strip()
rows.append(
{
"ip": host,
"port": port,
"protocol": proto,
"state": state,
"service": service or "unknown",
"product": product_info or "-",
}
)
ports_by_ip[host].append(f"{port}/{proto} {service or 'unknown'}".strip())
with open(csv_path, "w", newline="", encoding="utf-8") as fh:
writer = csv.DictWriter(
fh,
fieldnames=["ip", "port", "protocol", "state", "service", "product"],
)
writer.writeheader()
writer.writerows(rows)
sorted_hosts = sorted(ports_by_ip.items(), key=lambda item: (-len(item[1]), item[0]))
with open(md_path, "w", encoding="utf-8") as fh:
fh.write("# External Inventory Summary\n\n")
fh.write(f"- Total validated targets: {total_targets}\n")
fh.write(f"- Hosts with at least one open top port: {len(sorted_hosts)}\n")
fh.write(f"- CSV details: `{csv_path}`\n")
fh.write(f"- Raw Nmap files: `{gnmap_path[:-6]}` (`.nmap`, `.gnmap`, `.xml`)\n\n")
fh.write("## Prioritized review queue\n\n")
fh.write("| IP | Open ports found | Services |\n")
fh.write("| --- | ---: | --- |\n")
for ip, entries in sorted_hosts:
fh.write(f"| {ip} | {len(entries)} | {', '.join(entries)} |\n")
print(f"Wrote {len(rows)} rows to {csv_path}")
print(f"Wrote Markdown summary to {md_path}")
PY
echo
echo "Inventory complete"
echo "Output directory: $OUTPUT_DIR"
echo "Summary CSV: $SUMMARY_CSV"
echo "Summary report: $SUMMARY_MD"