mal-toolbox 1.0.5__tar.gz → 1.0.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {mal_toolbox-1.0.5/mal_toolbox.egg-info → mal_toolbox-1.0.6}/PKG-INFO +46 -18
  2. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/README.md +45 -17
  3. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6/mal_toolbox.egg-info}/PKG-INFO +46 -18
  4. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/SOURCES.txt +1 -0
  5. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/__init__.py +5 -2
  6. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/__main__.py +18 -5
  7. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/attackgraph/attackgraph.py +10 -5
  8. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/attackgraph/node.py +19 -9
  9. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/language/languagegraph.py +12 -0
  10. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/model.py +24 -1
  11. mal_toolbox-1.0.6/maltoolbox/visualization/__init__.py +9 -0
  12. mal_toolbox-1.0.6/maltoolbox/visualization/neo4j_utils.py +117 -0
  13. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/pyproject.toml +1 -1
  14. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/tests/test_model.py +1 -1
  15. mal_toolbox-1.0.5/maltoolbox/visualization/__init__.py +0 -0
  16. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/AUTHORS +0 -0
  17. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/LICENSE +0 -0
  18. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/dependency_links.txt +0 -0
  19. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/entry_points.txt +0 -0
  20. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/requires.txt +0 -0
  21. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/top_level.txt +0 -0
  22. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/attackgraph/__init__.py +0 -0
  23. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/attackgraph/analyzers/__init__.py +0 -0
  24. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/exceptions.py +0 -0
  25. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/file_utils.py +0 -0
  26. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/language/__init__.py +0 -0
  27. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/language/compiler/__init__.py +0 -0
  28. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/language/compiler/mal_lexer.py +0 -0
  29. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/language/compiler/mal_parser.py +0 -0
  30. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/patternfinder/__init__.py +0 -0
  31. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/patternfinder/attackgraph_patterns.py +0 -0
  32. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/py.typed +0 -0
  33. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/translators/__init__.py +0 -0
  34. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/translators/securicad.py +0 -0
  35. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/translators/updater.py +0 -0
  36. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/maltoolbox/visualization/graphviz_utils.py +0 -0
  37. {mal_toolbox-1.0.5 → mal_toolbox-1.0.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mal-toolbox
3
- Version: 1.0.5
3
+ Version: 1.0.6
4
4
  Summary: A collection of tools used to create MAL models and attack graphs.
5
5
  Author-email: Andrei Buhaiu <buhaiu@kth.se>, Joakim Loxdal <loxdal@kth.se>, Nikolaos Kakouros <nkak@kth.se>, Jakob Nyberg <jaknyb@kth.se>, Giuseppe Nebbione <nebbione@kth.se>, Sandor Berglund <sandor@kth.se>
6
6
  License: Apache Software License
@@ -34,21 +34,13 @@ MAL ([Meta Attack Language](https://mal-lang.org/)) models and attack graphs.
34
34
 
35
35
  Attack graphs can be used to run simulations in [MAL Simulator](https://github.com/mal-lang/mal-simulator) or run your own custom analysis on.
36
36
 
37
- [Documentation](https://mal-lang.org/mal-toolbox/index.html)(Work in progress)
37
+ - [MAL Toolbox Documentation](https://mal-lang.org/mal-toolbox/index.html)
38
+ - [MAL Toolbox tutorial](https://github.com/mal-lang/mal-toolbox-tutorial)
38
39
 
39
40
  ## The Language Module
40
41
 
41
42
  The language module provides various tools to process MAL languages.
42
43
 
43
- ### The Language Specification Submodule
44
-
45
- The language specification submodule provides functions to load the
46
- specification from a .mar archive(`load_language_specification_from_mar`) or a
47
- JSON file(`load_language_specification_from_json`). This specification will
48
- then be used to generate python classes representing the assets and
49
- associations of the language and to determine the attack steps for each asset
50
- when generating the attack graph.
51
-
52
44
  ## The Model Module
53
45
 
54
46
  With a MAL language a Model (a MAL instance model) can be created either
@@ -72,12 +64,6 @@ nodes related and the asset field which will contain the object in the model
72
64
  instance to which this attack step belongs to, if this information is
73
65
  available.
74
66
 
75
- ## Ingestors Module
76
-
77
- The ingestors module contains various tools that can make use of the instance
78
- model or attack graph. Currently the Neo4J ingestor is the only one available
79
- and it can be used to visualise the instance model and the attack graph.
80
-
81
67
 
82
68
  # Usage
83
69
 
@@ -100,6 +86,11 @@ logging:
100
86
  model_file: "logs/model.yml"
101
87
  langspec_file: "logs/langspec_file.yml"
102
88
  langgraph_file: "logs/langspec_file.yml"
89
+ neo4j:
90
+ uri: None
91
+ username: None
92
+ password: None
93
+ dbname: None
103
94
  ```
104
95
 
105
96
  Alternatively, you can use the `MALTOOLBOX_CONFIG`
@@ -143,12 +134,49 @@ Options:
143
134
  Notes:
144
135
  - <lang_file> can be either a .mar file (generated by the older MAL
145
136
  compiler) or a .mal file containing the DSL written in MAL.```
137
+ ```
146
138
 
147
139
  ## Code examples / Tutorial
148
140
 
149
- To find code examples and tutorials, visit the
141
+ To find more code examples and tutorials, visit the
150
142
  [MAL Toolbox Tutorial](https://github.com/mal-lang/mal-toolbox-tutorial/tree/main) repository.
151
143
 
144
+ ### Load a language
145
+ ```python
146
+
147
+ from maltoolbox.language import LanguageGraph
148
+
149
+ # Will load the MAL language (.mal/.mar) or a saved language graph (yml/json)
150
+ lang_graph = LanguageGraph.load_from_file(lang_file_path)
151
+
152
+ ```
153
+
154
+ ### Generate a model
155
+ ```python
156
+ from maltoolbox.model import Model
157
+
158
+ # Create an empty model
159
+ instance_model = Model("Example Model", lang_graph)
160
+
161
+ # Create and add assets of type supported by the MAL language
162
+ asset1 = instance_model.add_asset('Application', 'Application1')
163
+ asset2 = instance_model.add_asset('Application', 'Application2')
164
+
165
+ # Create association between the assets
166
+ asset1.add_associated_assets('appExecutedApps', asset2)
167
+ ```
168
+
169
+ ## Generate an attack graph
170
+
171
+ ```python
172
+
173
+ from maltoolbox.attackgraph import AttackGraph
174
+
175
+ attack_graph = AttackGraph(lang_graph, model)
176
+
177
+ ```
178
+
179
+
152
180
  # Tests
153
181
  There are unit tests inside of ./tests.
154
182
  Before running the tests, make sure to install the requirements in ./tests/requirements.txt with `python -m pip install -r ./tests/requirements.txt`.
@@ -5,21 +5,13 @@ MAL ([Meta Attack Language](https://mal-lang.org/)) models and attack graphs.
5
5
 
6
6
  Attack graphs can be used to run simulations in [MAL Simulator](https://github.com/mal-lang/mal-simulator) or run your own custom analysis on.
7
7
 
8
- [Documentation](https://mal-lang.org/mal-toolbox/index.html)(Work in progress)
8
+ - [MAL Toolbox Documentation](https://mal-lang.org/mal-toolbox/index.html)
9
+ - [MAL Toolbox tutorial](https://github.com/mal-lang/mal-toolbox-tutorial)
9
10
 
10
11
  ## The Language Module
11
12
 
12
13
  The language module provides various tools to process MAL languages.
13
14
 
14
- ### The Language Specification Submodule
15
-
16
- The language specification submodule provides functions to load the
17
- specification from a .mar archive(`load_language_specification_from_mar`) or a
18
- JSON file(`load_language_specification_from_json`). This specification will
19
- then be used to generate python classes representing the assets and
20
- associations of the language and to determine the attack steps for each asset
21
- when generating the attack graph.
22
-
23
15
  ## The Model Module
24
16
 
25
17
  With a MAL language a Model (a MAL instance model) can be created either
@@ -43,12 +35,6 @@ nodes related and the asset field which will contain the object in the model
43
35
  instance to which this attack step belongs to, if this information is
44
36
  available.
45
37
 
46
- ## Ingestors Module
47
-
48
- The ingestors module contains various tools that can make use of the instance
49
- model or attack graph. Currently the Neo4J ingestor is the only one available
50
- and it can be used to visualise the instance model and the attack graph.
51
-
52
38
 
53
39
  # Usage
54
40
 
@@ -71,6 +57,11 @@ logging:
71
57
  model_file: "logs/model.yml"
72
58
  langspec_file: "logs/langspec_file.yml"
73
59
  langgraph_file: "logs/langspec_file.yml"
60
+ neo4j:
61
+ uri: None
62
+ username: None
63
+ password: None
64
+ dbname: None
74
65
  ```
75
66
 
76
67
  Alternatively, you can use the `MALTOOLBOX_CONFIG`
@@ -114,12 +105,49 @@ Options:
114
105
  Notes:
115
106
  - <lang_file> can be either a .mar file (generated by the older MAL
116
107
  compiler) or a .mal file containing the DSL written in MAL.```
108
+ ```
117
109
 
118
110
  ## Code examples / Tutorial
119
111
 
120
- To find code examples and tutorials, visit the
112
+ To find more code examples and tutorials, visit the
121
113
  [MAL Toolbox Tutorial](https://github.com/mal-lang/mal-toolbox-tutorial/tree/main) repository.
122
114
 
115
+ ### Load a language
116
+ ```python
117
+
118
+ from maltoolbox.language import LanguageGraph
119
+
120
+ # Will load the MAL language (.mal/.mar) or a saved language graph (yml/json)
121
+ lang_graph = LanguageGraph.load_from_file(lang_file_path)
122
+
123
+ ```
124
+
125
+ ### Generate a model
126
+ ```python
127
+ from maltoolbox.model import Model
128
+
129
+ # Create an empty model
130
+ instance_model = Model("Example Model", lang_graph)
131
+
132
+ # Create and add assets of type supported by the MAL language
133
+ asset1 = instance_model.add_asset('Application', 'Application1')
134
+ asset2 = instance_model.add_asset('Application', 'Application2')
135
+
136
+ # Create association between the assets
137
+ asset1.add_associated_assets('appExecutedApps', asset2)
138
+ ```
139
+
140
+ ## Generate an attack graph
141
+
142
+ ```python
143
+
144
+ from maltoolbox.attackgraph import AttackGraph
145
+
146
+ attack_graph = AttackGraph(lang_graph, model)
147
+
148
+ ```
149
+
150
+
123
151
  # Tests
124
152
  There are unit tests inside of ./tests.
125
153
  Before running the tests, make sure to install the requirements in ./tests/requirements.txt with `python -m pip install -r ./tests/requirements.txt`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mal-toolbox
3
- Version: 1.0.5
3
+ Version: 1.0.6
4
4
  Summary: A collection of tools used to create MAL models and attack graphs.
5
5
  Author-email: Andrei Buhaiu <buhaiu@kth.se>, Joakim Loxdal <loxdal@kth.se>, Nikolaos Kakouros <nkak@kth.se>, Jakob Nyberg <jaknyb@kth.se>, Giuseppe Nebbione <nebbione@kth.se>, Sandor Berglund <sandor@kth.se>
6
6
  License: Apache Software License
@@ -34,21 +34,13 @@ MAL ([Meta Attack Language](https://mal-lang.org/)) models and attack graphs.
34
34
 
35
35
  Attack graphs can be used to run simulations in [MAL Simulator](https://github.com/mal-lang/mal-simulator) or run your own custom analysis on.
36
36
 
37
- [Documentation](https://mal-lang.org/mal-toolbox/index.html)(Work in progress)
37
+ - [MAL Toolbox Documentation](https://mal-lang.org/mal-toolbox/index.html)
38
+ - [MAL Toolbox tutorial](https://github.com/mal-lang/mal-toolbox-tutorial)
38
39
 
39
40
  ## The Language Module
40
41
 
41
42
  The language module provides various tools to process MAL languages.
42
43
 
43
- ### The Language Specification Submodule
44
-
45
- The language specification submodule provides functions to load the
46
- specification from a .mar archive(`load_language_specification_from_mar`) or a
47
- JSON file(`load_language_specification_from_json`). This specification will
48
- then be used to generate python classes representing the assets and
49
- associations of the language and to determine the attack steps for each asset
50
- when generating the attack graph.
51
-
52
44
  ## The Model Module
53
45
 
54
46
  With a MAL language a Model (a MAL instance model) can be created either
@@ -72,12 +64,6 @@ nodes related and the asset field which will contain the object in the model
72
64
  instance to which this attack step belongs to, if this information is
73
65
  available.
74
66
 
75
- ## Ingestors Module
76
-
77
- The ingestors module contains various tools that can make use of the instance
78
- model or attack graph. Currently the Neo4J ingestor is the only one available
79
- and it can be used to visualise the instance model and the attack graph.
80
-
81
67
 
82
68
  # Usage
83
69
 
@@ -100,6 +86,11 @@ logging:
100
86
  model_file: "logs/model.yml"
101
87
  langspec_file: "logs/langspec_file.yml"
102
88
  langgraph_file: "logs/langspec_file.yml"
89
+ neo4j:
90
+ uri: None
91
+ username: None
92
+ password: None
93
+ dbname: None
103
94
  ```
104
95
 
105
96
  Alternatively, you can use the `MALTOOLBOX_CONFIG`
@@ -143,12 +134,49 @@ Options:
143
134
  Notes:
144
135
  - <lang_file> can be either a .mar file (generated by the older MAL
145
136
  compiler) or a .mal file containing the DSL written in MAL.```
137
+ ```
146
138
 
147
139
  ## Code examples / Tutorial
148
140
 
149
- To find code examples and tutorials, visit the
141
+ To find more code examples and tutorials, visit the
150
142
  [MAL Toolbox Tutorial](https://github.com/mal-lang/mal-toolbox-tutorial/tree/main) repository.
151
143
 
144
+ ### Load a language
145
+ ```python
146
+
147
+ from maltoolbox.language import LanguageGraph
148
+
149
+ # Will load the MAL language (.mal/.mar) or a saved language graph (yml/json)
150
+ lang_graph = LanguageGraph.load_from_file(lang_file_path)
151
+
152
+ ```
153
+
154
+ ### Generate a model
155
+ ```python
156
+ from maltoolbox.model import Model
157
+
158
+ # Create an empty model
159
+ instance_model = Model("Example Model", lang_graph)
160
+
161
+ # Create and add assets of type supported by the MAL language
162
+ asset1 = instance_model.add_asset('Application', 'Application1')
163
+ asset2 = instance_model.add_asset('Application', 'Application2')
164
+
165
+ # Create association between the assets
166
+ asset1.add_associated_assets('appExecutedApps', asset2)
167
+ ```
168
+
169
+ ## Generate an attack graph
170
+
171
+ ```python
172
+
173
+ from maltoolbox.attackgraph import AttackGraph
174
+
175
+ attack_graph = AttackGraph(lang_graph, model)
176
+
177
+ ```
178
+
179
+
152
180
  # Tests
153
181
  There are unit tests inside of ./tests.
154
182
  Before running the tests, make sure to install the requirements in ./tests/requirements.txt with `python -m pip install -r ./tests/requirements.txt`.
@@ -30,4 +30,5 @@ maltoolbox/translators/securicad.py
30
30
  maltoolbox/translators/updater.py
31
31
  maltoolbox/visualization/__init__.py
32
32
  maltoolbox/visualization/graphviz_utils.py
33
+ maltoolbox/visualization/neo4j_utils.py
33
34
  tests/test_model.py
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # MAL Toolbox v1.0.5
2
+ # MAL Toolbox v1.0.6
3
3
  # Copyright 2025, Andrei Buhaiu.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,7 +21,7 @@ MAL-Toolbox Framework
21
21
  """
22
22
 
23
23
  __title__ = "maltoolbox"
24
- __version__ = "1.0.5"
24
+ __version__ = "1.0.6"
25
25
  __authors__ = [
26
26
  "Andrei Buhaiu",
27
27
  "Giuseppe Nebbione",
@@ -48,6 +48,7 @@ config: dict[str, Any] = {
48
48
  "langspec_file": "logs/langspec_file.json",
49
49
  "langgraph_file": "logs/langgraph.yml",
50
50
  },
51
+ "neo4j": {"uri": None, "username": None, "password": None, "dbname": None},
51
52
  }
52
53
 
53
54
  config_file = os.getenv("MALTOOLBOX_CONFIG", "maltoolbox.yml")
@@ -59,6 +60,8 @@ if os.path.exists(config_file):
59
60
  log_configs = config['logging']
60
61
  os.makedirs(os.path.dirname(log_configs["log_file"]), exist_ok=True)
61
62
 
63
+ neo4j_configs = config['logging']
64
+
62
65
  formatter = logging.Formatter(
63
66
  "%(asctime)s %(name)-12s %(levelname)-8s %(message)s", datefmt="%m-%d %H:%M"
64
67
  )
@@ -3,9 +3,9 @@ Command-line interface for MAL toolbox operations
3
3
 
4
4
  Usage:
5
5
  maltoolbox compile <lang_file> <output_file>
6
- maltoolbox generate-attack-graph [--graphviz] <model_file> <lang_file>
6
+ maltoolbox generate-attack-graph [--graphviz] [--neo4j] <model_file> <lang_file>
7
7
  maltoolbox upgrade-model <model_file> <lang_file> <output_file>
8
- maltoolbox visualize-model <model_file> <lang_file>
8
+ maltoolbox visualize-model [--neo4j] [--graphviz] <model_file> <lang_file>
9
9
 
10
10
  Arguments:
11
11
  <model_file> Path to JSON instance model file.
@@ -15,6 +15,7 @@ Arguments:
15
15
  Options:
16
16
  -h --help Show this screen.
17
17
  -g --graphviz Visualize with graphviz
18
+ -n --neo4j Send to neo4j
18
19
 
19
20
  Notes:
20
21
  - <lang_file> can be either a .mar file (generated by the older MAL
@@ -25,12 +26,17 @@ import logging
25
26
  import json
26
27
  import docopt
27
28
 
28
- from . import log_configs
29
+ from . import log_configs, neo4j_configs
29
30
  from .attackgraph import create_attack_graph, AttackGraph
30
31
  from .language.compiler import MalCompiler
31
32
  from .language.languagegraph import LanguageGraph
32
33
  from .translators.updater import load_model_from_older_version
33
- from .visualization.graphviz_utils import render_model, render_attack_graph
34
+ from .visualization import (
35
+ render_model,
36
+ render_attack_graph,
37
+ ingest_model_neo4j,
38
+ ingest_attack_graph_neo4j
39
+ )
34
40
  from .model import Model
35
41
 
36
42
  logger = logging.getLogger(__name__)
@@ -78,6 +84,8 @@ def main():
78
84
  )
79
85
  if args['--graphviz']:
80
86
  render_attack_graph(attack_graph)
87
+ if args['--neo4j']:
88
+ ingest_attack_graph_neo4j(attack_graph, neo4j_configs)
81
89
 
82
90
  elif args['compile']:
83
91
  compile(
@@ -90,7 +98,12 @@ def main():
90
98
  elif args['visualize-model']:
91
99
  lang_graph = LanguageGraph.load_from_file(args['<lang_file>'])
92
100
  model = Model.load_from_file(args['<model_file>'], lang_graph)
93
- render_model(model)
101
+ if args['--graphviz']:
102
+ render_model(model)
103
+ else:
104
+ print("Use flag --graphviz to generate a pdf")
105
+ if args['--neo4j']:
106
+ ingest_model_neo4j(model, neo4j_configs)
94
107
 
95
108
  if __name__ == "__main__":
96
109
  main()
@@ -167,10 +167,10 @@ class AttackGraph():
167
167
 
168
168
  attack_graph = AttackGraph(lang_graph)
169
169
  attack_graph.model = model
170
- serialized_attack_steps = serialized_object['attack_steps']
170
+ serialized_attack_steps: dict[str, dict] = serialized_object['attack_steps']
171
171
 
172
172
  # Create all of the nodes in the imported attack graph.
173
- for node_dict in serialized_attack_steps.values():
173
+ for node_full_name, node_dict in serialized_attack_steps.items():
174
174
 
175
175
  # Recreate asset links if model is available.
176
176
  node_asset = None
@@ -195,7 +195,10 @@ class AttackGraph():
195
195
  existence_status = (
196
196
  bool(node_dict['existence_status'])
197
197
  if 'existence_status' in node_dict else None
198
- )
198
+ ),
199
+ # Give explicit full name if model is missing, otherwise
200
+ # it will generate automatically in node.full_name
201
+ full_name=node_full_name if not model else None
199
202
  )
200
203
  ag_node.tags = list(node_dict.get('tags', []))
201
204
  ag_node.extras = node_dict.get('extras', {})
@@ -611,7 +614,8 @@ class AttackGraph():
611
614
  node_id: Optional[int] = None,
612
615
  model_asset: Optional[ModelAsset] = None,
613
616
  ttc_dist: Optional[dict] = None,
614
- existence_status: Optional[bool] = None
617
+ existence_status: Optional[bool] = None,
618
+ full_name: Optional[str] = None
615
619
  ) -> AttackGraphNode:
616
620
  """Create and add a node to the graph
617
621
  Arguments:
@@ -656,7 +660,8 @@ class AttackGraph():
656
660
  lg_attack_step = lg_attack_step,
657
661
  model_asset = model_asset,
658
662
  ttc_dist = ttc_dist,
659
- existence_status = existence_status
663
+ existence_status = existence_status,
664
+ full_name = full_name
660
665
  )
661
666
 
662
667
  self.nodes[node_id] = node
@@ -21,7 +21,8 @@ class AttackGraphNode:
21
21
  lg_attack_step: LanguageGraphAttackStep,
22
22
  model_asset: Optional[ModelAsset] = None,
23
23
  ttc_dist: Optional[dict] = None,
24
- existence_status: Optional[bool] = None
24
+ existence_status: Optional[bool] = None,
25
+ full_name: Optional[str] = None
25
26
  ):
26
27
  self.lg_attack_step = lg_attack_step
27
28
  self.name = lg_attack_step.name
@@ -30,6 +31,7 @@ class AttackGraphNode:
30
31
  self.tags = lg_attack_step.tags
31
32
  self.detectors = lg_attack_step.detectors
32
33
 
34
+ self._full_name = full_name
33
35
  self.id = node_id
34
36
  self.model_asset = model_asset
35
37
  self.existence_status = existence_status
@@ -45,10 +47,12 @@ class AttackGraphNode:
45
47
  'lang_graph_attack_step': self.lg_attack_step.full_name,
46
48
  'name': self.name,
47
49
  'ttc': self.ttc,
48
- 'children': {child.id: child.full_name for child in
49
- self.children},
50
- 'parents': {parent.id: parent.full_name for parent in
51
- self.parents},
50
+ 'children': {
51
+ child.id: child.full_name for child in self.children
52
+ },
53
+ 'parents': {
54
+ parent.id: parent.full_name for parent in self.parents
55
+ },
52
56
  }
53
57
 
54
58
  for detector in self.detectors.values():
@@ -105,13 +109,19 @@ class AttackGraphNode:
105
109
  @property
106
110
  def full_name(self) -> str:
107
111
  """
108
- Return the full name of the attack step. This is a combination of the
109
- asset name to which the attack step belongs and attack step name
110
- itself.
112
+ Return the full name of the attack step. This is normally a
113
+ combination of the asset name to which the attack step
114
+ belongs and attack step name itself, but can also be
115
+ explicitly set or a combination of the step id and step name.
111
116
  """
112
- if self.model_asset:
117
+ if self._full_name:
118
+ # Explicitly set
119
+ return self._full_name
120
+ elif self.model_asset:
121
+ # Inherited from model asset
113
122
  full_name = self.model_asset.name + ':' + self.name
114
123
  else:
124
+ # Fallback: use ID
115
125
  full_name = str(self.id) + ':' + self.name
116
126
  return full_name
117
127
 
@@ -171,6 +171,18 @@ class LanguageGraphAsset:
171
171
  current_asset = current_asset.own_super_asset
172
172
  return superassets
173
173
 
174
+ def associations_to(
175
+ self, asset_type: LanguageGraphAsset
176
+ ) -> dict[str, LanguageGraphAssociation]:
177
+ """
178
+ Return dict of association types that go from self
179
+ to given `asset_type`
180
+ """
181
+ associations_to_asset_type = {}
182
+ for fieldname, association in self.associations.items():
183
+ if association in asset_type.associations.values():
184
+ associations_to_asset_type[fieldname] = association
185
+ return associations_to_asset_type
174
186
 
175
187
  @cached_property
176
188
  def associations(self) -> dict[str, LanguageGraphAssociation]:
@@ -101,6 +101,12 @@ class Model():
101
101
  ' and we do not allow duplicates.'
102
102
  )
103
103
 
104
+ if asset_type not in self.lang_graph.assets:
105
+ raise ValueError(
106
+ f'Asset type "{asset_type}" does not exist in language, '
107
+ 'must be one of:\n -' +
108
+ '\n -'.join(self.lang_graph.assets.keys())
109
+ )
104
110
  lg_asset = self.lang_graph.assets[asset_type]
105
111
 
106
112
  asset = ModelAsset(
@@ -184,8 +190,11 @@ class Model():
184
190
  )
185
191
  return self._name_to_asset.get(asset_name, None)
186
192
 
187
-
188
193
  def _to_dict(self) -> dict:
194
+ """Backwards compatible"""
195
+ return self.to_dict()
196
+
197
+ def to_dict(self) -> dict:
189
198
  """Get dictionary representation of the model."""
190
199
  logger.debug('Translating model to dict.')
191
200
  contents: dict[str, Any] = {
@@ -446,6 +455,20 @@ class ModelAsset:
446
455
  assets dictionary entry corresponding to the given fieldname.
447
456
  """
448
457
 
458
+ if fieldname not in self.lg_asset.associations:
459
+ if assets:
460
+ to_asset_type = next(iter(assets)).lg_asset
461
+ possible_associations = self.lg_asset.associations_to(to_asset_type)
462
+ else:
463
+ to_asset_type = None
464
+ possible_associations = self.lg_asset.associations
465
+ raise ValueError(
466
+ f'Association fieldname "{fieldname}" does not exist from '
467
+ f'<{self.lg_asset.name}> to <{to_asset_type.name if to_asset_type else "Any"}>'
468
+ ', must be one of:\n -' +
469
+ '\n -'.join([a for a in possible_associations])
470
+ )
471
+
449
472
  lg_assoc = self.lg_asset.associations[fieldname]
450
473
  other_fieldname = lg_assoc.get_opposite_fieldname(fieldname)
451
474
 
@@ -0,0 +1,9 @@
1
+ from .graphviz_utils import render_attack_graph, render_model
2
+ from .neo4j_utils import ingest_attack_graph_neo4j, ingest_model_neo4j
3
+
4
+ __all__ = [
5
+ 'render_attack_graph',
6
+ 'render_model',
7
+ 'ingest_attack_graph_neo4j',
8
+ 'ingest_model_neo4j'
9
+ ]
@@ -0,0 +1,117 @@
1
+ """
2
+ MAL-Toolbox Neo4j Ingestor Module
3
+ """
4
+ # mypy: ignore-errors
5
+
6
+ import logging
7
+
8
+ from typing import Any
9
+ from py2neo import Graph, Node, Relationship, Subgraph
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ def ingest_attack_graph_neo4j(
14
+ graph,
15
+ neo4j_config: dict[str, Any],
16
+ delete: bool = True
17
+ ) -> None:
18
+ """
19
+ Ingest an attack graph into a neo4j database
20
+
21
+ Arguments:
22
+ graph - the attackgraph provided by the atkgraph.py module.
23
+ uri - the URI to a running neo4j instance
24
+ username - the username to login on Neo4J
25
+ password - the password to login on Neo4J
26
+ dbname - the selected database
27
+ delete - if True, the previous content of the database is deleted
28
+ before ingesting the new attack graph
29
+ """
30
+
31
+ uri = neo4j_config.get('uri')
32
+ username = neo4j_config.get('username')
33
+ password = neo4j_config.get('password')
34
+ dbname = neo4j_config.get('dbname')
35
+
36
+ g = Graph(uri=uri, user=username, password=password, name=dbname)
37
+ if delete:
38
+ g.delete_all()
39
+
40
+ nodes = {}
41
+ rels = []
42
+ for node in graph.nodes.values():
43
+ node_dict = node.to_dict()
44
+ nodes[node.id] = Node(
45
+ node_dict['asset'] if 'asset' in node_dict else node_dict['id'],
46
+ name = node_dict['name'],
47
+ full_name = node.full_name,
48
+ type = node_dict['type'],
49
+ ttc = str(node_dict['ttc']),
50
+ )
51
+
52
+
53
+ for node in graph.nodes.values():
54
+ for child in node.children:
55
+ rels.append(Relationship(nodes[node.id], nodes[child.id]))
56
+
57
+ subgraph = Subgraph(list(nodes.values()), rels)
58
+
59
+ tx = g.begin()
60
+ tx.create(subgraph)
61
+ g.commit(tx)
62
+
63
+
64
+ def ingest_model_neo4j(
65
+ model,
66
+ neo4j_config: dict[str, Any],
67
+ delete: bool = True
68
+ ) -> None:
69
+ """
70
+ Ingest an instance model graph into a Neo4J database
71
+
72
+ Arguments:
73
+ model - the instance model dictionary as provided by the model.py module
74
+ uri - the URI to a running neo4j instance
75
+ username - the username to login on Neo4J
76
+ password - the password to login on Neo4J
77
+ dbname - the selected database
78
+ delete - if True, the previous content of the database is deleted
79
+ before ingesting the new attack graph
80
+ """
81
+
82
+ uri = neo4j_config.get('uri')
83
+ username = neo4j_config.get('username')
84
+ password = neo4j_config.get('password')
85
+ dbname = neo4j_config.get('dbname')
86
+
87
+ g = Graph(uri=uri, user=username, password=password, name=dbname)
88
+ if delete:
89
+ g.delete_all()
90
+
91
+ nodes = {}
92
+ rels = []
93
+
94
+ for asset in model.assets.values():
95
+ nodes[str(asset.id)] = Node(
96
+ str(asset.type),
97
+ name=str(asset.name),
98
+ asset_id=str(asset.id),
99
+ type=str(asset.type)
100
+ )
101
+
102
+ for asset in model.assets.values():
103
+ for fieldname, other_assets in asset.associated_assets.items():
104
+ for other_asset in other_assets:
105
+ rels.append(
106
+ Relationship(
107
+ nodes[str(asset.id)],
108
+ str(fieldname),
109
+ nodes[str(other_asset.id)]
110
+ )
111
+ )
112
+
113
+ subgraph = Subgraph(list(nodes.values()), rels)
114
+
115
+ tx = g.begin()
116
+ tx.create(subgraph)
117
+ g.commit(tx)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mal-toolbox"
3
- version = "1.0.5"
3
+ version = "1.0.6"
4
4
  authors = [
5
5
  { name="Andrei Buhaiu", email="buhaiu@kth.se" },
6
6
  { name="Joakim Loxdal", email="loxdal@kth.se" },
@@ -167,7 +167,7 @@ def test_model_add_association_nonexisting_fieldname(model: Model):
167
167
  data = model.add_asset(asset_type = 'Data')
168
168
 
169
169
  # Try create an association between asset1 and data
170
- with pytest.raises(LookupError):
170
+ with pytest.raises(ValueError):
171
171
  # will raise error because fieldname does not exist
172
172
  asset1.add_associated_assets(
173
173
  fieldname = 'unknownFieldName', assets = {data}
File without changes
File without changes
File without changes
File without changes