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

Select server config at runtime #963

Merged
merged 10 commits into from
Feb 17, 2021
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ os: linux

before_install:
# Init server_config.json to default
- cp monkey/monkey_island/cc/server_config.json.default monkey/monkey_island/cc/server_config.json
- cp monkey/monkey_island/cc/server_config.json.develop monkey/monkey_island/cc/server_config.json

install:
# Python
Expand Down
15 changes: 10 additions & 5 deletions monkey/monkey_island.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@

gevent_monkey.patch_all()

from monkey_island.cc.main import main
from monkey_island.cc.main import main # noqa: E402
from monkey_island.cc.environment.environment_config import DEFAULT_SERVER_CONFIG_PATH # noqa: E402


def parse_cli_args():
import argparse
parser = argparse.ArgumentParser(description="Infection Monkey Island CnC Server. See https://infectionmonkey.com")
parser = argparse.ArgumentParser(description="Infection Monkey Island CnC Server. See https://infectionmonkey.com",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-s", "--setup-only", action="store_true",
help="Pass this flag to cause the Island to setup and exit without actually starting. "
"This is useful for preparing Island to boot faster later-on, so for "
"compiling/packaging Islands.")
parser.add_argument("-c", "--config", action="store",
help="The path to the server configuration file.",
default=DEFAULT_SERVER_CONFIG_PATH)
args = parser.parse_args()
return args.setup_only
return (args.setup_only, args.config)


if "__main__" == __name__:
is_setup_only = parse_cli_args()
main(is_setup_only)
(is_setup_only, config) = parse_cli_args()
main(is_setup_only, config)
12 changes: 10 additions & 2 deletions monkey/monkey_island/cc/consts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import os

__author__ = 'itay.mizeretz'
__author__ = "itay.mizeretz"

MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), 'monkey_island')
MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), "monkey_island")
DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5

DEFAULT_SERVER_CONFIG_PATH = os.path.join(
MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json"
)

DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join(
MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop"
)
63 changes: 27 additions & 36 deletions monkey/monkey_island/cc/environment/environment_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,47 @@
from typing import Dict, List

import monkey_island.cc.environment.server_config_generator as server_config_generator
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
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

SERVER_CONFIG_FILENAME = "server_config.json"


class EnvironmentConfig:
def __init__(self,
server_config: str,
deployment: str,
user_creds: UserCreds,
aws=None):
self.server_config = server_config
self.deployment = deployment
self.user_creds = user_creds
self.aws = aws

@staticmethod
def get_from_json(config_json: str) -> EnvironmentConfig:
data = json.loads(config_json)
return EnvironmentConfig.get_from_dict(data)
def __init__(self, file_path):
self._server_config_path = os.path.expanduser(file_path)
self.server_config = None
self.deployment = None
self.user_creds = None
self.aws = None

@staticmethod
def get_from_dict(dict_data: Dict) -> EnvironmentConfig:
user_creds = UserCreds.get_from_dict(dict_data)
aws = dict_data['aws'] if 'aws' in dict_data else None
return EnvironmentConfig(server_config=dict_data['server_config'],
deployment=dict_data['deployment'],
user_creds=user_creds,
aws=aws)
self._load_from_file(self._server_config_path)

def save_to_file(self):
file_path = EnvironmentConfig.get_config_file_path()
with open(file_path, 'w') as f:
f.write(json.dumps(self.to_dict(), indent=2))
def _load_from_file(self, file_path):
file_path = os.path.expanduser(file_path)

@staticmethod
def get_from_file() -> EnvironmentConfig:
file_path = EnvironmentConfig.get_config_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()
return EnvironmentConfig.get_from_json(config_content)

@staticmethod
def get_config_file_path() -> str:
return os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', SERVER_CONFIG_FILENAME)
self._load_from_json(config_content)

def _load_from_json(self, config_json: str) -> EnvironmentConfig:
data = json.loads(config_json)
self._load_from_dict(data)

def _load_from_dict(self, dict_data: Dict):
user_creds = UserCreds.get_from_dict(dict_data)
aws = dict_data['aws'] if 'aws' in dict_data else None

self.server_config = dict_data['server_config']
self.deployment = dict_data['deployment']
self.user_creds = user_creds
self.aws = aws

def save_to_file(self):
with open(self._server_config_path, 'w') as f:
f.write(json.dumps(self.to_dict(), indent=2))

def to_dict(self) -> Dict:
config_dict = {'server_config': self.server_config,
Expand Down
24 changes: 15 additions & 9 deletions monkey/monkey_island/cc/environment/environment_singleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import monkey_island.cc.resources.auth.user_store as user_store
from monkey_island.cc.environment import (EnvironmentConfig, aws, password,
standard, testing)
from monkey_island.cc.consts import DEFAULT_SERVER_CONFIG_PATH

__author__ = 'itay.mizeretz'

Expand Down Expand Up @@ -39,12 +40,17 @@ def set_to_standard():
user_store.UserStore.set_users(env.get_auth_users())


try:
config = EnvironmentConfig.get_from_file()
__env_type = config.server_config
set_env(__env_type, config)
# noinspection PyUnresolvedReferences
logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__))
except Exception:
logger.error('Failed initializing environment', exc_info=True)
raise
def initialize_from_file(file_path):
try:
config = EnvironmentConfig(file_path)

__env_type = config.server_config
set_env(__env_type, config)
# noinspection PyUnresolvedReferences
logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__))
except Exception:
logger.error('Failed initializing environment', exc_info=True)
raise


initialize_from_file(DEFAULT_SERVER_CONFIG_PATH)
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from pathlib import Path

from monkey_island.cc.consts import DEFAULT_DEVELOP_SERVER_CONFIG_PATH


def create_default_config_file(path):
default_config_path = f"{path}.default"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so now server_config.json.default file is no longer being used? Why I don't see it being deleted from the codebase?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's there for the convenience of developers. I can remove it or rename it to server_config.json.develop

Copy link
Contributor

@VakarisZ VakarisZ Feb 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is it convenient? The purpose of this file was to contain default value of server_config.json. We needed this whole process of config file deployment in order not to have server_config.json in our git, but have it on runtime.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is it convenient?

As a developer, doing cp server_config.json.default server_config.json in a new development environment is less error prone than editing the file by hand.

The purpose of this file was to contain default value of server_config.json.

Default for whom? Naming it something like .develop or .standard is more useful than default.

We needed this whole process of config file deployment in order not to have server_config.json in our git, but have it on runtime.

That functionality still works. It's just using server_config.json.standard by default if the specified server_config.json doesn't exist.

Copy link
Contributor

@VakarisZ VakarisZ Feb 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a developer, doing cp server_config.json.default server_config.json in a new development environment is less error prone than editing the file by hand.

How it worked before was that server_config.json was automatically created, which is even less error prone.

That functionality still works. It's just using server_config.json.standard by default if the specified server_config.json doesn't exist.

Then we're back at square one: why do we need server_config.json.default ? I think the downside of using standard config for development is that in our statistics we won't be able to distinguish our own monkey usage from client monkey usage, but that doesn't sound like a big problem. If we think this is a problem, we need our codebase to use develop config by default and change this behavior to "use standard instead" during release process. To phrase it differently, we want to use develop by default and change it to whatever as part of our release process.

default_config = Path(default_config_path).read_text()
default_config = Path(DEFAULT_DEVELOP_SERVER_CONFIG_PATH).read_text()
Path(path).write_text(default_config)
4 changes: 2 additions & 2 deletions monkey/monkey_island/cc/environment/set_server_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def add_monkey_dir_to_sys_path():

add_monkey_dir_to_sys_path()

from monkey_island.cc.environment.environment_config import EnvironmentConfig # noqa: E402 isort:skip
from monkey_island.cc.consts import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 isort:skip

SERVER_CONFIG = "server_config"
BACKUP_CONFIG_FILENAME = "./server_config.backup"
Expand All @@ -26,7 +26,7 @@ def add_monkey_dir_to_sys_path():

def main():
args = parse_args()
file_path = EnvironmentConfig.get_config_file_path()
file_path = DEFAULT_SERVER_CONFIG_PATH

if args.server_config == "restore":
restore_previous_config(file_path)
Expand Down
67 changes: 47 additions & 20 deletions monkey/monkey_island/cc/environment/test__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
import json
import os
import tempfile
from typing import Dict
from unittest import TestCase
from unittest.mock import MagicMock, patch

import monkey_island.cc.testing.environment.server_config_mocks as config_mocks
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
from common.utils.exceptions import (AlreadyRegisteredError,
CredentialsNotRequiredError,
InvalidRegistrationCredentialsError,
RegistrationNotNeededError)
from monkey_island.cc.environment import (Environment, EnvironmentConfig,
UserCreds)

TEST_RESOURCES_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing", "environment")

WITH_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_with_credentials.json")
NO_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_no_credentials.json")
PARTIAL_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_partial_credentials.json")
STANDARD_WITH_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR,
"server_config_standard_with_credentials.json")
STANDARD_ENV = os.path.join(TEST_RESOURCES_DIR,
"server_config_standard_env.json")


def get_tmp_file():
with tempfile.NamedTemporaryFile(delete=False) as f:
return f.name


class StubEnvironmentConfig(EnvironmentConfig):
def __init__(self, server_config, deployment, user_creds):
self.server_config = server_config
self.deployment = deployment
self.user_creds = user_creds
self.server_config_path = get_tmp_file()

def __del__(self):
os.remove(self.server_config_path)


def get_server_config_file_path_test_version():
return os.path.join(os.getcwd(), 'test_config.json')
Expand All @@ -21,7 +47,7 @@ class TestEnvironment(TestCase):

class EnvironmentCredentialsNotRequired(Environment):
def __init__(self):
config = EnvironmentConfig('test', 'test', UserCreds())
config = StubEnvironmentConfig('test', 'test', UserCreds())
super().__init__(config)

_credentials_required = False
Expand All @@ -31,7 +57,7 @@ def get_auth_users(self):

class EnvironmentCredentialsRequired(Environment):
def __init__(self):
config = EnvironmentConfig('test', 'test', UserCreds())
config = StubEnvironmentConfig('test', 'test', UserCreds())
super().__init__(config)

_credentials_required = True
Expand All @@ -41,7 +67,7 @@ def get_auth_users(self):

class EnvironmentAlreadyRegistered(Environment):
def __init__(self):
config = EnvironmentConfig('test', 'test', UserCreds('test_user', 'test_secret'))
config = StubEnvironmentConfig('test', 'test', UserCreds('test_user', 'test_secret'))
super().__init__(config)

_credentials_required = True
Expand Down Expand Up @@ -77,36 +103,37 @@ def test_try_needs_registration(self):
self.assertTrue(env._try_needs_registration())

def test_needs_registration(self):

env = TestEnvironment.EnvironmentCredentialsRequired()
self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_WITH_CREDENTIALS, False)
self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_NO_CREDENTIALS, True)
self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_PARTIAL_CREDENTIALS, True)
self._test_bool_env_method("needs_registration", env, WITH_CREDENTIALS, False)
self._test_bool_env_method("needs_registration", env, NO_CREDENTIALS, True)
self._test_bool_env_method("needs_registration", env, PARTIAL_CREDENTIALS, True)

env = TestEnvironment.EnvironmentCredentialsNotRequired()
self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_STANDARD_ENV, False)
self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_STANDARD_WITH_CREDENTIALS, False)
self._test_bool_env_method("needs_registration", env, STANDARD_ENV, False)
self._test_bool_env_method("needs_registration", env, STANDARD_WITH_CREDENTIALS, False)

def test_is_registered(self):
env = TestEnvironment.EnvironmentCredentialsRequired()
self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_WITH_CREDENTIALS, True)
self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_NO_CREDENTIALS, False)
self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_PARTIAL_CREDENTIALS, False)
self._test_bool_env_method("_is_registered", env, WITH_CREDENTIALS, True)
self._test_bool_env_method("_is_registered", env, NO_CREDENTIALS, False)
self._test_bool_env_method("_is_registered", env, PARTIAL_CREDENTIALS, False)

env = TestEnvironment.EnvironmentCredentialsNotRequired()
self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_STANDARD_ENV, False)
self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_STANDARD_WITH_CREDENTIALS, False)
self._test_bool_env_method("_is_registered", env, STANDARD_ENV, False)
self._test_bool_env_method("_is_registered", env, STANDARD_WITH_CREDENTIALS, False)

def test_is_credentials_set_up(self):
env = TestEnvironment.EnvironmentCredentialsRequired()
self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_NO_CREDENTIALS, False)
self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_WITH_CREDENTIALS, True)
self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_PARTIAL_CREDENTIALS, False)
self._test_bool_env_method("_is_credentials_set_up", env, NO_CREDENTIALS, False)
self._test_bool_env_method("_is_credentials_set_up", env, WITH_CREDENTIALS, True)
self._test_bool_env_method("_is_credentials_set_up", env, PARTIAL_CREDENTIALS, False)

env = TestEnvironment.EnvironmentCredentialsNotRequired()
self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_STANDARD_ENV, False)
self._test_bool_env_method("_is_credentials_set_up", env, STANDARD_ENV, False)

def _test_bool_env_method(self, method_name: str, env: Environment, config: Dict, expected_result: bool):
env._config = EnvironmentConfig.get_from_json(json.dumps(config))
env._config = EnvironmentConfig(config)
method = getattr(env, method_name)
if expected_result:
self.assertTrue(method())
Expand Down
Loading