2017-07-12 16:58:31 +02:00
|
|
|
"""
|
|
|
|
Python STIX 2.0 TAXII Source/Sink
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
Classes:
|
|
|
|
TAXIICollectionStore
|
|
|
|
TAXIICollectionSink
|
|
|
|
TAXIICollectionSource
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
TODO: Test everything
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
"""
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
import json
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-08-18 18:05:12 +02:00
|
|
|
from stix2.sources import DataSink, DataSource, DataStore, Filter, make_id
|
2017-05-30 22:56:27 +02:00
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
TAXII_FILTERS = ['added_after', 'id', 'type', 'version']
|
2017-05-24 17:25:40 +02:00
|
|
|
|
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
class TAXIICollectionStore(DataStore):
|
|
|
|
"""
|
|
|
|
"""
|
2017-07-20 21:36:54 +02:00
|
|
|
def __init__(self, collection, name="TAXIICollectionStore"):
|
|
|
|
"""
|
|
|
|
Create a new TAXII Collection Data store
|
|
|
|
|
|
|
|
Args:
|
|
|
|
collection (taxii2.Collection): Collection instance
|
2017-07-12 16:58:31 +02:00
|
|
|
|
2017-08-11 14:10:20 +02:00
|
|
|
"""
|
2017-08-11 15:52:29 +02:00
|
|
|
super(TAXIICollectionStore, self).__init__(name=name)
|
2017-07-20 21:36:54 +02:00
|
|
|
self.source = TAXIICollectionSource(collection)
|
|
|
|
self.sink = TAXIICollectionSink(collection)
|
2017-07-12 16:58:31 +02:00
|
|
|
|
|
|
|
|
|
|
|
class TAXIICollectionSink(DataSink):
|
|
|
|
"""
|
|
|
|
"""
|
2017-07-20 21:36:54 +02:00
|
|
|
def __init__(self, collection, name="TAXIICollectionSink"):
|
2017-07-12 16:58:31 +02:00
|
|
|
super(TAXIICollectionSink, self).__init__(name=name)
|
2017-07-20 21:36:54 +02:00
|
|
|
self.collection = collection
|
2017-07-12 16:58:31 +02:00
|
|
|
|
2017-07-13 00:03:59 +02:00
|
|
|
def add(self, stix_obj):
|
2017-07-12 16:58:31 +02:00
|
|
|
"""
|
|
|
|
"""
|
|
|
|
self.collection.add_objects(self.create_bundle([json.loads(str(stix_obj))]))
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
@staticmethod
|
|
|
|
def create_bundle(objects):
|
2017-08-11 14:10:20 +02:00
|
|
|
return dict(id="bundle--%s" % make_id(),
|
2017-07-12 16:58:31 +02:00
|
|
|
objects=objects,
|
|
|
|
spec_version="2.0",
|
|
|
|
type="bundle")
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
|
|
|
|
class TAXIICollectionSource(DataSource):
|
|
|
|
"""
|
|
|
|
"""
|
2017-07-20 21:36:54 +02:00
|
|
|
def __init__(self, collection, name="TAXIICollectionSource"):
|
2017-07-12 16:58:31 +02:00
|
|
|
super(TAXIICollectionSource, self).__init__(name=name)
|
2017-07-20 21:36:54 +02:00
|
|
|
self.collection = collection
|
2017-07-12 16:58:31 +02:00
|
|
|
|
|
|
|
def get(self, stix_id, _composite_filters=None):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
# combine all query filters
|
|
|
|
query = []
|
|
|
|
if self.filters:
|
|
|
|
query.extend(self.filters.values())
|
|
|
|
if _composite_filters:
|
|
|
|
query.extend(_composite_filters)
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
# separate taxii query terms (can be done remotely)
|
|
|
|
taxii_filters = self._parse_taxii_filters(query)
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
stix_objs = self.collection.get_object(stix_id, taxii_filters)["objects"]
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
stix_obj = self.apply_common_filters(stix_objs, query)
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
if len(stix_obj) > 0:
|
|
|
|
stix_obj = stix_obj[0]
|
|
|
|
else:
|
|
|
|
stix_obj = None
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
return stix_obj
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
def all_versions(self, stix_id, _composite_filters=None):
|
|
|
|
"""
|
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 = [
|
2017-08-18 18:05:12 +02:00
|
|
|
Filter("match[id]", "=", stix_id),
|
|
|
|
Filter("match[version]", "=", "all")
|
2017-05-24 17:25:40 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
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
|
|
|
"""
|
2017-07-12 16:58:31 +02:00
|
|
|
"""
|
2017-05-24 17:25:40 +02:00
|
|
|
if query is None:
|
|
|
|
query = []
|
|
|
|
|
|
|
|
# combine all query filters
|
|
|
|
if self.filters:
|
2017-07-12 16:58:31 +02:00
|
|
|
query.extend(self.filters.values())
|
2017-05-24 17:25:40 +02:00
|
|
|
if _composite_filters:
|
2017-07-12 16:58:31 +02:00
|
|
|
query.extend(_composite_filters)
|
2017-05-24 17:25:40 +02:00
|
|
|
|
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)
|
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
# query TAXII collection
|
|
|
|
all_data = self.collection.get_objects(filters=taxii_filters)["objects"]
|
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-08-11 14:10:20 +02:00
|
|
|
"""Parse out TAXII filters that the TAXII server can filter on.
|
2017-05-24 17:25:40 +02:00
|
|
|
|
2017-08-11 14:10:20 +02:00
|
|
|
Notes:
|
|
|
|
For instance - "?match[type]=indicator,sighting" should be in a
|
|
|
|
query dict as follows:
|
|
|
|
|
|
|
|
{
|
|
|
|
"field": "type"
|
|
|
|
"op": "=",
|
|
|
|
"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
|
|
|
|
2017-08-11 14:10:20 +02:00
|
|
|
"""
|
2017-05-24 17:25:40 +02:00
|
|
|
params = {}
|
|
|
|
|
2017-07-12 16:58:31 +02:00
|
|
|
for filter_ in query:
|
2017-08-09 21:25:06 +02:00
|
|
|
if filter_.field in TAXII_FILTERS:
|
|
|
|
if filter_.field == "added_after":
|
|
|
|
params[filter_.field] = filter_.value
|
2017-05-30 22:56:27 +02:00
|
|
|
else:
|
2017-08-14 21:01:07 +02:00
|
|
|
taxii_field = "match[%s]" % filter_.field
|
2017-08-09 21:25:06 +02:00
|
|
|
params[taxii_field] = filter_.value
|
2017-05-24 17:25:40 +02:00
|
|
|
return params
|