summaryrefslogtreecommitdiff
path: root/src/soc_collector/soc_collector_cli.py
diff options
context:
space:
mode:
authorVictor Näslund <victor@sunet.se>2022-11-16 19:09:46 +0100
committerVictor Näslund <victor@sunet.se>2022-11-16 19:09:46 +0100
commit43e87d84b15d12d52a4dcde6e80426cbd17e3d6f (patch)
tree18cef29b77973053522a67677121789ebe032285 /src/soc_collector/soc_collector_cli.py
parent4a56b3aae4114db731eff725e2c6292371a9b8ae (diff)
auth and CLI done
Diffstat (limited to 'src/soc_collector/soc_collector_cli.py')
-rw-r--r--src/soc_collector/soc_collector_cli.py259
1 files changed, 259 insertions, 0 deletions
diff --git a/src/soc_collector/soc_collector_cli.py b/src/soc_collector/soc_collector_cli.py
new file mode 100644
index 0000000..e32b5ad
--- /dev/null
+++ b/src/soc_collector/soc_collector_cli.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""Our database module"""
+from os.path import isfile
+from os import environ
+from typing import Dict, Any
+from argparse import ArgumentParser, RawTextHelpFormatter
+from sys import exit as app_exit
+import json
+import requests
+
+if "COLLECTOR_API_KEY" not in environ:
+ print("Missing 'COLLECTOR_API_KEY' in environment")
+ app_exit(1)
+API_KEY = environ["COLLECTOR_API_KEY"]
+
+API_URL = "https://collector-dev.soc.sunet.se:8000"
+ROOT_CA_FILE = __file__.replace("soc_collector_cli.py", "data/collector_root_ca.crt")
+
+
+def json_load_data(data: str) -> Dict[str, Any]:
+ """Load json from argument, json data or path to json file
+
+ :param data: String with either json or path to a json file.
+ :return: json dict as a Dict[str, Any].
+ """
+
+ ret: Dict[str, Any]
+ try:
+ if isfile(data):
+ with open(data, encoding="utf-8") as f_data:
+ ret = json.loads(f_data.read())
+ else:
+ ret = json.loads(data)
+ return ret
+
+ except Exception: # pylint: disable=broad-except
+ print(f"No such file or invalid json data: {data}")
+ app_exit(1)
+
+
+def info_action() -> None:
+ """Get database info, currently number of documents."""
+
+ req = requests.get(f"{API_URL}/info", headers={"API-KEY": API_KEY}, timeout=5)
+
+ # Ensure ok status
+ req.raise_for_status()
+
+ # Print data
+ json_data = json.loads(req.text)
+ print(f"Estimated document count: {json_data['Estimated document count']}")
+
+
+def search_action(data: str) -> None:
+ """Search for documents in the database.
+
+ :param data: String with either json or path to a json file.
+ """
+
+ search_data = json_load_data(data)
+
+ req = requests.post(f"{API_URL}/sc/v0/search", headers={"API-KEY": API_KEY}, json=search_data, timeout=5)
+
+ # Ensure ok status
+ req.raise_for_status()
+
+ # Print data
+ json_data = json.loads(req.text)
+ print(json.dumps(json_data["docs"], indent=4))
+
+
+def delete_action(data: str) -> None:
+ """Delete a document in the DB.
+
+ :param data: key or path to a json file containing "_id".
+ """
+
+ if isfile(data):
+ data = json_load_data(data)["_id"]
+ req = requests.delete(f"{API_URL}/sc/v0/{data}", headers={"API-KEY": API_KEY}, timeout=5)
+
+ # Check status
+ if req.status_code == 404:
+ print("ERROR: Document not found")
+ app_exit(1)
+
+ # Ensure ok status
+ req.raise_for_status()
+
+ print(f"Deleted data OK - key: {data}")
+
+
+def update_local_action(data: str, update_data: str) -> None:
+ """Update keys and their data in this document. Does not modify the database.
+
+ :param data: json blob or path to json file.
+ :param update_data: json blob or path to json file.
+ """
+
+ json_data = json_load_data(data)
+ json_update_data = json_load_data(update_data)
+
+ json_data.update(json_update_data)
+
+ # Print data
+ print(json.dumps(json_data, indent=4))
+
+
+def replace_action(data: str) -> None:
+ """Replace the entire document in the database with this document, "_id" must exist as a key.
+
+ :param data: json blob or path to json file, "_id" key must exist.
+ """
+
+ json_data = json_load_data(data)
+ req = requests.put(f"{API_URL}/sc/v0", json=json_data, headers={"API-KEY": API_KEY}, timeout=5)
+
+ # Check status
+ if req.status_code == 404:
+ print("ERROR: Document not found")
+ app_exit(1)
+
+ # Ensure ok status
+ req.raise_for_status()
+
+ json_data = json.loads(req.text)
+ print(f'Inserted data OK - key: {json_data["_id"]}')
+
+
+def insert_action(data: str) -> None:
+ """Insert a new document into the database, "_id" must not exist in the document.
+
+ :param data: json blob or path to json file, "_id" key must not exist.
+ """
+
+ json_data = json_load_data(data)
+ req = requests.post(f"{API_URL}/sc/v0", json=json_data, headers={"API-KEY": API_KEY}, timeout=5)
+
+ # Ensure ok status
+ req.raise_for_status()
+
+ data = json.loads(req.text)
+ print(f'Inserted data OK - key: {json_data["_id"]}')
+
+
+def get_action(data: str) -> None:
+ """Get a document from the database.
+
+ :param data: key or path to a json file containing "_id".
+ """
+ if isfile(data):
+ data = json_load_data(data)["_id"]
+ req = requests.get(f"{API_URL}/sc/v0/{data}", headers={"API-KEY": API_KEY}, timeout=5)
+
+ # Check status
+ if req.status_code == 404:
+ print("ERROR: Document not found")
+ app_exit(1)
+
+ # Ensure ok status
+ req.raise_for_status()
+
+ # Print data
+ json_data = json.loads(req.text)
+ print(json.dumps(json_data["doc"], indent=4))
+
+
+def main() -> None:
+ """Main function."""
+ parser = ArgumentParser(formatter_class=RawTextHelpFormatter, description="SOC Collector CLI")
+ parser.add_argument(
+ "action",
+ choices=["info", "search", "get", "insert", "replace", "update_local", "delete"],
+ help="""Action to take
+
+ info: Show info about the database, currently number of documents.
+
+ search: json blog OR path to file. skip defaults to 0 and limit to 25.
+ '{"search": {"port": 111, "ip": "192.0.2.28"}, "skip": 0, "limit": 25}' OR ./search_data.json
+ '{"search": {"asn_country_code": "SE", "result": {"$exists": "cve_2015_0002"}}}'
+
+ get: key OR path to document using its "_id".
+ 637162378c92893fff92bf7e OR ./data.json
+
+ insert: json blob OR path to file. Document MUST NOT contain "_id".
+ '{json_blob_here...}' OR ./data.json
+
+ replace: json blob OR path to file. Document MUST contain "_id".
+ '{json_blob_here...}' OR ./updated_document.json
+
+ update_local: json blob OR path to file json blob or path to file with json to update with.
+ This does NOT send data to the database, use replace for that.
+ 1st ARG: '{json_blob_here...}' OR ./data.json 2th ARG:'{"port": 555, "some_key": "some_data"}' OR ./data.json
+
+ delete: key OR path to file using its "_id".
+ 637162378c92893fff92bf7e OR ./data.json
+
+
+ Pro tip, in ~/.bashrc:
+
+ _soc_collector_cli()
+ {
+ local cur prev words cword
+ _init_completion || return
+
+ local commands command
+
+ commands='info search get insert replace update_local delete'
+
+ if ((cword == 1)); then
+ COMPREPLY=($(compgen -W "$commands" -- "$cur"))
+ else
+
+ case $prev in
+ search)
+ COMPREPLY="'{\\"search\\": {\\"asn_country_code\\": \\"SE\\", \\"ip\\": \\"8.8.8.8\\"}}'"
+ return
+ ;;
+ esac
+ command=${words[1]}
+ _filedir
+ return
+ fi
+ }
+ complete -F _soc_collector_cli -o default soc_collector_cli
+
+ """,
+ )
+ parser.add_argument("data", default="info", help="json blob or path to file")
+ parser.add_argument(
+ "extra_data",
+ nargs="?",
+ default=None,
+ help="json blob or path to file, only used for local_edit",
+ )
+
+ args = parser.parse_args()
+
+ if args.action == "get":
+ get_action(args.data)
+ elif args.action == "insert":
+ insert_action(args.data)
+ elif args.action == "replace":
+ replace_action(args.data)
+ elif args.action == "update_local" and args.extra_data is not None:
+ update_local_action(args.data, args.extra_data)
+ elif args.action == "delete":
+ delete_action(args.data)
+ elif args.action == "search":
+ search_action(args.data)
+ elif args.action == "info":
+ info_action()
+ else:
+ print("ERROR: Wrong action")
+ app_exit(1)
+
+
+if __name__ == "__main__":
+ main()