Skip to content

Commit 8981afa

Browse files
authored
Merge pull request #48 from DomainTools/IDEV-1986-implement-domain-discovery-feed
IDEV-1986: Add domain_discovery_feed action.
2 parents 3321eea + 3cafbbd commit 8981afa

File tree

2 files changed

+160
-35
lines changed

2 files changed

+160
-35
lines changed

domaintools_iris.json

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2326,6 +2326,118 @@
23262326
}
23272327
],
23282328
"versions": "EQ(*)"
2329+
},
2330+
{
2331+
"action": "domain discovery feed",
2332+
"description": "New domains as they are either discovered in domain registration information, observed by our global sensor network, or reported by trusted third parties.",
2333+
"type": "investigate",
2334+
"identifier": "domain_discovery_feed",
2335+
"read_only": true,
2336+
"parameters": {
2337+
"domain": {
2338+
"description": "Used to filter feed results. The filter can be an exact match or a partial match when the * character is included at the beginning and/or end of the value.",
2339+
"data_type": "string",
2340+
"order": 0
2341+
},
2342+
"before": {
2343+
"description": "The end of the query window in seconds or in ISO8601 format, relative to the current time, inclusive.",
2344+
"data_type": "string",
2345+
"order": 1
2346+
},
2347+
"after": {
2348+
"description": "The start of the query window in seconds in ISO8601 format, relative to the current time, inclusive.",
2349+
"data_type": "string",
2350+
"order": 2
2351+
},
2352+
"session_id": {
2353+
"description": "Serves as a unique identifier for the session. This parameter ensures that data retrieval begins from the latest timestamp recorded in the previous data pull.",
2354+
"data_type": "string",
2355+
"order": 3
2356+
},
2357+
"top": {
2358+
"description": "The number of results to return in the response payload. Primarily used for testing.",
2359+
"data_type": "string",
2360+
"order": 4
2361+
}
2362+
},
2363+
"render": {
2364+
"width": 12,
2365+
"title": "Domain Discovery List",
2366+
"type": "table",
2367+
"height": 10
2368+
},
2369+
"output": [
2370+
{
2371+
"data_path": "action_result.data",
2372+
"data_type": "string"
2373+
},
2374+
{
2375+
"data_path": "action_result.data.*.domain",
2376+
"data_type": "string",
2377+
"column_name": "Domain Names",
2378+
"column_order": 0,
2379+
"contains": [
2380+
"domain"
2381+
]
2382+
},
2383+
{
2384+
"data_path": "action_result.data.*.timestamp",
2385+
"data_type": "string",
2386+
"column_name": "Time Stamp",
2387+
"column_order": 1
2388+
},
2389+
{
2390+
"data_path": "action_result.status",
2391+
"data_type": "string",
2392+
"example_values": [
2393+
"success",
2394+
"failed"
2395+
]
2396+
},
2397+
{
2398+
"data_path": "action_result.summary",
2399+
"data_type": "string"
2400+
},
2401+
{
2402+
"data_path": "action_result.message",
2403+
"data_type": "string"
2404+
},
2405+
{
2406+
"data_path": "action_result.parameter.after",
2407+
"data_type": "string"
2408+
},
2409+
{
2410+
"data_path": "action_result.parameter.before",
2411+
"data_type": "string"
2412+
},
2413+
{
2414+
"data_path": "action_result.parameter.domain",
2415+
"data_type": "string"
2416+
},
2417+
{
2418+
"data_path": "action_result.parameter.session_id",
2419+
"data_type": "string"
2420+
},
2421+
{
2422+
"data_path": "action_result.parameter.top",
2423+
"data_type": "string"
2424+
},
2425+
{
2426+
"data_path": "summary.total_objects",
2427+
"data_type": "numeric",
2428+
"example_values": [
2429+
1
2430+
]
2431+
},
2432+
{
2433+
"data_path": "summary.total_objects_successful",
2434+
"data_type": "numeric",
2435+
"example_values": [
2436+
1
2437+
]
2438+
}
2439+
],
2440+
"versions": "EQ(*)"
23292441
}
23302442
],
23312443
"pip39_dependencies": {

domaintools_iris_connector.py

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,12 @@ class DomainToolsConnector(BaseConnector):
3333
ACTION_ID_LOAD_HASH = "load_hash"
3434
ACTION_ID_ON_POLL = "on_poll"
3535
ACTION_ID_CONFIGURE_SCHEDULED_PLAYBOOK = "configure_monitoring_scheduled_playbooks"
36+
37+
# RTUF action_ids
3638
ACTION_ID_NOD_FEED = "nod_feed"
3739
ACTION_ID_NAD_FEED = "nad_feed"
40+
ACTION_ID_DOMAIN_DISCOVERY_FEED = "domain_discovery_feed"
41+
RTUF_SERVICES_LIST = ["nod", "nad", "domaindiscovery"]
3842

3943
def __init__(self):
4044
# Call the BaseConnectors init first
@@ -46,6 +50,22 @@ def __init__(self):
4650
self._domains = None
4751
self._proxy_url = None
4852
self._scheduled_playbooks_list_name = "domaintools_scheduled_playbooks"
53+
self.ACTION_ID_TO_ACTION = {
54+
phantom.ACTION_ID_TEST_ASSET_CONNECTIVITY: self._test_connectivity,
55+
self.ACTION_ID_DOMAIN_REPUTATION: self._domain_reputation,
56+
self.ACTION_ID_DOMAIN_ENRICH: self._domain_enrich,
57+
self.ACTION_ID_DOMAIN_INVESTIGATE: self._domain_investigate,
58+
self.ACTION_ID_PIVOT: self._pivot_action,
59+
self.ACTION_ID_REVERSE_IP: self._reverse_lookup_ip,
60+
self.ACTION_ID_REVERSE_EMAIL: self._reverse_whois_email,
61+
self.ACTION_ID_REVERSE_DOMAIN: self._reverse_lookup_domain,
62+
self.ACTION_ID_LOAD_HASH: self._load_hash,
63+
self.ACTION_ID_ON_POLL: self._on_poll,
64+
self.ACTION_ID_CONFIGURE_SCHEDULED_PLAYBOOK: self._configure_monitoring_scheduled_playbooks,
65+
self.ACTION_ID_NOD_FEED: self._nod_feed,
66+
self.ACTION_ID_NAD_FEED: self._nad_feed,
67+
self.ACTION_ID_DOMAIN_DISCOVERY_FEED: self._domain_discovery_feed,
68+
}
4969

5070
def initialize(self):
5171
# get the app configuation - super class pulls domaintools_iris.json
@@ -67,9 +87,6 @@ def initialize(self):
6787

6888
return phantom.APP_SUCCESS
6989

70-
def _is_feeds_service(self, service):
71-
return service in ("nod", "nad")
72-
7390
def _handle_py_ver_for_byte(self, input_str):
7491
"""
7592
This method returns the binary|original string based on the Python version.
@@ -113,7 +130,7 @@ def _parse_feeds_response(self, service, action_result, feeds_results):
113130
rows = response.strip().split("\n")
114131

115132
for row in rows:
116-
if service in ("nod", "nad"):
133+
if service in self.RTUF_SERVICES_LIST:
117134
feed_result = json.loads(row)
118135
data.append(
119136
{
@@ -243,7 +260,7 @@ def _do_query(self, service, action_result, query_args=None):
243260
response = service_api(**query_args, position=position)
244261

245262
try:
246-
if self._is_feeds_service(service):
263+
if service in self.RTUF_SERVICES_LIST:
247264
# Separate parsing of feeds product
248265
return self._parse_feeds_response(service, action_result, response)
249266

@@ -323,8 +340,6 @@ def _test_connectivity(self):
323340
)
324341

325342
def handle_action(self, param):
326-
ret_val = phantom.APP_SUCCESS
327-
328343
# Get the action that we are supposed to execute for this App Run
329344
action_id = self.get_action_identifier()
330345

@@ -351,34 +366,15 @@ def handle_action(self, param):
351366
self._domains = self._get_domains(hostnames)
352367

353368
# Handle the actions
354-
if action_id == phantom.ACTION_ID_TEST_ASSET_CONNECTIVITY:
355-
ret_val = self._test_connectivity()
356-
elif action_id == self.ACTION_ID_DOMAIN_ENRICH:
357-
ret_val = self._domain_enrich(param)
358-
elif action_id == self.ACTION_ID_DOMAIN_INVESTIGATE:
359-
ret_val = self._domain_investigate(param)
360-
elif action_id == self.ACTION_ID_DOMAIN_REPUTATION:
361-
ret_val = self._domain_reputation(param)
362-
elif action_id == self.ACTION_ID_PIVOT:
363-
ret_val = self._pivot_action(param)
364-
elif action_id == self.ACTION_ID_REVERSE_IP:
365-
ret_val = self._reverse_lookup_ip(param)
366-
elif action_id == self.ACTION_ID_REVERSE_EMAIL:
367-
ret_val = self._reverse_whois_email(param)
368-
elif action_id == self.ACTION_ID_REVERSE_DOMAIN:
369-
ret_val = self._reverse_lookup_domain(param)
370-
elif action_id == self.ACTION_ID_LOAD_HASH:
371-
ret_val = self._load_hash(param)
372-
elif action_id == self.ACTION_ID_ON_POLL:
373-
ret_val = self._on_poll(param)
374-
elif action_id == self.ACTION_ID_CONFIGURE_SCHEDULED_PLAYBOOK:
375-
ret_val = self._configure_monitoring_scheduled_playbooks(param)
376-
elif action_id == self.ACTION_ID_NOD_FEED:
377-
ret_val = self._nod_feed(param)
378-
elif action_id == self.ACTION_ID_NAD_FEED:
379-
ret_val = self._nad_feed(param)
380-
381-
return ret_val
369+
action = self.ACTION_ID_TO_ACTION.get(action_id)
370+
if action:
371+
if action_id == phantom.ACTION_ID_TEST_ASSET_CONNECTIVITY:
372+
# Special handling as this requires no param
373+
return action()
374+
375+
return action(param)
376+
377+
return phantom.APP_SUCCESS
382378

383379
def _get_proxy_url(self, config):
384380
proxy_url = None
@@ -904,6 +900,23 @@ def _nad_feed(self, param):
904900

905901
return action_result.get_status()
906902

903+
def _domain_discovery_feed(self, param):
904+
self.save_progress(f"Starting {self.ACTION_ID_DOMAIN_DISCOVERY_FEED} action.")
905+
action_result = self.add_action_result(ActionResult(param))
906+
params = {"always_sign_api_key": False}
907+
params.update(param)
908+
session_id = params.pop("session_id", None)
909+
if session_id:
910+
params["sessionID"] = session_id
911+
912+
ret_val = self._do_query("domaindiscovery", action_result, query_args=params)
913+
self.save_progress(f"Completed {self.ACTION_ID_DOMAIN_DISCOVERY_FEED} action.")
914+
915+
if not ret_val:
916+
return action_result.get_data()
917+
918+
return action_result.get_status()
919+
907920

908921
if __name__ == "__main__":
909922
import argparse

0 commit comments

Comments
 (0)