Files

262 lines
8.1 KiB
Python

#!/usr/bin/env python3
"""
cloud_enrich.py — IP-to-cloud-provider enrichment for FLOCK_scan
Turns hardcoded FLOCK_CLOUD_IPS into dynamic enrichment:
- WHOIS / ASN lookup via ip-api.com or ipinfo.io (free, no key)
- Maps IP → { org, asn, country, region, cloud_provider }
- Cloud provider detection from ASN + org name
Usage:
from modules.cloud_enrich import enrich_ip, enrich_ip_batch
info = enrich_ip("52.72.49.79")
# → {"ip": "...", "org": "Amazon Technologies", "asn": "AS16509",
# "country": "US", "region": "Virginia", "cloud": "AWS"}
"""
import json
import re
import socket
# ── Fallback ASN database (no network call) ────────────────────────
# Maps ASN prefixes to known cloud providers.
# Helps when ip-api.com is unavailable.
CLOUD_ASN_MAP = {
# AWS
"16509": "AWS", "14618": "AWS", "7224": "AWS",
"8987": "AWS", "17493": "AWS", "39111": "AWS",
"31763": "AWS", "38895": "AWS", "7018": "AWS",
# GCP / Google Cloud
"15169": "GCP", "36040": "GCP", "36384": "GCP",
"41264": "GCP", "19448": "GCP", "26910": "GCP",
# Azure / Microsoft
"8075": "Azure", "12076": "Azure", "63314": "Azure",
"13100": "Azure", "31898": "Azure", "13526": "Azure",
# CloudFlare
"13335": "CloudFlare", "209242": "CloudFlare",
"14789": "CloudFlare", "203898": "CloudFlare",
# DigitalOcean
"14061": "DigitalOcean", "62567": "DigitalOcean",
# OVH
"16276": "OVH", "35540": "OVH",
# Linode
"63949": "Linode", "48270": "Linode",
# Vultr
"20473": "Vultr", "208722": "Vultr",
# Hetzner
"24940": "Hetzner", "213230": "Hetzner",
# Oracle Cloud
"31898": "Oracle", "395050": "Oracle",
# Fastly
"54113": "Fastly", "201737": "Fastly",
# Akamai
"16625": "Akamai", "12222": "Akamai", "21399": "Akamai",
# Linode
"63949": "Linode",
# Scaleway
"12876": "Scaleway",
# UpCloud
"202053": "UpCloud",
}
CLOUD_ORG_KEYWORDS = [
("amazon", "AWS"),
("aws", "AWS"),
("amazon technologies", "AWS"),
("amazon data services", "AWS"),
("amazon web services", "AWS"),
("amazon.com", "AWS"),
("elastic load balancing", "AWS"),
("google cloud", "GCP"),
("google compute", "GCP"),
("gcp", "GCP"),
("microsoft azure", "Azure"),
("azure", "Azure"),
("microsoft corporation", "Azure"),
("cloudflare", "CloudFlare"),
("digitalocean", "DigitalOcean"),
("linode", "Linode"),
("vultr", "Vultr"),
("hetzner", "Hetzner"),
("oracle cloud", "Oracle"),
("oracle public cloud", "Oracle"),
("ovh", "OVH"),
("fastly", "Fastly"),
("akamai", "Akamai"),
("scaleway", "Scaleway"),
("upcloud", "UpCloud"),
]
# ── ASN / WHOIS Lookup ──────────────────────────────────────────────
def _reverse_dns(ip, timeout=3):
"""Try to PTR the IP — sometimes reveals cloud hostname directly."""
try:
name, _, _ = socket.gethostbyaddr(ip)
return name
except Exception:
return None
def enrich_ip(ip, timeout=5):
"""
Look up IP enrichment data from ip-api.com (free, no API key).
Returns dict with:
ip, org, asn, country, region, city, cloud, reverse_dns
Falls back gracefully if the HTTP lookup fails.
"""
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
result = {
"ip": ip,
"org": None,
"asn": None,
"country": None,
"region": None,
"city": None,
"cloud": None,
"reverse_dns": None,
}
# PTR first (fast, local)
try:
rdns = _reverse_dns(ip)
result["reverse_dns"] = rdns
except Exception:
pass
# ip-api.com — limited to 45 req/min from a single IP (free tier)
try:
r = requests.get(
f"http://ip-api.com/json/{ip}",
timeout=timeout,
headers={"User-Agent": "FLOCK_scan/3.0"},
)
if r.status_code == 200:
data = r.json()
if data.get("status") == "success":
result["org"] = data.get("org")
result["asn"] = data.get("asn")
result["country"] = data.get("country")
result["region"] = data.get("regionName")
result["city"] = data.get("city")
result["isp"] = data.get("isp")
# Determine cloud provider
org = (result["org"] or "").lower()
asn = result.get("asn") or ""
result["cloud"] = _detect_cloud_provider(org, asn)
return result
except Exception:
pass
# Fallback: cli-based whois
try:
import subprocess
whois_out = subprocess.run(
["whois", ip],
capture_output=True, text=True, timeout=timeout
).stdout.lower()
for line in whois_out.split("\n"):
if "orgname:" in line:
result["org"] = line.split(":", 1)[1].strip()
if "originas:" in line or "origin:" in line:
asn = line.split(":", 1)[1].strip().lstrip("AS")
result["asn"] = f"AS{asn}"
if "netname:" in line:
if not result.get("org"):
result["org"] = line.split(":", 1)[1].strip()
if "country:" in line and not result.get("country"):
result["country"] = line.split(":", 1)[1].strip().upper()
org = (result.get("org") or "").lower()
asn = result.get("asn", "").replace("AS", "")
result["cloud"] = _detect_cloud_provider(org, asn)
except Exception:
pass
return result
def _detect_cloud_provider(org, asn):
"""Match org string + ASN against known cloud providers."""
# ASN match first
if asn and asn in CLOUD_ASN_MAP:
return CLOUD_ASN_MAP[asn]
# Org keyword match
for keyword, provider in CLOUD_ORG_KEYWORDS:
if keyword in org.lower():
return provider
# PTR-based: if we have a reverse DNS, check for cloud patterns
return None
def enrich_ip_batch(ips, timeout=10):
"""
Batch enrich multiple IPs.
Handles ip-api.com's 45 req/min rate limit with simple sleep.
Also uses batch endpoint for efficiency.
Returns dict of ip -> result.
"""
import requests
import time
# Try batch endpoint first (ip-api.com supports up to 100 IPs)
try:
r = requests.post(
"http://ip-api.com/batch",
json=ips[:100], # max 100 per batch
timeout=timeout,
headers={"User-Agent": "FLOCK_scan/3.0"},
)
if r.status_code == 200:
batch_data = r.json()
results = {}
for item in batch_data:
ip = item.get("query")
if not ip:
continue
org = item.get("org", "")
asn = item.get("asn", "")
results[ip] = {
"ip": ip,
"org": org,
"asn": asn,
"country": item.get("country"),
"region": item.get("regionName"),
"city": item.get("city"),
"isp": item.get("isp"),
"cloud": _detect_cloud_provider(
(org or "").lower(),
(asn or "").replace("AS", "")
),
"reverse_dns": _reverse_dns(ip),
}
return results
except Exception:
pass
# Fallback: one by one
results = {}
for ip in ips:
results[ip] = enrich_ip(ip, timeout=5)
time.sleep(1.5) # rate limit: ~40/min
return results
# ── CLI test ─────────────────────────────────────────────────────────
if __name__ == "__main__":
import sys
target = sys.argv[1] if len(sys.argv) > 1 else "52.72.49.79"
print(json.dumps(enrich_ip(target), indent=2, default=str))