From f6e02e2a6adcc22b531ede8501509235e86b0f7a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 22 Oct 2021 13:52:11 +0300 Subject: [PATCH 01/19] Project: bump version numbers from 1.11.0 to 1.12.0 for release --- monkey/common/version.py | 2 +- monkey/monkey_island/cc/ui/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/common/version.py b/monkey/common/version.py index 3582caa72..5c94adc8d 100644 --- a/monkey/common/version.py +++ b/monkey/common/version.py @@ -4,7 +4,7 @@ import argparse from pathlib import Path MAJOR = "1" -MINOR = "11" +MINOR = "12" PATCH = "0" build_file_path = Path(__file__).parent.joinpath("BUILD") diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 7689d95b7..da6200c25 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "1.11.0", + "version": "1.12.0", "name": "infection-monkey", "description": "Infection Monkey C&C UI", "scripts": { From 97642f45dc3d460c1c1de0cd2feeec0eeaa519ff Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Oct 2021 11:13:06 +0300 Subject: [PATCH 02/19] Island: if the data directory is empty no need to consider backing it up --- monkey/monkey_island/cc/setup/data_dir.py | 11 ++++++++++- .../monkey_island/cc/setup/test_data_dir.py | 7 +++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/setup/data_dir.py b/monkey/monkey_island/cc/setup/data_dir.py index c728dca04..e2697d5d5 100644 --- a/monkey/monkey_island/cc/setup/data_dir.py +++ b/monkey/monkey_island/cc/setup/data_dir.py @@ -1,4 +1,5 @@ import logging +import os import shutil from pathlib import Path @@ -15,7 +16,7 @@ class IncompatibleDataDirectory(Exception): def setup_data_dir(data_dir_path: Path) -> None: logger.info(f"Setting up data directory at {data_dir_path}.") - if data_dir_path.exists() and _data_dir_version_mismatch_exists(data_dir_path): + if _is_data_dir_old(data_dir_path): logger.info("Version in data directory does not match the Island's version.") _handle_old_data_directory(data_dir_path) create_secure_directory(str(data_dir_path)) @@ -23,6 +24,14 @@ def setup_data_dir(data_dir_path: Path) -> None: logger.info("Data directory set up.") +def _is_data_dir_old(data_dir_path: Path) -> bool: + dir_exists = data_dir_path.exists() + if not dir_exists or not os.listdir(data_dir_path): + return False + + return _data_dir_version_mismatch_exists(data_dir_path) + + def _handle_old_data_directory(data_dir_path: Path) -> None: should_delete_data_directory = _prompt_user_to_delete_data_directory(data_dir_path) if should_delete_data_directory: diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_data_dir.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_data_dir.py index fe60d227d..34912ef50 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_data_dir.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_data_dir.py @@ -77,3 +77,10 @@ def test_data_dir_setup_not_needed(temp_data_dir_path, temp_version_file_path): setup_data_dir(temp_data_dir_path) assert temp_version_file_path.read_text() == current_version assert bogus_file_path.is_file() + + +def test_empty_data_dir(temp_data_dir_path, temp_version_file_path): + temp_data_dir_path.mkdir() + + setup_data_dir(temp_data_dir_path) + assert temp_version_file_path.read_text() == current_version From 9f9744a77f5d08bfa5aafb7a31611c61692402a9 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 25 Oct 2021 14:49:22 +0200 Subject: [PATCH 03/19] Docs: Update docker Upgrading section --- docs/content/setup/docker.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index f195caf34..92f076655 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -63,16 +63,16 @@ been signed by a private certificate authority. ### 3b. Start Monkey Island with user-provided certificate -1. Create a directory named `monkey_island_data`. This will serve as the - location where Infection Monkey stores its configuration and runtime - artifacts. +1. Create a directory named `monkey_island_data`. If you already have it, + **make sure it's empty**. This will serve as the location where Infection + Monkey stores its configuration and runtime artifacts. ```bash mkdir ./monkey_island_data chmod 700 ./monkey_island_data ``` -1. Run Monkey Island with the `--setup-only` flag to populate the `./monkey_island_data` directory with a default `server_config.json` file. +2. Run Monkey Island with the `--setup-only` flag to populate the `./monkey_island_data` directory with a default `server_config.json` file. ```bash sudo docker run \ @@ -84,16 +84,16 @@ been signed by a private certificate authority. guardicore/monkey-island:VERSION --setup-only ``` -1. Move your `.crt` and `.key` files to `./monkey_island_data`. +3. Move your `.crt` and `.key` files to `./monkey_island_data`. -1. Make sure that your `.crt` and `.key` files are readable and writeable only by you. +4. Make sure that your `.crt` and `.key` files are readable and writeable only by you. ```bash chmod 600 ./monkey_island_data/ chmod 600 ./monkey_island_data/ ``` -1. Edit `./monkey_island_data/server_config.json` to configure Monkey Island +5. Edit `./monkey_island_data/server_config.json` to configure Monkey Island to use your certificate. Your config should look something like this: ```json {linenos=inline,hl_lines=["11-14"]} @@ -114,7 +114,7 @@ been signed by a private certificate authority. } ``` -1. Start the Monkey Island server: +6. Start the Monkey Island server: ```bash sudo docker run \ @@ -135,6 +135,10 @@ Currently, there's no "upgrade-in-place" option when a new version is released. To get an updated version, download it, stop the current container and run the installation commands again with the new file. +When running an updated version using a volume make sure that you have empty +volume for the Island. Also make sure that you don't have an old mongodb +volume. + If you'd like to keep your existing configuration, you can export it to a file using the *Export config* button and then import it to the new Monkey Island. From cebd41b26425ea51a4087600bf73d0088df0bdd8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Oct 2021 16:25:33 +0300 Subject: [PATCH 04/19] Build: change docker container to set MONKEY_DOCKER_CONTAINER env var. This variable is needed because we can't prompt for data dir removal on docker like we do on other deployments Due to the fact that docker is not running interactively and user might be running on an old data dir if he uses volumes, we need special case for docker --- build_scripts/docker/Dockerfile | 1 + monkey/monkey_island/cc/setup/data_dir.py | 12 ++++++ monkey/monkey_island/cc/setup/env_utils.py | 8 ++++ .../monkey_island/cc/setup/test_data_dir.py | 38 ++++++++++++++++--- 4 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 monkey/monkey_island/cc/setup/env_utils.py diff --git a/build_scripts/docker/Dockerfile b/build_scripts/docker/Dockerfile index 2637d3725..ecd2ce296 100644 --- a/build_scripts/docker/Dockerfile +++ b/build_scripts/docker/Dockerfile @@ -18,6 +18,7 @@ COPY --from=builder /monkey /monkey WORKDIR /monkey EXPOSE 5000 EXPOSE 5001 +ENV MONKEY_DOCKER_CONTAINER=true RUN groupadd -r monkey-island && useradd --no-log-init -r -g monkey-island monkey-island RUN chmod 444 /monkey/monkey_island/cc/server.key RUN chmod 444 /monkey/monkey_island/cc/server.csr diff --git a/monkey/monkey_island/cc/setup/data_dir.py b/monkey/monkey_island/cc/setup/data_dir.py index e2697d5d5..72a99b59f 100644 --- a/monkey/monkey_island/cc/setup/data_dir.py +++ b/monkey/monkey_island/cc/setup/data_dir.py @@ -5,6 +5,7 @@ from pathlib import Path from common.version import get_version from monkey_island.cc.server_utils.file_utils import create_secure_directory +from monkey_island.cc.setup.env_utils import is_running_on_docker from monkey_island.cc.setup.version_file_setup import get_version_from_dir, write_version logger = logging.getLogger(__name__) @@ -26,6 +27,17 @@ def setup_data_dir(data_dir_path: Path) -> None: def _is_data_dir_old(data_dir_path: Path) -> bool: dir_exists = data_dir_path.exists() + + if is_running_on_docker(): + if _data_dir_version_mismatch_exists(data_dir_path): + error_message = "Found an old volume. " + "You must create an empty volume for each docker container " + "as specified in setup documentation: " + "https://www.guardicore.com/infectionmonkey/docs/setup/docker/" + raise IncompatibleDataDirectory(error_message) + else: + return False + if not dir_exists or not os.listdir(data_dir_path): return False diff --git a/monkey/monkey_island/cc/setup/env_utils.py b/monkey/monkey_island/cc/setup/env_utils.py new file mode 100644 index 000000000..07f6417ea --- /dev/null +++ b/monkey/monkey_island/cc/setup/env_utils.py @@ -0,0 +1,8 @@ +import os + +# Must match evn var name in build_scripts/docker/Dockerfile:21 +DOCKER_ENV_VAR = "MONKEY_DOCKER_CONTAINER" + + +def is_running_on_docker(): + return os.environ.get(DOCKER_ENV_VAR) == "true" diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_data_dir.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_data_dir.py index 34912ef50..fd1dbb693 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_data_dir.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_data_dir.py @@ -3,6 +3,7 @@ from pathlib import Path import pytest from monkey_island.cc.setup.data_dir import IncompatibleDataDirectory, setup_data_dir +from monkey_island.cc.setup.env_utils import DOCKER_ENV_VAR from monkey_island.cc.setup.version_file_setup import _version_filename current_version = "1.1.1" @@ -27,6 +28,12 @@ def temp_version_file_path(temp_data_dir_path) -> Path: return temp_data_dir_path / _version_filename +def create_bogus_file(dir_path: Path) -> Path: + bogus_file_path = dir_path / "test.txt" + bogus_file_path.touch() + return bogus_file_path + + def test_setup_data_dir(temp_data_dir_path, temp_version_file_path): data_dir_path = temp_data_dir_path setup_data_dir(data_dir_path) @@ -41,8 +48,7 @@ def test_old_version_removed(monkeypatch, temp_data_dir_path, temp_version_file_ temp_data_dir_path.mkdir() temp_version_file_path.write_text(old_version) - bogus_file_path = temp_data_dir_path / "test.txt" - bogus_file_path.touch() + bogus_file_path = create_bogus_file(temp_data_dir_path) setup_data_dir(temp_data_dir_path) @@ -58,8 +64,7 @@ def test_old_version_not_removed( temp_data_dir_path.mkdir() temp_version_file_path.write_text(old_version) - bogus_file_path = temp_data_dir_path / "test.txt" - bogus_file_path.touch() + bogus_file_path = create_bogus_file(temp_data_dir_path) with pytest.raises(IncompatibleDataDirectory): setup_data_dir(temp_data_dir_path) @@ -71,8 +76,7 @@ def test_old_version_not_removed( def test_data_dir_setup_not_needed(temp_data_dir_path, temp_version_file_path): temp_data_dir_path.mkdir() temp_version_file_path.write_text(current_version) - bogus_file_path = temp_data_dir_path / "test.txt" - bogus_file_path.touch() + bogus_file_path = create_bogus_file(temp_data_dir_path) setup_data_dir(temp_data_dir_path) assert temp_version_file_path.read_text() == current_version @@ -84,3 +88,25 @@ def test_empty_data_dir(temp_data_dir_path, temp_version_file_path): setup_data_dir(temp_data_dir_path) assert temp_version_file_path.read_text() == current_version + + +def test_new_data_dir_docker(monkeypatch, temp_data_dir_path, temp_version_file_path): + monkeypatch.setenv(DOCKER_ENV_VAR, "true") + + temp_data_dir_path.mkdir() + bogus_file_path = create_bogus_file(temp_data_dir_path) + temp_version_file_path.write_text(current_version) + + setup_data_dir(temp_data_dir_path) + assert temp_version_file_path.read_text() == current_version + assert bogus_file_path.is_file() + + +def test_old_data_dir_docker(monkeypatch, temp_data_dir_path, temp_version_file_path): + monkeypatch.setenv(DOCKER_ENV_VAR, "true") + + temp_data_dir_path.mkdir() + temp_version_file_path.write_text(old_version) + + with pytest.raises(IncompatibleDataDirectory): + setup_data_dir(temp_data_dir_path) From a399e8a0ea3decbb7e907d5ce416e7330c467dc9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Oct 2021 17:30:25 +0300 Subject: [PATCH 05/19] Docs: reverted the numbering to use 1. instead of manual numeration in docker.md --- docs/content/setup/docker.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index 92f076655..774813530 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -72,7 +72,7 @@ been signed by a private certificate authority. chmod 700 ./monkey_island_data ``` -2. Run Monkey Island with the `--setup-only` flag to populate the `./monkey_island_data` directory with a default `server_config.json` file. +1. Run Monkey Island with the `--setup-only` flag to populate the `./monkey_island_data` directory with a default `server_config.json` file. ```bash sudo docker run \ @@ -84,16 +84,16 @@ been signed by a private certificate authority. guardicore/monkey-island:VERSION --setup-only ``` -3. Move your `.crt` and `.key` files to `./monkey_island_data`. +1. Move your `.crt` and `.key` files to `./monkey_island_data`. -4. Make sure that your `.crt` and `.key` files are readable and writeable only by you. +1. Make sure that your `.crt` and `.key` files are readable and writeable only by you. ```bash chmod 600 ./monkey_island_data/ chmod 600 ./monkey_island_data/ ``` -5. Edit `./monkey_island_data/server_config.json` to configure Monkey Island +1. Edit `./monkey_island_data/server_config.json` to configure Monkey Island to use your certificate. Your config should look something like this: ```json {linenos=inline,hl_lines=["11-14"]} @@ -114,7 +114,7 @@ been signed by a private certificate authority. } ``` -6. Start the Monkey Island server: +1. Start the Monkey Island server: ```bash sudo docker run \ From 9ef9ba002452d43eda4cb8ab61dc244977307f3a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Oct 2021 18:28:44 +0300 Subject: [PATCH 06/19] Island: improve and fix data directory exception handling/logging --- monkey/monkey_island/cc/server_setup.py | 3 ++- monkey/monkey_island/cc/setup/data_dir.py | 33 +++++++++++++++-------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index dd5547659..c6dc9c0b9 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -70,7 +70,8 @@ def _setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, st except json.JSONDecodeError as ex: print(f"Error loading server config: {ex}") exit(1) - except IncompatibleDataDirectory: + except IncompatibleDataDirectory as ex: + print(f"Incompatible data directory: {ex}") exit(1) diff --git a/monkey/monkey_island/cc/setup/data_dir.py b/monkey/monkey_island/cc/setup/data_dir.py index 72a99b59f..af01da050 100644 --- a/monkey/monkey_island/cc/setup/data_dir.py +++ b/monkey/monkey_island/cc/setup/data_dir.py @@ -29,33 +29,44 @@ def _is_data_dir_old(data_dir_path: Path) -> bool: dir_exists = data_dir_path.exists() if is_running_on_docker(): - if _data_dir_version_mismatch_exists(data_dir_path): - error_message = "Found an old volume. " - "You must create an empty volume for each docker container " - "as specified in setup documentation: " - "https://www.guardicore.com/infectionmonkey/docs/setup/docker/" - raise IncompatibleDataDirectory(error_message) - else: - return False + return _is_docker_data_dir_old(data_dir_path) - if not dir_exists or not os.listdir(data_dir_path): + if not dir_exists or _is_directory_empty(data_dir_path): return False return _data_dir_version_mismatch_exists(data_dir_path) +def _is_docker_data_dir_old(data_dir_path: Path) -> bool: + if _data_dir_version_mismatch_exists(data_dir_path): + if _is_directory_empty(data_dir_path): + return False + else: + raise IncompatibleDataDirectory( + "Found an old volume. " + "You must create an empty volume for each docker container " + "as specified in setup documentation: " + "https://www.guardicore.com/infectionmonkey/docs/setup/docker/" + ) + else: + return False + + +def _is_directory_empty(path: Path) -> bool: + return not os.listdir(path) + + def _handle_old_data_directory(data_dir_path: Path) -> None: should_delete_data_directory = _prompt_user_to_delete_data_directory(data_dir_path) if should_delete_data_directory: shutil.rmtree(data_dir_path) logger.info(f"{data_dir_path} was deleted.") else: - logger.error( + raise IncompatibleDataDirectory( "Unable to set up data directory. Please backup and delete the existing data directory" f" ({data_dir_path}). Then, try again. To learn how to restore and use a backup, please" " refer to the documentation." ) - raise IncompatibleDataDirectory() def _prompt_user_to_delete_data_directory(data_dir_path: Path) -> bool: From 01f8488b0735e2c98e7383891c32670d4cc45753 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Oct 2021 18:30:53 +0300 Subject: [PATCH 07/19] UT's: assert correct behavior on docker if empty data directory is present and if no version file, but other files are present in the data directory --- .../monkey_island/cc/setup/test_data_dir.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_data_dir.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_data_dir.py index fd1dbb693..e476784b1 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_data_dir.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_data_dir.py @@ -102,7 +102,7 @@ def test_new_data_dir_docker(monkeypatch, temp_data_dir_path, temp_version_file_ assert bogus_file_path.is_file() -def test_old_data_dir_docker(monkeypatch, temp_data_dir_path, temp_version_file_path): +def test_data_dir_docker_old_version(monkeypatch, temp_data_dir_path, temp_version_file_path): monkeypatch.setenv(DOCKER_ENV_VAR, "true") temp_data_dir_path.mkdir() @@ -110,3 +110,22 @@ def test_old_data_dir_docker(monkeypatch, temp_data_dir_path, temp_version_file_ with pytest.raises(IncompatibleDataDirectory): setup_data_dir(temp_data_dir_path) + + +def test_empty_data_dir_docker(monkeypatch, temp_data_dir_path, temp_version_file_path): + monkeypatch.setenv(DOCKER_ENV_VAR, "true") + + temp_data_dir_path.mkdir() + + setup_data_dir(temp_data_dir_path) + assert temp_version_file_path.read_text() == current_version + + +def test_old_data_dir_docker_no_version(monkeypatch, temp_data_dir_path): + monkeypatch.setenv(DOCKER_ENV_VAR, "true") + + temp_data_dir_path.mkdir() + create_bogus_file(temp_data_dir_path) + + with pytest.raises(IncompatibleDataDirectory): + setup_data_dir(temp_data_dir_path) From d14e4dee3198e65cef10a86df953c1452ce6d2b1 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 25 Oct 2021 18:54:28 +0200 Subject: [PATCH 08/19] Docs: Reword Docker upgrading section --- docs/content/setup/docker.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index 774813530..64f436335 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -135,9 +135,10 @@ Currently, there's no "upgrade-in-place" option when a new version is released. To get an updated version, download it, stop the current container and run the installation commands again with the new file. -When running an updated version using a volume make sure that you have empty -volume for the Island. Also make sure that you don't have an old mongodb -volume. +If you've configured Monkey Island to use a docker volume to store runtime data, +make sure that it is empty before upgrading the Infection Monkey. Also make sure +that you use a clean volume for MongoDB, as database schemas may not be compatible +between different versions of the Infection Monkey. If you'd like to keep your existing configuration, you can export it to a file using the *Export config* button and then import it to the new Monkey Island. From c91d922277aa88d94156e6db0cb831b8ad7a2ed1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 25 Oct 2021 12:57:40 -0400 Subject: [PATCH 09/19] Docs: Clarify "upgrade proceedure" for docker --- docs/content/setup/docker.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index 64f436335..b38a27ee9 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -33,6 +33,10 @@ The Infection Monkey Docker container works on Linux only. It is not compatible ``` ### 2. Start MongoDB +{{% notice info %}} +If you are upgrading the Infection Monkey to a new version, be sure to remove +any MongoDB containers or volumes associated with the previous version. +{{% /notice %}} 1. Start a MongoDB Docker container: @@ -62,6 +66,10 @@ been signed by a private certificate authority. ``` ### 3b. Start Monkey Island with user-provided certificate +{{% notice info %}} +If you are upgrading the Infection Monkey to a new version, be sure to remove +any volumes associated with the previous version. +{{% /notice %}} 1. Create a directory named `monkey_island_data`. If you already have it, **make sure it's empty**. This will serve as the location where Infection @@ -132,13 +140,9 @@ After the Monkey Island docker container starts, you can access Monkey Island by ## Upgrading Currently, there's no "upgrade-in-place" option when a new version is released. -To get an updated version, download it, stop the current container and run the -installation commands again with the new file. - -If you've configured Monkey Island to use a docker volume to store runtime data, -make sure that it is empty before upgrading the Infection Monkey. Also make sure -that you use a clean volume for MongoDB, as database schemas may not be compatible -between different versions of the Infection Monkey. +To get an updated version, download it, stop and remove the current Monkey +Island and MongoDB containers and volumes, and run the installation commands +again with the new file. If you'd like to keep your existing configuration, you can export it to a file using the *Export config* button and then import it to the new Monkey Island. From bc3b1b274f5ce557015fd4b07eb55c6eb69b4edf Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 25 Oct 2021 14:40:28 -0400 Subject: [PATCH 10/19] Changelog: Formatting changes and other small fixes --- CHANGELOG.md | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 148637c72..f59c1c55b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,12 +13,12 @@ Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - The name of the "Communicate as new user" post-breach action to "Communicate - as backdoor user". #1410 + as backdoor user". #1410 - Resetting login credentials also cleans the contents of the database. #1495 - ATT&CK report messages (more accurate now). #1483 - T1086 (PowerShell) now also reports if ps1 scripts were run by PBAs. #1513 -- ATT&CK report messages to include empty internal config options as reasons for unscanned attack - techniques. #1518 +- ATT&CK report messages to include internal config options as reasons + for unscanned attack techniques. #1518 ### Removed - Internet access check on agent start. #1402 @@ -32,13 +32,12 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - The "deployment" field from the server_config.json #1205 - The "Execution through module load" ATT&CK technique, since it can no longer be exercise with current code. #1416 -- Browser window popup when Monkey Island starts on Windows. #1428 +- Browser window pop-up when Monkey Island starts on Windows. #1428 ### Fixed - Misaligned buttons and input fields on exploiter and network configuration pages. #1353 - Credentials shown in plain text on configuration screens. #1183 -- Typo "trough" -> "through" in telemetry and docstring. - Crash when unexpected character encoding is used by ping command on German language systems. #1175 - Malfunctioning timestomping PBA. #1405 @@ -47,24 +46,28 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - Overlapping Guardicore logo in the landing page. #1441 - PBA table collapse in security report on data change. #1423 - Unsigned Windows agent binaries in Linux packages are now signed. #1444 -- Some of the gathered credentials no longer appear in database plaintext. #1454 -- Encryptor breaking with UTF-8 characters. (Passwords in different languages can be submitted in - the config successfully now.) #1490 -- Mimikatz collector no longer fails if Azure credential collector is disabled. #1512 #1493 -- Unhandled error when "modify shell startup files PBA" is unable to find regular users. #1507 -- ATT&CK report bug that showed different techniques' results under a technique if the PBA behind - them was the same. #1514 -- ATT&CK report bug that said that the technique "`.bash_profile` and `.bashrc`" was not attempted - when it actually was attempted but failed. #1511 +- Some of the gathered credentials no longer appear in plaintext in the + database. #1454 +- Encryptor breaking with UTF-8 characters. (Passwords in different languages + can be submitted in the config successfully now.) #1490 +- Mimikatz collector no longer fails if Azure credential collector is disabled. + #1512, #1493 +- Unhandled error when "modify shell startup files PBA" is unable to find + regular users. #1507 +- ATT&CK report bug that showed different techniques' results under a technique + if the PBA behind them was the same. #1514 +- ATT&CK report bug that said that the technique "`.bash_profile` and + `.bashrc`" was not attempted when it actually was attempted but failed. #1511 - Bug that periodically cleared the telemetry table's filter. #1392 -- Crashes, stack traces, and other malfunctions when data from older versions of Infection Monkey is - present in the data directory. #1114 +- Crashes, stack traces, and other malfunctions when data from older versions + of Infection Monkey is present in the data directory. #1114 - Broken update links. #1524 ### Security - Generate a random password when creating a new user for CommunicateAsNewUser PBA. #1434 -- Credentials gathered from victim machines are no longer stored plaintext in the database. #1454 +- Credentials gathered from victim machines are no longer stored plaintext in + the database. #1454 - Encrypt the database key with user's credentials. #1463 From 2df588ca59726594cf0990f9e8e9c99f6da93549 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 25 Oct 2021 14:56:29 -0400 Subject: [PATCH 11/19] Changelog: Add missing period --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f59c1c55b..eb28783ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - Stale code in the Windows system info collector that collected installed packages and WMI info. #1389 - Insecure access feature in the Monkey Island. #1418 -- The "deployment" field from the server_config.json #1205 +- The "deployment" field from the server_config.json. #1205 - The "Execution through module load" ATT&CK technique, since it can no longer be exercise with current code. #1416 - Browser window pop-up when Monkey Island starts on Windows. #1428 From 8e6a2d8e7dbdc072e5f77c0ea0c5b88834230982 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 26 Oct 2021 15:23:07 +0300 Subject: [PATCH 12/19] UI: bugfix the need to double click on the import when importing an encrypted configuration When back-end sends the schema for ui to validate that no unsafe options are selected, UI didn't automatically send a response back in case there were no unsafe options selected --- .../components/configuration-components/ImportConfigModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx index 9456e7dd8..8a600ab28 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -37,7 +37,7 @@ const ConfigImportModal = (props: Props) => { if (configContents !== null) { sendConfigToServer(); } - }, [configContents]) + }, [configContents, unsafeOptionsVerified]) function sendConfigToServer() { @@ -67,6 +67,7 @@ const ConfigImportModal = (props: Props) => { } else if (res['import_status'] === 'unsafe_options_verification_required') { setUploadStatus(UploadStatuses.success); setErrorMessage(''); + if (isUnsafeOptionSelected(res['config_schema'], JSON.parse(res['config']))) { setShowUnsafeOptionsConfirmation(true); setCandidateConfig(res['config']); From aa6f202a8fdd449c78d14e4ec0e29c21ed744a92 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 26 Oct 2021 15:47:33 +0300 Subject: [PATCH 13/19] Island: change the log message level of wrong password in password_based_bytes_encryptor.py to debug Wrong password is in some cases expected behavior, not an error of an application --- .../server_utils/encryption/password_based_bytes_encryptor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_utils/encryption/password_based_bytes_encryptor.py b/monkey/monkey_island/cc/server_utils/encryption/password_based_bytes_encryptor.py index b50e77e87..dd9ea329f 100644 --- a/monkey/monkey_island/cc/server_utils/encryption/password_based_bytes_encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryption/password_based_bytes_encryptor.py @@ -47,7 +47,7 @@ class PasswordBasedBytesEncryptor(IEncryptor): ) except ValueError as ex: if str(ex).startswith("Wrong password"): - logger.error("Wrong password provided for decryption.") + logger.debug("Wrong password provided for decryption.") raise InvalidCredentialsError else: logger.error("The provided ciphertext was corrupt.") From 820d47c9ccf2741befc2abdd5dbd7d2569b8f47a Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 26 Oct 2021 19:24:50 +0530 Subject: [PATCH 14/19] Agent: Change logic for generating random password --- .../actions/communicate_as_backdoor_user.py | 2 +- .../utils/random_password_generator.py | 8 +++++--- .../utils/test_random_password_generator.py | 11 ++++++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py index 8e0758c77..dba5daad4 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py @@ -41,7 +41,7 @@ class CommunicateAsBackdoorUser(PBA): def run(self): username = CommunicateAsBackdoorUser.get_random_new_user_name() try: - password = get_random_password() + password = get_random_password(14) with create_auto_new_user(username, password) as new_user: http_request_commandline = ( CommunicateAsBackdoorUser.get_commandline_for_http_request( diff --git a/monkey/infection_monkey/utils/random_password_generator.py b/monkey/infection_monkey/utils/random_password_generator.py index 273343c22..3d77f1629 100644 --- a/monkey/infection_monkey/utils/random_password_generator.py +++ b/monkey/infection_monkey/utils/random_password_generator.py @@ -1,8 +1,10 @@ import secrets +import string -SECRET_BYTE_LENGTH = 32 +SECRET_LENGTH = 32 -def get_random_password(length: int = SECRET_BYTE_LENGTH) -> str: - password = secrets.token_urlsafe(length) +def get_random_password(length: int = SECRET_LENGTH) -> str: + alphabet = string.ascii_letters + string.digits + string.punctuation + password = "".join(secrets.choice(alphabet) for i in range(length)) return password diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_random_password_generator.py b/monkey/tests/unit_tests/infection_monkey/utils/test_random_password_generator.py index bdd97cdfd..6131fa34b 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_random_password_generator.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_random_password_generator.py @@ -2,12 +2,17 @@ from infection_monkey.utils.random_password_generator import get_random_password def test_get_random_password__length(): - password_byte_length = len(get_random_password().encode()) + password_length = len(get_random_password()) # 32 is the recommended secure byte length for secrets - assert password_byte_length >= 32 + assert password_length == 32 + + +def test_get_random_password__custom_length(): + password_length = len(get_random_password(14)) + assert password_length == 14 def test_get_random_password__randomness(): random_password1 = get_random_password() random_password2 = get_random_password() - assert not random_password1 == random_password2 + assert random_password1 != random_password2 From 1ad74a4bff4cd0b7b695cdc30aae2ba5d2e8aceb Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 26 Oct 2021 17:14:02 +0300 Subject: [PATCH 15/19] BB: fix zerologon test to check propagation via SMB as well ZeroLogon doesn't propagate to the machine it only steals the credentials. It's best to make sure that propagation is also possible by running SMB exploiter --- envs/monkey_zoo/blackbox/config_templates/zerologon.py | 2 +- envs/monkey_zoo/blackbox/test_blackbox.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/envs/monkey_zoo/blackbox/config_templates/zerologon.py b/envs/monkey_zoo/blackbox/config_templates/zerologon.py index 93ebd5301..0c0266857 100644 --- a/envs/monkey_zoo/blackbox/config_templates/zerologon.py +++ b/envs/monkey_zoo/blackbox/config_templates/zerologon.py @@ -10,7 +10,7 @@ class Zerologon(ConfigTemplate): config_values.update( { - "basic.exploiters.exploiter_classes": ["ZerologonExploiter"], + "basic.exploiters.exploiter_classes": ["ZerologonExploiter", "SmbExploiter"], "basic_network.scope.subnet_scan_list": ["10.2.2.25"], # Empty list to make sure ZeroLogon adds "Administrator" username "basic.credentials.exploit_user_list": [], diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index cc4d6ba97..3b74f8961 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -221,7 +221,10 @@ class TestMonkeyBlackbox: "2864b62ea4496934a5d6e86f50b834a5", ] raw_config = IslandConfigParser.get_raw_config(Zerologon, island_client) - analyzer = ZerologonAnalyzer(island_client, expected_creds) + zero_logon_analyzer = ZerologonAnalyzer(island_client, expected_creds) + communication_analyzer = CommunicationAnalyzer( + island_client, IslandConfigParser.get_ips_of_targets(raw_config) + ) log_handler = TestLogsHandler( test_name, island_client, TestMonkeyBlackbox.get_log_dir_path() ) @@ -229,7 +232,7 @@ class TestMonkeyBlackbox: name=test_name, island_client=island_client, raw_config=raw_config, - analyzers=[analyzer], + analyzers=[zero_logon_analyzer, communication_analyzer], timeout=DEFAULT_TIMEOUT_SECONDS, log_handler=log_handler, ).run() From ea31d27bf18a5e7fd2a4f3727c6c6897d029f3a4 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 26 Oct 2021 19:55:01 +0530 Subject: [PATCH 16/19] Island: Update Zerologon's description in the configuration --- .../config_schema/definitions/exploiter_classes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py index 92898fdad..85cc09014 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py @@ -144,10 +144,10 @@ EXPLOITER_CLASSES = { "title": "Zerologon Exploiter", "safe": False, "info": "Exploits a privilege escalation vulnerability (CVE-2020-1472) in a Windows " - "server domain controller by using the Netlogon Remote Protocol (MS-NRPC). " - "This exploiter changes the password of a Windows server domain controller " - "account and then attempts to restore it. The victim domain controller " - "will be unable to communicate with other domain controllers until the original " + "server domain controller (DC) by using the Netlogon Remote Protocol (MS-NRPC). " + "This exploiter changes the password of a Windows server DC account, steals " + "credentials, and then attempts to restore the original DC password. The victim DC " + "will be unable to communicate with other DCs until the original " "password has been restored. If Infection Monkey fails to restore the " "password automatically, you'll have to do it manually. For more " "information, see the documentation.", From a55f86ceea347cc4813a8cd5552e0a25a2558b29 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 26 Oct 2021 19:56:33 +0530 Subject: [PATCH 17/19] Docs: Update Zerologon documentation to mention that brute force exploiters use its stolen creds --- docs/content/reference/exploiters/Zerologon.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/reference/exploiters/Zerologon.md b/docs/content/reference/exploiters/Zerologon.md index 76a524c03..90ece682b 100644 --- a/docs/content/reference/exploiters/Zerologon.md +++ b/docs/content/reference/exploiters/Zerologon.md @@ -10,7 +10,7 @@ The Zerologon exploiter exploits [CVE-2020-1472](https://cve.mitre.org/cgi-bin/c ### Description -An elevation of privilege vulnerability exists when an attacker establishes a vulnerable Netlogon secure channel connection to a domain controller, using the Netlogon Remote Protocol (MS-NRPC). +An elevation of privilege vulnerability exists when an attacker establishes a vulnerable Netlogon secure channel connection to a domain controller, using the Netlogon Remote Protocol (MS-NRPC). The Zerologon exploiter takes advantage of this vulnerability to steal credentials from the domain controller. This allows the Infection Monkey to propagate to the machine using one of the brute force exploiters (for example, the SMB Exploiter). To download the relevant security update and read more, click [here](https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2020-1472). From bc5ca5b613ea8417aca2d05a438e1f5bfcaf359c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 27 Oct 2021 07:58:39 -0400 Subject: [PATCH 18/19] Docs: Add --tty and --interactive to docker commands These options allow the monkey-island docker container to be killed with --- docs/content/setup/docker.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index b38a27ee9..db5979fc6 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -60,6 +60,8 @@ been signed by a private certificate authority. 1. Run the Monkey Island server ```bash sudo docker run \ + --tty \ + --interactive \ --name monkey-island \ --network=host \ guardicore/monkey-island:VERSION @@ -126,6 +128,8 @@ any volumes associated with the previous version. ```bash sudo docker run \ + --tty \ + --interactive \ --name monkey-island \ --network=host \ --user "$(id -u ${USER}):$(id -g ${USER})" \ From d5e12725a9a7962f8ce93f469ac0b02e415c15e4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 27 Oct 2021 10:14:36 -0400 Subject: [PATCH 19/19] Changelog: Release v1.12.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb28783ca..295fc9442 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## [Unreleased] +## [1.12.0] - 2021-10-27 ### Added - A new exploiter that allows propagation via PowerShell Remoting. #1246 - A warning regarding antivirus when agent binaries are missing. #1450