Skip to content

Commit 3726a14

Browse files
authored
Merge pull request #663 from guardicore/release/1.8.2
Release/1.8.2
2 parents 98636a5 + 9ea6718 commit 3726a14

File tree

201 files changed

+14066
-19826
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

201 files changed

+14066
-19826
lines changed

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,11 @@ MonkeyZoo/*
8282
!MonkeyZoo/config.tf
8383
!MonkeyZoo/MonkeyZooDocs.pdf
8484

85+
# Exported monkey telemetries
86+
/monkey/telem_sample/
87+
88+
# Profiling logs
89+
profiler_logs/
90+
8591
# vim swap files
8692
*.swp

.travis.yml

+21-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
# Infection Monkey travis.yml. See Travis documentation for information about this file structure.
22

3+
# If you change this file, you can validate using Travis CI's Build Config Explorer https://config.travis-ci.com/explore
4+
35
group: travis_latest
46

57
language: python
68

7-
cache: pip
9+
cache:
10+
- pip
11+
- directories:
12+
- "$HOME/.npm"
813

914
python:
1015
- 3.7
@@ -18,6 +23,16 @@ install:
1823
- pip install coverage # for code coverage
1924
- pip install -r monkey/infection_monkey/requirements.txt # for unit tests
2025

26+
# node + npm + eslint
27+
- node --version
28+
- npm --version
29+
- nvm --version
30+
- nvm install node
31+
- nvm use node
32+
- npm i -g eslint
33+
- node --version
34+
- npm --version
35+
2136
before_script:
2237
# Set the server config to `testing`. This is required for for the UTs to pass.
2338
- python monkey/monkey_island/cc/set_server_config.py testing
@@ -36,7 +51,7 @@ script:
3651
## Display the linter issues
3752
- cat flake8_warnings.txt
3853
## Make sure that we haven't increased the amount of warnings.
39-
- PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT=190
54+
- PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT=120
4055
- if [ $(tail -n 1 flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi
4156

4257
## Run unit tests
@@ -48,13 +63,10 @@ script:
4863

4964
# Check JS code. The npm install must happen AFTER the flake8 because the node_modules folder will cause a lot of errors.
5065
- cd monkey_island/cc/ui
51-
- npm i
52-
- npm i -g eslint
53-
- cd -
54-
- cd monkey_island/cc/ui
55-
- eslint ./src --quiet
56-
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=37
57-
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT
66+
- npm ci # See https://docs.npmjs.com/cli/ci.html
67+
- eslint ./src --quiet # Test for errors
68+
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=490
69+
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings
5870

5971
after_success:
6072
# Upload code coverage results to codecov.io, see https://github.com/codecov/codecov-bash for more information

envs/monkey_zoo/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
logs/
2+
/blackbox/tests/performance/telem_sample

envs/monkey_zoo/blackbox/README.md

+30-3
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,37 @@ In order to execute the entire test suite, you must know the external IP of the
1010
this information in the GCP Console `Compute Engine/VM Instances` under _External IP_.
1111

1212
#### Running in command line
13-
Run the following command:
13+
Blackbox tests have following parameters:
14+
- `--island=IP` Sets island's IP
15+
- `--no-gcp` (Optional) Use for no interaction with the cloud (local test).
16+
- `--quick-performance-tests` (Optional) If enabled performance tests won't reset island and won't send telemetries,
17+
instead will just test performance of endpoints in already present island state.
1418

15-
`monkey\envs\monkey_zoo\blackbox>python -m pytest --island=35.207.152.72:5000 test_blackbox.py`
19+
Example run command:
20+
21+
`monkey\envs\monkey_zoo\blackbox>python -m pytest -s --island=35.207.152.72:5000 test_blackbox.py`
1622

1723
#### Running in PyCharm
18-
Configure a PyTest configuration with the additional argument `--island=35.207.152.72` on the
24+
Configure a PyTest configuration with the additional arguments `-s --island=35.207.152.72` on the
1925
`monkey\envs\monkey_zoo\blackbox`.
26+
27+
### Running telemetry performance test
28+
29+
**Before running performance test make sure browser is not sending requests to island!**
30+
31+
To run telemetry performance test follow these steps:
32+
1. Gather monkey telemetries.
33+
1. Enable "Export monkey telemetries" in Configuration -> Internal -> Tests if you don't have
34+
exported telemetries already.
35+
2. Run monkey and wait until infection is done.
36+
3. All telemetries are gathered in `monkey/telem_sample`
37+
2. Run telemetry performance test.
38+
1. Move directory `monkey/test_telems` to `envs/monkey_zoo/blackbox/tests/performance/test_telems`
39+
2. (Optional) Use `envs/monkey_zoo/blackbox/tests/performance/utils/telem_parser.py` to multiply
40+
telemetries gathered.
41+
1. Run `telem_parser.py` script with working directory set to `monkey\envs\monkey_zoo\blackbox`
42+
2. Pass integer to indicate the multiplier. For example running `telem_parser.py 4` will replicate
43+
telemetries 4 times.
44+
3. If you're using pycharm check "Emulate terminal in output console" on debug/run configuraion.
45+
3. Performance test will run as part of BlackBox tests or you can run it separately by adding
46+
`-k 'test_telem_performance'` option.
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,48 @@
11
import logging
22
from datetime import timedelta
3+
from typing import Dict
34

45
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
5-
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
6+
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
67

7-
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
8-
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
9-
10-
REPORT_URLS = [
11-
"api/report/security",
12-
"api/attack/report",
13-
"api/report/zero_trust/findings",
14-
"api/report/zero_trust/principles",
15-
"api/report/zero_trust/pillars"
16-
]
17-
18-
logger = logging.getLogger(__name__)
8+
LOGGER = logging.getLogger(__name__)
199

2010

2111
class PerformanceAnalyzer(Analyzer):
2212

23-
def __init__(self, island_client: MonkeyIslandClient, break_if_took_too_long=False):
24-
self.break_if_took_too_long = break_if_took_too_long
25-
self.island_client = island_client
26-
27-
def analyze_test_results(self) -> bool:
28-
if not self.island_client.is_all_monkeys_dead():
29-
raise RuntimeError("Can't test report times since not all Monkeys have died.")
13+
def __init__(self, performance_test_config: PerformanceTestConfig, endpoint_timings: Dict[str, timedelta]):
14+
self.performance_test_config = performance_test_config
15+
self.endpoint_timings = endpoint_timings
3016

31-
# Collect timings for all pages
32-
self.island_client.clear_caches()
33-
report_resource_to_response_time = {}
34-
for url in REPORT_URLS:
35-
report_resource_to_response_time[url] = self.island_client.get_elapsed_for_get_request(url)
36-
37-
# Calculate total time and check each page
17+
def analyze_test_results(self):
18+
# Calculate total time and check each endpoint
3819
single_page_time_less_then_max = True
3920
total_time = timedelta()
40-
for page, elapsed in report_resource_to_response_time.items():
41-
logger.info(f"page {page} took {str(elapsed)}")
21+
for endpoint, elapsed in self.endpoint_timings.items():
4222
total_time += elapsed
43-
if elapsed > MAX_ALLOWED_SINGLE_PAGE_TIME:
23+
if elapsed > self.performance_test_config.max_allowed_single_page_time:
4424
single_page_time_less_then_max = False
4525

46-
total_time_less_then_max = total_time < MAX_ALLOWED_TOTAL_TIME
26+
total_time_less_then_max = total_time < self.performance_test_config.max_allowed_total_time
4727

48-
logger.info(f"total time is {str(total_time)}")
28+
PerformanceAnalyzer.log_slowest_endpoints(self.endpoint_timings)
29+
LOGGER.info(f"Total time is {str(total_time)}")
4930

5031
performance_is_good_enough = total_time_less_then_max and single_page_time_less_then_max
5132

52-
if self.break_if_took_too_long and not performance_is_good_enough:
53-
logger.warning(
33+
if self.performance_test_config.break_on_timeout and not performance_is_good_enough:
34+
LOGGER.warning(
5435
"Calling breakpoint - pausing to enable investigation of island. Type 'c' to continue once you're done "
5536
"investigating. Type 'p timings' and 'p total_time' to see performance information."
5637
)
5738
breakpoint()
5839

5940
return performance_is_good_enough
41+
42+
@staticmethod
43+
def log_slowest_endpoints(endpoint_timings, max_endpoints_to_display=100):
44+
slow_endpoint_list = list(endpoint_timings.items())
45+
slow_endpoint_list.sort(key=lambda x: x[1], reverse=True)
46+
slow_endpoint_list = slow_endpoint_list[:max_endpoints_to_display]
47+
for endpoint in slow_endpoint_list:
48+
LOGGER.info(f"{endpoint[0]} took {str(endpoint[1])}")

envs/monkey_zoo/blackbox/conftest.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,23 @@
44
def pytest_addoption(parser):
55
parser.addoption("--island", action="store", default="",
66
help="Specify the Monkey Island address (host+port).")
7+
parser.addoption("--no-gcp", action="store_true", default=False,
8+
help="Use for no interaction with the cloud.")
9+
parser.addoption("--quick-performance-tests", action="store_true", default=False,
10+
help="If enabled performance tests won't reset island and won't send telemetries, "
11+
"instead will just test performance of already present island state.")
712

813

9-
@pytest.fixture(scope='module')
14+
@pytest.fixture(scope='session')
1015
def island(request):
1116
return request.config.getoption("--island")
17+
18+
19+
@pytest.fixture(scope='session')
20+
def no_gcp(request):
21+
return request.config.getoption("--no-gcp")
22+
23+
24+
@pytest.fixture(scope='session')
25+
def quick_performance_tests(request):
26+
return request.config.getoption("--quick-performance-tests")

envs/monkey_zoo/blackbox/island_client/monkey_island_client.py

+3-13
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from datetime import timedelta
2-
from time import sleep
31
import json
42

53
import logging
4+
from time import sleep
5+
66
from bson import json_util
77

88
from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import MonkeyIslandRequests
@@ -31,7 +31,7 @@ def import_config(self, config_contents):
3131

3232
@avoid_race_condition
3333
def run_monkey_local(self):
34-
response = self.requests.post_json("api/local-monkey", dict_data={"action": "run"})
34+
response = self.requests.post_json("api/local-monkey", data={"action": "run"})
3535
if MonkeyIslandClient.monkey_ran_successfully(response):
3636
LOGGER.info("Running the monkey.")
3737
else:
@@ -96,13 +96,3 @@ def clear_caches(self):
9696
response = self.requests.get("api/test/clear_caches")
9797
response.raise_for_status()
9898
return response
99-
100-
def get_elapsed_for_get_request(self, url):
101-
response = self.requests.get(url)
102-
if response.ok:
103-
LOGGER.debug(f"Got ok for {url} content peek:\n{response.content[:120].strip()}")
104-
return response.elapsed
105-
else:
106-
LOGGER.error(f"Trying to get {url} but got unexpected {str(response)}")
107-
# instead of raising for status, mark failed responses as maxtime
108-
return timedelta.max()

envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py

+37-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
from typing import Dict
2+
from datetime import timedelta
3+
4+
15
import requests
26
import functools
37

4-
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
8+
from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod
9+
510
import logging
611

12+
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
713
NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
814
'8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
915
LOGGER = logging.getLogger(__name__)
@@ -14,6 +20,26 @@ class MonkeyIslandRequests(object):
1420
def __init__(self, server_address):
1521
self.addr = "https://{IP}/".format(IP=server_address)
1622
self.token = self.try_get_jwt_from_server()
23+
self.supported_request_methods = {SupportedRequestMethod.GET: self.get,
24+
SupportedRequestMethod.POST: self.post,
25+
SupportedRequestMethod.PATCH: self.patch,
26+
SupportedRequestMethod.DELETE: self.delete}
27+
28+
def get_request_time(self, url, method: SupportedRequestMethod, data=None):
29+
response = self.send_request_by_method(url, method, data)
30+
if response.ok:
31+
LOGGER.debug(f"Got ok for {url} content peek:\n{response.content[:120].strip()}")
32+
return response.elapsed
33+
else:
34+
LOGGER.error(f"Trying to get {url} but got unexpected {str(response)}")
35+
# instead of raising for status, mark failed responses as maxtime
36+
return timedelta.max
37+
38+
def send_request_by_method(self, url, method=SupportedRequestMethod.GET, data=None):
39+
if data:
40+
return self.supported_request_methods[method](url, data)
41+
else:
42+
return self.supported_request_methods[method](url)
1743

1844
def try_get_jwt_from_server(self):
1945
try:
@@ -55,12 +81,20 @@ def post(self, url, data):
5581
verify=False)
5682

5783
@_Decorators.refresh_jwt_token
58-
def post_json(self, url, dict_data):
84+
def post_json(self, url, data: Dict):
5985
return requests.post(self.addr + url, # noqa: DUO123
60-
json=dict_data,
86+
json=data,
6187
headers=self.get_jwt_header(),
6288
verify=False)
6389

90+
@_Decorators.refresh_jwt_token
91+
def patch(self, url, data: Dict):
92+
return requests.patch(self.addr + url, # noqa: DUO123
93+
data=data,
94+
headers=self.get_jwt_header(),
95+
verify=False
96+
)
97+
6498
@_Decorators.refresh_jwt_token
6599
def delete(self, url):
66100
return requests.delete( # noqa: DOU123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from enum import Enum
2+
3+
4+
class SupportedRequestMethod(Enum):
5+
GET = "GET"
6+
POST = "POST"
7+
PATCH = "PATCH"
8+
DELETE = "DELETE"

0 commit comments

Comments
 (0)