From 30449af94e36032bf0e4c22e14226ee3fe364c84 Mon Sep 17 00:00:00 2001 From: ackatz Date: Wed, 4 Oct 2023 13:38:25 -0600 Subject: [PATCH] Cleanup and config helper script --- README.md | 8 +++-- config.ini.sample | 17 ++++++----- seclook/cli.py | 32 +++++++------------- seclook/config_helper.py | 44 ++++++++++++++++++++++++++++ seclook/find_config.py | 19 ++++++------ seclook/lookups/abuseipdb_lookup.py | 9 ++---- seclook/lookups/emailrep_lookup.py | 9 ++---- seclook/lookups/greynoise_lookup.py | 9 ++---- seclook/lookups/shodan_lookup.py | 9 ++---- seclook/lookups/threatfox_lookup.py | 9 ++---- seclook/lookups/virustotal_lookup.py | 9 ++---- setup.py | 2 +- 12 files changed, 94 insertions(+), 82 deletions(-) create mode 100644 seclook/config_helper.py diff --git a/README.md b/README.md index 358ecf2..82820e7 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,17 @@ # seclook -`seclook` is a security lookup CLI tool that allows you to query various security services on the fly. You can look up information using commands like `seclook [service] [value]`, where the service can be `virustotal`, `shodan`, `emailrep`, and so on. The value is the information you're querying for and varies by service. +`seclook` is a security lookup CLI tool that allows you to query various security services on the fly. It is essentially a wrapper over the `requests` library that removes the need to manually search within Web UIs or write your own requests in Postman or cURL to query these services. + +You can look up information using commands like `seclook [service] [value]`, where the service can be `virustotal`, `shodan`, `emailrep`, and so on. The value is the information you're querying for and varies by service. ## Installation 1. `pip install seclook` 2. Copy [config.ini.sample](https://github.com/ackatz/seclook/blob/main/config.ini.sample) from this directory and place it in `~/.seclook/config.ini` -3. Open `~/.seclook/config.ini` and add in your own API keys for the services you want to use +3. Open `~/.seclook/config.ini` and add in your own API keys for the services you want to use. + +> Some services (e.g., GreyNoise, ThreatFox) _don't require API keys_, but may be rate-limited more quickly without one or have other limitations. ## Usage diff --git a/config.ini.sample b/config.ini.sample index 12f8536..9c52de4 100644 --- a/config.ini.sample +++ b/config.ini.sample @@ -1,17 +1,20 @@ -[abuseipdb] +# Where API key is optional, requests to that service will still work without a key +# However, requests may be rate-limited or otherwise limited in scope + +[greynoise] # API key optional api_key = -[emailrep] +[shodan] # API key optional +api_key = + +[threatfox] # API key optional api_key = -[greynoise] +[abuseipdb] api_key = -[shodan] +[emailrep] api_key = [virustotal] api_key = - -[threatfox] -api_key = \ No newline at end of file diff --git a/seclook/cli.py b/seclook/cli.py index ad55209..35ce255 100755 --- a/seclook/cli.py +++ b/seclook/cli.py @@ -25,38 +25,30 @@ def main(service, value, export): - Use `seclook list` to see a list of available services. """ - if not service: - raise click.UsageError("Missing service argument.") - - # If service doesn't exist, print an error message - if service.lower() not in [ - "list", + services = [ + "list", # Keep at top, so services.pop(0) always removes it "shodan", "virustotal", "emailrep", "abuseipdb", "greynoise", "threatfox", - ]: - raise click.UsageError(f"'{service}'a is not available in seclook.") + ] + + if not service: + raise click.UsageError("Missing service argument.") + + if service.lower() not in services: + raise click.UsageError(f"'{service}' is not available in seclook.") - # Special service name to list available services if service.lower() == "list": - services = [ - "- Shodan", - "- VirusTotal", - "- Emailrep", - "- AbuseIPDB", - "- GreyNoise", - "- ThreatFox", - ] + services.pop(0) # L29 click.echo("Available services:") for service in services: - click.echo(service) + click.echo("- " + service.capitalize()) click.echo("Run 'seclook [service] [value]' to perform a lookup.") return - # If value is not provided for a service lookup, print an error message if not value: raise click.UsageError(f"Missing value argument for '{service}'.") @@ -75,7 +67,6 @@ def main(service, value, export): else: raise click.UsageError("Unknown service.") - # If export flag is set, save to a JSON file on the desktop if export: desktop = os.path.join(os.path.expanduser("~"), "Desktop") filename = os.path.join(desktop, f"seclook_{service}_{value}.json") @@ -86,7 +77,6 @@ def main(service, value, export): click.echo(f"Results exported to {filename}") return - # Pretty print the result pretty_result = json.dumps(result, indent=4) click.echo(pretty_result) diff --git a/seclook/config_helper.py b/seclook/config_helper.py new file mode 100644 index 0000000..dc56537 --- /dev/null +++ b/seclook/config_helper.py @@ -0,0 +1,44 @@ +import configparser +from seclook.find_config import find_config +import textwrap +import sys + + +def load_config(service_name, key_required=True): + try: + config_path = find_config() + config = configparser.ConfigParser() + config.read(config_path) + except configparser.ParsingError: + print( + textwrap.dedent( + f""" + ParsingError: Ensure the following lines are present in config.ini: + + [{service_name}] + api_key = + """ + ) + ) + sys.exit() + + if key_required: + try: + api_key = config[service_name]["api_key"] + except KeyError: + print( + textwrap.dedent( + f""" + KeyError: Ensure the following lines are present in config.ini: + + [{service_name}] + api_key = + """ + ) + ) + sys.exit() + else: + api_key = config[service_name].get("api_key", "") + + return config, api_key diff --git a/seclook/find_config.py b/seclook/find_config.py index 45a8c81..1009b8b 100644 --- a/seclook/find_config.py +++ b/seclook/find_config.py @@ -2,20 +2,21 @@ def find_config(): - # List of directories to look in dirs_to_check = [ - os.getcwd(), # Current working directory - os.path.dirname(os.path.abspath(__file__)), # Directory of this script - os.path.join( - os.path.expanduser("~"), ".seclook" - ), # A .seclook directory in the user's home directory + os.getcwd(), + os.path.dirname(os.path.abspath(__file__)), + os.path.join(os.path.expanduser("~"), ".seclook"), ] - for dir in dirs_to_check: - config_path = os.path.join(dir, "config.ini") + for directory in dirs_to_check: + config_path = os.path.join(directory, "config.ini") if os.path.exists(config_path): return config_path raise FileNotFoundError( - "No config.ini file found in any of the checked directories" + "No config.ini file found in any of the checked directories:\n" + "({})".format(", ".join(dirs_to_check)) + "\n" + "Copy and rename config.ini.sample as config.ini into ~/.seclook/ " + "or any of the other directories mentioned above:\n" + "https://github.com/ackatz/seclook/blob/main/config.ini.sample" ) diff --git a/seclook/lookups/abuseipdb_lookup.py b/seclook/lookups/abuseipdb_lookup.py index bbd71af..9a88c3d 100644 --- a/seclook/lookups/abuseipdb_lookup.py +++ b/seclook/lookups/abuseipdb_lookup.py @@ -1,16 +1,11 @@ import requests -import configparser -from seclook.find_config import find_config - -config_path = find_config() -config = configparser.ConfigParser() -config.read(config_path) +from seclook.config_helper import load_config base_url = "https://api.abuseipdb.com/api/v2/check" def search(value): - abuseipdb_api_key = config["abuseipdb"]["api_key"] + config, abuseipdb_api_key = load_config("abuseipdb") headers = {"Accept": "application/json", "Key": abuseipdb_api_key} params = {"ipAddress": value, "maxAgeInDays": 90, "verbose": True} response = requests.get(base_url, headers=headers, params=params) diff --git a/seclook/lookups/emailrep_lookup.py b/seclook/lookups/emailrep_lookup.py index e156870..5668d37 100644 --- a/seclook/lookups/emailrep_lookup.py +++ b/seclook/lookups/emailrep_lookup.py @@ -1,16 +1,11 @@ import requests -import configparser -from seclook.find_config import find_config - -config_path = find_config() -config = configparser.ConfigParser() -config.read(config_path) +from seclook.config_helper import load_config base_url = "https://emailrep.io/{}" def search(value): - emailrep_api_key = config["emailrep"]["api_key"] + config, emailrep_api_key = load_config("emailrep") headers = {"Key": emailrep_api_key} response = requests.get(base_url.format(value), headers=headers) return response.json() diff --git a/seclook/lookups/greynoise_lookup.py b/seclook/lookups/greynoise_lookup.py index 819924e..b436aec 100644 --- a/seclook/lookups/greynoise_lookup.py +++ b/seclook/lookups/greynoise_lookup.py @@ -1,16 +1,11 @@ import requests -import configparser -from seclook.find_config import find_config - -config_path = find_config() -config = configparser.ConfigParser() -config.read(config_path) +from seclook.config_helper import load_config base_url = "https://api.greynoise.io/v3/community/{}" def search(value): - greynoise_api_key = config["greynoise"]["api_key"] + config, greynoise_api_key = load_config("greynoise", key_required=False) headers = {"Accept": "application/json", "key": greynoise_api_key} response = requests.get(base_url.format(value), headers=headers) return response.json() diff --git a/seclook/lookups/shodan_lookup.py b/seclook/lookups/shodan_lookup.py index d8a5bf3..baf1ec9 100644 --- a/seclook/lookups/shodan_lookup.py +++ b/seclook/lookups/shodan_lookup.py @@ -1,16 +1,11 @@ import requests -import configparser -from seclook.find_config import find_config - -config_path = find_config() -config = configparser.ConfigParser() -config.read(config_path) +from seclook.config_helper import load_config base_url = "https://api.shodan.io/shodan/host/{}" def search(value): - shodan_api_key = config["shodan"]["api_key"] + config, shodan_api_key = load_config("shodan", key_required=False) headers = {"Accept": "application/json"} response = requests.get( base_url.format(value), headers=headers, params={"key": shodan_api_key} diff --git a/seclook/lookups/threatfox_lookup.py b/seclook/lookups/threatfox_lookup.py index 42c1ca9..ad688cd 100644 --- a/seclook/lookups/threatfox_lookup.py +++ b/seclook/lookups/threatfox_lookup.py @@ -1,16 +1,11 @@ import requests -import configparser -from seclook.find_config import find_config - -config_path = find_config() -config = configparser.ConfigParser() -config.read(config_path) +from seclook.config_helper import load_config base_url = "https://threatfox-api.abuse.ch/api/v1/" def search(value): - threatfox_api_key = config["threatfox"]["api_key"] + config, threatfox_api_key = load_config("threatfox", key_required=False) headers = { "Content-Type": "application/json", "API-KEY": threatfox_api_key, diff --git a/seclook/lookups/virustotal_lookup.py b/seclook/lookups/virustotal_lookup.py index 24c8261..06b950d 100644 --- a/seclook/lookups/virustotal_lookup.py +++ b/seclook/lookups/virustotal_lookup.py @@ -1,16 +1,11 @@ import requests -import configparser -from seclook.find_config import find_config - -config_path = find_config() -config = configparser.ConfigParser() -config.read(config_path) +from seclook.config_helper import load_config base_url = "https://www.virustotal.com/api/v3/search" def search(value): - virustotal_api_key = config["virustotal"]["api_key"] + config, virustotal_api_key = load_config("virustotal") headers = {"x-apikey": virustotal_api_key} params = {"query": value} response = requests.get(base_url, headers=headers, params=params) diff --git a/setup.py b/setup.py index df9c940..2f9eab2 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="seclook", - version="0.2.4", + version="0.3.0", packages=find_packages(), include_package_data=True, install_requires=[