Skip to content

Commit 370c375

Browse files
authored
Merge pull request #23 from nbargnesi/report-api
start building out MongoDB impl of report API
2 parents 3c97376 + bdb8075 commit 370c375

File tree

7 files changed

+251
-104
lines changed

7 files changed

+251
-104
lines changed

lib/cuckoo/core/reporting/__init__.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import enum
2+
3+
from lib.cuckoo.common import config
4+
from .api import Reports
5+
from .backends import mongodb, elasticsearch, null
6+
7+
8+
class Backend(enum.Enum):
9+
MONGODB = enum.auto()
10+
ELASTICSEARCH = enum.auto()
11+
NONE = enum.auto()
12+
13+
14+
# Reports are disabled by default. Enable it by configuring MongoDB or Elasticsearch
15+
# and calling init_reports.
16+
_enabled: bool = False
17+
_backend: Backend = Backend.NONE
18+
19+
20+
def enabled():
21+
return _enabled
22+
23+
24+
def disabled():
25+
return not enabled()
26+
27+
28+
def configured(cfg: config.Config) -> bool:
29+
if cfg.mongodb.enabled:
30+
return True
31+
if cfg.elasticsearchdb.enabled:
32+
return True
33+
return False
34+
35+
36+
def init_reports(cfg: config.Config) -> Reports:
37+
global _enabled, _backend
38+
if cfg.mongodb.enabled:
39+
_enabled, _backend = True, Backend.MONGODB
40+
return mongodb.MongoDBReports(cfg)
41+
elif cfg.elasticsearchdb.enabled:
42+
_enabled, _backend = True, Backend.ELASTICSEARCH
43+
return elasticsearch.ElasticsearchReports(cfg)
44+
_enabled, _backend = False, null.Null(cfg)

lib/cuckoo/core/reporting/api.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
class Reports:
2+
"""Interface to report backends."""
3+
4+
def get(self, task_id: int) -> dict:
5+
raise NotImplementedError()
6+
7+
def delete(self, task_id: int) -> bool:
8+
raise NotImplementedError()
9+
10+
def search(self, term, value, limit=False, projection=None) -> list:
11+
raise NotImplementedError()
12+
13+
def search_by_user(self, term, value, user_id=False, privs=False) -> list:
14+
raise NotImplementedError()
15+
16+
def search_by_sha256(self, sha256: str, limit=False) -> list:
17+
raise NotImplementedError()
18+
19+
def cape_configs(self, task_id: int) -> dict:
20+
raise NotImplementedError()
21+
22+
def detections_by_sha256(self, sha256: str) -> dict:
23+
raise NotImplementedError()
24+
25+
def iocs(self, task_id: int) -> dict:
26+
raise NotImplementedError()
27+
28+
def summary(self, task_id: int) -> dict:
29+
raise NotImplementedError()
30+
31+
def recent_suricata_alerts(self, minutes=60) -> list:
32+
raise NotImplementedError()

lib/cuckoo/core/reporting/backends/__init__.py

Whitespace-only changes.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from lib.cuckoo.common import config
2+
from lib.cuckoo.core.reporting import api
3+
4+
5+
class ElasticsearchReports(api.Reports):
6+
def __init__(self, cfg: config.Config):
7+
pass
8+
9+
def get(self, task_id: int) -> dict:
10+
pass
11+
12+
def delete(self, task_id: int) -> bool:
13+
pass
14+
15+
def search(self, term, value, limit=False, projection=None) -> list:
16+
pass
17+
18+
def search_by_user(self, term, value, user_id=False, privs=False) -> list:
19+
pass
20+
21+
def search_by_sha256(self, sha256: str, limit=False) -> list:
22+
pass
23+
24+
def cape_configs(self, task_id: int) -> dict:
25+
pass
26+
27+
def detections_by_sha256(self, sha256: str) -> dict:
28+
pass
29+
30+
def iocs(self, task_id: int) -> dict:
31+
pass
32+
33+
def summary(self, task_id: int) -> dict:
34+
pass
35+
36+
def recent_suricata_alerts(self, minutes=60) -> list:
37+
pass
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import logging
2+
3+
import pymongo
4+
import pymongo.collection
5+
import pymongo.database
6+
import pymongo.results
7+
8+
from lib.cuckoo.common import config
9+
from lib.cuckoo.common.exceptions import CuckooOperationalError
10+
from lib.cuckoo.core.reporting import api
11+
12+
log = logging.getLogger(__name__)
13+
14+
15+
class MongoDBReports(api.Reports):
16+
def __init__(self, cfg: config.Config):
17+
if not hasattr(cfg, "mongodb"):
18+
raise CuckooOperationalError("mongodb must be configured")
19+
mongodb_cfg: dict = cfg.mongodb
20+
21+
self._client: pymongo.MongoClient = _pymongo_client(mongodb_cfg)
22+
dbname = mongodb_cfg.get("db", "cuckoo")
23+
self._database: pymongo.database.Database = self._client[dbname]
24+
self._reports: pymongo.collection.Collection = self._database[_analysis_coll]
25+
26+
_init_pymongo_logging(mongodb_cfg)
27+
28+
def get(self, task_id: int) -> dict:
29+
query = {_info_id: task_id}
30+
report = self._reports.find_one(filter=query)
31+
return {} if not report else report
32+
33+
def delete(self, task_id: int) -> bool:
34+
query = {_info_id: task_id}
35+
rslt: pymongo.results.DeleteResult = self._reports.delete_one(filter=query)
36+
return True if rslt.deleted_count > 0 else False
37+
38+
def search(self, term, value, limit=False, projection=None) -> list:
39+
pass
40+
41+
def search_by_user(self, term, value, user_id=False, privs=False) -> list:
42+
pass
43+
44+
def search_by_sha256(self, sha256: str, limit=False) -> list:
45+
pass
46+
47+
def cape_configs(self, task_id: int) -> dict:
48+
pass
49+
50+
def detections_by_sha256(self, sha256: str) -> dict:
51+
pass
52+
53+
def iocs(self, task_id: int) -> dict:
54+
# there's no well-defined representation of iocs data yet; defer to full get
55+
return self.get(task_id)
56+
57+
def summary(self, task_id: int) -> dict:
58+
query = {_info_id: task_id}
59+
projection = {
60+
_id: 0,
61+
_info: 1,
62+
"target.file.virustotal.summary": 1,
63+
"url.virustotal.summary": 1,
64+
"malscore": 1,
65+
"detections": 1,
66+
"network.pcap_sha256": 1,
67+
"mlist_cnt": 1,
68+
"f_mlist_cnt": 1,
69+
"target.file.clamav": 1,
70+
"suri_tls_cnt": 1,
71+
"suri_alert_cnt": 1,
72+
"suri_http_cnt": 1,
73+
"suri_file_cnt": 1,
74+
"trid": 1,
75+
}
76+
report = self._reports.find_one(filter=query, projection=projection)
77+
return {} if not report else report
78+
79+
def recent_suricata_alerts(self, minutes=60) -> list:
80+
pass
81+
82+
83+
# Temporarily duped with mongodb_constants
84+
_analysis_coll = "analysis"
85+
_calls_coll = "calls"
86+
_cuckoo_coll = "cuckoo_schema"
87+
_files_coll = "files"
88+
_file_key = "sha256"
89+
_id = "_id"
90+
_info = "info"
91+
_info_id = "info.id"
92+
_target = "target"
93+
_task_ids_key = "_task_ids"
94+
_version = "version"
95+
96+
97+
def _pymongo_client(cfg: dict) -> pymongo.MongoClient:
98+
return pymongo.MongoClient(
99+
host=cfg.get("host", "127.0.0.1"),
100+
port=cfg.get("port", 27017),
101+
username=cfg.get("username"),
102+
password=cfg.get("password"),
103+
authSource=cfg.get("authsource", "cuckoo"),
104+
tlsCAFile=cfg.get("tlscafile", None),
105+
)
106+
107+
108+
def _init_pymongo_logging(cfg: dict) -> None:
109+
mongodb_log_level = cfg.get("log_level", "ERROR")
110+
level = logging.getLevelName(mongodb_log_level.upper())
111+
logging.getLogger("pymongo").setLevel(level)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from lib.cuckoo.common import config
2+
from lib.cuckoo.common.exceptions import CuckooOperationalError
3+
from lib.cuckoo.core.reporting import api
4+
5+
6+
class Null(api.Reports):
7+
def __init__(self, cfg: config.Config):
8+
pass
9+
10+
def __getattr__(self, name):
11+
raise CuckooOperationalError("reporting backend does not support '%s'" % name)

0 commit comments

Comments
 (0)