izar / pytm

A Pythonic framework for threat modeling
Other
876 stars 165 forks source link

Implementation question: Supporting multiple threats.json files #187

Open per-oestergaard opened 2 years ago

per-oestergaard commented 2 years ago

The current threatsFile property of class TM is defaulted to -

    threatsFile = varString(
        os.path.dirname(__file__) + "/threatlib/threats.json",
        onSet=lambda i, v: i._init_threats(),
        doc="JSON file with custom threats",
    )

If I want to add extra rules keeping the built-in threats, I am challenged as setting threatsfile resets the existing threats. E.g., threatsfile must have all threats in a single file. To work around this, I have been using -

linddunThreats = os.path.join(os.path.split(
    __file__)[0], '../../tools/threats.json')
buildInThreatsFile = tm.threatsFile
with open(buildInThreatsFile, "r", encoding="utf8") as threat_file:
    threats_json = json.load(threat_file)
with open(linddunThreats, "r", encoding="utf8") as threat_file:
    for e in json.load(threat_file):`
        threats_json.append(e)
mergedThreatsFile = os.path.join(tempfile.mkdtemp(), 'threats.json')
with open(mergedThreatsFile, "w", encoding="utf8") as merged_threat_file:
    json.dump(threats_json, merged_threat_file)
tm.threatsFile = mergedThreatsFile

Not something I wanted to repeat all over ;).

Hence, I was looking at creating a pull request on a better approach. However, what would be the best approach?

  1. If I convert threatsfile to varStrings, I could specify multiple files, but where would I get the built-in from? Some kind of "magic" value for that (ugly)? Or a builtInThreatFile property returning the built-in path?
  2. If I added a new property TM.extraThreatsFiles, that would work - but that is an ugly API
  3. A TM.addThreatsFile(file) method? However, TM's API is property based so that also feels wrong.
  4. Or

Please advice.

raphaelahrens commented 2 years ago

Make tm.threatsFile a list of files, which will be merged via dict.update, something like

 with open(self.threatsFile[0], "r", encoding="utf8") as threat_file:
            threats_json = json.load(threat_file)
  for file in self.threatsFile[1:]:
       with open(file, "r", encoding="utf8") as threat_file:
            t = json.load(threat_file)
            threats_json.update(t)

Use the default threat file if tm.threatsFile is not set, by setting it to [pytm.pytm.default_threats]

If you want to define your own threats you can reuse pytm.pytm.default_threats by writing

tm.threatsFile = [pytm.pytm.default_threats, "path/to/my/threat_lib.json"]

Or not use the default and write

tm.threatsFile = ["path/to/my/threat_lib.json", "path/to/my/threat_lib_for_java.json"]

By using pytm.pytm.default_threats you don't have to use some magic and it is quite obvious what would happen.

For backwards compatibility it should be possible to also define the threats via the old way

 tm.threatsFile = "path/to/my/threat_lib.json"