mal-toolbox 1.0.4__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.4/mal_toolbox.egg-info → mal_toolbox-1.0.6}/PKG-INFO +56 -22
  2. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/README.md +52 -20
  3. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6/mal_toolbox.egg-info}/PKG-INFO +56 -22
  4. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/SOURCES.txt +1 -0
  5. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/requires.txt +3 -0
  6. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/__init__.py +5 -2
  7. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/__main__.py +18 -5
  8. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/attackgraph/attackgraph.py +13 -5
  9. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/attackgraph/node.py +20 -10
  10. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/language/languagegraph.py +24 -5
  11. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/model.py +53 -2
  12. mal_toolbox-1.0.6/maltoolbox/visualization/__init__.py +9 -0
  13. mal_toolbox-1.0.6/maltoolbox/visualization/neo4j_utils.py +117 -0
  14. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/pyproject.toml +8 -2
  15. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/tests/test_model.py +7 -1
  16. mal_toolbox-1.0.4/maltoolbox/visualization/__init__.py +0 -0
  17. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/AUTHORS +0 -0
  18. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/LICENSE +0 -0
  19. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/dependency_links.txt +0 -0
  20. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/entry_points.txt +0 -0
  21. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/top_level.txt +0 -0
  22. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/attackgraph/__init__.py +0 -0
  23. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/attackgraph/analyzers/__init__.py +0 -0
  24. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/exceptions.py +0 -0
  25. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/file_utils.py +0 -0
  26. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/language/__init__.py +0 -0
  27. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/language/compiler/__init__.py +0 -0
  28. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/language/compiler/mal_lexer.py +0 -0
  29. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/language/compiler/mal_parser.py +0 -0
  30. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/patternfinder/__init__.py +0 -0
  31. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/patternfinder/attackgraph_patterns.py +0 -0
  32. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/py.typed +0 -0
  33. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/translators/__init__.py +0 -0
  34. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/translators/securicad.py +0 -0
  35. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/translators/updater.py +0 -0
  36. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/visualization/graphviz_utils.py +0 -0
  37. {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/setup.cfg +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mal-toolbox
3
- Version: 1.0.4
3
+ Version: 1.0.6
4
4
  Summary: A collection of tools used to create MAL models and attack graphs.
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>
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
7
7
  Project-URL: Homepage, https://github.com/mal-lang/mal-toolbox
8
8
  Project-URL: Bug Tracker, https://github.com/mal-lang/mal-toolbox/issues
@@ -23,6 +23,8 @@ Requires-Dist: antlr4-tools
23
23
  Requires-Dist: antlr4-python3-runtime
24
24
  Requires-Dist: docopt
25
25
  Requires-Dist: PyYAML
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest; extra == "dev"
26
28
  Dynamic: license-file
27
29
 
28
30
  # MAL Toolbox overview
@@ -30,23 +32,15 @@ Dynamic: license-file
30
32
  MAL Toolbox is a collection of python modules to help developers create and work with
31
33
  MAL ([Meta Attack Language](https://mal-lang.org/)) models and attack graphs.
32
34
 
33
- Attack graphs can be used to run simulations (see MAL Simulator) or analysis.
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.
34
36
 
35
- [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)
36
39
 
37
40
  ## The Language Module
38
41
 
39
42
  The language module provides various tools to process MAL languages.
40
43
 
41
- ### The Language Specification Submodule
42
-
43
- The language specification submodule provides functions to load the
44
- specification from a .mar archive(`load_language_specification_from_mar`) or a
45
- JSON file(`load_language_specification_from_json`). This specification will
46
- then be used to generate python classes representing the assets and
47
- associations of the language and to determine the attack steps for each asset
48
- when generating the attack graph.
49
-
50
44
  ## The Model Module
51
45
 
52
46
  With a MAL language a Model (a MAL instance model) can be created either
@@ -70,12 +64,6 @@ nodes related and the asset field which will contain the object in the model
70
64
  instance to which this attack step belongs to, if this information is
71
65
  available.
72
66
 
73
- ## Ingestors Module
74
-
75
- The ingestors module contains various tools that can make use of the instance
76
- model or attack graph. Currently the Neo4J ingestor is the only one available
77
- and it can be used to visualise the instance model and the attack graph.
78
-
79
67
 
80
68
  # Usage
81
69
 
@@ -98,6 +86,11 @@ logging:
98
86
  model_file: "logs/model.yml"
99
87
  langspec_file: "logs/langspec_file.yml"
100
88
  langgraph_file: "logs/langspec_file.yml"
89
+ neo4j:
90
+ uri: None
91
+ username: None
92
+ password: None
93
+ dbname: None
101
94
  ```
102
95
 
103
96
  Alternatively, you can use the `MALTOOLBOX_CONFIG`
@@ -124,25 +117,66 @@ You can use the maltoolbox cli to:
124
117
  Command-line interface for MAL toolbox operations
125
118
 
126
119
  Usage:
127
- maltoolbox attack-graph generate [options] <model_file> <lang_file>
128
120
  maltoolbox compile <lang_file> <output_file>
121
+ maltoolbox generate-attack-graph [--graphviz] <model_file> <lang_file>
129
122
  maltoolbox upgrade-model <model_file> <lang_file> <output_file>
123
+ maltoolbox visualize-model <model_file> <lang_file>
130
124
 
131
125
  Arguments:
132
126
  <model_file> Path to JSON instance model file.
133
127
  <lang_file> Path to .mar or .mal file containing MAL spec.
134
128
  <output_file> Path to write the result of the compilation (yml/json).
135
129
 
130
+ Options:
131
+ -h --help Show this screen.
132
+ -g --graphviz Visualize with graphviz
133
+
136
134
  Notes:
137
135
  - <lang_file> can be either a .mar file (generated by the older MAL
138
- compiler) or a .mal file containing the DSL written in MAL.
136
+ compiler) or a .mal file containing the DSL written in MAL.```
139
137
  ```
140
138
 
141
139
  ## Code examples / Tutorial
142
140
 
143
- To find code examples and tutorials, visit the
141
+ To find more code examples and tutorials, visit the
144
142
  [MAL Toolbox Tutorial](https://github.com/mal-lang/mal-toolbox-tutorial/tree/main) repository.
145
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
+
146
180
  # Tests
147
181
  There are unit tests inside of ./tests.
148
182
  Before running the tests, make sure to install the requirements in ./tests/requirements.txt with `python -m pip install -r ./tests/requirements.txt`.
@@ -3,23 +3,15 @@
3
3
  MAL Toolbox is a collection of python modules to help developers create and work with
4
4
  MAL ([Meta Attack Language](https://mal-lang.org/)) models and attack graphs.
5
5
 
6
- Attack graphs can be used to run simulations (see MAL Simulator) or analysis.
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`
@@ -97,25 +88,66 @@ You can use the maltoolbox cli to:
97
88
  Command-line interface for MAL toolbox operations
98
89
 
99
90
  Usage:
100
- maltoolbox attack-graph generate [options] <model_file> <lang_file>
101
91
  maltoolbox compile <lang_file> <output_file>
92
+ maltoolbox generate-attack-graph [--graphviz] <model_file> <lang_file>
102
93
  maltoolbox upgrade-model <model_file> <lang_file> <output_file>
94
+ maltoolbox visualize-model <model_file> <lang_file>
103
95
 
104
96
  Arguments:
105
97
  <model_file> Path to JSON instance model file.
106
98
  <lang_file> Path to .mar or .mal file containing MAL spec.
107
99
  <output_file> Path to write the result of the compilation (yml/json).
108
100
 
101
+ Options:
102
+ -h --help Show this screen.
103
+ -g --graphviz Visualize with graphviz
104
+
109
105
  Notes:
110
106
  - <lang_file> can be either a .mar file (generated by the older MAL
111
- compiler) or a .mal file containing the DSL written in MAL.
107
+ compiler) or a .mal file containing the DSL written in MAL.```
112
108
  ```
113
109
 
114
110
  ## Code examples / Tutorial
115
111
 
116
- To find code examples and tutorials, visit the
112
+ To find more code examples and tutorials, visit the
117
113
  [MAL Toolbox Tutorial](https://github.com/mal-lang/mal-toolbox-tutorial/tree/main) repository.
118
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
+
119
151
  # Tests
120
152
  There are unit tests inside of ./tests.
121
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,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mal-toolbox
3
- Version: 1.0.4
3
+ Version: 1.0.6
4
4
  Summary: A collection of tools used to create MAL models and attack graphs.
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>
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
7
7
  Project-URL: Homepage, https://github.com/mal-lang/mal-toolbox
8
8
  Project-URL: Bug Tracker, https://github.com/mal-lang/mal-toolbox/issues
@@ -23,6 +23,8 @@ Requires-Dist: antlr4-tools
23
23
  Requires-Dist: antlr4-python3-runtime
24
24
  Requires-Dist: docopt
25
25
  Requires-Dist: PyYAML
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest; extra == "dev"
26
28
  Dynamic: license-file
27
29
 
28
30
  # MAL Toolbox overview
@@ -30,23 +32,15 @@ Dynamic: license-file
30
32
  MAL Toolbox is a collection of python modules to help developers create and work with
31
33
  MAL ([Meta Attack Language](https://mal-lang.org/)) models and attack graphs.
32
34
 
33
- Attack graphs can be used to run simulations (see MAL Simulator) or analysis.
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.
34
36
 
35
- [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)
36
39
 
37
40
  ## The Language Module
38
41
 
39
42
  The language module provides various tools to process MAL languages.
40
43
 
41
- ### The Language Specification Submodule
42
-
43
- The language specification submodule provides functions to load the
44
- specification from a .mar archive(`load_language_specification_from_mar`) or a
45
- JSON file(`load_language_specification_from_json`). This specification will
46
- then be used to generate python classes representing the assets and
47
- associations of the language and to determine the attack steps for each asset
48
- when generating the attack graph.
49
-
50
44
  ## The Model Module
51
45
 
52
46
  With a MAL language a Model (a MAL instance model) can be created either
@@ -70,12 +64,6 @@ nodes related and the asset field which will contain the object in the model
70
64
  instance to which this attack step belongs to, if this information is
71
65
  available.
72
66
 
73
- ## Ingestors Module
74
-
75
- The ingestors module contains various tools that can make use of the instance
76
- model or attack graph. Currently the Neo4J ingestor is the only one available
77
- and it can be used to visualise the instance model and the attack graph.
78
-
79
67
 
80
68
  # Usage
81
69
 
@@ -98,6 +86,11 @@ logging:
98
86
  model_file: "logs/model.yml"
99
87
  langspec_file: "logs/langspec_file.yml"
100
88
  langgraph_file: "logs/langspec_file.yml"
89
+ neo4j:
90
+ uri: None
91
+ username: None
92
+ password: None
93
+ dbname: None
101
94
  ```
102
95
 
103
96
  Alternatively, you can use the `MALTOOLBOX_CONFIG`
@@ -124,25 +117,66 @@ You can use the maltoolbox cli to:
124
117
  Command-line interface for MAL toolbox operations
125
118
 
126
119
  Usage:
127
- maltoolbox attack-graph generate [options] <model_file> <lang_file>
128
120
  maltoolbox compile <lang_file> <output_file>
121
+ maltoolbox generate-attack-graph [--graphviz] <model_file> <lang_file>
129
122
  maltoolbox upgrade-model <model_file> <lang_file> <output_file>
123
+ maltoolbox visualize-model <model_file> <lang_file>
130
124
 
131
125
  Arguments:
132
126
  <model_file> Path to JSON instance model file.
133
127
  <lang_file> Path to .mar or .mal file containing MAL spec.
134
128
  <output_file> Path to write the result of the compilation (yml/json).
135
129
 
130
+ Options:
131
+ -h --help Show this screen.
132
+ -g --graphviz Visualize with graphviz
133
+
136
134
  Notes:
137
135
  - <lang_file> can be either a .mar file (generated by the older MAL
138
- compiler) or a .mal file containing the DSL written in MAL.
136
+ compiler) or a .mal file containing the DSL written in MAL.```
139
137
  ```
140
138
 
141
139
  ## Code examples / Tutorial
142
140
 
143
- To find code examples and tutorials, visit the
141
+ To find more code examples and tutorials, visit the
144
142
  [MAL Toolbox Tutorial](https://github.com/mal-lang/mal-toolbox-tutorial/tree/main) repository.
145
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
+
146
180
  # Tests
147
181
  There are unit tests inside of ./tests.
148
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
@@ -3,3 +3,6 @@ antlr4-tools
3
3
  antlr4-python3-runtime
4
4
  docopt
5
5
  PyYAML
6
+
7
+ [dev]
8
+ pytest
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # MAL Toolbox v1.0.4
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.4"
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
@@ -192,7 +192,13 @@ class AttackGraph():
192
192
  node_id = node_dict['id'],
193
193
  model_asset = node_asset,
194
194
  ttc_dist = node_dict['ttc'],
195
- existence_status = node_dict.get('existence_status', None)
195
+ existence_status = (
196
+ bool(node_dict['existence_status'])
197
+ if 'existence_status' in node_dict else None
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
196
202
  )
197
203
  ag_node.tags = list(node_dict.get('tags', []))
198
204
  ag_node.extras = node_dict.get('extras', {})
@@ -608,7 +614,8 @@ class AttackGraph():
608
614
  node_id: Optional[int] = None,
609
615
  model_asset: Optional[ModelAsset] = None,
610
616
  ttc_dist: Optional[dict] = None,
611
- existence_status: Optional[bool] = None
617
+ existence_status: Optional[bool] = None,
618
+ full_name: Optional[str] = None
612
619
  ) -> AttackGraphNode:
613
620
  """Create and add a node to the graph
614
621
  Arguments:
@@ -653,7 +660,8 @@ class AttackGraph():
653
660
  lg_attack_step = lg_attack_step,
654
661
  model_asset = model_asset,
655
662
  ttc_dist = ttc_dist,
656
- existence_status = existence_status
663
+ existence_status = existence_status,
664
+ full_name = full_name
657
665
  )
658
666
 
659
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():
@@ -56,7 +60,7 @@ class AttackGraphNode:
56
60
  if self.model_asset is not None:
57
61
  node_dict['asset'] = str(self.model_asset.name)
58
62
  if self.existence_status is not None:
59
- node_dict['existence_status'] = str(self.existence_status)
63
+ node_dict['existence_status'] = self.existence_status
60
64
  if self.tags:
61
65
  node_dict['tags'] = list(self.tags)
62
66
  if self.extras:
@@ -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
 
@@ -10,7 +10,7 @@ import zipfile
10
10
 
11
11
  from dataclasses import dataclass, field
12
12
  from functools import cached_property
13
- from typing import Any, Optional
13
+ from typing import Any, Literal, Optional
14
14
 
15
15
  from maltoolbox.file_utils import (
16
16
  load_dict_from_yaml_file, load_dict_from_json_file,
@@ -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]:
@@ -222,7 +234,7 @@ class LanguageGraphAsset:
222
234
  return self_superassets.intersection(other_superassets)
223
235
 
224
236
 
225
- @dataclass
237
+ @dataclass(frozen=True)
226
238
  class LanguageGraphAssociationField:
227
239
  """A field in an association"""
228
240
  asset: LanguageGraphAsset
@@ -231,7 +243,7 @@ class LanguageGraphAssociationField:
231
243
  maximum: int
232
244
 
233
245
 
234
- @dataclass
246
+ @dataclass(frozen=True, eq=True)
235
247
  class LanguageGraphAssociation:
236
248
  """
237
249
  An association type between asset types as defined in the MAL language
@@ -239,7 +251,7 @@ class LanguageGraphAssociation:
239
251
  name: str
240
252
  left_field: LanguageGraphAssociationField
241
253
  right_field: LanguageGraphAssociationField
242
- info: dict = field(default_factory = dict)
254
+ info: dict = field(default_factory = dict, compare=False)
243
255
 
244
256
  def to_dict(self) -> dict:
245
257
  """Convert LanguageGraphAssociation to dictionary"""
@@ -356,7 +368,7 @@ class LanguageGraphAttackStep:
356
368
  An attack step belonging to an asset type in the MAL language
357
369
  """
358
370
  name: str
359
- type: str
371
+ type: Literal["or", "and", "defense", "exist", "notExist"]
360
372
  asset: LanguageGraphAsset
361
373
  ttc: Optional[dict] = field(default_factory = dict)
362
374
  overrides: bool = False
@@ -739,6 +751,13 @@ class LanguageGraph():
739
751
 
740
752
  return serialized_graph
741
753
 
754
+ @property
755
+ def associations(self) -> set[LanguageGraphAssociation]:
756
+ """
757
+ Return all associations in the language graph.
758
+ """
759
+ return {assoc for asset in self.assets.values() for assoc in asset.associations.values()}
760
+
742
761
  @staticmethod
743
762
  def _link_association_to_assets(
744
763
  assoc: LanguageGraphAssociation,
@@ -3,7 +3,6 @@ MAL-Toolbox Model Module
3
3
  """
4
4
 
5
5
  from __future__ import annotations
6
- from dataclasses import dataclass, field
7
6
  import json
8
7
  import logging
9
8
  from typing import TYPE_CHECKING
@@ -23,6 +22,7 @@ if TYPE_CHECKING:
23
22
  from .language import (
24
23
  LanguageGraph,
25
24
  LanguageGraphAsset,
25
+ LanguageGraphAssociation
26
26
  )
27
27
 
28
28
  logger = logging.getLogger(__name__)
@@ -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] = {
@@ -361,6 +370,34 @@ class ModelAsset:
361
370
  return (f'ModelAsset(name: "{self.name}", id: {self.id}, '
362
371
  f'type: {self.type})')
363
372
 
373
+ def associations_with(
374
+ self, b: ModelAsset
375
+ ) -> set[LanguageGraphAssociation]:
376
+ """Returns all associations from self to `b`"""
377
+ assocs_in_common = set()
378
+ for assoc in self.lg_asset.associations.values():
379
+ assets_to_left = self.associated_assets.get(
380
+ assoc.left_field.fieldname, set()
381
+ )
382
+ assets_to_right = self.associated_assets.get(
383
+ assoc.right_field.fieldname, set()
384
+ )
385
+ if b in assets_to_left or b in assets_to_right:
386
+ assocs_in_common.add(assoc)
387
+
388
+ return assocs_in_common
389
+
390
+ def has_association_with(self, b: ModelAsset, assoc_name: str) -> bool:
391
+ """
392
+ Returns True if association `assoc_name` exists between self and `b`
393
+ """
394
+
395
+ for fieldname, associated_assets in self.associated_assets.items():
396
+ assoc = self.lg_asset.associations[fieldname]
397
+ if assoc.name == assoc_name and b in associated_assets:
398
+ return True
399
+
400
+ return False
364
401
 
365
402
  def validate_associated_assets(
366
403
  self, fieldname: str, assets_to_add: set[ModelAsset]
@@ -418,6 +455,20 @@ class ModelAsset:
418
455
  assets dictionary entry corresponding to the given fieldname.
419
456
  """
420
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
+
421
472
  lg_assoc = self.lg_asset.associations[fieldname]
422
473
  other_fieldname = lg_assoc.get_opposite_fieldname(fieldname)
423
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,12 +1,13 @@
1
1
  [project]
2
2
  name = "mal-toolbox"
3
- version = "1.0.4"
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" },
7
7
  { name="Nikolaos Kakouros", email="nkak@kth.se" },
8
8
  { name="Jakob Nyberg", email="jaknyb@kth.se" },
9
- { name="Giuseppe Nebbione", email="nebbione@kth.se" }
9
+ { name="Giuseppe Nebbione", email="nebbione@kth.se" },
10
+ { name="Sandor Berglund", email="sandor@kth.se" }
10
11
  ]
11
12
  description = "A collection of tools used to create MAL models and attack graphs."
12
13
  readme = "README.md"
@@ -34,6 +35,11 @@ classifiers = [
34
35
  "Bug Tracker" = "https://github.com/mal-lang/mal-toolbox/issues"
35
36
  "Repository" = "https://github.com/mal-lang/mal-toolbox"
36
37
 
38
+ [project.optional-dependencies]
39
+ dev = [
40
+ "pytest",
41
+ ]
42
+
37
43
  [project.scripts]
38
44
  "maltoolbox" = "maltoolbox.__main__:main"
39
45
 
@@ -125,6 +125,12 @@ def test_model_add_associated_asset(model: Model):
125
125
  assert 'hostApp' in asset2.associated_assets
126
126
  assert asset1 in asset2.associated_assets['hostApp']
127
127
 
128
+ associations_in_common = asset1.associations_with(asset2)
129
+ assert associations_in_common
130
+
131
+ for assoc in associations_in_common:
132
+ assert asset1.has_association_with(asset2, assoc.name)
133
+
128
134
 
129
135
  def test_model_add_appexecution_association_two_assets(model: Model):
130
136
  """coreLang specifies that AppExecution only can have one 'left' asset"""
@@ -161,7 +167,7 @@ def test_model_add_association_nonexisting_fieldname(model: Model):
161
167
  data = model.add_asset(asset_type = 'Data')
162
168
 
163
169
  # Try create an association between asset1 and data
164
- with pytest.raises(LookupError):
170
+ with pytest.raises(ValueError):
165
171
  # will raise error because fieldname does not exist
166
172
  asset1.add_associated_assets(
167
173
  fieldname = 'unknownFieldName', assets = {data}
File without changes
File without changes
File without changes
File without changes