mal-toolbox 0.0.27__py3-none-any.whl → 0.1.12__py3-none-any.whl

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-0.0.27.dist-info → mal_toolbox-0.1.12.dist-info}/METADATA +60 -28
  2. mal_toolbox-0.1.12.dist-info/RECORD +32 -0
  3. {mal_toolbox-0.0.27.dist-info → mal_toolbox-0.1.12.dist-info}/WHEEL +1 -1
  4. maltoolbox/__init__.py +31 -31
  5. maltoolbox/__main__.py +80 -4
  6. maltoolbox/attackgraph/__init__.py +8 -0
  7. maltoolbox/attackgraph/analyzers/__init__.py +0 -0
  8. maltoolbox/attackgraph/analyzers/apriori.py +173 -27
  9. maltoolbox/attackgraph/attacker.py +99 -21
  10. maltoolbox/attackgraph/attackgraph.py +507 -217
  11. maltoolbox/attackgraph/node.py +143 -21
  12. maltoolbox/attackgraph/query.py +128 -26
  13. maltoolbox/default.conf +8 -7
  14. maltoolbox/exceptions.py +45 -0
  15. maltoolbox/file_utils.py +66 -0
  16. maltoolbox/ingestors/__init__.py +0 -0
  17. maltoolbox/ingestors/neo4j.py +95 -84
  18. maltoolbox/language/__init__.py +4 -0
  19. maltoolbox/language/classes_factory.py +145 -64
  20. maltoolbox/language/{lexer_parser/__main__.py → compiler/__init__.py} +5 -12
  21. maltoolbox/language/{lexer_parser → compiler}/mal_lexer.py +1 -1
  22. maltoolbox/language/{lexer_parser → compiler}/mal_parser.py +1 -1
  23. maltoolbox/language/{lexer_parser → compiler}/mal_visitor.py +4 -5
  24. maltoolbox/language/languagegraph.py +569 -168
  25. maltoolbox/model.py +858 -0
  26. maltoolbox/translators/__init__.py +0 -0
  27. maltoolbox/translators/securicad.py +76 -52
  28. maltoolbox/translators/updater.py +132 -0
  29. maltoolbox/wrappers.py +62 -0
  30. mal_toolbox-0.0.27.dist-info/RECORD +0 -26
  31. maltoolbox/cl_parser.py +0 -89
  32. maltoolbox/language/specification.py +0 -265
  33. maltoolbox/main.py +0 -84
  34. maltoolbox/model/model.py +0 -279
  35. {mal_toolbox-0.0.27.dist-info → mal_toolbox-0.1.12.dist-info}/AUTHORS +0 -0
  36. {mal_toolbox-0.0.27.dist-info → mal_toolbox-0.1.12.dist-info}/LICENSE +0 -0
  37. {mal_toolbox-0.0.27.dist-info → mal_toolbox-0.1.12.dist-info}/top_level.txt +0 -0
File without changes
@@ -7,24 +7,28 @@ import json
7
7
  import logging
8
8
  import xml.etree.ElementTree as ET
9
9
 
10
- from maltoolbox.model import model
11
- from maltoolbox.language import specification
10
+ from typing import Optional
11
+
12
+ from ..model import AttackerAttachment, Model
13
+ from ..language import LanguageGraph, LanguageClassesFactory
12
14
 
13
15
  logger = logging.getLogger(__name__)
14
16
 
15
- def load_model_from_scad_archive(scad_archive: str,
16
- lang_spec,
17
- lang_classes_factory) -> model.Model:
17
+ def load_model_from_scad_archive(
18
+ scad_archive: str,
19
+ lang_graph: LanguageGraph,
20
+ lang_classes_factory: LanguageClassesFactory
21
+ ) -> Optional[Model]:
18
22
  """
19
23
  Reads a '.sCAD' archive generated by securiCAD representing an instance
20
24
  model and loads the information into a maltoobox.model.Model object.
21
25
 
22
26
  Arguments:
23
27
  scad_archive - the path to a '.sCAD' archive
24
- lang_spec - a dictionary containing the MAL language
25
- specification
28
+ lang_graph - a language graph representing the MAL
29
+ language specification
26
30
  lang_classes_factory - a language classes factory that contains
27
- the same classes defined by the
31
+ the classes defined by the
28
32
  language specification
29
33
 
30
34
  Return:
@@ -36,24 +40,34 @@ def load_model_from_scad_archive(scad_archive: str,
36
40
  scad_model = archive.read(model_file)
37
41
  root = ET.fromstring(scad_model)
38
42
 
39
- instance_model = model.Model(scad_archive,
40
- lang_spec,
43
+ instance_model = Model(scad_archive,
41
44
  lang_classes_factory)
42
45
 
43
46
  for child in root.iter('objects'):
44
- logger.debug(f'Loading asset from \"{scad_archive}\":\n' \
45
- + json.dumps(child.attrib, indent = 2))
47
+
48
+ if logger.isEnabledFor(logging.DEBUG):
49
+ # Avoid running json.dumps when not in debug
50
+ logger.debug(
51
+ 'Loading asset from "%s": \n%s',
52
+ scad_archive, json.dumps(child.attrib, indent=2)
53
+ )
54
+
46
55
  if child.attrib['metaConcept'] == 'Attacker':
47
- attacker_id = int(child.attrib['id'])
48
- attacker = model.Attacker()
49
- attacker.entry_points = []
50
- instance_model.add_attacker(attacker, attacker_id = attacker_id)
56
+ attacker_obj_id = int(child.attrib['id'])
57
+ attacker_at = AttackerAttachment()
58
+ attacker_at.entry_points = []
59
+ instance_model.add_attacker(
60
+ attacker_at,
61
+ attacker_id = attacker_obj_id
62
+ )
51
63
  continue
52
64
 
53
65
  if not hasattr(lang_classes_factory.ns,
54
66
  child.attrib['metaConcept']):
55
- logger.error(f'Failed to find {child.attrib["metaConcept"]} '
56
- 'asset in language specification!')
67
+ logger.error(
68
+ 'Failed to find %s asset in language specification!',
69
+ child.attrib["metaConcept"]
70
+ )
57
71
  return None
58
72
  asset = getattr(lang_classes_factory.ns,
59
73
  child.attrib['metaConcept'])(name = child.attrib['name'])
@@ -62,7 +76,6 @@ def load_model_from_scad_archive(scad_archive: str,
62
76
  defense_name = subchild.attrib['metaConcept']
63
77
  defense_name = defense_name[0].lower() + defense_name[1:]
64
78
  for distrib in subchild.iter('evidenceDistribution'):
65
- distrib_type = distrib.attrib['type']
66
79
  for d in distrib.iter('parameters'):
67
80
  if 'value' in d.attrib:
68
81
  dist_value = d.attrib['value']
@@ -70,13 +83,12 @@ def load_model_from_scad_archive(scad_archive: str,
70
83
  instance_model.add_asset(asset, asset_id)
71
84
 
72
85
  for child in root.iter('associations'):
73
- logger.debug(f'Load association '
74
- f'(\"{child.attrib["sourceObject"]}\",'
75
- f'\"{child.attrib["targetObject"]}\",'
76
- f'\"{child.attrib["targetProperty"]}\",'
77
- f'\"{child.attrib["sourceProperty"]}\") '
78
- f'from \"{scad_archive}\"')
79
-
86
+ logger.debug(
87
+ 'Load association ("%s", "%s", "%s", "%s") from %s',
88
+ child.attrib["sourceObject"], child.attrib["targetObject"],
89
+ child.attrib["targetProperty"], child.attrib["sourceProperty"],
90
+ scad_archive
91
+ )
80
92
  # Note: This is not a bug in the code. The fields and assets are
81
93
  # listed incorrectly in the securiCAD format where the source asset
82
94
  # matches the target field and vice versa.
@@ -92,16 +104,20 @@ def load_model_from_scad_archive(scad_archive: str,
92
104
  target_id = right_id
93
105
  target_prop = child.attrib['sourceProperty']
94
106
 
95
- if attacker_id:
107
+ if attacker_id is not None:
96
108
  attacker = instance_model.get_attacker_by_id(attacker_id)
97
109
  if not attacker:
98
- logger.error(f'Failed to find attacker with id {attacker_id} '
99
- 'in model!')
110
+ logger.error(
111
+ 'Failed to find attacker with id %s in model!',
112
+ attacker_id
113
+ )
100
114
  return None
101
115
  target_asset = instance_model.get_asset_by_id(target_id)
102
116
  if not target_asset:
103
- logger.error(f'Failed to find asset with id {target_id} '
104
- 'in model!')
117
+ logger.error(
118
+ 'Failed to find asset with id %s in model!',
119
+ target_id
120
+ )
105
121
  return None
106
122
  attacker.entry_points.append((target_asset,
107
123
  [target_prop.split('.')[0]]))
@@ -109,13 +125,15 @@ def load_model_from_scad_archive(scad_archive: str,
109
125
 
110
126
  left_asset = instance_model.get_asset_by_id(left_id)
111
127
  if not left_asset:
112
- logger.error(f'Failed to find asset with id {left_id} '
113
- 'in model!')
128
+ logger.error(
129
+ 'Failed to find asset with id %s in model!', left_id
130
+ )
114
131
  return None
115
132
  right_asset = instance_model.get_asset_by_id(right_id)
116
133
  if not right_asset:
117
- logger.error(f'Failed to find asset with id {right_id} '
118
- 'in model!')
134
+ logger.error(
135
+ 'Failed to find asset with id %s in model!', right_id
136
+ )
119
137
  return None
120
138
 
121
139
  # Note: This is not a bug in the code. The fields and assets are
@@ -123,27 +141,33 @@ def load_model_from_scad_archive(scad_archive: str,
123
141
  # matches the target field and vice versa.
124
142
  left_field = child.attrib['sourceProperty']
125
143
  right_field = child.attrib['targetProperty']
126
- assoc_name = specification.get_association_by_fields_and_assets(
127
- lang_spec,
144
+ lang_graph_assoc = lang_graph.get_association_by_fields_and_assets(
128
145
  left_field,
129
146
  right_field,
130
- left_asset.metaconcept,
131
- right_asset.metaconcept)
132
- logger.debug(f'Found \"{assoc_name}\" association.')
133
-
134
- if not assoc_name:
135
- logger.error(f'Failed to find '
136
- f'(\"{left_asset.metaconcept}\",'
137
- f'\"{right_asset.metaconcept}\",'
138
- f'\"{left_field}\",'
139
- f'\"{right_field}\") '
140
- 'association in language specification!')
147
+ left_asset.type,
148
+ right_asset.type)
149
+
150
+ if not lang_graph_assoc:
151
+ raise LookupError(
152
+ 'Failed to find ("%s", "%s", "%s", "%s")'
153
+ 'association in lang specification.' %
154
+ (left_asset.type, right_asset.type,
155
+ left_field, right_field)
156
+ )
141
157
  return None
142
158
 
143
- if not hasattr(lang_classes_factory.ns,
144
- assoc_name):
145
- logger.error(f'Failed to find {assoc_name} '
146
- 'association in language specification!')
159
+ logger.debug('Found "%s" association.', lang_graph_assoc.name)
160
+ assoc_name = lang_classes_factory.get_association_by_signature(
161
+ lang_graph_assoc.name,
162
+ left_asset.type,
163
+ right_asset.type
164
+ )
165
+
166
+ if assoc_name is None:
167
+ logger.error(
168
+ 'Failed to find association with name \"%s\" in model!',
169
+ lang_graph_assoc.name
170
+ )
147
171
  return None
148
172
 
149
173
  assoc = getattr(lang_classes_factory.ns, assoc_name)()
@@ -0,0 +1,132 @@
1
+ import json
2
+ import logging
3
+
4
+ import yaml
5
+
6
+ from ..model import Model, AttackerAttachment
7
+ from ..language import LanguageClassesFactory
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ def load_model_from_older_version(
12
+ filename: str,
13
+ lang_classes_factory: LanguageClassesFactory,
14
+ version: str
15
+ ) -> Model:
16
+ match (version):
17
+ case '0.0.39':
18
+ return load_model_from_version_0_0_39(filename,
19
+ lang_classes_factory)
20
+ case _:
21
+ msg = ('Unknown version "%s" format. Could not '
22
+ 'load model from file "%s"')
23
+ logger.error(msg % (version, filename))
24
+ raise ValueError(msg % (version, filename))
25
+
26
+ def load_model_from_version_0_0_39(
27
+ filename: str,
28
+ lang_classes_factory: LanguageClassesFactory
29
+ ) -> Model:
30
+ """
31
+ Load model from file.
32
+
33
+ Arguments:
34
+ filename - the name of the input file
35
+ lang_classes_factory - the language classes factory that defines the
36
+ classes needed to build the model
37
+ """
38
+
39
+ def _process_model(model_dict, lang_classes_factory) -> Model:
40
+ model = Model(model_dict['metadata']['name'], lang_classes_factory)
41
+
42
+ # Reconstruct the assets
43
+ for asset_id, asset_object in model_dict['assets'].items():
44
+ logger.debug(f"Loading asset:\n{json.dumps(asset_object, indent=2)}")
45
+
46
+ # Allow defining an asset via the metaconcept only.
47
+ asset_object = (
48
+ asset_object
49
+ if isinstance(asset_object, dict)
50
+ else {'metaconcept': asset_object, 'name': f"{asset_object}:{asset_id}"}
51
+ )
52
+
53
+ asset = getattr(model.lang_classes_factory.ns,
54
+ asset_object['metaconcept'])(name = asset_object['name'])
55
+
56
+ for defense in (defenses:=asset_object.get('defenses', [])):
57
+ setattr(asset, defense, float(defenses[defense]))
58
+
59
+ model.add_asset(asset, asset_id = int(asset_id))
60
+
61
+ # Reconstruct the associations
62
+ for assoc_dict in model_dict.get('associations', []):
63
+ association = getattr(model.lang_classes_factory.ns, assoc_dict.pop('metaconcept'))()
64
+
65
+ # compatibility with old format
66
+ assoc_dict = assoc_dict.get('association', assoc_dict)
67
+
68
+ for field, targets in assoc_dict.items():
69
+ targets = targets if isinstance(targets, list) else [targets]
70
+ setattr(
71
+ association,
72
+ field,
73
+ [model.get_asset_by_id(int(id)) for id in targets]
74
+ )
75
+ model.add_association(association)
76
+
77
+ # Reconstruct the attackers
78
+ if 'attackers' in model_dict:
79
+ attackers_info = model_dict['attackers']
80
+ for attacker_id in attackers_info:
81
+ attacker = AttackerAttachment(
82
+ name = attackers_info[attacker_id]['name']
83
+ )
84
+ attacker.entry_points = []
85
+ for asset_id in attackers_info[attacker_id]['entry_points']:
86
+ attacker.entry_points.append(
87
+ (model.get_asset_by_id(int(asset_id)),
88
+ attackers_info[attacker_id]['entry_points']\
89
+ [asset_id]['attack_steps']))
90
+ model.add_attacker(attacker, attacker_id = int(attacker_id))
91
+ return model
92
+
93
+ def load_from_json(
94
+ filename: str,
95
+ lang_classes_factory: LanguageClassesFactory
96
+ ) -> Model:
97
+ """
98
+ Load model from a json file.
99
+
100
+ Arguments:
101
+ filename - the name of the input file
102
+ """
103
+ with open(filename, 'r', encoding='utf-8') as model_file:
104
+ model_dict = json.loads(model_file.read())
105
+
106
+ return _process_model(model_dict, lang_classes_factory)
107
+
108
+ def load_from_yaml(
109
+ filename: str,
110
+ lang_classes_factory: LanguageClassesFactory
111
+ ) -> Model:
112
+ """
113
+ Load model from a yaml file.
114
+
115
+ Arguments:
116
+ filename - the name of the input file
117
+ """
118
+ with open(filename, 'r', encoding='utf-8') as model_file:
119
+ model_dict = yaml.safe_load(model_file)
120
+
121
+ return _process_model(model_dict, lang_classes_factory)
122
+
123
+ logger.info(f'Loading model from {filename} file.')
124
+ if filename.endswith('.yml') or filename.endswith('.yaml'):
125
+ return load_from_yaml(filename, lang_classes_factory)
126
+ elif filename.endswith('.json'):
127
+ return load_from_json(filename, lang_classes_factory)
128
+ else:
129
+ msg = 'Unknown file extension for model file to load from.'
130
+ logger.error(msg)
131
+ raise ValueError(msg)
132
+ return None
maltoolbox/wrappers.py ADDED
@@ -0,0 +1,62 @@
1
+ """Contains wrappers combining more than one of the maltoolbox submodules"""
2
+
3
+ import logging
4
+ import sys
5
+ import zipfile
6
+
7
+ from maltoolbox.model import Model
8
+ from maltoolbox.language import LanguageGraph, LanguageClassesFactory
9
+ from maltoolbox.attackgraph import AttackGraph
10
+ from maltoolbox.attackgraph.analyzers.apriori import (
11
+ calculate_viability_and_necessity
12
+ )
13
+ from maltoolbox.exceptions import AttackGraphStepExpressionError
14
+ from maltoolbox import log_configs
15
+
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ def create_attack_graph(
20
+ lang_file: str,
21
+ model_file: str,
22
+ attach_attackers=True,
23
+ calc_viability_and_necessity=True
24
+ ) -> AttackGraph:
25
+ """Create and return an attack graph
26
+
27
+ Args:
28
+ lang_file - path to language file (.mar or .mal)
29
+ model_file - path to model file (yaml or json)
30
+ attach_attackers - whether to run attach_attackers or not
31
+ calc_viability_and_necessity - whether run apriori calculations or not
32
+ """
33
+ try:
34
+ lang_graph = LanguageGraph.from_mar_archive(lang_file)
35
+ except zipfile.BadZipFile:
36
+ lang_graph = LanguageGraph.from_mal_spec(lang_file)
37
+
38
+ if log_configs['langspec_file']:
39
+ lang_graph.save_to_file(log_configs['langspec_file'])
40
+
41
+ lang_classes_factory = LanguageClassesFactory(lang_graph)
42
+ instance_model = Model.load_from_file(model_file, lang_classes_factory)
43
+
44
+ if log_configs['model_file']:
45
+ instance_model.save_to_file(log_configs['model_file'])
46
+
47
+ try:
48
+ attack_graph = AttackGraph(lang_graph, instance_model)
49
+ except AttackGraphStepExpressionError:
50
+ logger.error(
51
+ 'Attack graph generation failed when attempting '
52
+ 'to resolve attack step expression!'
53
+ )
54
+ sys.exit(1)
55
+
56
+ if attach_attackers:
57
+ attack_graph.attach_attackers()
58
+
59
+ if calc_viability_and_necessity:
60
+ calculate_viability_and_necessity(attack_graph)
61
+
62
+ return attack_graph
@@ -1,26 +0,0 @@
1
- maltoolbox/__init__.py,sha256=_amttbxXrB-PZlmyyI3cTPlzbiaUMQNXpMm9awVy4Yo,2958
2
- maltoolbox/__main__.py,sha256=_kfJOkJluA3gfq3HAJM0rAK_SvGO-4Jr6wQQGrItiWs,94
3
- maltoolbox/cl_parser.py,sha256=zpFAhEx-ZVOQlZkXR3MfxMiYR8aPLKDeUbGhlaap2Sk,2550
4
- maltoolbox/default.conf,sha256=YbGeYq4aR61G43I7RGEGwgNikIGTuKT56uz_j_6t9K4,282
5
- maltoolbox/main.py,sha256=OvaMFZGM76DFC6KceYvt4qMU13khEVbqJZzf-N20754,3285
6
- maltoolbox/attackgraph/attacker.py,sha256=AahJZo3q3STbQvywK4Qa1UJDg1gMY1D-WuzPDPaCaY0,1329
7
- maltoolbox/attackgraph/attackgraph.py,sha256=fzmu1fsVRe5QDxrxp7Y2DDs8r6cT_Ua-5C6M3wEgzkU,19781
8
- maltoolbox/attackgraph/node.py,sha256=2UcewlSM64cYen6aHkxW2sR3Ni7WGdjsTOBrrtkuV4A,2139
9
- maltoolbox/attackgraph/query.py,sha256=Io-JjP1Jj8_cZkYkCc0FkXrcZNgEQUDCinEzCd9FSK0,3268
10
- maltoolbox/attackgraph/analyzers/apriori.py,sha256=el7Dv42FAP7RRe2xLhzhrWDiJw9TYjQrVfe8U41MuNM,3182
11
- maltoolbox/ingestors/neo4j.py,sha256=xvEnbCG4Z0zuPPOeR8AjrNTA_-EtCo4JKy04EQci6Qk,8307
12
- maltoolbox/language/classes_factory.py,sha256=US3dTlyLWncqb3SJxpzesa0eRf2RTPxUX64gb_vESsE,7018
13
- maltoolbox/language/languagegraph.py,sha256=0SMB5Qpm-1wfzqIzTfoc_si-kpb8JjCyzkSv6ApNtvI,31892
14
- maltoolbox/language/specification.py,sha256=Q96NQmnkskxJlMOsKZBVsg2hMqwU2XOb4qPl16PoR9w,9563
15
- maltoolbox/language/lexer_parser/__main__.py,sha256=aD1WWM82DDGS5tcEVzfeTQtP6MkwSLZA48FxBMO3soQ,970
16
- maltoolbox/language/lexer_parser/mal_lexer.py,sha256=_dqX1xGnGxEyqnhWjEMKuywfhe3WamCsnt4hNFel3SI,10778
17
- maltoolbox/language/lexer_parser/mal_parser.py,sha256=0awGDJDy6YxmGizhT17sbB7cgeD2x3UNxzNymNfq99c,91351
18
- maltoolbox/language/lexer_parser/mal_visitor.py,sha256=UhejWVvN8lKGw1t0rD8Yx7-Qqn9kfemIlHajV3tsXAI,13203
19
- maltoolbox/model/model.py,sha256=I6G0cLuWPjYIlKqbCpyhjUnsI4GJL3ofWY93TFebVng,10181
20
- maltoolbox/translators/securicad.py,sha256=YizxoIsKkp1AZIYudjxvR3c450vY5iGejAULG8YHIyI,6203
21
- mal_toolbox-0.0.27.dist-info/AUTHORS,sha256=zxLrLe8EY39WtRKlAY4Oorx4Z2_LHV2ApRvDGZgY7xY,127
22
- mal_toolbox-0.0.27.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
23
- mal_toolbox-0.0.27.dist-info/METADATA,sha256=3G5i2JHyqLxYc-Wj0wSa7Ku5nBigWFQJ-RsU3kMscbs,4890
24
- mal_toolbox-0.0.27.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
25
- mal_toolbox-0.0.27.dist-info/top_level.txt,sha256=phqRVLRKGdSUgRY03mcpi2cmbbDo5YGjkV4gkqHFFcM,11
26
- mal_toolbox-0.0.27.dist-info/RECORD,,
maltoolbox/cl_parser.py DELETED
@@ -1,89 +0,0 @@
1
- """
2
- MAL-Toolbox Command Line Parsing Module
3
- """
4
-
5
- import sys
6
- import argparse
7
- import logging
8
- from typing import Sequence
9
-
10
- from maltoolbox import __version__
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
- def parse_args(args: Sequence[str]) -> argparse.Namespace:
15
- """
16
- This function parses the arguments which have been passed from the command
17
- line. It returns an argparse parser object.
18
-
19
- Arguments:
20
- args - the list of arguments passed from the command line in the sys.argv
21
- format
22
-
23
- Return:
24
- A parser with the provided arguments, which can be used in a simpler format
25
- """
26
- parser = argparse.ArgumentParser(
27
- prog='maltoolbox',
28
- description='Generate Attack Graphs from MAL specifications')
29
-
30
- parser.add_argument(
31
- '--version',
32
- action='version',
33
- version=f'%(prog)s {__version__}')
34
-
35
- subparsers = parser.add_subparsers(help='commands', dest='command')
36
- subparsers.required = True
37
-
38
- gen_ag_parser = subparsers.add_parser(
39
- 'gen_ag',
40
- help='Generate an attack graph from an instance model from a json ' \
41
- 'file and language specification from a mar archive.')
42
- help_parser = subparsers.add_parser(
43
- 'help', help='Show help for a particular command')
44
-
45
- gen_ag_parser.add_argument(
46
- 'model',
47
- help='Path to the instance model json file',
48
- type=str,
49
- )
50
-
51
- gen_ag_parser.add_argument(
52
- 'language',
53
- help='Path to the language specification ".mar" archive',
54
- type=str,
55
- )
56
-
57
- gen_ag_parser.add_argument(
58
- '--neo4j',
59
- help='Injest attack graph and instance model into a local Neo4j ' \
60
- 'instance',
61
- action='store_true'
62
- )
63
-
64
- help_parser.add_argument(
65
- 'cmd',
66
- help='Name of command to get help for',
67
- nargs='?')
68
-
69
- if len(args) == 0:
70
- parser.print_help(sys.stderr)
71
- logger.error('Received no arugments will print help and exit.')
72
- sys.exit(1)
73
-
74
- parsed_args = parser.parse_args()
75
- if parsed_args.command == 'help':
76
- if not parsed_args.cmd:
77
- parser.print_help(sys.stderr)
78
- else:
79
- try:
80
- subparsers.choices[parsed_args.cmd].print_help()
81
- except KeyError:
82
- logger.error(f'Unknown command name {parsed_args.cmd}')
83
- print(f'Unknown command name {parsed_args.cmd}')
84
- print(
85
- f'Valid commands are: {", ".join(subparsers.choices.keys())}')
86
-
87
- sys.exit(1)
88
-
89
- return parsed_args