From 8e2a230ec2fc341842be2931341419c279510b18 Mon Sep 17 00:00:00 2001 From: Izar Tarandach Date: Mon, 18 Dec 2023 12:59:04 -0500 Subject: [PATCH 1/7] Added colormap functionality to dfd. --- colormap.dot | 134 +++++++++++++++++++++++++++ no_colormap.dot | 134 +++++++++++++++++++++++++++ pytm/images/datastore_black.png | Bin 0 -> 13242 bytes pytm/images/datastore_darkgreen.png | Bin 0 -> 3805 bytes pytm/images/datastore_firebrick3.png | Bin 0 -> 3806 bytes pytm/images/datastore_gold.png | Bin 0 -> 3797 bytes 6 files changed, 268 insertions(+) create mode 100644 colormap.dot create mode 100644 no_colormap.dot create mode 100644 pytm/images/datastore_black.png create mode 100644 pytm/images/datastore_darkgreen.png create mode 100644 pytm/images/datastore_firebrick3.png create mode 100644 pytm/images/datastore_gold.png diff --git a/colormap.dot b/colormap.dot new file mode 100644 index 0000000..0a220ee --- /dev/null +++ b/colormap.dot @@ -0,0 +1,134 @@ +digraph tm { + graph [ + fontname = Arial; + fontsize = 14; + ] + node [ + fontname = Arial; + fontsize = 14; + rankdir = lr; + ] + edge [ + shape = none; + arrowtail = onormal; + fontname = Arial; + fontsize = 12; + ] + labelloc = "t"; + fontsize = 20; + nodesep = 1; + + subgraph cluster_boundary_AWSVPC_579e9aae81 { + graph [ + fontsize = 10; + fontcolor = black; + style = dashed; + color = black; + label = <AWS VPC>; + ] + + lambda_AWSLambda_0291419f72 [ + shape = rectangle; style=rounded; + + color = gold; + fontcolor = gold; + label = < + + +
AWS Lambda
+ >; + ] + + } + + subgraph cluster_boundary_Internet_acf3059e70 { + graph [ + fontsize = 10; + fontcolor = black; + style = dashed; + color = black; + label = <Internet>; + ] + + actor_User_f2eb7a3ff7 [ + shape = square; + color = darkgreen; + fontcolor = darkgreen; + label = "User"; + margin = 0.02; + ] + + } + + subgraph cluster_boundary_ServerDB_88f2d9c06f { + graph [ + fontsize = 10; + fontcolor = black; + style = dashed; + color = black; + label = <Server/DB>; + ] + + datastore_SQLDatabase_f8af758679 [ + shape = none; + fixedsize = shape; + image = "/Users/izar.tarandach/Src/pytm/pytm/images/datastore_gold.png"; + imagescale = true; + color = gold; + fontcolor = gold; + xlabel = "SQL Database"; + label = ""; + ] + + datastore_RealIdentityDatabase_2c440ebe53 [ + shape = none; + fixedsize = shape; + image = "/Users/izar.tarandach/Src/pytm/pytm/images/datastore_gold.png"; + imagescale = true; + color = gold; + fontcolor = gold; + xlabel = "Real Identity\nDatabase"; + label = ""; + ] + + } + + server_WebServer_d2006ce1bb [ + shape = circle; + color = firebrick3; + fontcolor = firebrick3; + label = "Web Server"; + margin = 0.02; + ] + + datastore_SQLDatabase_f8af758679 -> datastore_RealIdentityDatabase_2c440ebe53 [ + color = firebrick3; + fontcolor = firebrick3; + dir = forward; + label = "(1) Database\nverify real user\nidentity"; + ] + + actor_User_f2eb7a3ff7 -> server_WebServer_d2006ce1bb [ + color = firebrick3; + fontcolor = firebrick3; + dir = both; + label = "(2) User enters\ncomments (*) + (5) Show comments\n(*)"; + ] + + server_WebServer_d2006ce1bb -> datastore_SQLDatabase_f8af758679 [ + color = firebrick3; + fontcolor = firebrick3; + dir = both; + label = "(3) Insert query\nwith comments + (4) Retrieve\ncomments"; + ] + + lambda_AWSLambda_0291419f72 -> datastore_SQLDatabase_f8af758679 [ + color = firebrick3; + fontcolor = firebrick3; + dir = forward; + label = "(6) Serverless\nfunction\nperiodically\ncleans DB"; + ] + +} diff --git a/no_colormap.dot b/no_colormap.dot new file mode 100644 index 0000000..b745fc9 --- /dev/null +++ b/no_colormap.dot @@ -0,0 +1,134 @@ +digraph tm { + graph [ + fontname = Arial; + fontsize = 14; + ] + node [ + fontname = Arial; + fontsize = 14; + rankdir = lr; + ] + edge [ + shape = none; + arrowtail = onormal; + fontname = Arial; + fontsize = 12; + ] + labelloc = "t"; + fontsize = 20; + nodesep = 1; + + subgraph cluster_boundary_AWSVPC_579e9aae81 { + graph [ + fontsize = 10; + fontcolor = firebrick2; + style = dashed; + color = firebrick2; + label = <AWS VPC>; + ] + + lambda_AWSLambda_0291419f72 [ + shape = rectangle; style=rounded; + + color = black; + fontcolor = black; + label = < + + +
AWS Lambda
+ >; + ] + + } + + subgraph cluster_boundary_Internet_acf3059e70 { + graph [ + fontsize = 10; + fontcolor = firebrick2; + style = dashed; + color = firebrick2; + label = <Internet>; + ] + + actor_User_f2eb7a3ff7 [ + shape = square; + color = black; + fontcolor = black; + label = "User"; + margin = 0.02; + ] + + } + + subgraph cluster_boundary_ServerDB_88f2d9c06f { + graph [ + fontsize = 10; + fontcolor = firebrick2; + style = dashed; + color = firebrick2; + label = <Server/DB>; + ] + + datastore_SQLDatabase_f8af758679 [ + shape = none; + fixedsize = shape; + image = "/Users/izar.tarandach/Src/pytm/pytm/images/datastore_black.png"; + imagescale = true; + color = black; + fontcolor = black; + xlabel = "SQL Database"; + label = ""; + ] + + datastore_RealIdentityDatabase_2c440ebe53 [ + shape = none; + fixedsize = shape; + image = "/Users/izar.tarandach/Src/pytm/pytm/images/datastore_black.png"; + imagescale = true; + color = black; + fontcolor = black; + xlabel = "Real Identity\nDatabase"; + label = ""; + ] + + } + + server_WebServer_d2006ce1bb [ + shape = circle; + color = black; + fontcolor = black; + label = "Web Server"; + margin = 0.02; + ] + + datastore_SQLDatabase_f8af758679 -> datastore_RealIdentityDatabase_2c440ebe53 [ + color = black; + fontcolor = black; + dir = forward; + label = "(1) Database\nverify real user\nidentity"; + ] + + actor_User_f2eb7a3ff7 -> server_WebServer_d2006ce1bb [ + color = black; + fontcolor = black; + dir = both; + label = "(2) User enters\ncomments (*) + (5) Show comments\n(*)"; + ] + + server_WebServer_d2006ce1bb -> datastore_SQLDatabase_f8af758679 [ + color = black; + fontcolor = black; + dir = both; + label = "(3) Insert query\nwith comments + (4) Retrieve\ncomments"; + ] + + lambda_AWSLambda_0291419f72 -> datastore_SQLDatabase_f8af758679 [ + color = black; + fontcolor = black; + dir = forward; + label = "(6) Serverless\nfunction\nperiodically\ncleans DB"; + ] + +} diff --git a/pytm/images/datastore_black.png b/pytm/images/datastore_black.png new file mode 100644 index 0000000000000000000000000000000000000000..3258f6693da15cd46f9bf1461eea9467af961c94 GIT binary patch literal 13242 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 Ms;){S-0sGI0UsN~%m4rY literal 0 HcmV?d00001 diff --git a/pytm/images/datastore_darkgreen.png b/pytm/images/datastore_darkgreen.png new file mode 100644 index 0000000000000000000000000000000000000000..759a1ced6d069c6d24e49e5a5002e592e067604c GIT binary patch literal 3805 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 K*568vHR(jpWbtn`{+TM=p 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 literal 0 HcmV?d00001 diff --git a/pytm/images/datastore_gold.png b/pytm/images/datastore_gold.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed29d9001b4cffa54fd2e90e768c3d1f28bfc49 GIT binary patch literal 3797 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 Date: Mon, 18 Dec 2023 13:00:24 -0500 Subject: [PATCH 2/7] Added colormap functionality to dfd --- README.md | 7 ++++ foo.png | Bin 0 -> 61811 bytes pytm/pytm.py | 89 +++++++++++++++++++++++++++++++++-------- test.txt | 6 +++ tests/dfd.dot | 10 ++--- tests/dfd_level0.txt | 6 +-- tests/dfd_level1.txt | 4 +- tests/output.json | 21 +++++++++- tests/test_pytmfunc.py | 7 ++-- 9 files changed, 119 insertions(+), 31 deletions(-) create mode 100644 foo.png create mode 100644 test.txt diff --git a/README.md b/README.md index a738345..5399cd8 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ optional arguments: --exclude EXCLUDE specify threat IDs to be ignored --seq output sequential diagram --list list all available threats + --colormap color the risk in the diagram --describe DESCRIBE describe the properties available for a given element --list-elements list all elements which can be part of a threat model --json JSON output a JSON file @@ -113,6 +114,9 @@ Element class attributes: ``` +The *colormap* argument, used together with *dfd*, outputs a color-coded DFD where the elements are painted red, yellow or green depending on their risk level (as identified by running the rules). + + ## Creating a Threat Model The following is a sample `tm.py` file that describes a simple application where a User logs into the application @@ -282,10 +286,13 @@ user_to_web.overrides = [ threat_id="INP02", cvss="9.3", response="""**To Mitigate**: run a memory sanitizer to validate the binary""", + severity="Very High", ) ] ``` +If you are adding a Finding, make sure to add a severity: "Very High", "High", "Medium", "Low", "Very Low". + ## Threats database For the security practitioner, you may supply your own threats file by setting `TM.threatsFile`. It should contain entries like: diff --git a/foo.png b/foo.png new file mode 100644 index 0000000000000000000000000000000000000000..685b90f6932687bd6ecc019f866270a9b00e3e1a GIT binary patch literal 61811 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

;EtB(Dg{Xn&DY6Y%p%l3_qkgp0Dyz9KXykGo- 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 RI#j}cv^7^OPS&v8^FRMk6Yu~4 literal 0 HcmV?d00001 diff --git a/pytm/pytm.py b/pytm/pytm.py index e6e005f..275d5f6 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -33,6 +33,17 @@ By Chris Beaumont """ +def sev_to_color(sev): + # calculate the color depending on the severity + if sev == 5: + return "firebrick3" + elif sev <= 4 and sev >= 2: + return "gold" + elif sev < 2 and sev >= 0: + return "darkgreen" + + return "black" + class UIError(Exception): def __init__(self, e, context): @@ -757,6 +768,7 @@ class TM: required=False, doc="A list of assumptions about the design/model.", ) + _colormap = False def __init__(self, name, **kwargs): for key, value in kwargs.items(): @@ -819,9 +831,9 @@ def resolve(self): finding_count += 1 f = Finding(e, id=str(finding_count), threat=t) - logger.debug(f"new finding: {f}") findings.append(f) elements[e].append(f) + e._set_severity(f.severity) self.findings = findings for e, findings in elements.items(): e.findings = findings @@ -1065,7 +1077,9 @@ def _process(self): print(self.seq()) if result.dfd is True: - print(self.dfd(levels=(result.levels or set()))) + if result.colormap is True: + self.resolve() + print(self.dfd(colormap=result.colormap, levels=(result.levels or set()))) if ( result.report is not None @@ -1322,6 +1336,7 @@ class Element: doc="Location of the source code that describes this element relative to the directory of the model script.", ) controls = varControls(None) + severity = 0 def __init__(self, name, **kwargs): for key, value in kwargs.items(): @@ -1353,7 +1368,7 @@ def _dfd_template(self): return """{uniq_name} [ shape = {shape}; color = {color}; - fontcolor = {color}; + fontcolor = black; label = "{label}"; margin = 0.02; ] @@ -1366,10 +1381,14 @@ def dfd(self, **kwargs): if levels and not levels & self.levels: return "" + color = self._color() + if kwargs.get("colormap", False): + color = sev_to_color(self.severity) + return self._dfd_template().format( uniq_name=self._uniq_name(), label=self._label(), - color=self._color(), + color=color, shape=self._shape(), ) @@ -1463,6 +1482,23 @@ def _attr_values(self): def checkTLSVersion(self, flows): return any(f.tlsVersion < self.minTLSVersion for f in flows) + def _set_severity(self, sev): + sevs = { + "very high": 5, + "high": 4, + "medium": 3, + "low": 2, + "very low": 1, + "info": 0 + } + + if(sev.lower() not in sevs.keys()): + return + + if(self.severity < sevs[sev.lower()]): + self.severity = sevs[sev.lower()] + return + class Data: """Represents a single piece of data that traverses the system""" @@ -1564,7 +1600,7 @@ def _dfd_template(self): shape = {shape}; color = {color}; - fontcolor = {color}; + fontcolor = "black"; label = < @@ -1580,10 +1616,15 @@ def dfd(self, **kwargs): if levels and not levels & self.levels: return "" + color = self._color() + + if kwargs.get("colormap", False): + color = sev_to_color(self.severity) + return self._dfd_template().format( uniq_name=self._uniq_name(), label=self._label(), - color=self._color(), + color=color, shape=self._shape(), ) @@ -1647,7 +1688,7 @@ def _dfd_template(self): image = "{image}"; imagescale = true; color = {color}; - fontcolor = {color}; + fontcolor = black; xlabel = "{label}"; label = ""; ] @@ -1663,12 +1704,17 @@ def dfd(self, **kwargs): if levels and not levels & self.levels: return "" + color = self._color() + + if kwargs.get("colormap", False): + color = sev_to_color(self.severity) + return self._dfd_template().format( uniq_name=self._uniq_name(), label=self._label(), - color=self._color(), + color=color, shape=self._shape(), - image=os.path.join(os.path.dirname(__file__), "images", "datastore.png"), + image=os.path.join(os.path.dirname(__file__), "images", f"datastore_{color}.png"), ) @@ -1734,6 +1780,7 @@ class Dataflow(Element): note = varString("") usesVPN = varBool(False) usesSessionTokens = varBool(False) + severity = 0 def __init__(self, source, sink, name, **kwargs): self.source = source @@ -1766,6 +1813,11 @@ def dfd(self, mergeResponses=False, **kwargs): ): return "" + color = self._color() + + if kwargs.get("colormap", False): + color = sev_to_color(self.severity) + direction = "forward" label = self._label() if mergeResponses and self.response is not None: @@ -1777,7 +1829,7 @@ def dfd(self, mergeResponses=False, **kwargs): sink=self.sink._uniq_name(), direction=direction, label=label, - color=self._color(), + color=color, ) def hasDataLeaks(self): @@ -1801,7 +1853,7 @@ def _dfd_template(self): return """subgraph cluster_{uniq_name} {{ graph [ fontsize = 10; - fontcolor = {color}; + fontcolor = black; style = dashed; color = {color}; label = <{label}>; @@ -1817,23 +1869,25 @@ def dfd(self, **kwargs): self._is_drawn = True - logger.debug("Now drawing boundary " + self.name) edges = [] for e in TM._elements: if e.inBoundary != self or e._is_drawn: continue # The content to draw can include Boundary objects - logger.debug("Now drawing content {}".format(e.name)) edges.append(e.dfd(**kwargs)) + return self._dfd_template().format( uniq_name=self._uniq_name(), label=self._label(), - color=self._color(), + color=self._color(**kwargs), edges=indent("\n".join(edges), " "), ) - def _color(self): - return "firebrick2" + def _color(self, **kwargs): + if kwargs.get("colormap", False): + return "black" + else: + return "firebrick2" def parents(self): result = [] @@ -1905,7 +1959,7 @@ def encode_element_threat_data(obj): """Used to html encode threat data from a list of Elements""" encoded_elements = [] if (type(obj) is not list): - raise ValueError("expecting a list value, got a {}".format(type(value))) + raise ValueError("expecting a list value, got a {}".format(type(obj))) for o in obj: c = copy.deepcopy(o) @@ -1978,6 +2032,7 @@ def get_args(): _parser.add_argument( "--list", action="store_true", help="list all available threats" ) + _parser.add_argument("--colormap", action="store_true", help="color the risk in the diagram") _parser.add_argument( "--describe", help="describe the properties available for a given element" ) diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..86b5ed8 --- /dev/null +++ b/test.txt @@ -0,0 +1,6 @@ +.......................................................................................................................... +---------------------------------------------------------------------- +Ran 122 tests in 0.047s + +OK +/var/folders/w3/24r9gvg51fl48q4192fnfvrh0000gp/T diff --git a/tests/dfd.dot b/tests/dfd.dot index 1a16d24..2f707bb 100644 --- a/tests/dfd.dot +++ b/tests/dfd.dot @@ -21,7 +21,7 @@ digraph tm { subgraph cluster_boundary_Companynet_88f2d9c06f { graph [ fontsize = 10; - fontcolor = firebrick2; + fontcolor = black; style = dashed; color = firebrick2; label = <Company net>; @@ -30,7 +30,7 @@ digraph tm { subgraph cluster_boundary_dmz_579e9aae81 { graph [ fontsize = 10; - fontcolor = firebrick2; + fontcolor = black; style = dashed; color = firebrick2; label = <dmz>; @@ -49,7 +49,7 @@ digraph tm { subgraph cluster_boundary_backend_f2eb7a3ff7 { graph [ fontsize = 10; - fontcolor = firebrick2; + fontcolor = black; style = dashed; color = firebrick2; label = <backend>; @@ -66,7 +66,7 @@ digraph tm { datastore_SQLDatabase_0291419f72 [ shape = none; fixedsize = shape; - image = "INSTALL_PATH/pytm/images/datastore.png"; + image = "INSTALL_PATH/pytm/images/datastore_black.png"; imagescale = true; color = black; fontcolor = black; @@ -81,7 +81,7 @@ digraph tm { subgraph cluster_boundary_Internet_acf3059e70 { graph [ fontsize = 10; - fontcolor = firebrick2; + fontcolor = black; style = dashed; color = firebrick2; label = <Internet>; diff --git a/tests/dfd_level0.txt b/tests/dfd_level0.txt index 6624edb..bf888d2 100644 --- a/tests/dfd_level0.txt +++ b/tests/dfd_level0.txt @@ -21,7 +21,7 @@ digraph tm { subgraph cluster_boundary_Internet_acf3059e70 { graph [ fontsize = 10; - fontcolor = firebrick2; + fontcolor = black; style = dashed; color = firebrick2; label = <Internet>; @@ -40,7 +40,7 @@ digraph tm { subgraph cluster_boundary_ServerDB_88f2d9c06f { graph [ fontsize = 10; - fontcolor = firebrick2; + fontcolor = black; style = dashed; color = firebrick2; label = <Server/DB>; @@ -49,7 +49,7 @@ digraph tm { datastore_SQLDatabase_d2006ce1bb [ shape = none; fixedsize = shape; - image = "INSTALL_PATH/pytm/images/datastore.png"; + image = "INSTALL_PATH/pytm/images/datastore_black.png"; imagescale = true; color = black; fontcolor = black; diff --git a/tests/dfd_level1.txt b/tests/dfd_level1.txt index 8509f3c..61e4dfa 100644 --- a/tests/dfd_level1.txt +++ b/tests/dfd_level1.txt @@ -21,7 +21,7 @@ digraph tm { subgraph cluster_boundary_Internet_acf3059e70 { graph [ fontsize = 10; - fontcolor = firebrick2; + fontcolor = black; style = dashed; color = firebrick2; label = <Internet>; @@ -40,7 +40,7 @@ digraph tm { subgraph cluster_boundary_ServerDB_88f2d9c06f { graph [ fontsize = 10; - fontcolor = firebrick2; + fontcolor = black; style = dashed; color = firebrick2; label = <Server/DB>; diff --git a/tests/output.json b/tests/output.json index bfd2627..94d3326 100644 --- a/tests/output.json +++ b/tests/output.json @@ -68,6 +68,7 @@ "overrides": [], "port": -1, "protocol": "", + "severity": 0, "sourceFiles": [] } ], @@ -145,6 +146,7 @@ "overrides": [], "port": -1, "protocol": "", + "severity": 0, "sourceFiles": [], "usesCache": false, "usesEnvironmentVariables": false, @@ -222,6 +224,7 @@ "overrides": [], "port": -1, "protocol": "", + "severity": 0, "sourceFiles": [], "usesEnvironmentVariables": false }, @@ -298,6 +301,7 @@ "overrides": [], "port": -1, "protocol": "", + "severity": 0, "sourceFiles": [], "tracksExecutionFlow": false, "usesEnvironmentVariables": false @@ -377,6 +381,7 @@ "overrides": [], "port": -1, "protocol": "", + "severity": 0, "sourceFiles": [], "storesLogData": false, "storesPII": false, @@ -444,6 +449,7 @@ "minTLSVersion": "TLSVersion.NONE", "name": "Internet", "overrides": [], + "severity": 0, "sourceFiles": [] }, { @@ -503,9 +509,11 @@ "minTLSVersion": "TLSVersion.NONE", "name": "Server/DB", "overrides": [], + "severity": 0, "sourceFiles": [] } ], + "colormap": false, "data": [ { "carriedBy": [ @@ -597,6 +605,7 @@ "overrides": [], "port": -1, "protocol": "", + "severity": 0, "sourceFiles": [] }, { @@ -672,6 +681,7 @@ "overrides": [], "port": -1, "protocol": "", + "severity": 0, "sourceFiles": [], "usesCache": false, "usesEnvironmentVariables": false, @@ -749,6 +759,7 @@ "overrides": [], "port": -1, "protocol": "", + "severity": 0, "sourceFiles": [], "usesEnvironmentVariables": false }, @@ -825,6 +836,7 @@ "overrides": [], "port": -1, "protocol": "", + "severity": 0, "sourceFiles": [], "tracksExecutionFlow": false, "usesEnvironmentVariables": false @@ -904,6 +916,7 @@ "overrides": [], "port": -1, "protocol": "", + "severity": 0, "sourceFiles": [], "storesLogData": false, "storesPII": false, @@ -982,6 +995,7 @@ "protocol": "", "response": null, "responseTo": null, + "severity": 0, "sink": "Web Server", "source": "User", "sourceFiles": [], @@ -1056,6 +1070,7 @@ "protocol": "", "response": null, "responseTo": null, + "severity": 0, "sink": "SQL Database", "source": "Web Server", "sourceFiles": [], @@ -1130,6 +1145,7 @@ "protocol": "", "response": null, "responseTo": null, + "severity": 0, "sink": "Lambda func", "source": "Web Server", "sourceFiles": [], @@ -1204,6 +1220,7 @@ "protocol": "", "response": null, "responseTo": null, + "severity": 0, "sink": "Web Server", "source": "SQL Database", "sourceFiles": [], @@ -1278,6 +1295,7 @@ "protocol": "", "response": null, "responseTo": null, + "severity": 0, "sink": "User", "source": "Web Server", "sourceFiles": [], @@ -1352,6 +1370,7 @@ "protocol": "", "response": null, "responseTo": null, + "severity": 0, "sink": "SQL Database", "source": "Task queue worker", "sourceFiles": [], @@ -1368,4 +1387,4 @@ "onDuplicates": "Action.NO_ACTION", "threatsExcluded": [], "threatsFile": "pytm/threatlib/threats.json" -} \ No newline at end of file +} diff --git a/tests/test_pytmfunc.py b/tests/test_pytmfunc.py index ab1a8d4..12d093b 100644 --- a/tests/test_pytmfunc.py +++ b/tests/test_pytmfunc.py @@ -227,7 +227,7 @@ def test_resolve(self): resp = Dataflow(web, user, "Show comments (*)") TM._threats = [ - Threat(SID=klass, target=klass) + Threat(SID=klass, target=klass, severity="") for klass in ["Actor", "Server", "Datastore", "Dataflow"] ] tm.resolve() @@ -276,8 +276,8 @@ def test_overrides(self): resp = Dataflow(web, user, "Show comments (*)") TM._threats = [ - Threat(SID="Server", target="Server", condition="False"), - Threat(SID="Datastore", target="Datastore"), + Threat(SID="Server", severity="High", target="Server", condition="False"), + Threat(SID="Datastore", target="Datastore", severity="High"), ] tm.resolve() @@ -437,6 +437,7 @@ def test_multilevel_dfd(self): output = tm.dfd(levels={0}) with open(os.path.join(output_path, "0.txt"), "w") as x: x.write(output) + self.maxDiff = None self.assertEqual(output, level_0) TM.reset() From 3721365d623a6aa7784dff5d9fa6af3a1c6cc3d3 Mon Sep 17 00:00:00 2001 From: izar Date: Wed, 27 Dec 2023 15:07:59 -0500 Subject: [PATCH 3/7] removing files added by mistake --- colormap.dot | 134 ------------------------------------------------ foo.png | Bin 61811 -> 0 bytes no_colormap.dot | 134 ------------------------------------------------ test.txt | 6 --- 4 files changed, 274 deletions(-) delete mode 100644 colormap.dot delete mode 100644 foo.png delete mode 100644 no_colormap.dot delete mode 100644 test.txt diff --git a/colormap.dot b/colormap.dot deleted file mode 100644 index 0a220ee..0000000 --- a/colormap.dot +++ /dev/null @@ -1,134 +0,0 @@ -digraph tm { - graph [ - fontname = Arial; - fontsize = 14; - ] - node [ - fontname = Arial; - fontsize = 14; - rankdir = lr; - ] - edge [ - shape = none; - arrowtail = onormal; - fontname = Arial; - fontsize = 12; - ] - labelloc = "t"; - fontsize = 20; - nodesep = 1; - - subgraph cluster_boundary_AWSVPC_579e9aae81 { - graph [ - fontsize = 10; - fontcolor = black; - style = dashed; - color = black; - label = <AWS VPC>; - ] - - lambda_AWSLambda_0291419f72 [ - shape = rectangle; style=rounded; - - color = gold; - fontcolor = gold; - label = < -
{label}
- -
AWS Lambda
- >; - ] - - } - - subgraph cluster_boundary_Internet_acf3059e70 { - graph [ - fontsize = 10; - fontcolor = black; - style = dashed; - color = black; - label = <Internet>; - ] - - actor_User_f2eb7a3ff7 [ - shape = square; - color = darkgreen; - fontcolor = darkgreen; - label = "User"; - margin = 0.02; - ] - - } - - subgraph cluster_boundary_ServerDB_88f2d9c06f { - graph [ - fontsize = 10; - fontcolor = black; - style = dashed; - color = black; - label = <Server/DB>; - ] - - datastore_SQLDatabase_f8af758679 [ - shape = none; - fixedsize = shape; - image = "/Users/izar.tarandach/Src/pytm/pytm/images/datastore_gold.png"; - imagescale = true; - color = gold; - fontcolor = gold; - xlabel = "SQL Database"; - label = ""; - ] - - datastore_RealIdentityDatabase_2c440ebe53 [ - shape = none; - fixedsize = shape; - image = "/Users/izar.tarandach/Src/pytm/pytm/images/datastore_gold.png"; - imagescale = true; - color = gold; - fontcolor = gold; - xlabel = "Real Identity\nDatabase"; - label = ""; - ] - - } - - server_WebServer_d2006ce1bb [ - shape = circle; - color = firebrick3; - fontcolor = firebrick3; - label = "Web Server"; - margin = 0.02; - ] - - datastore_SQLDatabase_f8af758679 -> datastore_RealIdentityDatabase_2c440ebe53 [ - color = firebrick3; - fontcolor = firebrick3; - dir = forward; - label = "(1) Database\nverify real user\nidentity"; - ] - - actor_User_f2eb7a3ff7 -> server_WebServer_d2006ce1bb [ - color = firebrick3; - fontcolor = firebrick3; - dir = both; - label = "(2) User enters\ncomments (*) - (5) Show comments\n(*)"; - ] - - server_WebServer_d2006ce1bb -> datastore_SQLDatabase_f8af758679 [ - color = firebrick3; - fontcolor = firebrick3; - dir = both; - label = "(3) Insert query\nwith comments - (4) Retrieve\ncomments"; - ] - - lambda_AWSLambda_0291419f72 -> datastore_SQLDatabase_f8af758679 [ - color = firebrick3; - fontcolor = firebrick3; - dir = forward; - label = "(6) Serverless\nfunction\nperiodically\ncleans DB"; - ] - -} diff --git a/foo.png b/foo.png deleted file mode 100644 index 685b90f6932687bd6ecc019f866270a9b00e3e1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61811 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

;EtB(Dg{Xn&DY6Y%p%l3_qkgp0Dyz9KXykGo- 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 RI#j}cv^7^OPS&v8^FRMk6Yu~4 diff --git a/no_colormap.dot b/no_colormap.dot deleted file mode 100644 index b745fc9..0000000 --- a/no_colormap.dot +++ /dev/null @@ -1,134 +0,0 @@ -digraph tm { - graph [ - fontname = Arial; - fontsize = 14; - ] - node [ - fontname = Arial; - fontsize = 14; - rankdir = lr; - ] - edge [ - shape = none; - arrowtail = onormal; - fontname = Arial; - fontsize = 12; - ] - labelloc = "t"; - fontsize = 20; - nodesep = 1; - - subgraph cluster_boundary_AWSVPC_579e9aae81 { - graph [ - fontsize = 10; - fontcolor = firebrick2; - style = dashed; - color = firebrick2; - label = <AWS VPC>; - ] - - lambda_AWSLambda_0291419f72 [ - shape = rectangle; style=rounded; - - color = black; - fontcolor = black; - label = < - - -
AWS Lambda
- >; - ] - - } - - subgraph cluster_boundary_Internet_acf3059e70 { - graph [ - fontsize = 10; - fontcolor = firebrick2; - style = dashed; - color = firebrick2; - label = <Internet>; - ] - - actor_User_f2eb7a3ff7 [ - shape = square; - color = black; - fontcolor = black; - label = "User"; - margin = 0.02; - ] - - } - - subgraph cluster_boundary_ServerDB_88f2d9c06f { - graph [ - fontsize = 10; - fontcolor = firebrick2; - style = dashed; - color = firebrick2; - label = <Server/DB>; - ] - - datastore_SQLDatabase_f8af758679 [ - shape = none; - fixedsize = shape; - image = "/Users/izar.tarandach/Src/pytm/pytm/images/datastore_black.png"; - imagescale = true; - color = black; - fontcolor = black; - xlabel = "SQL Database"; - label = ""; - ] - - datastore_RealIdentityDatabase_2c440ebe53 [ - shape = none; - fixedsize = shape; - image = "/Users/izar.tarandach/Src/pytm/pytm/images/datastore_black.png"; - imagescale = true; - color = black; - fontcolor = black; - xlabel = "Real Identity\nDatabase"; - label = ""; - ] - - } - - server_WebServer_d2006ce1bb [ - shape = circle; - color = black; - fontcolor = black; - label = "Web Server"; - margin = 0.02; - ] - - datastore_SQLDatabase_f8af758679 -> datastore_RealIdentityDatabase_2c440ebe53 [ - color = black; - fontcolor = black; - dir = forward; - label = "(1) Database\nverify real user\nidentity"; - ] - - actor_User_f2eb7a3ff7 -> server_WebServer_d2006ce1bb [ - color = black; - fontcolor = black; - dir = both; - label = "(2) User enters\ncomments (*) - (5) Show comments\n(*)"; - ] - - server_WebServer_d2006ce1bb -> datastore_SQLDatabase_f8af758679 [ - color = black; - fontcolor = black; - dir = both; - label = "(3) Insert query\nwith comments - (4) Retrieve\ncomments"; - ] - - lambda_AWSLambda_0291419f72 -> datastore_SQLDatabase_f8af758679 [ - color = black; - fontcolor = black; - dir = forward; - label = "(6) Serverless\nfunction\nperiodically\ncleans DB"; - ] - -} diff --git a/test.txt b/test.txt deleted file mode 100644 index 86b5ed8..0000000 --- a/test.txt +++ /dev/null @@ -1,6 +0,0 @@ -.......................................................................................................................... ----------------------------------------------------------------------- -Ran 122 tests in 0.047s - -OK -/var/folders/w3/24r9gvg51fl48q4192fnfvrh0000gp/T From b55d4235dd8d9ad9c7d5c15c7cddac63becb732f Mon Sep 17 00:00:00 2001 From: izar Date: Wed, 27 Dec 2023 15:35:26 -0500 Subject: [PATCH 4/7] added Matt's suggestion to colorize the element's background. --- pytm/pytm.py | 132 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 79 insertions(+), 53 deletions(-) diff --git a/pytm/pytm.py b/pytm/pytm.py index 275d5f6..9906648 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -33,17 +33,18 @@ By Chris Beaumont """ + def sev_to_color(sev): # calculate the color depending on the severity if sev == 5: - return "firebrick3" + return 'firebrick3; fillcolor="#b2222222"; style=filled ' elif sev <= 4 and sev >= 2: - return "gold" + return 'gold; fillcolor="#ffd80022"; style=filled' elif sev < 2 and sev >= 0: - return "darkgreen" - + return 'darkgreen; fillcolor="#00630022"; style=filled' + return "black" - + class UIError(Exception): def __init__(self, e, context): @@ -259,15 +260,16 @@ def __ne__(self, other): def __str__(self): return ", ".join(sorted(set(d.name for d in self))) + class varControls(var): def __set__(self, instance, value): if not isinstance(value, Controls): raise ValueError( - "expecting an Controls " - "value, got a {}".format(type(value)) + "expecting an Controls " "value, got a {}".format(type(value)) ) super().__set__(instance, value) + class Action(Enum): """Action taken when validating a threat model.""" @@ -459,18 +461,25 @@ def _apply_defaults(flows, data): e._safeset("dstPort", e.sink.port) if hasattr(e.sink.controls, "isEncrypted"): e.controls._safeset("isEncrypted", e.sink.controls.isEncrypted) - e.controls._safeset("authenticatesDestination", e.source.controls.authenticatesDestination) - e.controls._safeset("checksDestinationRevocation", e.source.controls.checksDestinationRevocation) + e.controls._safeset( + "authenticatesDestination", e.source.controls.authenticatesDestination + ) + e.controls._safeset( + "checksDestinationRevocation", e.source.controls.checksDestinationRevocation + ) for d in e.data: if d.isStored: if hasattr(e.sink.controls, "isEncryptedAtRest"): for d in e.data: - d._safeset("isDestEncryptedAtRest", e.sink.controls.isEncryptedAtRest) + d._safeset( + "isDestEncryptedAtRest", e.sink.controls.isEncryptedAtRest + ) if hasattr(e.source, "isEncryptedAtRest"): for d in e.data: d._safeset( - "isSourceEncryptedAtRest", e.source.controls.isEncryptedAtRest + "isSourceEncryptedAtRest", + e.source.controls.isEncryptedAtRest, ) if d.credentialsLife != Lifetime.NONE and not d.isCredentials: d._safeset("isCredentials", True) @@ -539,28 +548,26 @@ def _describe_classes(classes): def _list_elements(): """List all elements which can be used in a threat model with the corresponding description""" + def all_subclasses(cls): """Get all sub classes of a class""" subclasses = set(cls.__subclasses__()) - return subclasses.union( - (s for c in subclasses for s in all_subclasses(c))) + return subclasses.union((s for c in subclasses for s in all_subclasses(c))) def print_components(cls_list): elements = sorted(cls_list, key=lambda c: c.__name__) max_len = max((len(e.__name__) for e in elements)) for sc in elements: - doc = sc.__doc__ if sc.__doc__ is not None else '' - print(f'{sc.__name__:<{max_len}} -- {doc}') - #print all elements - print('Elements:') + doc = sc.__doc__ if sc.__doc__ is not None else "" + print(f"{sc.__name__:<{max_len}} -- {doc}") + + # print all elements + print("Elements:") print_components(all_subclasses(Element)) # Print Attributes - print('\nAtributes:') - print_components( - all_subclasses(OrderedEnum) | {Data, Action, Lifetime} - ) - + print("\nAtributes:") + print_components(all_subclasses(OrderedEnum) | {Data, Action, Lifetime}) def _get_elements_and_boundaries(flows): @@ -746,7 +753,14 @@ class TM: _data = [] _threatsExcluded = [] _sf = None - _duplicate_ignored_attrs = "name", "note", "order", "response", "responseTo", "controls" + _duplicate_ignored_attrs = ( + "name", + "note", + "order", + "response", + "responseTo", + "controls", + ) name = varString("", required=True, doc="Model name") description = varString("", required=True, doc="Model description") threatsFile = varString( @@ -799,7 +813,9 @@ def _add_threats(self): with open(self.threatsFile, "r", encoding="utf8") as threat_file: threats_json = json.load(threat_file) except (FileNotFoundError, PermissionError, IsADirectoryError) as e: - raise UIError(e, f"while trying to open the the threat file ({self.threatsFile}).") + raise UIError( + e, f"while trying to open the the threat file ({self.threatsFile})." + ) for i in threats_json: TM._threats.append(Threat(**i)) @@ -898,7 +914,7 @@ def _check_duplicates(self, flows): left_controls_attrs = left.controls._attr_values() right_controls_attrs = right.controls._attr_values() - #for a in self._duplicate_ignored_attrs: + # for a in self._duplicate_ignored_attrs: # del left_controls_attrs[a], right_controls_attrs[a] if left_controls_attrs != right_controls_attrs: continue @@ -906,7 +922,6 @@ def _check_duplicates(self, flows): right._is_drawn = True continue - raise ValueError( "Duplicate Dataflow found between {} and {}: " "{} is same as {}".format( @@ -1025,7 +1040,9 @@ def report(self, template_path): with open(template_path) as file: template = file.read() except (FileNotFoundError, PermissionError, IsADirectoryError) as e: - raise UIError(e, f"while trying to open the report template file ({template_path}).") + raise UIError( + e, f"while trying to open the report template file ({template_path})." + ) threats = encode_threat_data(TM._threats) findings = encode_threat_data(self.findings) @@ -1061,7 +1078,6 @@ def process(self): sys.stderr.write(erromsg) sys.exit(127) - def _process(self): self.check() result = get_args() @@ -1097,7 +1113,9 @@ def _process(self): with open(result.json, "w", encoding="utf8") as f: json.dump(self, f, default=to_serializable) except (FileExistsError, PermissionError, IsADirectoryError) as e: - raise UIError(e, f"while trying to write to the result file ({result.json})") + raise UIError( + e, f"while trying to write to the result file ({result.json})" + ) if result.report is not None: print(self.report(result.report)) @@ -1128,7 +1146,6 @@ def _stale(self, days): print(f"Checking for code {days} days older than this model.") for e in TM._elements: - for src in e.sourceFiles: try: src_mtime = datetime.fromtimestamp( @@ -1205,6 +1222,7 @@ def get_table(self, db, klass): ] return db.define_table(name, fields) + class Controls: """Controls implemented by/on and Element""" @@ -1297,7 +1315,6 @@ def _attr_values(self): result[i] = value return result - def _safeset(self, attr, value): try: setattr(self, attr, value) @@ -1305,7 +1322,6 @@ def _safeset(self, attr, value): pass - class Element: """A generic element""" @@ -1489,14 +1505,14 @@ def _set_severity(self, sev): "medium": 3, "low": 2, "very low": 1, - "info": 0 + "info": 0, } - if(sev.lower() not in sevs.keys()): + if sev.lower() not in sevs.keys(): return - if(self.severity < sevs[sev.lower()]): - self.severity = sevs[sev.lower()] + if self.severity < sevs[sev.lower()]: + self.severity = sevs[sev.lower()] return @@ -1675,7 +1691,7 @@ class Datastore(Asset): * FILE_SYSTEM - files on a file system * SQL - A SQL Database * LDAP - An LDAP Server -* AWS_S3 - An S3 Bucket within AWS""" +* AWS_S3 - An S3 Bucket within AWS""", ) def __init__(self, name, **kwargs): @@ -1705,16 +1721,20 @@ def dfd(self, **kwargs): return "" color = self._color() + color_file = "black" if kwargs.get("colormap", False): color = sev_to_color(self.severity) + color_file = color.split(";")[0] return self._dfd_template().format( uniq_name=self._uniq_name(), label=self._label(), color=color, shape=self._shape(), - image=os.path.join(os.path.dirname(__file__), "images", f"datastore_{color}.png"), + image=os.path.join( + os.path.dirname(__file__), "images", f"datastore_{color_file}.png" + ), ) @@ -1875,7 +1895,7 @@ def dfd(self, **kwargs): continue # The content to draw can include Boundary objects edges.append(e.dfd(**kwargs)) - + return self._dfd_template().format( uniq_name=self._uniq_name(), label=self._label(), @@ -1955,27 +1975,29 @@ def serialize(obj, nested=False): result[i.lstrip("_")] = value return result + def encode_element_threat_data(obj): """Used to html encode threat data from a list of Elements""" encoded_elements = [] - if (type(obj) is not list): - raise ValueError("expecting a list value, got a {}".format(type(obj))) + if type(obj) is not list: + raise ValueError("expecting a list value, got a {}".format(type(obj))) for o in obj: - c = copy.deepcopy(o) - for a in o._attr_values(): - if (a == "findings"): - encoded_findings = encode_threat_data(o.findings) - c._safeset("findings", encoded_findings) + c = copy.deepcopy(o) + for a in o._attr_values(): + if a == "findings": + encoded_findings = encode_threat_data(o.findings) + c._safeset("findings", encoded_findings) else: - v = getattr(o, a) - if (type(v) is not list or (type(v) is list and len(v) != 0)): - c._safeset(a, v) - - encoded_elements.append(c) + v = getattr(o, a) + if type(v) is not list or (type(v) is list and len(v) != 0): + c._safeset(a, v) + + encoded_elements.append(c) return encoded_elements + def encode_threat_data(obj): """Used to html encode threat data from a list of threats or findings""" encoded_threat_data = [] @@ -2032,12 +2054,16 @@ def get_args(): _parser.add_argument( "--list", action="store_true", help="list all available threats" ) - _parser.add_argument("--colormap", action="store_true", help="color the risk in the diagram") + _parser.add_argument( + "--colormap", action="store_true", help="color the risk in the diagram" + ) _parser.add_argument( "--describe", help="describe the properties available for a given element" ) _parser.add_argument( - "--list-elements", action="store_true", help="list all elements which can be part of a threat model" + "--list-elements", + action="store_true", + help="list all elements which can be part of a threat model", ) _parser.add_argument("--json", help="output a JSON file") _parser.add_argument( From 79c32af8c3f4a9d33919af4c6ea301ee7885ba83 Mon Sep 17 00:00:00 2001 From: izar Date: Wed, 27 Dec 2023 15:40:27 -0500 Subject: [PATCH 5/7] added a note about pytmGPT --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5399cd8..71bc633 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,8 @@ tm.process() ``` +You also have the option of using [pytmGPT](https://chat.openai.com/g/g-soISG24ix-pytmgpt) to create your models from prose! + ### Generating Diagrams Diagrams are output as [Dot](https://graphviz.gitlab.io/) and [PlantUML](https://plantuml.com/). From 01b2ce9f5d360bf162168043bd0ecc23d52cce84 Mon Sep 17 00:00:00 2001 From: Izar Tarandach Date: Tue, 16 Jan 2024 10:15:14 -0500 Subject: [PATCH 6/7] Added multi-severity to the colormap test --- tests/dfd_colormap.dot | 142 +++++++++++++++++++++++++++++++++++++++++ tests/test_pytmfunc.py | 37 +++++++++++ 2 files changed, 179 insertions(+) create mode 100644 tests/dfd_colormap.dot diff --git a/tests/dfd_colormap.dot b/tests/dfd_colormap.dot new file mode 100644 index 0000000..d3ed759 --- /dev/null +++ b/tests/dfd_colormap.dot @@ -0,0 +1,142 @@ +digraph tm { + graph [ + fontname = Arial; + fontsize = 14; + ] + node [ + fontname = Arial; + fontsize = 14; + rankdir = lr; + ] + edge [ + shape = none; + arrowtail = onormal; + fontname = Arial; + fontsize = 12; + ] + labelloc = "t"; + fontsize = 20; + nodesep = 1; + + subgraph cluster_boundary_Companynet_88f2d9c06f { + graph [ + fontsize = 10; + fontcolor = black; + style = dashed; + color = black; + label = <Company net>; + ] + + subgraph cluster_boundary_dmz_579e9aae81 { + graph [ + fontsize = 10; + fontcolor = black; + style = dashed; + color = black; + label = <dmz>; + ] + + server_Gateway_f8af758679 [ + shape = circle; + color = firebrick3; fillcolor="#b2222222"; style=filled ; + fontcolor = black; + label = "Gateway"; + margin = 0.02; + ] + + } + + subgraph cluster_boundary_backend_f2eb7a3ff7 { + graph [ + fontsize = 10; + fontcolor = black; + style = dashed; + color = black; + label = <backend>; + ] + + server_WebServer_2c440ebe53 [ + shape = circle; + color = firebrick3; fillcolor="#b2222222"; style=filled ; + fontcolor = black; + label = "Web Server"; + margin = 0.02; + ] + + datastore_SQLDatabase_0291419f72 [ + shape = none; + fixedsize = shape; + image = "/Users/izar.tarandach/Src/pytm/pytm/images/datastore_gold.png"; + imagescale = true; + color = gold; fillcolor="#ffd80022"; style=filled; + fontcolor = black; + xlabel = "SQL Database"; + label = ""; + ] + + } + + } + + subgraph cluster_boundary_Internet_acf3059e70 { + graph [ + fontsize = 10; + fontcolor = black; + style = dashed; + color = black; + label = <Internet>; + ] + + actor_User_d2006ce1bb [ + shape = square; + color = darkgreen; fillcolor="#00630022"; style=filled; + fontcolor = black; + label = "User"; + margin = 0.02; + ] + + } + + actor_User_d2006ce1bb -> server_Gateway_f8af758679 [ + color = gold; fillcolor="#ffd80022"; style=filled; + fontcolor = gold; fillcolor="#ffd80022"; style=filled; + dir = forward; + label = "User enters\ncomments (*)"; + ] + + server_Gateway_f8af758679 -> server_WebServer_2c440ebe53 [ + color = gold; fillcolor="#ffd80022"; style=filled; + fontcolor = gold; fillcolor="#ffd80022"; style=filled; + dir = forward; + label = "Request"; + ] + + server_WebServer_2c440ebe53 -> datastore_SQLDatabase_0291419f72 [ + color = gold; fillcolor="#ffd80022"; style=filled; + fontcolor = gold; fillcolor="#ffd80022"; style=filled; + dir = forward; + label = "Insert query with\ncomments"; + ] + + datastore_SQLDatabase_0291419f72 -> server_WebServer_2c440ebe53 [ + color = gold; fillcolor="#ffd80022"; style=filled; + fontcolor = gold; fillcolor="#ffd80022"; style=filled; + dir = forward; + label = "Retrieve comments"; + ] + + server_WebServer_2c440ebe53 -> server_Gateway_f8af758679 [ + color = gold; fillcolor="#ffd80022"; style=filled; + fontcolor = gold; fillcolor="#ffd80022"; style=filled; + dir = forward; + label = "Response"; + ] + + server_Gateway_f8af758679 -> actor_User_d2006ce1bb [ + color = gold; fillcolor="#ffd80022"; style=filled; + fontcolor = gold; fillcolor="#ffd80022"; style=filled; + dir = forward; + label = "Show comments (*)"; + ] + +} diff --git a/tests/test_pytmfunc.py b/tests/test_pytmfunc.py index 12d093b..f5129d5 100644 --- a/tests/test_pytmfunc.py +++ b/tests/test_pytmfunc.py @@ -126,6 +126,43 @@ def test_dfd(self): self.maxDiff = None self.assertEqual(output, expected) + def test_dfd_colormap(self): + dir_path = os.path.dirname(os.path.realpath(__file__)) + install_path = os.path.dirname(os.path.realpath(pytm.__file__)) + + with open(os.path.join(dir_path, "dfd_colormap.dot")) as x: + expected = ( + x.read().strip().replace("INSTALL_PATH", os.path.dirname(install_path)) + ) + + random.seed(0) + + TM.reset() + tm = TM("my test tm", description="aaa") + internet = Boundary("Internet") + net = Boundary("Company net") + dmz = Boundary("dmz", inBoundary=net) + backend = Boundary("backend", inBoundary=net) + user = Actor("User", inBoundary=internet) + gw = Server("Gateway", inBoundary=dmz) + web = Server("Web Server", inBoundary=backend) + db = Datastore("SQL Database", inBoundary=backend, isEncryptedAtRest=True) + comment = Data("Comment", isStored=True) + + Dataflow(user, gw, "User enters comments (*)") + Dataflow(gw, web, "Request") + Dataflow(web, db, "Insert query with comments", data=[comment]) + Dataflow(db, web, "Retrieve comments") + Dataflow(web, gw, "Response") + Dataflow(gw, user, "Show comments (*)") + + self.assertTrue(tm.check()) + tm.resolve() + output = tm.dfd(colormap=True) + + self.maxDiff = None + self.assertEqual(output, expected) + def test_dfd_duplicates_ignore(self): dir_path = os.path.dirname(os.path.realpath(__file__)) install_path = os.path.dirname(os.path.realpath(pytm.__file__)) From eb9af4a264b100995deee870fe10313b7a3b2474 Mon Sep 17 00:00:00 2001 From: Izar Tarandach Date: Tue, 16 Jan 2024 10:18:36 -0500 Subject: [PATCH 7/7] Ops. Always forget to fix the path. --- tests/dfd_colormap.dot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dfd_colormap.dot b/tests/dfd_colormap.dot index d3ed759..1c4e61a 100644 --- a/tests/dfd_colormap.dot +++ b/tests/dfd_colormap.dot @@ -66,7 +66,7 @@ digraph tm { datastore_SQLDatabase_0291419f72 [ shape = none; fixedsize = shape; - image = "/Users/izar.tarandach/Src/pytm/pytm/images/datastore_gold.png"; + image = "INSTALL_PATH/pytm/images/datastore_gold.png"; imagescale = true; color = gold; fillcolor="#ffd80022"; style=filled; fontcolor = black;