From 51a0c28bcffcfee72798be5d6f08272fd6f8c71d Mon Sep 17 00:00:00 2001 From: Luke Tainton Date: Sat, 25 Jun 2022 20:28:57 +0100 Subject: [PATCH] Working --- .github/workflows/ci.yml | 1 + app/__init__.py | 0 app/args.py | 30 ++++++++++++++++ app/ip_info.py | 25 +++++++++++++ app/main.py | 58 +++++++++++++++++++++++++++++-- app/print_table.py | 18 ++++++++++ app/query_normalisation.py | 26 ++++++++++++++ tests/__init__.py | 0 tests/test_ip_info.py | 25 +++++++++++++ tests/test_main.py | 18 ++++++++++ tests/test_query_normalisation.py | 34 ++++++++++++++++++ 11 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 app/__init__.py create mode 100644 app/args.py create mode 100644 app/ip_info.py create mode 100644 app/print_table.py create mode 100644 app/query_normalisation.py create mode 100644 tests/__init__.py create mode 100644 tests/test_ip_info.py create mode 100644 tests/test_query_normalisation.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f7495a..01ee9a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: - 'README.md' - 'LICENSE.md' - '.gitignore' + - 'renovate.json' pull_request: jobs: diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/args.py b/app/args.py new file mode 100644 index 0000000..db7fee8 --- /dev/null +++ b/app/args.py @@ -0,0 +1,30 @@ +#!/usr/local/bin/python3 + +import argparse + +from app.query_normalisation import get_public_ip + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Query information about an IP address or domain name." + ) + parser.add_argument( + "-q", + "--query", + help="IP/domain name to query (default: current public IP)", + default=get_public_ip(), + ) + parser.add_argument( + "-p", + "--prefixes", + help="show advertised prefixes", + action="store_true", + ) + parser.add_argument( + "-n", + "--noheader", + help="do not print header", + action="store_true", + ) + return parser.parse_args() diff --git a/app/ip_info.py b/app/ip_info.py new file mode 100644 index 0000000..1888465 --- /dev/null +++ b/app/ip_info.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +import ipaddress +import requests + + +def get_ip_information(ipv4_address: ipaddress.IPv4Address) -> dict: + api_endpoint = "http://ip-api.com/json/{}".format(ipv4_address) + resp = requests.get(api_endpoint).json() + return resp + + +def get_autonomous_system_number(as_info: str) -> str: + as_number = as_info.split(" ")[0] + return as_number + + +def get_prefix_information(autonomous_system: int) -> list: + api_endpoint = "https://api.hackertarget.com/aslookup/?q={}".format( + str(autonomous_system) + ) + resp = requests.get(api_endpoint).text + prefixes = resp.split("\n") + prefixes.pop(0) + return prefixes diff --git a/app/main.py b/app/main.py index a6053f8..c0f5d6f 100644 --- a/app/main.py +++ b/app/main.py @@ -1,8 +1,62 @@ #!/usr/local/bin/python3 +from app.args import parse_args +from app.print_table import print_table, generate_prefix_string +from app.query_normalisation import is_ip_address, resolve_domain_name +from app.ip_info import ( + get_ip_information, + get_autonomous_system_number, + get_prefix_information, +) + + +HEADER = """----------------------------------------------- +| IP Address Information Lookup Tool (iPilot) | +| By Luke Tainton (@luketainton) | +-----------------------------------------------\n""" + + def main(): - # Commands here - + args = parse_args() + if not args.noheader: + print(HEADER) + + # Set IP to passed IP address, or resolve passed domain name to IPv4 + ip = ( + resolve_domain_name(args.query) if not is_ip_address(args.query) else args.query + ) + + # If not given an IPv4, and can't resolve to IPv4, then throw error and exit + if not ip: + print("ERROR: could not resolve query to IPv4 address.") + exit(1) + + # Get information from API + ip_info = get_ip_information(ip) + as_number = get_autonomous_system_number(ip_info.get("as")) + + # Assemble list for table generation + table_data = [ + ["IP Address", ip_info.get("query")], + ["Organization", ip_info.get("org")], + [ + "Location", + "{}/{}/{}".format( + ip_info.get("country"), ip_info.get("regionName"), ip_info.get("city") + ), + ], + ["Timezone", ip_info.get("timezone")], + ["Internet Service Provider", ip_info.get("isp")], + ["Autonomous System", as_number], + ] + + # If wanted, get prefix information + if args.prefixes: + prefix_info = get_prefix_information(as_number) + table_data.append(["Prefixes", generate_prefix_string(prefix_info)]) + + print_table(table_data) + if __name__ == "__main__": main() diff --git a/app/print_table.py b/app/print_table.py new file mode 100644 index 0000000..17d8e6d --- /dev/null +++ b/app/print_table.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +from tabulate import tabulate + + +def generate_prefix_string(prefixes: list) -> str: + n = 4 + try: + ret = "" + for i in range(0, len(prefixes), n): + ret += ", ".join(prefixes[i : i + n]) + "\n" + return ret + except AttributeError: + return None + + +def print_table(table_data) -> None: + print(tabulate(table_data)) diff --git a/app/query_normalisation.py b/app/query_normalisation.py new file mode 100644 index 0000000..68c24d7 --- /dev/null +++ b/app/query_normalisation.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +import ipaddress +import requests +import socket + + +def is_ip_address(query: str) -> bool: + try: + ipaddress.ip_address(query) + return True + except ValueError: + return False + + +def resolve_domain_name(domain_name: str) -> ipaddress.IPv4Address: + try: + ip = socket.gethostbyname(domain_name) + except socket.gaierror: + ip = None + return ip + + +def get_public_ip() -> ipaddress.IPv4Address: + ip = requests.get("https://api.ipify.org").text + return ip diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_ip_info.py b/tests/test_ip_info.py new file mode 100644 index 0000000..6c9cf1a --- /dev/null +++ b/tests/test_ip_info.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +from app.ip_info import ( + get_ip_information, + get_autonomous_system_number, + get_prefix_information, +) + + +def test_get_ip_information() -> None: + test_query = "1.2.3.4" + ip_info = get_ip_information(test_query) + assert ip_info.get("status") == "success" and ip_info.get("query") == test_query + + +def test_get_autonomous_system_number() -> None: + as_info = "AS5089 Virgin Media Limited" + as_number = get_autonomous_system_number(as_info) + assert as_number == "AS5089" + + +def test_get_prefix_information() -> None: + autonomous_system = "AS109" + prefixes = get_prefix_information(autonomous_system) + assert "144.254.0.0/16" in prefixes diff --git a/tests/test_main.py b/tests/test_main.py index e69de29..147f528 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +# from app.ip import is_ip_address + + +# def test_is_ip_address_true() -> None: +# test_query = "1.2.3.4" +# assert is_ip_address(test_query) + + +# def test_is_ip_address_false_ip() -> None: +# test_query = "256.315.16.23" +# assert not is_ip_address(test_query) + + +# def test_is_ip_address_false_fqdn() -> None: +# test_query = "google.com" +# assert not is_ip_address(test_query) diff --git a/tests/test_query_normalisation.py b/tests/test_query_normalisation.py new file mode 100644 index 0000000..6519a37 --- /dev/null +++ b/tests/test_query_normalisation.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +from app.query_normalisation import is_ip_address, resolve_domain_name, get_public_ip + + +def test_is_ip_address_true() -> None: + test_query = "1.2.3.4" + assert is_ip_address(test_query) + + +def test_is_ip_address_false_ip() -> None: + test_query = "256.315.16.23" + assert not is_ip_address(test_query) + + +def test_is_ip_address_false_fqdn() -> None: + test_query = "google.com" + assert not is_ip_address(test_query) + + +def test_resolve_domain_name_true() -> None: + domain_name = "one.one.one.one" + expected_results = ["1.1.1.1", "1.0.0.1"] # Could resolve to either IP + assert resolve_domain_name(domain_name) in expected_results + + +def test_resolve_domain_name_false() -> None: + domain_name = "hrrijoresdo.com" + assert not resolve_domain_name(domain_name) + + +def test_get_public_ip() -> None: + public_ip = get_public_ip() + assert is_ip_address(public_ip)