Skip to content

Commit

Permalink
Merge pull request #12 from ackatz/v0.3.0
Browse files Browse the repository at this point in the history
Cleanup and config helper script
  • Loading branch information
ackatz authored Oct 4, 2023
2 parents c2df87f + 30449af commit 91fadce
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 82 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 10 additions & 7 deletions config.ini.sample
Original file line number Diff line number Diff line change
@@ -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 =
32 changes: 11 additions & 21 deletions seclook/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}'.")

Expand All @@ -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")
Expand All @@ -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)

Expand Down
44 changes: 44 additions & 0 deletions seclook/config_helper.py
Original file line number Diff line number Diff line change
@@ -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 = <Note: A key is{' required' if key_required
else ' optional'} to use this API>
"""
)
)
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 = <Note: A key is required to use this API>
"""
)
)
sys.exit()
else:
api_key = config[service_name].get("api_key", "")

return config, api_key
19 changes: 10 additions & 9 deletions seclook/find_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
9 changes: 2 additions & 7 deletions seclook/lookups/abuseipdb_lookup.py
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
9 changes: 2 additions & 7 deletions seclook/lookups/emailrep_lookup.py
Original file line number Diff line number Diff line change
@@ -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()
9 changes: 2 additions & 7 deletions seclook/lookups/greynoise_lookup.py
Original file line number Diff line number Diff line change
@@ -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()
9 changes: 2 additions & 7 deletions seclook/lookups/shodan_lookup.py
Original file line number Diff line number Diff line change
@@ -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}
Expand Down
9 changes: 2 additions & 7 deletions seclook/lookups/threatfox_lookup.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
9 changes: 2 additions & 7 deletions seclook/lookups/virustotal_lookup.py
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name="seclook",
version="0.2.4",
version="0.3.0",
packages=find_packages(),
include_package_data=True,
install_requires=[
Expand Down

0 comments on commit 91fadce

Please sign in to comment.