diff --git a/README.md b/README.md index d5af9d4..e5e2ddc 100644 --- a/README.md +++ b/README.md @@ -24,22 +24,22 @@ Basic usage of `seclook` is as follows: seclook [service] [value] ``` -For example, to look up IP 1.1.1.1 on VirusTotal, you'd run: +For example, to look up IP 1.1.1.1 on AbuseIPDB, you'd run: ```bash -seclook virustotal 1.1.1.1 +seclook abuseipdb 1.1.1.1 ``` -You can pipe the output to `jq` or `fx` for further processing: +You can pipe the output to `fx` or `jq` for further processing: ```bash -seclook virustotal 1.1.1.1 | jq +seclook emailrep andrew@akatz.org | fx ``` You can `grep` the output for known keys to get specific information: ```bash -seclook virustotal 1.1.1.1 | grep malicious +seclook virustotal 44d88612fea8a8f36de82e1278abb02f | grep malicious ``` ## Options @@ -54,6 +54,7 @@ seclook virustotal 1.1.1.1 | grep malicious - [x] [AbuseIPDB](https://www.abuseipdb.com/) - [x] [GreyNoise](https://www.greynoise.io/) - [x] [ThreatFox](https://threatfox.abuse.ch/) +- [x] [Pulsedive](https://pulsedive.com/) You can also view supported services by passing `list` as the service name: diff --git a/seclook/cli.py b/seclook/cli.py index 35ce255..9c28eba 100755 --- a/seclook/cli.py +++ b/seclook/cli.py @@ -8,6 +8,7 @@ abuseipdb_lookup, greynoise_lookup, threatfox_lookup, + pulsedive_lookup, ) import json import os @@ -33,6 +34,7 @@ def main(service, value, export): "abuseipdb", "greynoise", "threatfox", + "pulsedive", ] if not service: @@ -64,6 +66,8 @@ def main(service, value, export): result = greynoise_lookup.search(value) elif service.lower() == "threatfox": result = threatfox_lookup.search(value) + elif service.lower() == "pulsedive": + result = pulsedive_lookup.search(value) else: raise click.UsageError("Unknown service.") diff --git a/seclook/config_helper.py b/seclook/config_helper.py index dc56537..f9161bc 100644 --- a/seclook/config_helper.py +++ b/seclook/config_helper.py @@ -39,6 +39,19 @@ def load_config(service_name, key_required=True): ) sys.exit() else: - api_key = config[service_name].get("api_key", "") + try: + api_key = config[service_name].get("api_key", "") + except KeyError: + print( + textwrap.dedent( + f""" + KeyError: Ensure the following lines are present in config.ini: + + [{service_name}] + api_key = + """ + ) + ) + sys.exit() return config, api_key diff --git a/seclook/find_config.py b/seclook/find_config.py index 1009b8b..893e100 100644 --- a/seclook/find_config.py +++ b/seclook/find_config.py @@ -16,7 +16,7 @@ def find_config(): raise FileNotFoundError( "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" + "Copy and rename config.ini.sample as config.ini into " + "any of the directories mentioned above:\n" "https://github.com/ackatz/seclook/blob/main/config.ini.sample" ) diff --git a/seclook/lookups/pulsedive_lookup.py b/seclook/lookups/pulsedive_lookup.py new file mode 100644 index 0000000..dc84405 --- /dev/null +++ b/seclook/lookups/pulsedive_lookup.py @@ -0,0 +1,15 @@ +import requests +from seclook.config_helper import load_config + +base_url = "https://pulsedive.com/api/info.php" + + +def search(value): + config, pulsedive_api_key = load_config("pulsedive", key_required=False) + headers = {"Accept": "application/json"} + response = requests.get( + base_url.format(value), + headers=headers, + params={"key": pulsedive_api_key, "indicator": value}, + ) + return response.json() diff --git a/seclook/test_cli.py b/seclook/test_cli.py index 448ad45..f41eeae 100644 --- a/seclook/test_cli.py +++ b/seclook/test_cli.py @@ -23,6 +23,13 @@ def test_threatfox_missing_value(): assert "Missing value argument for 'threatfox'." in result.output +def test_pulsedive_missing_value(): + runner = CliRunner() + result = runner.invoke(main, ["pulsedive"]) + assert result.exit_code != 0 + assert "Missing value argument for 'pulsedive'." in result.output + + def test_shodan_missing_value(): runner = CliRunner() result = runner.invoke(main, ["shodan"]) @@ -71,6 +78,12 @@ def test_threatfox_valid_value(): assert result.exit_code == 0 +def test_pulsedive_valid_value(): + runner = CliRunner() + result = runner.invoke(main, ["pulsedive", "1.1.1.1"]) + assert result.exit_code == 0 + + def test_shodan_valid_value(): runner = CliRunner() result = runner.invoke(main, ["shodan", "1.1.1.1"]) diff --git a/setup.py b/setup.py index 2f9eab2..af4a314 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,10 @@ setup( name="seclook", - version="0.3.0", + author="Andrew Katz", + author_email="andrew@akatz.org", + license="MIT", + version="0.4.0", packages=find_packages(), include_package_data=True, install_requires=[