Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create data directory on Island initialisation #1170

Merged
merged 39 commits into from
May 26, 2021
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
76d82ce
Create `data_dir` if no `--server-config` is passed during Monkey Isl…
shreyamalviya May 12, 2021
8246341
Create data dir if `--server-config` is passed, "data_dir" field exis…
shreyamalviya May 13, 2021
808e86d
Create data_dir before logger is set up
shreyamalviya May 14, 2021
9eedac4
Move `create_data_dir()` from monkey_island.py to monkey_island/cc/se…
shreyamalviya May 17, 2021
805e5e6
Restructure code for creating default data directory and server config
shreyamalviya May 17, 2021
d8927a5
Create constant SERVER_CONFIG_FILENAME
shreyamalviya May 17, 2021
af42c01
Replace missed out function name
shreyamalviya May 17, 2021
ff1e6bd
Remove logic for creating default server config in appimage script
shreyamalviya May 17, 2021
a1beee9
Change data_dir permissions on Windows
shreyamalviya May 18, 2021
3201672
Move `is_windows_os` to data_dir_generator.py and add user write
shreyamalviya May 19, 2021
e7a26aa
Rename `set_data_dir_security_to_read_by_owner()` to
shreyamalviya May 19, 2021
8c575b9
Import Windows specific modules only on Windows
shreyamalviya May 19, 2021
8ce506a
Refactored windows permission handling into a separate file
VakarisZ May 19, 2021
409a3c5
Refactored code duplication by adding a parameter for create_data_dir
VakarisZ May 19, 2021
2fb77fc
Get default directory depending on OS
shreyamalviya May 20, 2021
37b7815
Move `is_windows_os()` to separate file
shreyamalviya May 20, 2021
dc129c0
Fix unit test in test_consts.py
shreyamalviya May 20, 2021
4640a76
Update CHANGELOG (create data dir on island init)
shreyamalviya May 20, 2021
b4708fc
Import Windows specific modules only on Windows systems
shreyamalviya May 20, 2021
9705c73
Remove code in environment_config.py which was calling `write_default…
shreyamalviya May 21, 2021
23b0492
Remove unit test related to code removed in the previous commit
shreyamalviya May 21, 2021
43d919a
Create default server_config.json when running unit tests
shreyamalviya May 21, 2021
70b9a9f
Refactored config_loader.py and server_config_handler.py into a singl…
VakarisZ May 24, 2021
8afe937
Improved monkey_island.py setup readability by extracting workflows o…
VakarisZ May 24, 2021
5f88f6f
Moved a comment to a proper place
VakarisZ May 24, 2021
3098ac1
Fixed a failing environment setup in unit tests
VakarisZ May 24, 2021
d33c5d6
Use `os.path.expandvars()` on server config argument
shreyamalviya May 24, 2021
f500ca8
Merge pull request #1184 from guardicore/data-dir-on-island-refactor
shreyamalviya May 24, 2021
7d1c5dd
Merged develop into data-dir-on-island-init
VakarisZ May 24, 2021
0a7cf1d
Improved readability in the arg_parser.py by extracting defaults to t…
VakarisZ May 24, 2021
2b257f0
Fixed bugs merge bugs where structures are being accessed in an outda…
VakarisZ May 24, 2021
d273e85
Fixed bugs in argument parser passing default server config path even…
VakarisZ May 24, 2021
3a800d9
Fixed a bug where models were getting imported without mongodb connec…
VakarisZ May 24, 2021
9337c68
Removed the infrastructure from starting main.py - it can no longer b…
VakarisZ May 24, 2021
057a579
Rolled back the changes that made default server config on model pack…
VakarisZ May 25, 2021
5b7329b
Fixed stack-overflow that has been happening due to gevent unpatched …
VakarisZ May 25, 2021
17e994c
Fixed UT's for models by mocking an environment singleton with needed…
VakarisZ May 25, 2021
a89a62c
Remove unneeded "noqa" statements
shreyamalviya May 26, 2021
baee74b
Add exception messages during data directory creation
shreyamalviya May 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Improved the structure of unit tests by scoping fixtures only to relevant modules
instead of having a one huge fixture file, improved and renamed the directory
structure of unit tests and unit test infrastructure. #1178
- Create/check data directory on Island init. #1170

### Removed
- Relevant dead code as reported by Vulture. #1149
Expand Down
8 changes: 0 additions & 8 deletions appimage/run_appimage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,12 @@
PYTHON_CMD="$APPDIR"/opt/python3.7/bin/python3.7
DOT_MONKEY="$HOME"/.monkey_island/

configure_default_server() {
VakarisZ marked this conversation as resolved.
Show resolved Hide resolved
if [ ! -f "$DOT_MONKEY"/server_config.json ]; then
cp "$APPDIR"/usr/src/monkey_island/cc/server_config.json.standard "$DOT_MONKEY"/server_config.json
fi
}

# shellcheck disable=SC2174
mkdir --mode=0700 --parents "$DOT_MONKEY"

DB_DIR="$DOT_MONKEY"/db
mkdir --parents "$DB_DIR"

configure_default_server

cd "$APPDIR"/usr/src || exit 1
./monkey_island/bin/mongodb/bin/mongod --dbpath "$DB_DIR" &
${PYTHON_CMD} ./monkey_island.py --server-config "$DOT_MONKEY"/server_config.json
34 changes: 21 additions & 13 deletions monkey/monkey_island.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
from gevent import monkey as gevent_monkey
# This import patches other imports and needs to be first
import monkey_island.setup.gevent_setup # noqa: F401 isort:skip

from monkey_island.cc.arg_parser import parse_cli_args
from monkey_island.config_file_parser import load_island_config_from_file

gevent_monkey.patch_all()

import json # noqa: E402
import json

from monkey_island.cc.server_utils.island_logger import setup_logging # noqa: E402
import monkey_island.cc.environment.environment_singleton as env_singleton
from monkey_island.cc.arg_parser import parse_cli_args
from monkey_island.cc.server_utils.island_logger import setup_logging
from monkey_island.setup.config_setup import setup_config_by_cmd_arg, setup_default_config

if "__main__" == __name__:
island_args = parse_cli_args()

# This is here in order to catch EVERYTHING, some functions are being called on
# imports, so the log init needs to be first.
try:
# This is here in order to catch EVERYTHING, some functions are being called on
# imports, so the log init needs to be first.
config_options = load_island_config_from_file(island_args.server_config_path)
setup_logging(config_options.data_dir, config_options.log_level)
if island_args.server_config_path:
config, server_config_path = setup_config_by_cmd_arg(island_args.server_config_path)
else:
config, server_config_path = setup_default_config()

setup_logging(config.data_dir, config.log_level)

except OSError as ex:
print(f"Error opening server config file: {ex}")
Expand All @@ -26,6 +29,11 @@
print(f"Error loading server config: {ex}")
exit(1)

# We need to initialize environment singleton before importing main,
# because main imports modules from monkey_island/cc/models and models need a connection to the
# mongodb. Mongodb connection parameters are initialized in environment singleton.
env_singleton.initialize_from_file(server_config_path)

from monkey_island.cc.main import main # noqa: E402

main(island_args.setup_only, island_args.server_config_path, config_options)
main(island_args.setup_only, config)
16 changes: 4 additions & 12 deletions monkey/monkey_island/cc/arg_parser.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
from monkey_island.cc.server_utils.consts import (
DEFAULT_SERVER_CONFIG_PATH,
DEFAULT_SHOULD_SETUP_ONLY,
)
from dataclasses import dataclass


@dataclass
class IslandCmdArgs:
setup_only: bool = DEFAULT_SHOULD_SETUP_ONLY
server_config_path: str = DEFAULT_SERVER_CONFIG_PATH

def __init__(self, setup_only: None, server_config_path: None):
if setup_only:
self.setup_only = setup_only
if server_config_path:
self.server_config_path = server_config_path
setup_only: bool
server_config_path: str


def parse_cli_args() -> IslandCmdArgs:
Expand Down
14 changes: 14 additions & 0 deletions monkey/monkey_island/cc/environment/data_dir_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import os

from monkey_island.cc.environment.utils import is_windows_os
from monkey_island.cc.environment.windows_permissions import set_full_folder_access


def create_data_dir(data_dir: str, create_parent_dirs: bool) -> None:
if not os.path.isdir(data_dir):
if create_parent_dirs:
os.makedirs(data_dir, mode=0o700)
else:
os.mkdir(data_dir, mode=0o700)
if is_windows_os(): # `mode=0o700` doesn't work on Windows
set_full_folder_access(folder_path=data_dir)
4 changes: 0 additions & 4 deletions monkey/monkey_island/cc/environment/environment_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

import json
import os
from pathlib import Path
from typing import Dict, List

import monkey_island.cc.environment.server_config_generator as server_config_generator
from monkey_island.cc.environment.user_creds import UserCreds
from monkey_island.cc.resources.auth.auth_user import User
from monkey_island.cc.resources.auth.user_store import UserStore
Expand All @@ -24,8 +22,6 @@ def __init__(self, file_path):
def _load_from_file(self, file_path):
file_path = os.path.expanduser(file_path)

if not Path(file_path).is_file():
server_config_generator.create_default_config_file(file_path)
with open(file_path, "r") as f:
config_content = f.read()

Expand Down
6 changes: 0 additions & 6 deletions monkey/monkey_island/cc/environment/environment_singleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import monkey_island.cc.resources.auth.user_store as user_store
from monkey_island.cc.environment import EnvironmentConfig, aws, password, standard
from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH

__author__ = "itay.mizeretz"

Expand Down Expand Up @@ -48,8 +47,3 @@ def initialize_from_file(file_path):
except Exception:
logger.error("Failed initializing environment", exc_info=True)
raise


# TODO: This is only needed so that unit tests pass. After PR #848 is merged, we may be
# able to remove this line.
initialize_from_file(DEFAULT_SERVER_CONFIG_PATH)

This file was deleted.

27 changes: 27 additions & 0 deletions monkey/monkey_island/cc/environment/server_config_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import json
import os
from pathlib import Path

from monkey_island.cc.server_utils.consts import (
DEFAULT_DEVELOP_SERVER_CONFIG_PATH,
DEFAULT_SERVER_CONFIG_PATH,
)
from monkey_island.setup.island_config_options import IslandConfigOptions


def create_default_server_config_file() -> None:
if not os.path.isfile(DEFAULT_SERVER_CONFIG_PATH):
write_default_server_config_to_file(DEFAULT_SERVER_CONFIG_PATH)


def write_default_server_config_to_file(path: str) -> None:
default_config = Path(DEFAULT_DEVELOP_SERVER_CONFIG_PATH).read_text()
Path(path).write_text(default_config)


def load_server_config_from_file(server_config_path) -> IslandConfigOptions:
with open(server_config_path, "r") as f:
config_content = f.read()
config = json.loads(config_content)

return IslandConfigOptions(config)
5 changes: 5 additions & 0 deletions monkey/monkey_island/cc/environment/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import sys


def is_windows_os() -> bool:
return sys.platform.startswith("win")
34 changes: 34 additions & 0 deletions monkey/monkey_island/cc/environment/windows_permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from monkey_island.cc.environment.utils import is_windows_os

if is_windows_os():
import ntsecuritycon
import win32api
import win32con
import win32security


def set_full_folder_access(folder_path: str) -> None:
user = get_user_pySID_object()

security_descriptor = win32security.GetFileSecurity(
folder_path, win32security.DACL_SECURITY_INFORMATION
)
dacl = win32security.ACL()
dacl.AddAccessAllowedAce(
win32security.ACL_REVISION,
ntsecuritycon.FILE_ALL_ACCESS,
user,
)
security_descriptor.SetSecurityDescriptorDacl(1, dacl, 0)
win32security.SetFileSecurity(
folder_path, win32security.DACL_SECURITY_INFORMATION, security_descriptor
)


def get_user_pySID_object():
# get current user's name
username = win32api.GetUserNameEx(win32con.NameSamCompatible)
# pySID object for the current user
user, _, _ = win32security.LookupAccountName("", username)

return user
8 changes: 1 addition & 7 deletions monkey/monkey_island/cc/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@
MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0"


def main(setup_only: bool, server_config_path: str, config_options: IslandConfigOptions):

env_singleton.initialize_from_file(server_config_path)
def main(setup_only: bool, config_options: IslandConfigOptions):
initialize_encryptor(config_options.data_dir)
initialize_services(config_options.data_dir)

Expand Down Expand Up @@ -122,7 +120,3 @@ def assert_mongo_db_version(mongo_url):
sys.exit(-1)
else:
logger.info("Mongo DB version OK. Got {0}".format(str(server_version)))


if __name__ == "__main__":
main()
15 changes: 8 additions & 7 deletions monkey/monkey_island/cc/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
from mongoengine import connect

import monkey_island.cc.environment.environment_singleton as env_singleton
import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402

from .command_control_channel import CommandControlChannel # noqa: F401
from .command_control_channel import CommandControlChannel # noqa: F401, E402

# Order of importing matters here, for registering the embedded and referenced documents before
# using them.
from .config import Config # noqa: F401
from .creds import Creds # noqa: F401
from .monkey import Monkey # noqa: F401
from .monkey_ttl import MonkeyTtl # noqa: F401
from .pba_results import PbaResults # noqa: F401
from .config import Config # noqa: F401, E402
from .creds import Creds # noqa: F401, E402
from .monkey import Monkey # noqa: F401, E402
from .monkey_ttl import MonkeyTtl # noqa: F401, E402
from .pba_results import PbaResults # noqa: F401, E402

# TODO refactor into explicit call when implementing mongodb startup
connect(
db=env_singleton.env.mongo_db_name,
host=env_singleton.env.mongo_db_host,
Expand Down
24 changes: 19 additions & 5 deletions monkey/monkey_island/cc/server_utils/consts.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import os

from monkey_island.cc.environment.utils import is_windows_os

__author__ = "itay.mizeretz"


def get_default_data_dir() -> str:
if is_windows_os():
return r"%AppData%\monkey_island"
else:
return r"$HOME/.monkey_island"


SERVER_CONFIG_FILENAME = "server_config.json"

MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), "monkey_island")

DEFAULT_DATA_DIR = os.path.expandvars(get_default_data_dir())

DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5

# TODO move setup consts
DEFAULT_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json")
DEFAULT_SERVER_CONFIG_PATH = os.path.expandvars(
os.path.join(DEFAULT_DATA_DIR, SERVER_CONFIG_FILENAME)
)

DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join(
MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop"
MONKEY_ISLAND_ABS_PATH, "cc", f"{SERVER_CONFIG_FILENAME}.develop"
)

DEFAULT_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc")
DEFAULT_LOG_LEVEL = "INFO"
DEFAULT_START_MONGO_DB = True
DEFAULT_SHOULD_SETUP_ONLY = False
23 changes: 23 additions & 0 deletions monkey/monkey_island/setup/config_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import os
from typing import Tuple

from monkey_island.cc.environment import server_config_handler
from monkey_island.cc.environment.data_dir_generator import create_data_dir # noqa: E402
from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_SERVER_CONFIG_PATH
from monkey_island.setup.island_config_options import IslandConfigOptions


def setup_config_by_cmd_arg(server_config_path) -> Tuple[IslandConfigOptions, str]:
server_config_path = os.path.expandvars(os.path.expanduser(server_config_path))
config = server_config_handler.load_server_config_from_file(server_config_path)

create_data_dir(config.data_dir, create_parent_dirs=True)
return config, server_config_path


def setup_default_config() -> Tuple[IslandConfigOptions, str]:
server_config_path = DEFAULT_SERVER_CONFIG_PATH
create_data_dir(DEFAULT_DATA_DIR, create_parent_dirs=False)
server_config_handler.create_default_server_config_file()
config = server_config_handler.load_server_config_from_file(server_config_path)
return config, server_config_path
6 changes: 6 additions & 0 deletions monkey/monkey_island/setup/gevent_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from gevent import monkey as gevent_monkey

# We need to monkeypatch before any other imports to
# make standard libraries compatible with gevent.
# http://www.gevent.org/api/gevent.monkey.html
gevent_monkey.patch_all()
10 changes: 9 additions & 1 deletion monkey/tests/unit_tests/monkey_island/cc/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import monkey_island.cc.environment.environment_singleton as env_singleton
from monkey_island.cc.environment.testing import TestingEnvironment

# Mock environment singleton because it contains mongodb parameters
# needed for model tests. See monkey/monkey_island/cc/models/__init__.py
env_config = {}
env_singleton.env = TestingEnvironment(env_config)

# Without these imports pytests can't use fixtures,
# because they are not found
from tests.unit_tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401,F403
from tests.unit_tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401,F403,E402
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,3 @@ def test_get_users(standard_with_credentials):
assert users[0].id == 1
assert users[0].username == "test"
assert users[0].secret == "abcdef"


def test_generate_default_file(config_file):
environment_config = EnvironmentConfig(config_file)

assert os.path.isfile(config_file)

assert environment_config.server_config == "password"
assert environment_config.deployment == "develop"
assert environment_config.user_creds.username == ""
assert environment_config.user_creds.password_hash == ""
assert environment_config.aws is None
4 changes: 2 additions & 2 deletions monkey/tests/unit_tests/monkey_island/cc/test_consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

def test_default_server_config_file_path():
if platform.system() == "Windows":
server_file_path = consts.MONKEY_ISLAND_ABS_PATH + r"\cc\server_config.json"
server_file_path = f"{consts.DEFAULT_DATA_DIR}\\{consts.SERVER_CONFIG_FILENAME}"
else:
server_file_path = consts.MONKEY_ISLAND_ABS_PATH + "/cc/server_config.json"
server_file_path = f"{consts.DEFAULT_DATA_DIR}/{consts.SERVER_CONFIG_FILENAME}"

assert consts.DEFAULT_SERVER_CONFIG_PATH == server_file_path