Merge branch 'release/1.12.0' into develop
This commit is contained in:
commit
8554ab6fd5
41
CHANGELOG.md
41
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
|
||||
|
@ -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
|
||||
|
@ -29,16 +29,15 @@ 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 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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
@ -56,16 +60,22 @@ 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
|
||||
```
|
||||
|
||||
### 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
|
||||
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
|
||||
|
@ -118,6 +128,8 @@ been signed by a private certificate authority.
|
|||
|
||||
```bash
|
||||
sudo docker run \
|
||||
--tty \
|
||||
--interactive \
|
||||
--name monkey-island \
|
||||
--network=host \
|
||||
--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
|
||||
|
||||
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.
|
||||
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.
|
||||
|
|
|
@ -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": [],
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import logging
|
||||
import os
|
||||
import shutil
|
||||
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__)
|
||||
|
@ -15,7 +17,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,18 +25,48 @@ 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 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:
|
||||
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:
|
||||
|
|
|
@ -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"
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "1.11.0",
|
||||
"version": "1.12.0",
|
||||
"name": "infection-monkey",
|
||||
"description": "Infection Monkey C&C UI",
|
||||
"scripts": {
|
||||
|
|
|
@ -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']);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,9 +76,56 @@ 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
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue