Merge branch 'release/1.12.0' into develop

This commit is contained in:
Mike Salvatore 2021-10-27 10:15:02 -04:00
commit 8554ab6fd5
18 changed files with 174 additions and 53 deletions

View File

@ -5,7 +5,7 @@ file.
The format is based on [Keep a The format is based on [Keep a
Changelog](https://keepachangelog.com/en/1.0.0/). Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [1.12.0] - 2021-10-27
### Added ### Added
- A new exploiter that allows propagation via PowerShell Remoting. #1246 - A new exploiter that allows propagation via PowerShell Remoting. #1246
- A warning regarding antivirus when agent binaries are missing. #1450 - A warning regarding antivirus when agent binaries are missing. #1450
@ -13,12 +13,12 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed ### Changed
- The name of the "Communicate as new user" post-breach action to "Communicate - 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 - Resetting login credentials also cleans the contents of the database. #1495
- ATT&CK report messages (more accurate now). #1483 - ATT&CK report messages (more accurate now). #1483
- T1086 (PowerShell) now also reports if ps1 scripts were run by PBAs. #1513 - 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 - ATT&CK report messages to include internal config options as reasons
techniques. #1518 for unscanned attack techniques. #1518
### Removed ### Removed
- Internet access check on agent start. #1402 - Internet access check on agent start. #1402
@ -29,16 +29,15 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- Stale code in the Windows system info collector that collected installed - Stale code in the Windows system info collector that collected installed
packages and WMI info. #1389 packages and WMI info. #1389
- Insecure access feature in the Monkey Island. #1418 - 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, - The "Execution through module load" ATT&CK technique,
since it can no longer be exercise with current code. #1416 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 ### Fixed
- Misaligned buttons and input fields on exploiter and network configuration - Misaligned buttons and input fields on exploiter and network configuration
pages. #1353 pages. #1353
- Credentials shown in plain text on configuration screens. #1183 - 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 - Crash when unexpected character encoding is used by ping command on German
language systems. #1175 language systems. #1175
- Malfunctioning timestomping PBA. #1405 - Malfunctioning timestomping PBA. #1405
@ -47,24 +46,28 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- Overlapping Guardicore logo in the landing page. #1441 - Overlapping Guardicore logo in the landing page. #1441
- PBA table collapse in security report on data change. #1423 - PBA table collapse in security report on data change. #1423
- Unsigned Windows agent binaries in Linux packages are now signed. #1444 - Unsigned Windows agent binaries in Linux packages are now signed. #1444
- Some of the gathered credentials no longer appear in database plaintext. #1454 - Some of the gathered credentials no longer appear in plaintext in the
- Encryptor breaking with UTF-8 characters. (Passwords in different languages can be submitted in database. #1454
the config successfully now.) #1490 - Encryptor breaking with UTF-8 characters. (Passwords in different languages
- Mimikatz collector no longer fails if Azure credential collector is disabled. #1512 #1493 can be submitted in the config successfully now.) #1490
- Unhandled error when "modify shell startup files PBA" is unable to find regular users. #1507 - Mimikatz collector no longer fails if Azure credential collector is disabled.
- ATT&CK report bug that showed different techniques' results under a technique if the PBA behind #1512, #1493
them was the same. #1514 - Unhandled error when "modify shell startup files PBA" is unable to find
- ATT&CK report bug that said that the technique "`.bash_profile` and `.bashrc`" was not attempted regular users. #1507
when it actually was attempted but failed. #1511 - 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 - 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 - Crashes, stack traces, and other malfunctions when data from older versions
present in the data directory. #1114 of Infection Monkey is present in the data directory. #1114
- Broken update links. #1524 - Broken update links. #1524
### Security ### Security
- Generate a random password when creating a new user for CommunicateAsNewUser - Generate a random password when creating a new user for CommunicateAsNewUser
PBA. #1434 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 - Encrypt the database key with user's credentials. #1463

View File

@ -18,6 +18,7 @@ COPY --from=builder /monkey /monkey
WORKDIR /monkey WORKDIR /monkey
EXPOSE 5000 EXPOSE 5000
EXPOSE 5001 EXPOSE 5001
ENV MONKEY_DOCKER_CONTAINER=true
RUN groupadd -r monkey-island && useradd --no-log-init -r -g monkey-island monkey-island 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.key
RUN chmod 444 /monkey/monkey_island/cc/server.csr RUN chmod 444 /monkey/monkey_island/cc/server.csr

View File

@ -10,7 +10,7 @@ The Zerologon exploiter exploits [CVE-2020-1472](https://cve.mitre.org/cgi-bin/c
### Description ### 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). To download the relevant security update and read more, click [here](https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2020-1472).

View File

@ -33,6 +33,10 @@ The Infection Monkey Docker container works on Linux only. It is not compatible
``` ```
### 2. Start MongoDB ### 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: 1. Start a MongoDB Docker container:
@ -56,16 +60,22 @@ been signed by a private certificate authority.
1. Run the Monkey Island server 1. Run the Monkey Island server
```bash ```bash
sudo docker run \ sudo docker run \
--tty \
--interactive \
--name monkey-island \ --name monkey-island \
--network=host \ --network=host \
guardicore/monkey-island:VERSION guardicore/monkey-island:VERSION
``` ```
### 3b. Start Monkey Island with user-provided certificate ### 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`. This will serve as the 1. Create a directory named `monkey_island_data`. If you already have it,
location where Infection Monkey stores its configuration and runtime **make sure it's empty**. This will serve as the location where Infection
artifacts. Monkey stores its configuration and runtime artifacts.
```bash ```bash
mkdir ./monkey_island_data mkdir ./monkey_island_data
@ -118,6 +128,8 @@ been signed by a private certificate authority.
```bash ```bash
sudo docker run \ sudo docker run \
--tty \
--interactive \
--name monkey-island \ --name monkey-island \
--network=host \ --network=host \
--user "$(id -u ${USER}):$(id -g ${USER})" \ --user "$(id -u ${USER}):$(id -g ${USER})" \
@ -132,8 +144,9 @@ After the Monkey Island docker container starts, you can access Monkey Island by
## Upgrading ## Upgrading
Currently, there's no "upgrade-in-place" option when a new version is released. 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 To get an updated version, download it, stop and remove the current Monkey
installation commands again with the new file. 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 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. using the *Export config* button and then import it to the new Monkey Island.

View File

@ -10,7 +10,7 @@ class Zerologon(ConfigTemplate):
config_values.update( config_values.update(
{ {
"basic.exploiters.exploiter_classes": ["ZerologonExploiter"], "basic.exploiters.exploiter_classes": ["ZerologonExploiter", "SmbExploiter"],
"basic_network.scope.subnet_scan_list": ["10.2.2.25"], "basic_network.scope.subnet_scan_list": ["10.2.2.25"],
# Empty list to make sure ZeroLogon adds "Administrator" username # Empty list to make sure ZeroLogon adds "Administrator" username
"basic.credentials.exploit_user_list": [], "basic.credentials.exploit_user_list": [],

View File

@ -221,7 +221,10 @@ class TestMonkeyBlackbox:
"2864b62ea4496934a5d6e86f50b834a5", "2864b62ea4496934a5d6e86f50b834a5",
] ]
raw_config = IslandConfigParser.get_raw_config(Zerologon, island_client) 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( log_handler = TestLogsHandler(
test_name, island_client, TestMonkeyBlackbox.get_log_dir_path() test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()
) )
@ -229,7 +232,7 @@ class TestMonkeyBlackbox:
name=test_name, name=test_name,
island_client=island_client, island_client=island_client,
raw_config=raw_config, raw_config=raw_config,
analyzers=[analyzer], analyzers=[zero_logon_analyzer, communication_analyzer],
timeout=DEFAULT_TIMEOUT_SECONDS, timeout=DEFAULT_TIMEOUT_SECONDS,
log_handler=log_handler, log_handler=log_handler,
).run() ).run()

View File

@ -4,7 +4,7 @@ import argparse
from pathlib import Path from pathlib import Path
MAJOR = "1" MAJOR = "1"
MINOR = "11" MINOR = "12"
PATCH = "0" PATCH = "0"
build_file_path = Path(__file__).parent.joinpath("BUILD") build_file_path = Path(__file__).parent.joinpath("BUILD")

View File

@ -41,7 +41,7 @@ class CommunicateAsBackdoorUser(PBA):
def run(self): def run(self):
username = CommunicateAsBackdoorUser.get_random_new_user_name() username = CommunicateAsBackdoorUser.get_random_new_user_name()
try: try:
password = get_random_password() password = get_random_password(14)
with create_auto_new_user(username, password) as new_user: with create_auto_new_user(username, password) as new_user:
http_request_commandline = ( http_request_commandline = (
CommunicateAsBackdoorUser.get_commandline_for_http_request( CommunicateAsBackdoorUser.get_commandline_for_http_request(

View File

@ -1,8 +1,10 @@
import secrets import secrets
import string
SECRET_BYTE_LENGTH = 32 SECRET_LENGTH = 32
def get_random_password(length: int = SECRET_BYTE_LENGTH) -> str: def get_random_password(length: int = SECRET_LENGTH) -> str:
password = secrets.token_urlsafe(length) alphabet = string.ascii_letters + string.digits + string.punctuation
password = "".join(secrets.choice(alphabet) for i in range(length))
return password return password

View File

@ -70,7 +70,8 @@ def _setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, st
except json.JSONDecodeError as ex: except json.JSONDecodeError as ex:
print(f"Error loading server config: {ex}") print(f"Error loading server config: {ex}")
exit(1) exit(1)
except IncompatibleDataDirectory: except IncompatibleDataDirectory as ex:
print(f"Incompatible data directory: {ex}")
exit(1) exit(1)

View File

@ -47,7 +47,7 @@ class PasswordBasedBytesEncryptor(IEncryptor):
) )
except ValueError as ex: except ValueError as ex:
if str(ex).startswith("Wrong password"): if str(ex).startswith("Wrong password"):
logger.error("Wrong password provided for decryption.") logger.debug("Wrong password provided for decryption.")
raise InvalidCredentialsError raise InvalidCredentialsError
else: else:
logger.error("The provided ciphertext was corrupt.") logger.error("The provided ciphertext was corrupt.")

View File

@ -144,10 +144,10 @@ EXPLOITER_CLASSES = {
"title": "Zerologon Exploiter", "title": "Zerologon Exploiter",
"safe": False, "safe": False,
"info": "Exploits a privilege escalation vulnerability (CVE-2020-1472) in a Windows " "info": "Exploits a privilege escalation vulnerability (CVE-2020-1472) in a Windows "
"server domain controller by using the Netlogon Remote Protocol (MS-NRPC). " "server domain controller (DC) by using the Netlogon Remote Protocol (MS-NRPC). "
"This exploiter changes the password of a Windows server domain controller " "This exploiter changes the password of a Windows server DC account, steals "
"account and then attempts to restore it. The victim domain controller " "credentials, and then attempts to restore the original DC password. The victim DC "
"will be unable to communicate with other domain controllers until the original " "will be unable to communicate with other DCs until the original "
"password has been restored. If Infection Monkey fails to restore the " "password has been restored. If Infection Monkey fails to restore the "
"password automatically, you'll have to do it manually. For more " "password automatically, you'll have to do it manually. For more "
"information, see the documentation.", "information, see the documentation.",

View File

@ -1,9 +1,11 @@
import logging import logging
import os
import shutil import shutil
from pathlib import Path from pathlib import Path
from common.version import get_version from common.version import get_version
from monkey_island.cc.server_utils.file_utils import create_secure_directory 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 from monkey_island.cc.setup.version_file_setup import get_version_from_dir, write_version
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -15,7 +17,7 @@ class IncompatibleDataDirectory(Exception):
def setup_data_dir(data_dir_path: Path) -> None: def setup_data_dir(data_dir_path: Path) -> None:
logger.info(f"Setting up data directory at {data_dir_path}.") 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.") logger.info("Version in data directory does not match the Island's version.")
_handle_old_data_directory(data_dir_path) _handle_old_data_directory(data_dir_path)
create_secure_directory(str(data_dir_path)) create_secure_directory(str(data_dir_path))
@ -23,18 +25,48 @@ def setup_data_dir(data_dir_path: Path) -> None:
logger.info("Data directory set up.") logger.info("Data directory set up.")
def _is_data_dir_old(data_dir_path: Path) -> bool:
dir_exists = data_dir_path.exists()
if is_running_on_docker():
return _is_docker_data_dir_old(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: def _handle_old_data_directory(data_dir_path: Path) -> None:
should_delete_data_directory = _prompt_user_to_delete_data_directory(data_dir_path) should_delete_data_directory = _prompt_user_to_delete_data_directory(data_dir_path)
if should_delete_data_directory: if should_delete_data_directory:
shutil.rmtree(data_dir_path) shutil.rmtree(data_dir_path)
logger.info(f"{data_dir_path} was deleted.") logger.info(f"{data_dir_path} was deleted.")
else: else:
logger.error( raise IncompatibleDataDirectory(
"Unable to set up data directory. Please backup and delete the existing data directory" "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" f" ({data_dir_path}). Then, try again. To learn how to restore and use a backup, please"
" refer to the documentation." " refer to the documentation."
) )
raise IncompatibleDataDirectory()
def _prompt_user_to_delete_data_directory(data_dir_path: Path) -> bool: def _prompt_user_to_delete_data_directory(data_dir_path: Path) -> bool:

View File

@ -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"

View File

@ -1,6 +1,6 @@
{ {
"private": true, "private": true,
"version": "1.11.0", "version": "1.12.0",
"name": "infection-monkey", "name": "infection-monkey",
"description": "Infection Monkey C&C UI", "description": "Infection Monkey C&C UI",
"scripts": { "scripts": {

View File

@ -37,7 +37,7 @@ const ConfigImportModal = (props: Props) => {
if (configContents !== null) { if (configContents !== null) {
sendConfigToServer(); sendConfigToServer();
} }
}, [configContents]) }, [configContents, unsafeOptionsVerified])
function sendConfigToServer() { function sendConfigToServer() {
@ -67,6 +67,7 @@ const ConfigImportModal = (props: Props) => {
} else if (res['import_status'] === 'unsafe_options_verification_required') { } else if (res['import_status'] === 'unsafe_options_verification_required') {
setUploadStatus(UploadStatuses.success); setUploadStatus(UploadStatuses.success);
setErrorMessage(''); setErrorMessage('');
if (isUnsafeOptionSelected(res['config_schema'], JSON.parse(res['config']))) { if (isUnsafeOptionSelected(res['config_schema'], JSON.parse(res['config']))) {
setShowUnsafeOptionsConfirmation(true); setShowUnsafeOptionsConfirmation(true);
setCandidateConfig(res['config']); setCandidateConfig(res['config']);

View File

@ -2,12 +2,17 @@ from infection_monkey.utils.random_password_generator import get_random_password
def test_get_random_password__length(): 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 # 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(): def test_get_random_password__randomness():
random_password1 = get_random_password() random_password1 = get_random_password()
random_password2 = get_random_password() random_password2 = get_random_password()
assert not random_password1 == random_password2 assert random_password1 != random_password2

View File

@ -3,6 +3,7 @@ from pathlib import Path
import pytest import pytest
from monkey_island.cc.setup.data_dir import IncompatibleDataDirectory, setup_data_dir 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 from monkey_island.cc.setup.version_file_setup import _version_filename
current_version = "1.1.1" 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 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): def test_setup_data_dir(temp_data_dir_path, temp_version_file_path):
data_dir_path = temp_data_dir_path data_dir_path = temp_data_dir_path
setup_data_dir(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_data_dir_path.mkdir()
temp_version_file_path.write_text(old_version) temp_version_file_path.write_text(old_version)
bogus_file_path = temp_data_dir_path / "test.txt" bogus_file_path = create_bogus_file(temp_data_dir_path)
bogus_file_path.touch()
setup_data_dir(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_data_dir_path.mkdir()
temp_version_file_path.write_text(old_version) temp_version_file_path.write_text(old_version)
bogus_file_path = temp_data_dir_path / "test.txt" bogus_file_path = create_bogus_file(temp_data_dir_path)
bogus_file_path.touch()
with pytest.raises(IncompatibleDataDirectory): with pytest.raises(IncompatibleDataDirectory):
setup_data_dir(temp_data_dir_path) setup_data_dir(temp_data_dir_path)
@ -71,9 +76,56 @@ def test_old_version_not_removed(
def test_data_dir_setup_not_needed(temp_data_dir_path, temp_version_file_path): def test_data_dir_setup_not_needed(temp_data_dir_path, temp_version_file_path):
temp_data_dir_path.mkdir() temp_data_dir_path.mkdir()
temp_version_file_path.write_text(current_version) temp_version_file_path.write_text(current_version)
bogus_file_path = temp_data_dir_path / "test.txt" bogus_file_path = create_bogus_file(temp_data_dir_path)
bogus_file_path.touch()
setup_data_dir(temp_data_dir_path) setup_data_dir(temp_data_dir_path)
assert temp_version_file_path.read_text() == current_version assert temp_version_file_path.read_text() == current_version
assert bogus_file_path.is_file() 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
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_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()
temp_version_file_path.write_text(old_version)
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)