From 4a2ac6df3a8528eaac738e6d74b8ad0822d46aaa Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Fri, 30 Mar 2018 11:53:15 -0400 Subject: [PATCH] Add/fix workbench docs, rename add() -> save() --- docs/guide/workbench.ipynb | 472 +++++++++++++++++++++++++++++++++++ stix2/datastore/__init__.py | 10 +- stix2/test/test_workbench.py | 38 +-- stix2/workbench.py | 6 +- 4 files changed, 501 insertions(+), 25 deletions(-) create mode 100644 docs/guide/workbench.ipynb diff --git a/docs/guide/workbench.ipynb b/docs/guide/workbench.ipynb new file mode 100644 index 0000000..bc7942a --- /dev/null +++ b/docs/guide/workbench.ipynb @@ -0,0 +1,472 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "nbsphinx": "hidden" + }, + "outputs": [], + "source": [ + "# Delete this cell to re-enable tracebacks\n", + "import sys\n", + "ipython = get_ipython()\n", + "\n", + "def hide_traceback(exc_tuple=None, filename=None, tb_offset=None,\n", + " exception_only=False, running_compiled_code=False):\n", + " etype, value, tb = sys.exc_info()\n", + " return ipython._showtraceback(etype, value, ipython.InteractiveTB.get_exception_only(etype, value))\n", + "\n", + "ipython.showtraceback = hide_traceback" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "nbsphinx": "hidden" + }, + "outputs": [], + "source": [ + "# JSON output syntax highlighting\n", + "from __future__ import print_function\n", + "from pygments import highlight\n", + "from pygments.lexers import JsonLexer\n", + "from pygments.formatters import HtmlFormatter\n", + "from six.moves import builtins\n", + "from IPython.display import display, HTML\n", + "\n", + "def json_print(inpt):\n", + " string = str(inpt)\n", + " if string[0] == '{':\n", + " formatter = HtmlFormatter()\n", + " display(HTML('{}'.format(\n", + " formatter.get_style_defs('.highlight'),\n", + " highlight(string, JsonLexer(), formatter))))\n", + " else:\n", + " builtins.print(inpt)\n", + "\n", + "globals()['print'] = json_print" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using A Workbench" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The [Workbench API](../api/stix2.workbench.rst) hides most of the complexity of the rest of the library to make it easy to interact with STIX data. To use it, just import everything from ``stix2.workbench``:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2.workbench import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Retrieving STIX Data\n", + "\n", + "To get some STIX data to work with, let's set up a DataSource and add it to our workbench." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "from taxii2client import Collection\n", + "\n", + "collection = Collection(\"http://127.0.0.1:5000/trustgroup1/collections/91a7b528-80eb-42ed-a74d-c6fbd5a26116/\", user=\"admin\", password=\"Password0\")\n", + "tc_source = TAXIICollectionSource(collection)\n", + "add_data_source(tc_source)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Now we can get all of the indicators from the data source." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "response = indicators()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similar functions are available for the other STIX Object types. See the full list [here](../api/stix2.workbench.rst#stix2.workbench.attack_patterns).\n", + "\n", + "If you want to only retrieve *some* indicators, you can pass in one or more [Filters](../api/datastore/stix2.datastore.filters.rst). This example finds all the indicators created by a specific identity:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "response = indicators(filters=Filter('created_by_ref', '=', 'identity--adede3e8-bf44-4e6f-b3c9-1958cbc3b188'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The objects returned let you easily traverse their relationships. Get all Relationship objects involving that object with ``.relationships()``, all other objects related to this object with ``.related()``, and the Identity object for the creator of the object (if one exists) with ``.created_by()``. For full details on these methods and their arguments, see the [Workbench API](../api/stix2.workbench.rst) documentation." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "indicator--a932fcc6-e032-176c-126f-cb970a5a1ade\n", + "indicates\n", + "malware--fdd60b30-b67c-11e3-b0b9-f01faf20d111\n" + ] + } + ], + "source": [ + "for i in indicators():\n", + " for rel in i.relationships():\n", + " print(rel.source_ref)\n", + " print(rel.relationship_type)\n", + " print(rel.target_ref)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
{\n",
+       "    "type": "malware",\n",
+       "    "id": "malware--fdd60b30-b67c-11e3-b0b9-f01faf20d111",\n",
+       "    "created": "2017-01-27T13:49:53.997Z",\n",
+       "    "modified": "2017-01-27T13:49:53.997Z",\n",
+       "    "name": "Poison Ivy",\n",
+       "    "description": "Poison Ivy",\n",
+       "    "labels": [\n",
+       "        "remote-access-trojan"\n",
+       "    ]\n",
+       "}\n",
+       "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "for i in indicators():\n", + " for obj in i.related():\n", + " print(obj)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If there are a lot of related objects, you can narrow it down by passing in one or more [Filters](../api/datastore/stix2.datastore.filters.rst) just as before. For example, if we want to get only the indicators related to a specific piece of malware (and not any entities that use it or are targeted by it):" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
{\n",
+       "    "type": "indicator",\n",
+       "    "id": "indicator--a932fcc6-e032-176c-126f-cb970a5a1ade",\n",
+       "    "created": "2014-05-08T09:00:00.000Z",\n",
+       "    "modified": "2014-05-08T09:00:00.000Z",\n",
+       "    "name": "File hash for Poison Ivy variant",\n",
+       "    "pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']",\n",
+       "    "valid_from": "2014-05-08T09:00:00Z",\n",
+       "    "labels": [\n",
+       "        "file-hash-watchlist"\n",
+       "    ]\n",
+       "}\n",
+       "
\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "malware = get('malware--fdd60b30-b67c-11e3-b0b9-f01faf20d111')\n", + "indicator = malware.related(filters=Filter('type', '=', 'indicator'))\n", + "print(indicator[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Creating STIX Data\n", + "\n", + "To create a STIX object, just use that object's class constructor. Once it's created, add it to the workbench with [save()](../api/datastore/stix2.workbench.rst#stix2.workbench.save)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "identity = Identity(name=\"ACME Threat Intel Co.\", identity_class=\"organization\")\n", + "save(identity)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also set defaults for certain properties when creating objects. For example, let's set the default creator to be the identity object we just created:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "set_default_creator(identity)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now when we create an indicator (or any other STIX Domain Object), it will automatically have the right ``create_by_ref`` value." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ACME Threat Intel Co.\n" + ] + } + ], + "source": [ + "indicator = Indicator(labels=[\"malicious-activity\"], pattern=\"[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']\")\n", + "save(indicator)\n", + "\n", + "indicator_creator = get(indicator.created_by_ref)\n", + "print(indicator_creator.name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Defaults can also be set for the [created timestamp](../api/datastore/stix2.workbench.rst#stix2.workbench.set_default_created), [external references](../api/datastore/stix2.workbench.rst#stix2.workbench.set_default_external_refs) and [object marking references](../api/datastore/stix2.workbench.rst#stix2.workbench.set_default_object_marking_refs)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/stix2/datastore/__init__.py b/stix2/datastore/__init__.py index f02d773..9bf23a2 100644 --- a/stix2/datastore/__init__.py +++ b/stix2/datastore/__init__.py @@ -181,6 +181,8 @@ class DataStoreMixin(object): object is the source_ref. Default: False. target_only (bool): Only examine Relationships for which this object is the target_ref. Default: False. + filters (list): list of additional filters the related objects must + match. Returns: list: The STIX objects related to the given STIX object. @@ -194,8 +196,8 @@ class DataStoreMixin(object): def add(self, *args, **kwargs): """Method for storing STIX objects. - Define custom behavior before storing STIX objects using the associated - DataSink. Translates add() to the appropriate DataSink call. + Defines custom behavior before storing STIX objects using the + appropriate method call on the associated DataSink. Args: stix_objs (list): a list of STIX objects @@ -416,7 +418,7 @@ class DataSource(with_metaclass(ABCMeta)): ids = set() for r in rels: ids.update((r.source_ref, r.target_ref)) - ids.remove(obj_id) + ids.discard(obj_id) # Assemble filters filter_list = [] @@ -678,6 +680,8 @@ class CompositeDataSource(DataSource): object is the source_ref. Default: False. target_only (bool): Only examine Relationships for which this object is the target_ref. Default: False. + filters (list): list of additional filters the related objects must + match. Returns: list: The STIX objects related to the given STIX object. diff --git a/stix2/test/test_workbench.py b/stix2/test/test_workbench.py index a2016cc..7857eb2 100644 --- a/stix2/test/test_workbench.py +++ b/stix2/test/test_workbench.py @@ -6,12 +6,12 @@ from stix2.workbench import (AttackPattern, Campaign, CourseOfAction, Identity, Indicator, IntrusionSet, Malware, MarkingDefinition, ObservedData, Relationship, Report, StatementMarking, ThreatActor, Tool, - Vulnerability, add, add_data_source, all_versions, + Vulnerability, add_data_source, all_versions, attack_patterns, campaigns, courses_of_action, create, get, identities, indicators, intrusion_sets, malware, observed_data, query, - reports, set_default_created, set_default_creator, - set_default_external_refs, + reports, save, set_default_created, + set_default_creator, set_default_external_refs, set_default_object_marking_refs, threat_actors, tools, vulnerabilities) @@ -30,7 +30,7 @@ def test_workbench_environment(): # Create a STIX object ind = create(Indicator, id=INDICATOR_ID, **INDICATOR_KWARGS) - add(ind) + save(ind) resp = get(INDICATOR_ID) assert resp['labels'][0] == 'malicious-activity' @@ -46,7 +46,7 @@ def test_workbench_environment(): def test_workbench_get_all_attack_patterns(): mal = AttackPattern(id=ATTACK_PATTERN_ID, **ATTACK_PATTERN_KWARGS) - add(mal) + save(mal) resp = attack_patterns() assert len(resp) == 1 @@ -55,7 +55,7 @@ def test_workbench_get_all_attack_patterns(): def test_workbench_get_all_campaigns(): cam = Campaign(id=CAMPAIGN_ID, **CAMPAIGN_KWARGS) - add(cam) + save(cam) resp = campaigns() assert len(resp) == 1 @@ -64,7 +64,7 @@ def test_workbench_get_all_campaigns(): def test_workbench_get_all_courses_of_action(): coa = CourseOfAction(id=COURSE_OF_ACTION_ID, **COURSE_OF_ACTION_KWARGS) - add(coa) + save(coa) resp = courses_of_action() assert len(resp) == 1 @@ -73,7 +73,7 @@ def test_workbench_get_all_courses_of_action(): def test_workbench_get_all_identities(): idty = Identity(id=IDENTITY_ID, **IDENTITY_KWARGS) - add(idty) + save(idty) resp = identities() assert len(resp) == 1 @@ -88,7 +88,7 @@ def test_workbench_get_all_indicators(): def test_workbench_get_all_intrusion_sets(): ins = IntrusionSet(id=INTRUSION_SET_ID, **INTRUSION_SET_KWARGS) - add(ins) + save(ins) resp = intrusion_sets() assert len(resp) == 1 @@ -97,7 +97,7 @@ def test_workbench_get_all_intrusion_sets(): def test_workbench_get_all_malware(): mal = Malware(id=MALWARE_ID, **MALWARE_KWARGS) - add(mal) + save(mal) resp = malware() assert len(resp) == 1 @@ -106,7 +106,7 @@ def test_workbench_get_all_malware(): def test_workbench_get_all_observed_data(): od = ObservedData(id=OBSERVED_DATA_ID, **OBSERVED_DATA_KWARGS) - add(od) + save(od) resp = observed_data() assert len(resp) == 1 @@ -115,7 +115,7 @@ def test_workbench_get_all_observed_data(): def test_workbench_get_all_reports(): rep = Report(id=REPORT_ID, **REPORT_KWARGS) - add(rep) + save(rep) resp = reports() assert len(resp) == 1 @@ -124,7 +124,7 @@ def test_workbench_get_all_reports(): def test_workbench_get_all_threat_actors(): thr = ThreatActor(id=THREAT_ACTOR_ID, **THREAT_ACTOR_KWARGS) - add(thr) + save(thr) resp = threat_actors() assert len(resp) == 1 @@ -133,7 +133,7 @@ def test_workbench_get_all_threat_actors(): def test_workbench_get_all_tools(): tool = Tool(id=TOOL_ID, **TOOL_KWARGS) - add(tool) + save(tool) resp = tools() assert len(resp) == 1 @@ -142,7 +142,7 @@ def test_workbench_get_all_tools(): def test_workbench_get_all_vulnerabilities(): vuln = Vulnerability(id=VULNERABILITY_ID, **VULNERABILITY_KWARGS) - add(vuln) + save(vuln) resp = vulnerabilities() assert len(resp) == 1 @@ -151,7 +151,7 @@ def test_workbench_get_all_vulnerabilities(): def test_workbench_relationships(): rel = Relationship(INDICATOR_ID, 'indicates', MALWARE_ID) - add(rel) + save(rel) ind = get(INDICATOR_ID) resp = ind.relationships() @@ -163,7 +163,7 @@ def test_workbench_relationships(): def test_workbench_created_by(): intset = IntrusionSet(name="Breach 123", created_by_ref=IDENTITY_ID) - add(intset) + save(intset) creator = intset.created_by() assert creator.id == IDENTITY_ID @@ -171,7 +171,7 @@ def test_workbench_created_by(): def test_workbench_related(): rel1 = Relationship(MALWARE_ID, 'targets', IDENTITY_ID) rel2 = Relationship(CAMPAIGN_ID, 'uses', MALWARE_ID) - add([rel1, rel2]) + save([rel1, rel2]) resp = get(MALWARE_ID).related() assert len(resp) == 3 @@ -186,7 +186,7 @@ def test_workbench_related(): def test_workbench_related_with_filters(): malware = Malware(labels=["ransomware"], name="CryptorBit", created_by_ref=IDENTITY_ID) rel = Relationship(malware.id, 'variant-of', MALWARE_ID) - add([malware, rel]) + save([malware, rel]) filters = [Filter('created_by_ref', '=', IDENTITY_ID)] resp = get(MALWARE_ID).related(filters=filters) diff --git a/stix2/workbench.py b/stix2/workbench.py index f57b5f4..0338353 100644 --- a/stix2/workbench.py +++ b/stix2/workbench.py @@ -12,7 +12,7 @@ .. autofunction:: creator_of .. autofunction:: relationships .. autofunction:: related_to -.. autofunction:: add +.. autofunction:: save .. autofunction:: add_filters .. autofunction:: add_filter .. autofunction:: parse @@ -65,7 +65,7 @@ query_by_type = _environ.query_by_type creator_of = _environ.creator_of relationships = _environ.relationships related_to = _environ.related_to -add = _environ.add +save = _environ.add add_filters = _environ.add_filters add_filter = _environ.add_filter parse = _environ.parse @@ -132,7 +132,7 @@ def _setup_workbench(): for obj_type in STIX_OBJS: new_class_dict = { '__new__': _constructor_wrapper(obj_type), - '__doc__': 'Workbench wrapper around the `{0} `__. object. {1}'.format(obj_type.__name__, STIX_OBJ_DOCS) + '__doc__': 'Workbench wrapper around the `{0} `__ object. {1}'.format(obj_type.__name__, STIX_OBJ_DOCS) } new_class = type(obj_type.__name__, (), new_class_dict)