First pass at a lambda-supporting combination class#15
First pass at a lambda-supporting combination class#15ArtificialErmine merged 8 commits intomitre-attack:masterfrom
Conversation
|
@isaisabel I'm trying to figure out how I can go about testing this functionality for the PR. Currently, if you load layers into /nav-app/src/assets and enable the default_layers property you can load layers. But the example usage code above appears to be python, and I'm trying to figure out what the overall workflow looks like. Really hope this feature gets implemented! |
|
Hi @beerMT, This WIP code is designed to work with ATT&CK Navigator layer files independently of the ATT&CK Navigator. The objective is that you should be able to use this in lieu of "create layer from other layers" interface of the Navigator. This implementation would be slightly more powerful (and notably able to be included in automated workflows) than the Navigator interface, but their purposes are the same. See the example usage at the top of the layerops file for a python usage example. We'll likely be writing additional documentation for how to use the interface before it gets merged. |
Thanks for the response on that question. I've noticed in doing multiple layer joins on the Navigator GUI that you can only have 10 layers open. I'm assuming that this limitation would not exist, since that seems to be a browser/UI design decision vs. a coding limitation. Layer joins don't appear to be computationally complex since it's jsons and counting for the most part. |
|
@beerMT You're correct, the Navigator 10-layer limitation is due to performance issues that arise in that application when more than 10 layers are open simultaneously. |
isaisabel
left a comment
There was a problem hiding this comment.
Added some documentation level feedback proposed a minor change to how the layer classes are imported. A lot of the documentation level stuff is pretty pedantic so if you feel differently about any of the proposed changes please let me know.
| #### Example Usage | ||
| ```python | ||
| from layers.manipulators.layerops import LayerOps | ||
| from layers.core.layer import Layer | ||
|
|
||
| demo = Layer() | ||
| demo.load_file("C:\Users\attack\Downloads\layer.json") | ||
| demo2 = Layer() | ||
| demo2.load_input({"name": "example layer", ... }) | ||
|
|
||
| lo = LayerOps(score=lambda x: x[0] * x[1], | ||
| name=lambda x: x[1], | ||
| desc=lambda x: "This is an list example") | ||
| out_layer = lo.process([demo, demo2]) | ||
| out_layer.export_file("C:\demo_layer1.json") | ||
|
|
||
| lo2 = LayerOps(score=lambda x: x['a'], | ||
| color=lambda x: x['b'], | ||
| desc=lambda x: "This is a dict example") | ||
| out_layer2 = lo2.process({'a': demo, 'b': demo2}) | ||
| dict_layer = out_layer2.get_dict() | ||
| print(dict_layer) | ||
| out_layer2.export_file("C:\demo_layer2.json") | ||
| ``` No newline at end of file |
There was a problem hiding this comment.
We need additional examples here and better explanations about what the existing examples do.
For the additional examples, we should have actual applications of how layerops might be used. Some possibilities:
- average scores of any number of input layers
- reverse the score of the input layer (
100-x[0]assuming the score is on a 0-100 scale) - Merge comments (
"; ".join(x)I think?)
Each example should have a comment explaining what that layerops instance's purpose is.
We should also demonstrate the functionality of a single layerops instance being reused. For example, show the "average layers" being used on layers A,B, and then C,D,E in two separate executions.
There was a problem hiding this comment.
I've got examples for each of these. Let me know what you think.
isaisabel
left a comment
There was a problem hiding this comment.
Sorry for the neverending review. Should be one of the last passes before it's ready to be merged. Mostly just cleaning up APIs and a few bugs.
| from layers.manipulators.layerops import LayerOps | ||
| from layers.core.layer import Layer |
There was a problem hiding this comment.
This doesn't seem to work for me:
$ python3
Python 3.7.4 (default, Sep 7 2019, 18:27:02)
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from layers.core.layer import Layer
>>> from layers.manipulators.layerops import LayerOps
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/isabel/working/temp/attack-scripts/layers/manipulators/layerops.py", line 23, in <module>
from layers.core import Layer
ImportError: cannot import name 'Layer' from 'layers.core' (unknown location)
>>>
Changing the import at the beginning of the layerops script to from layers.core.layer import Layer seems to fix the issue, but it assumes the working directory is the root of this repo. If I'm running from the layers folder and run from manipulators.layerops import LayerOps, the absolute import breaks.
I'm not sure what the best practice is with this — a relative import, or just tell the user to always import from the repo root folder? If the latter, please specify where this is being run from in the example as well as putting towards the top of the file "all scripts should be imported and run from the root folder of this repository."
Long term if this is going to become a pip package this probably needs to be relative. Not sure if it's worth doing now.
There was a problem hiding this comment.
Speaking of importing, this conversation doesn't seem to have been addressed: #15 (comment)
There was a problem hiding this comment.
It was, I just failed to notice that the updated init.py wasn't included in the update for some reason. I've fixed this, and it appears to work properly from wherever now.
isaisabel
left a comment
There was a problem hiding this comment.
Still having trouble importing layerops from within the layers folder (see #15 (comment))
isaisabel
left a comment
There was a problem hiding this comment.
Looks and works great!
Adds support for custom combinations of attack layers.
Example usage:
from layerops import layeropsobj = layerops([layer_data])obj.process(score=lambda t:t[0] * 2, defaults={'score':3})