terminal = false packages = [ "typing_extensions", "pyyaml", "pandas", "rdflib", "static/d3fendtools-0.0.1-py3-none-any.whl" ] [[fetch]] files = [ "./app.py", "./static/ontology.ttl", "./static/d3fend-short.ttl", "./static/examples/mermaid/application.md", "./static/examples/mermaid/network.md", "./static/examples/mermaid/webmail.md", ]

Design & D3FEND an experimental webapp to support secure architectural design.

Mermaid

ATT&CK Summary with artifacts and attacks.


Click on the Generate button to generate or update the report. The operation will take ~20 seconds.

D3FEND Summary with artifacts and defenses.


Click on the Generate button to generate or update the report. The operation will take ~20 seconds.

This is a description of your IT architecture in RDF format. Nodes are described using MITRE categories.

graph placeholder

This is a python console that you can use to inspect and query the semantic graphs `g` and `g1`. Moreover, you can issue SPARQL queries using the `g.query()` method. Currently it does only show the returned value of the last python line, so statements such as print(), while evaluated, are not shown.

g1 = status["d3fend"].g d3f = dict(g1.namespaces())["d3f"] nodes = ontology.query(""" PREFIX d3f: <%s> SELECT ?s WHERE { ?s rdfs:subClassOf+ d3f:DefensiveTechnique . } LIMIT 5 """ % d3f) list(nodes)

    

Intro

This tool shows how to use the D3FEND ontology to support the design and review of IT architectures.

  1. Represent the components of your infrastructure and their relationships in the left pane editor using Mermaid JS syntax.
  2. Classify your components using the D3FEND vocabulary that is available via auto-complete. For example, you can type d3f:mail CTRL+space and the auto-complete will suggest the possible choices. The following text represents a Client that sends emails to a MTA:
    Client -->|sends d3f:Email to| MTA
    The application is capable to label major sofware applications (e.g. nginx, postfix, ...) and to map them to the corresponding D3FEND classes (e.g. d3f:MailTransferAgent). You can also use some icons from font-awesome (see the gallery) to make your diagram more fancy. For example you can use the fab:fa-react icon to indicate that a component is a WebUI.
  3. Now, click on the ATT&CK tab to show the attacks associated with the specific DigitalArtifacts, or on the D3FEND tab to see the corresponding defensive measures. The tables contain hyperlinks to the corresponding D3FEND classes and ATT&CK techniques.
  4. The D3FEND graph is a RDF graph represented in Turtle format. You can copy and paste it in your favorite RDF editor (e.g. W3C RDF validator).

Open Source

This tool is Open Source, contributions are welcome.

import logging logging.basicConfig(level=logging.INFO) log = logging.getLogger(__name__) # Pyodide imports import pyodide_js from pyodide.ffi import create_proxy log.info(f"{pyodide_js.version= }") import js from pathlib import Path from time import time import re import html import rdflib # Import local application. import app # Import library via js-proxy, requires pyodide>0.21 from js import mermaid, d3 log.info(f"mermaid: {dir(mermaid)}") mermaidAPI = mermaid.default.mermaidAPI mermaidAPI.initialize(**{ "maxTextSize": 100_000, "securityLevel": "loose", "flowchart": { "useMaxWidth": False, "htmlLabels": False, } }) CONFIG = { "strip_prefix": True, # "urn:k8s:" } ontology = app.initialize_graph(["static/ontology.ttl","static/d3fend-short.ttl"]) # Publish information in the global namespace # to be accessible from the console. text_mmd = "" status = { "mermaid": { "last-rendered-unfilter-text": None, "diagram-text": None, }, "editor": None, "d3fend": None, "last_update": time(), "graph": None, } def mmd_to_graph(event): """Generate a d3f graph from mermaid text. """ global status log.warning("mmd_to_graph: %s" % (event,)) if time() - status["last_update"] < 1: log.warning("Skipping update, too fast.") return if hasattr(status["d3fend"], 'g') and (status["mermaid"]["last-rendered-unfilter-text"] == status["editor"].getValue()): log.warning(f"No change in mermaid text.") return status["mermaid"]["last-rendered-unfilter-text"] = status["editor"].getValue() log.warning("Creating RDF graph..") t0 = time() log.warning("Load graph from mermaid.") try: status["d3fend"] = app.mermaidrdf.D3fendMermaid( status["editor"].getValue(), ontology=ontology ) status["d3fend"].parse() log.warning("Loaded %s rdf entries in %d.." % (len(status["d3fend"].g), time()-t0,)) Element("error-panel").clear() except Exception as e: msg = "Failed to create RDF graph: %s" % (e,) app.alert(msg) log.exception(msg) log.info("Original mermaid text: %s" % (status["editor"].getValue(),)) return False return True def update_graph_and_render_mermaid(event): global status log.warning("update_graph_and_render_mermaid: %s" % (event,)) if hasattr(event, "inputType") and len(status["editor"].getValue()) > 6000: log.warning("Skipping auto-update, too big.") return refresh_all(event) def refresh_all(event): # Populate the graph from the mermaid text. if mmd_to_graph(event): render_mmd(event) def create_report(event, id_, f): # Reload the graph from the mermaid text. mmd_to_graph(event) # Generate the d3fend tables. t0 = time() _g = status["d3fend"].annotate() try: html = f(_g, aggregate=True) Element(f"{id_}-summary").clear() Element(f"{id_}-summary").element.innerHTML = html app.alert_clear() log.warning("Created summary in %d.." % (time()-t0,)) except Exception as e: log.error(f"Error in {id_} summary: {e}") app.alert(f"Error in {id_} summary: {e}") def render_mmd(event): """Render the `mermaid-graph` with `mermaid`.text. This does not update the RDF graph. """ global status log.warning(f"render_mmd: event {event}") try: text_mmd, mermaid_svg = app.generate_diagram_mmd( text=status["editor"].getValue(), filter_=Element("mermaid-filter").element.value, flip=Element("mermaid-chk-flip").element.checked, mermaidAPI=mermaidAPI, ) status["mermaid"]["diagram-text"] = text_mmd js.document.getElementById("mermaid-graph").innerHTML = mermaid_svg # Enable zooming via d3 library. svgs = js.d3.selectAll("svg") for svg in svgs: js.mermaid_enable_zoom(svg) app.alert_clear() except Exception as e: log.exception(f"Error in mermaid: {e}") app.alert(f"Error in mermaid: {e}") def refresh_mermaid_on_input(event): log.warning(dir(event)) if (event.key == "Enter"): # Cancel the default action, if needed event.preventDefault() # Trigger the button element with a click Element("mermaid-btn-redraw").element.click() def _create_monaco(init_text=app.MERMAID_INIT_TEXT): eid = js.document.getElementById("mermaid") lang = "python" js.monaco.languages.registerCompletionItemProvider( lang, **{"provideCompletionItems": js.provideCompletionItems} ) editor = js.monaco.editor.create(eid, **{ "value": init_text, "language": lang, "theme": "vs-dark", "automaticLayout": False, "acceptSuggestionOnEnter": False, }) editor.layout(**{ "width": Element("tab-input-mermaid").element.clientWidth, "height": Element("input-panel").element.clientHeight - 100, }) status["editor"] = editor initialized = False if not initialized: _create_monaco() render_mmd(None) # Register events. Element("mermaid").element.addEventListener( "input", create_proxy(update_graph_and_render_mermaid) ) # when pressing enter on mermaid-filter, run update_graph_and_render_mermaid Element("mermaid-filter").element.addEventListener( "keypress", create_proxy(refresh_mermaid_on_input) ) Element("mermaid-btn-fullscreen").element.addEventListener( "click", create_proxy(app.mermaid_toggle_fullscreen) ) js.document.getElementById("mermaid-chk-flip").addEventListener( "click", create_proxy(render_mmd) ) for element_id in ("mermaid-btn-redraw", "tab1-tab"): js.document.getElementById(element_id).addEventListener( "click", create_proxy(render_mmd) ) @app.event_listener("tab-rdf-tab", "click") def _show_turtle_graph(event): if status["d3fend"] is None: render_mmd(event) try: graph_ttl = status["d3fend"].g.serialize() except Exception as e: log.warning(f"Error in _show_turtle_graph: {e}") graph_ttl = "Uninitialized graph" Element("turtle-graph").write(graph_ttl) for example_md in ("application.md", "network.md", "webmail.md", # "lan.md", "diagram.md" ): fpath = Path("static/examples/mermaid") / example_md @app.event_listener(f"example-{fpath.stem}", "click") def _set_content(event, example_md=example_md, fpath=fpath): status["editor"].setValue(fpath.read_text()) refresh_all(event) # Copy to clipboard. js.document.getElementById("attack-summary-copy").addEventListener( "click", create_proxy(lambda event: app._copy_element_to_clipboard_with_js("attack-summary")) ) js.document.getElementById("d3fend-summary-copy").addEventListener( "click", create_proxy(lambda event: app._copy_element_to_clipboard_with_js("d3fend-summary")) ) js.document.getElementById("turtle-graph-copy").addEventListener( "click", create_proxy(lambda event: app._copy_element_to_clipboard_with_js("turtle-graph")) ) js.document.getElementById("mermaid-graph-copy").addEventListener( "click", create_proxy(lambda event: app._copy_text_to_clipboard(status["mermaid"]["diagram-text"])) ) @app.event_listener("attack-summary-reload", "click") def create_report_attack(event): return create_report(event, "attack", app.attack_summary_html) @app.event_listener("d3fend-summary-reload", "click") def create_report_d3fend(event): return create_report(event, "d3fend", app.d3fend_summary_html) initialized = True