cti-python-stix2/stix2/sources/taxii.py

263 lines
8.4 KiB
Python
Raw Normal View History

2017-05-24 17:25:40 +02:00
import requests
from requests.auth import HTTPBasicAuth
from stix2.sources import DataSource
2017-05-26 21:24:33 +02:00
# TODO: -Should we make properties for the TAXIIDataSource address and other
# possible variables that are found in "self.taxii_info"
2017-05-24 17:25:40 +02:00
TAXII_FILTERS = ['added_after', 'id', 'type', 'version']
test = True
2017-05-24 17:25:40 +02:00
class TAXIIDataSource(DataSource):
2017-05-26 21:24:33 +02:00
"""STIX 2.0 Data Source - TAXII 2.0 module"""
2017-05-24 17:25:40 +02:00
2017-05-26 21:24:33 +02:00
def __init__(self, api_root=None, auth=None, name="TAXII"):
2017-05-24 17:25:40 +02:00
super(TAXIIDataSource, self).__init__(name=name)
if not api_root:
api_root = "http://localhost:5000"
if not auth:
2017-05-31 17:02:37 +02:00
auth = {"user": "admin", "pass": "taxii"}
2017-05-24 17:25:40 +02:00
self.taxii_info = {
"api_root": {
2017-05-26 21:24:33 +02:00
"url": api_root
},
2017-05-24 17:25:40 +02:00
"auth": auth
}
if test:
return
2017-05-24 17:25:40 +02:00
try:
# check api-root is reachable/exists and grab api collections
coll_url = self.taxii_info['api_root']['url'] + "/collections/"
headers = {}
resp = requests.get(coll_url,
headers=headers,
2017-05-26 21:24:33 +02:00
auth=HTTPBasicAuth(self.taxii_info['auth']['user'],
self.taxii_info['auth']['pass']))
2017-05-24 17:25:40 +02:00
# TESTING
# print("\n-------__init__() ----\n")
# print(resp.text)
# print("\n")
# print(resp.status_code)
# END TESTING
# raise http error if request returned error code
resp.raise_for_status()
resp_json = resp.json()
try:
self.taxii_info['api_root']['collections'] = resp_json['collections']
except KeyError as e:
if e == "collections":
raise
# raise type(e), type(e)(e.message +
2017-05-26 21:24:33 +02:00
# "To connect to the TAXII collections, the API root
# resource must contain a collection endpoint URL.
# This was not found in the API root resource received
# from the API root" ), sys.exc_info()[2]
2017-05-24 17:25:40 +02:00
except requests.ConnectionError as e:
raise
# raise type(e), type(e)(e.message +
# "Attempting to connect to %s" % coll_url)
def get(self, id_, _composite_filters=None):
2017-05-26 21:24:33 +02:00
"""Get STIX 2.0 object from TAXII source by specified 'id'
2017-05-24 17:25:40 +02:00
2017-05-26 21:24:33 +02:00
Notes:
Just pass _composite_filters to the query() as they are applied
there. de-duplication of results is also done within query()
2017-05-24 17:25:40 +02:00
Args:
id_ (str): id of STIX object to retrieve
2017-05-26 21:24:33 +02:00
_composite_filters (list): filters passed from a Composite Data
Source (if this data source is attached to one)
2017-05-24 17:25:40 +02:00
Returns:
2017-05-26 21:24:33 +02:00
"""
2017-05-24 17:25:40 +02:00
# make query in TAXII query format since 'id' is TAXii field
query = [
{
"field": "match[id]",
"op": "=",
"value": id_
}
]
all_data = self.query(query=query, _composite_filters=_composite_filters)
# reduce to most recent version
stix_obj = sorted(all_data, key=lambda k: k['modified'])[0]
return stix_obj
def all_versions(self, id_, _composite_filters=None):
2017-05-26 21:24:33 +02:00
"""Get all versions of STIX 2.0 object from TAXII source by
specified 'id'
2017-05-24 17:25:40 +02:00
2017-05-26 21:24:33 +02:00
Notes:
Just passes _composite_filters to the query() as they are applied
there. de-duplication of results is also done within query()
2017-05-24 17:25:40 +02:00
Args:
id_ (str): id of STIX objects to retrieve
2017-05-26 21:24:33 +02:00
_composite_filters (list): filters passed from a Composite Data
Source (if this data source is attached to one)
2017-05-24 17:25:40 +02:00
Returns:
2017-05-31 17:02:37 +02:00
The query results with filters applied.
2017-05-26 21:24:33 +02:00
"""
2017-05-24 17:25:40 +02:00
# make query in TAXII query format since 'id' is TAXII field
query = [
{
"field": "match[id]",
"op": "=",
"value": id_
}
]
all_data = self.query(query=query, _composite_filters=_composite_filters)
return all_data
def query(self, query=None, _composite_filters=None):
2017-05-26 21:24:33 +02:00
"""Query the TAXII data source for STIX objects matching the query
2017-05-24 17:25:40 +02:00
The final full query could contain filters from:
-the current API call
2017-05-26 21:24:33 +02:00
-Composite Data source filters (that are passed in via
'_composite_filters')
2017-05-24 17:25:40 +02:00
-TAXII data source filters that are attached
2017-05-26 21:24:33 +02:00
TAXII filters ['added_after', 'match[<>]'] are extracted and sent
to TAXII if they are present
2017-05-24 17:25:40 +02:00
TODO: Authentication for TAXII
Args:
query(list): list of filters (dicts) to search on
2017-05-26 21:24:33 +02:00
_composite_filters (list): filters passed from a
Composite Data Source (if this data source is attached to one)
2017-05-24 17:25:40 +02:00
Returns:
2017-05-26 21:24:33 +02:00
"""
2017-05-24 17:25:40 +02:00
all_data = []
if query is None:
query = []
# combine all query filters
if self.filters:
query += self.filters.values()
if _composite_filters:
query += _composite_filters
2017-05-26 21:24:33 +02:00
# separate taxii query terms (can be done remotely)
2017-05-24 17:25:40 +02:00
taxii_filters = self._parse_taxii_filters(query)
# for each collection endpoint - send query request
for collection in self.taxii_info['api_root']['collections']:
2017-05-26 21:24:33 +02:00
coll_obj_url = "/".join([self.taxii_info['api_root']['url'],
"collections", str(collection['id']),
"objects"])
2017-05-24 17:25:40 +02:00
headers = {}
try:
resp = requests.get(coll_obj_url,
params=taxii_filters,
headers=headers,
2017-05-26 21:24:33 +02:00
auth=HTTPBasicAuth(self.taxii_info['auth']['user'],
self.taxii_info['auth']['pass']))
2017-05-24 17:25:40 +02:00
# TESTING
# print("\n-------query() ----\n")
# print("Request that was sent: \n")
# print(resp.url)
2017-05-26 21:24:33 +02:00
# print("Response: \n")
2017-05-24 17:25:40 +02:00
# print(json.dumps(resp.json(),indent=4))
# print("\n")
# print(resp.status_code)
# print("------------------")
# END TESTING
# raise http error if request returned error code
resp.raise_for_status()
resp_json = resp.json()
# grab all STIX 2.0 objects in json response
for stix_obj in resp_json['objects']:
all_data.append(stix_obj)
except requests.exceptions.RequestException as e:
raise
# raise type(e), type(e)(e.message +
# "Attempting to connect to %s" % coll_url)
2017-05-26 21:24:33 +02:00
# TODO: Is there a way to collect exceptions while carrying
# on then raise all of them at the end?
2017-05-24 17:25:40 +02:00
# deduplicate data (before filtering as reduces wasted filtering)
all_data = self.deduplicate(all_data)
# apply local (composite and data source filters)
all_data = self.apply_common_filters(all_data, query)
return all_data
def _parse_taxii_filters(self, query):
2017-05-26 21:24:33 +02:00
"""Parse out TAXII filters that the TAXII server can filter on
2017-05-24 17:25:40 +02:00
TAXII filters should be analgous to how they are supplied
in the url to the TAXII endpoint. For instance
"?match[type]=indicator,sighting" should be in a query dict as follows
{
2017-05-26 21:24:33 +02:00
"field": "match[type]"
2017-05-24 17:25:40 +02:00
"op": "=",
2017-05-26 21:24:33 +02:00
"value": "indicator,sighting"
2017-05-24 17:25:40 +02:00
}
Args:
2017-05-26 21:24:33 +02:00
query (list): list of filters to extract which ones are TAXII
specific.
2017-05-24 17:25:40 +02:00
Returns:
2017-05-26 21:24:33 +02:00
params (dict): dict of the TAXII filters but in format required
for 'requests.get()'.
"""
2017-05-24 17:25:40 +02:00
params = {}
for q in query:
if q['field'] in TAXII_FILTERS:
if q['field'] == 'added_after':
params[q['field']] = q['value']
else:
taxii_field = 'match[' + q['field'] + ']'
params[taxii_field] = q['value']
2017-05-24 17:25:40 +02:00
return params
def close(self):
2017-05-26 21:24:33 +02:00
"""Close down the Data Source - if any clean up is required.
2017-05-24 17:25:40 +02:00
2017-05-26 21:24:33 +02:00
"""
2017-05-24 17:25:40 +02:00
pass
2017-05-26 21:24:33 +02:00
# TODO: - getters/setters (properties) for TAXII config info