Add safe external inventory scripts
This commit is contained in:
Executable
+151
@@ -0,0 +1,151 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user