terminal = false packages = [ "typing_extensions", "pyyaml", "pandas", "rdflib", ] [[fetch]] files = [ "./mermaidrdf.py", "./mermaidrdf.yaml", "./kuberdf.py", "./app.py", "./ontology.ttl", "./d3fend-short.ttl"]

Here you can paste your kubernetes manifest file and generate mermaid graphs out of them. Importing a Kubernetes manifest file will only generate a nice mermaid diagram. It is useful for documentation purposes but not for security assessment.

ATT&CK Summary with artifacts and attacks.

att&ck summary placeholder

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.

d3f = dict(g1.namespaces())["d3f"] nodes = g.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.

  • The first step is to represent your components and their relationships in a mermaid graph.
  • You can classify your components using font-awesome icons (see the gallery). For example the fa:fa-envelope icon is used to reference is an email.
    Client -->|fa:fa-envelope| 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 the fab:fa-react icon to indicate that a component is a WebUI.
  • Once you have created your mermaid graph, you can click on the D3FEND tab to see the corresponding D3FEND graph. The D3FEND graph is represented as a turtle file. You can copy and paste it in your favorite RDF editor (e.g. W3C RDF validator).
  • The "Summary" tabs shows a table with the main entities of the D3FEND graph and the attacks associated with the specific DigitalArtifacts. The table contains hyperlinks to the corresponding D3FEND classes and ATT&CK techniques.

    Font-awesome reference

    • fa:fa-server - d3f:Server
    • fa:fa-desktop - d3f:WebServerApplication
    • fa:fa-envelope - d3f:Email
    • fa:fa-user-secret - d3f:UserAccount
    • fa:fa-globe - d3f:InternetNetworkTraffic
    • fab:fa-react, fab:fa-angular, fab:fa-vuejs - d3f:WebServerApplication, d3f:GraphicalUserInterface
    • fa:fa-folder - d3f:FileSystem
    • fab:fa-docker - d3f:ContainerProcess

Open Source

This tool is Open Source, contributions are welcome.

D3FEND Summary with artifacts and defeses.

d3f summary placeholder
import logging logging.basicConfig(level=logging.INFO) log = logging.getLogger(__name__) # Pyodide imports import pyodide_js from pyodide import create_proxy log.info(f"{pyodide_js.version= }") import js 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, "flowchart": { "useMaxWidth": False, } }) CONFIG = { "strip_prefix": True, # "urn:k8s:" } g = app.initialize_graph(["ontology.ttl","d3fend-short.ttl"]) g1 = None last_update = time() # Publish information in the global namespace # to be accessible from the console. text_mmd = "" status = { "mermaid": { "last-rendered-unfilter-text": None, "diagram-text": None } } def mmd_to_graph(event): """Generate a d3f graph from mermaid text. This only works in "mermaid" mode. """ global g1, status log.warning("mmd_to_graph: %s" % (event,)) if time() - last_update < 1: log.warning("Skipping update, too fast.") return kube_view = js.document.querySelector("#toggle-kube-view").checked if kube_view: log.warning("In kube view, skipping..") return if g1 and (status["mermaid"]["last-rendered-unfilter-text"] == Element("mermaid").value): log.warning(f"No change in mermaid text.") return status["mermaid"]["last-rendered-unfilter-text"] = Element("mermaid").value log.warning("Creating RDF graph..") t0 = time() log.warning("Load graph from mermaid.") graph_ttl = app.content_to_rdf(Element("mermaid").value) Element("turtle-graph").write(graph_ttl) log.warning("Created RDF graph in %d.." % (time()-t0,)) g1 = rdflib.Graph() g1.parse(data=graph_ttl, format="turtle") def kube_to_graph(event): """Generate a d3f graph from kubernetes text. This only works in "kubernetes" mode. """ global g1 log.warning(f"event {event}") kube_view = js.document.querySelector("#toggle-kube-view").checked if not kube_view: log.warning("Not in kube view, skipping..") return text = Element("kubernetes").value # Always re-create the RDF graph. log.warning("No RDF graph, creating one..") t0 = time() graph_ttl = app.content_to_rdf(text) Element("turtle-graph").write(graph_ttl) log.warning("Created RDF graph in %d.." % (time()-t0,)) g1 = rdflib.Graph() g1.parse(data=graph_ttl, format="turtle") def update_graph_and_render_mermaid(event): global g, g1, status log.warning("update_graph_and_render_mermaid: %s" % (event,)) if hasattr(event, "inputType") and len(Element("mermaid").value) > 1000: log.warning("Skipping update, too big.") return # If toggle-kube-view is checked, don't update the graph. kube_view = js.document.querySelector("#toggle-kube-view").checked if kube_view: log.warning("In kube view, skipping..") return # Populate g1 from the mermaid text. mmd_to_graph(event) render_mmd(event) def create_report(event): kube_view = js.document.querySelector("#toggle-kube-view").checked if kube_view: # Graph is already loaded. if not g1: log.error("Graph is not loaded.") return log.warning("Graph is already loaded.") else: # Reload g1 from the mermaid text. mmd_to_graph(event) # Generate the d3fend tables. t0 = time() for (id_, f) in [("attack", app.attack_summary_html), ("d3fend", app.d3fend_summary_html)]: try: html = f(g + g1, aggregate=True) Element(f"{id_}-summary").clear() Element(f"{id_}-summary").element.innerHTML = html except Exception as e: log.error(f"Error in {id_} summary: {e}") log.warning("Created summary in %d.." % (time()-t0,)) def render_mmd(event): """Render the `mermaid-graph` with `mermaid`.text""" global text_mmd, g, g1, status log.warning(f"render_mmd: event {event}") text = Element("mermaid").value flip = Element("mermaid-chk-flip").element.checked try: content_type = app.guess_content(text) if content_type == "markdown": log.warning("Markdown detected.") text_mmd = app.markdown_to_mermaid(text) elif content_type == "mermaid": log.warning("Mermaid detected.") text_mmd = text else: log.warning("Unknown content.") text_mmd = text text_mmd = app.render_unicode_emojis(text_mmd) # # Filter text_mmd matching mermaid_filter or subgraph # mermaid_filter = Element("mermaid-filter").value re_filter =re.compile(f"{mermaid_filter}|^\s+(subgraph|end|graph)") if mermaid_filter: log.warning(f"Filtering mermaid text with {mermaid_filter}") text_mmd = app.filter_mermaid(text_mmd, mermaid_filter) status["mermaid"]["diagram-text"] = text_mmd # Flip the graph switching TD with LR log.warning(f"Flip: {flip}") if flip: if "graph TD" in text_mmd: text_mmd = text_mmd.replace("graph TD", "graph LR") text_mmd = re.sub(r"subgraph\s+(.*?)\n", r"subgraph \1\ndirection TD\n\n", text_mmd) else: text_mmd = text_mmd.replace("graph LR", "graph TD") text_mmd = re.sub(r"subgraph\s+(.*?)\n", r"subgraph \1\ndirection LR\n\n", text_mmd) log.warning(f"mermaid text: {text_mmd[:100]}") mmd = mermaidAPI.render('mermaid-diagram-svg', text_mmd) js.document.getElementById("mermaid-graph").innerHTML = mmd # Enable zooming via d3 library. svgs = js.d3.selectAll("svg") for svg in svgs: js.mermaid_enable_zoom(svg) except Exception as e: log.exception(f"Error in mermaid: {e}") def resolve_unicode(): text_mmd = text_mmd.replace("u:u-gear", "\N{GEAR}") raise NotImplementedError def kube_to_mmd(event): kube_view = js.document.querySelector("#toggle-kube-view").checked if not kube_view: log.warning("Not in kube view, skipping..") return kube_to_graph(event) graph_to_mmd(event) def graph_to_mmd(event): global g1, CONFIG, status kube_view = js.document.querySelector("#toggle-kube-view").checked if not kube_view: log.warning("Not in kube view, skipping..") return if not g1: log.error("No RDF graph..") return text_mmd = app.rdf_to_mermaid(g1) if CONFIG["strip_prefix"]: text_mmd = text_mmd.replace("urn:k8s:", "") # Update the mermaid textarea Element("mermaid").element.innerHTML = text_mmd status["mermaid"]["last-rendered-unfilter-text"] = str(time()) render_mmd(None) def _copy_to_clipboard_with_js(id_): """Copy text to clipboard using javascript. :param id_: id of the element to copy When a js object is not available in pyodide, we can use the inherited `proxy.new` method to call the constructor. Dictionaries are passed as keyword arguments using the `**` syntax. """ text = Element(id_).element.innerHTML blob = js.Blob.new([text], **{"type": "text/html"}) ci = js.ClipboardItem.new(**{"text/html": blob}) js.navigator.clipboard.write([ci]) def refresh_mermaid_on_input(event): log.warning(dir(event)) initialized = False if not initialized: render_mmd(None) # Register events. js.document.getElementById("mermaid").addEventListener( "input", create_proxy(update_graph_and_render_mermaid) ) # when pressing enter on mermaid-filter, run update_graph_and_render_mermaid js.document.getElementById("mermaid-filter").addEventListener( "input", create_proxy(refresh_mermaid_on_input) ) # when pressing mermaid-btn-fullscreen set the mermaid-graph class to full def _toggle_fullscreen(event): global CONFIG log.warning("Toggle fullscreen") mermaid_graph = Element("mermaid-graph") inputCol = Element("inputCol") header = Element("header") if "diagram-normal" in mermaid_graph.element.classList: mermaid_graph.remove_class("diagram-normal") mermaid_graph.add_class("diagram-full") inputCol.add_class("visually-hidden") header.add_class("visually-hidden") else: mermaid_graph.add_class("diagram-normal") mermaid_graph.remove_class("diagram-full") inputCol.remove_class("visually-hidden") header.remove_class("visually-hidden") js.document.getElementById("mermaid-btn-fullscreen").addEventListener( "click", create_proxy(_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) ) js.document.getElementById("kubernetes-reload").addEventListener( "click", create_proxy(kube_to_mmd) ) js.document.getElementById("tab-attack-tab").addEventListener( "click", create_proxy(create_report) ) js.document.getElementById("tab-d3fend-tab").addEventListener( "click", create_proxy(create_report) ) # Copy to clipboard. js.document.getElementById("attack-summary-copy").addEventListener( "click", create_proxy(lambda event: _copy_to_clipboard_with_js("attack-summary")) ) js.document.getElementById("d3fend-summary-copy").addEventListener( "click", create_proxy(lambda event: _copy_to_clipboard_with_js("d3fend-summary")) ) js.document.getElementById("turtle-graph-copy").addEventListener( "click", create_proxy(lambda event: _copy_to_clipboard_with_js("turtle-graph")) ) initialized = True