diff --git a/example_nest.py b/example_nest.py new file mode 100644 index 0000000..22c029f --- /dev/null +++ b/example_nest.py @@ -0,0 +1,25 @@ +from fluentm import Actor, Boundary, Process, Data, DataFlow, HTTP, TLS, SQL +from fluentm import report + + +scenes = { + # Example using variables, which is fine for small things but gets hard with longer flows + "Towers":[ + DataFlow( + Process("Alice").inBoundary(Boundary("A Inner").inBoundary(Boundary("A Mid").inBoundary(Boundary("A Outer")))), + Process("Bob").inBoundary(Boundary("B Inner").inBoundary(Boundary("B Mid").inBoundary(Boundary("B Outer")))), + TLS("Helo"), + response=TLS("Hai") + ) + ], + "Enter Charlie":[ + DataFlow( + Process("Charlie").inBoundary(Boundary("B Outer")), + Process("Alice"), + TLS("Yo") + ) + ] +} + +if __name__ == "__main__": + report(scenes, outputDir="examples/nest") diff --git a/examples/nest/AggregatedDfd-dfd b/examples/nest/AggregatedDfd-dfd new file mode 100644 index 0000000..8a77f12 --- /dev/null +++ b/examples/nest/AggregatedDfd-dfd @@ -0,0 +1,30 @@ +digraph all { + color=blue rankdir=LR + node [fontname=Arial fontsize=14] + subgraph "cluster_A Outer" { + graph [color=red fontname=Arial fontsize=12 label="A Outer" line=dotted] + subgraph "cluster_A Mid" { + graph [color=red fontname=Arial fontsize=12 label="A Mid" line=dotted] + subgraph "cluster_A Inner" { + graph [color=red fontname=Arial fontsize=12 label="A Inner" line=dotted] + Alice + } + } + } + subgraph "cluster_B Outer" { + graph [color=red fontname=Arial fontsize=12 label="B Outer" line=dotted] + subgraph "cluster_B Mid" { + graph [color=red fontname=Arial fontsize=12 label="B Mid" line=dotted] + subgraph "cluster_B Inner" { + graph [color=red fontname=Arial fontsize=12 label="B Inner" line=dotted] + Bob + } + } + } + subgraph "cluster_B Outer" { + graph [color=red fontname=Arial fontsize=12 label="B Outer" line=dotted] + Charlie + } + Alice -> Bob [dir=both] + Charlie -> Alice [dir=forward] +} diff --git a/examples/nest/AggregatedDfd-dfd.png b/examples/nest/AggregatedDfd-dfd.png new file mode 100644 index 0000000..87d16a8 Binary files /dev/null and b/examples/nest/AggregatedDfd-dfd.png differ diff --git a/examples/nest/Enter Charlie-dfd b/examples/nest/Enter Charlie-dfd new file mode 100644 index 0000000..c54b12b --- /dev/null +++ b/examples/nest/Enter Charlie-dfd @@ -0,0 +1,19 @@ +digraph "Enter Charlie" { + color=blue rankdir=LR + node [fontname=Arial fontsize=14] + subgraph "cluster_B Outer" { + graph [color=red fontname=Arial fontsize=12 label="B Outer" line=dotted] + Charlie + } + subgraph "cluster_A Outer" { + graph [color=red fontname=Arial fontsize=12 label="A Outer" line=dotted] + subgraph "cluster_A Mid" { + graph [color=red fontname=Arial fontsize=12 label="A Mid" line=dotted] + subgraph "cluster_A Inner" { + graph [color=red fontname=Arial fontsize=12 label="A Inner" line=dotted] + Alice + } + } + } + Charlie -> Alice [label="(1) Yo"] +} diff --git a/examples/nest/Enter Charlie-dfd.png b/examples/nest/Enter Charlie-dfd.png new file mode 100644 index 0000000..f956256 Binary files /dev/null and b/examples/nest/Enter Charlie-dfd.png differ diff --git a/examples/nest/ThreatModel.html b/examples/nest/ThreatModel.html new file mode 100644 index 0000000..89a0e93 --- /dev/null +++ b/examples/nest/ThreatModel.html @@ -0,0 +1,126 @@ + + + + Threat Models + + + + +

Aggregated Model

+

This high level diagram gathers all flows in this model to show how components interact at a high level.

+ +
+ +

Towers

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Flow IDPitcherCatcherData Flow
1AliceBobTLS( Helo )
2BobAliceTLS( Hai )
+ + +
+ +

Enter Charlie

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Flow IDPitcherCatcherData Flow
1CharlieAliceTLS( Yo )
+ + +
+ + + \ No newline at end of file diff --git a/examples/nest/Towers-dfd b/examples/nest/Towers-dfd new file mode 100644 index 0000000..591e180 --- /dev/null +++ b/examples/nest/Towers-dfd @@ -0,0 +1,26 @@ +digraph Towers { + color=blue rankdir=LR + node [fontname=Arial fontsize=14] + subgraph "cluster_A Outer" { + graph [color=red fontname=Arial fontsize=12 label="A Outer" line=dotted] + subgraph "cluster_A Mid" { + graph [color=red fontname=Arial fontsize=12 label="A Mid" line=dotted] + subgraph "cluster_A Inner" { + graph [color=red fontname=Arial fontsize=12 label="A Inner" line=dotted] + Alice + } + } + } + subgraph "cluster_B Outer" { + graph [color=red fontname=Arial fontsize=12 label="B Outer" line=dotted] + subgraph "cluster_B Mid" { + graph [color=red fontname=Arial fontsize=12 label="B Mid" line=dotted] + subgraph "cluster_B Inner" { + graph [color=red fontname=Arial fontsize=12 label="B Inner" line=dotted] + Bob + } + } + } + Alice -> Bob [label="(1) Helo"] + Bob -> Alice [label="(2) Hai"] +} diff --git a/examples/nest/Towers-dfd.png b/examples/nest/Towers-dfd.png new file mode 100644 index 0000000..5e05c97 Binary files /dev/null and b/examples/nest/Towers-dfd.png differ diff --git a/fluentm.py b/fluentm.py index fe89683..cdd1b63 100644 --- a/fluentm.py +++ b/fluentm.py @@ -559,87 +559,7 @@ def renderDfd(graph: Digraph, title: str, outputDir: str): return f"{title}-dfd.png" -def aggregatedDfd(scenes: dict): - print("### Starting Aggregated DFD ###") - graph = Digraph("Aggregated DFD") - graph.attr(rankdir="LR", color="blue") - graph.attr("node", fontname="Arial", fontsize="14") - - clusterAttr = { - "fontname": "Arial", - "fontsize": "12", - "color": "red", - "line": "dotted", - } - - boundaryClusters = {} - - # Track which nodes should be placed in which clusters but place neither until we've built the subgraph structure. - placements = {} - edges = {} - - # Gather the boundaries and understand how they're nested (but don't nest the graphviz objects ,yet) - # Graphviz subgraphs can't have nodes added, so you need to populate a graph with nodes first, then subgraph it under another graph - for scene in scenes: - # print(f"scene: {scene}") - for flow in scenes[scene]: - # print(flow) - for e in (flow.pitcher, flow.catcher): - if e.name in placements: - continue # skip to next loop - - ptr = e - while hasattr(ptr, "boundary"): - if ptr.boundary not in boundaryClusters: - boundaryClusters[ptr.boundary] = Digraph( - name=f"cluster_{ptr.boundary.name}", - graph_attr=clusterAttr | {"label": ptr.boundary.name}, - ) - ptr = ptr.boundary - - if hasattr(e, "boundary"): - placements[e.name] = boundaryClusters[e.boundary] - else: - placements[e.name] = graph - - # Figure out which edges we need to draw (We want double ended lines) - for flow in scenes[scene]: - # Look to see if the reverse flow is already there in reverse - # If it is, update the line description to say it should go both ways - f = (flow.pitcher.name, flow.catcher.name) # Directional flow - revf = (f[1], f[0]) # Reverse that directional flow - - # First, check if the flow is already in there but the other way around - if revf in edges: - edges[revf] = "BOTH" - elif f not in edges: - edges[f] = "LR" - - # Place nodes in Graphs, ready for subgraphing - print(placements) - for n in placements: - placements[n].node(n) - - # Subgraph the nodes - for c in boundaryClusters: - if hasattr(c, "boundary"): - boundaryClusters[c.boundary].subgraph(boundaryClusters[c]) - else: - graph.subgraph(boundaryClusters[c]) - - for edge in edges: - print(edge) - if edges[edge] == "LR": - graph.edge_attr.update(dir="forward") - graph.edge(edge[0], edge[1]) - elif edges[edge] == "BOTH": - graph.edge_attr.update(dir="both") - graph.edge(edge[0], edge[1]) - - return graph - - -def dfd(scenes: dict, title: str, dfdLabels=True, render=False): +def dfd(scenes: dict, title: str, dfdLabels=True, render=False, simplified=False): graph = Digraph(title) graph.attr(rankdir="LR", color="blue") graph.attr("node", fontname="Arial", fontsize="14") @@ -690,15 +610,39 @@ def dfd(scenes: dict, title: str, dfdLabels=True, render=False): graph.subgraph(boundaryClusters[c]) # Add the edges - flowCounter = 1 - for flow in scenes[title]: - if dfdLabels is True: - graph.edge( - flow.pitcher.name, flow.catcher.name, f"({flowCounter}) {flow.name}" - ) - else: - graph.edge(flow.pitcher.name, flow.catcher.name, f"({flowCounter})") - flowCounter += 1 + + if simplified is True: + edges = ( + {} + ) # Map the edges and figure out if we need to be double or single ended + for flow in scenes[title]: + # This edge is flow.pitcher.name -> flow.catcher.name + # If we don't have this edge, first check to see if we have it the other way + if (flow.pitcher.name, flow.catcher.name) not in edges and ( + flow.catcher.name, + flow.pitcher.name, + ) not in edges: + edges[(flow.pitcher.name, flow.catcher.name)] = "forward" + elif (flow.pitcher.name, flow.catcher.name) not in edges and ( + flow.catcher.name, + flow.pitcher.name, + ) in edges: + edges[(flow.catcher.name, flow.pitcher.name)] = "both" + + for edge in edges: + print(edge) + graph.edge(edge[0], edge[1], dir=edges[edge]) + + else: # simplified is False + flowCounter = 1 + for flow in scenes[title]: + if dfdLabels is True: + graph.edge( + flow.pitcher.name, flow.catcher.name, f"({flowCounter}) {flow.name}" + ) + else: + graph.edge(flow.pitcher.name, flow.catcher.name, f"({flowCounter})") + flowCounter += 1 return graph @@ -747,7 +691,11 @@ def report(scenes: dict, outputDir: str, select=None, dfdLabels=True): "dataFlowTable": dataFlowTable(scenes, key), } - agg = aggregatedDfd(scenes) + compoundFlows = [] + for flow in scenes.values(): + compoundFlows = compoundFlows + flow + + agg = dfd({"all": compoundFlows}, "all", simplified=True) aggDfd = { "graph": agg, "dfdImage": renderDfd(agg, "AggregatedDfd", outputDir=outputDir), diff --git a/requirements.txt b/requirements.txt index 63b5d50..d10eb08 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ graphviz==0.16 Jinja2==3.0.1 +black==21.7b0 \ No newline at end of file