Merge branch 'appimage' into develop

This commit is contained in:
Mike Salvatore 2021-04-04 21:53:06 -04:00
commit e616fcdf50
41 changed files with 945 additions and 263 deletions

View File

@ -18,7 +18,7 @@ os: linux
before_install: before_install:
# Init server_config.json to default # 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: install:
# Python # Python

View File

@ -6,3 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Added ### Added
- PostgreSQL fingerprinter. #892 - PostgreSQL fingerprinter. #892
- A runtime-configurable option to specify a data directory where runtime
configuration and other artifacts can be stored. #994
- Scripts to build a prototype AppImage for Monkey Island. #1069
### Changed
- server_config.json can be selected at runtime. #963
- Logger configuration can be selected at runtime. #971
- `mongo_key.bin` file location can be selected at runtime. #994
- Monkey agents are stored in the configurable data_dir when monkey is "run
from the island". #997

View File

@ -0,0 +1,36 @@
# Monkey Island AppImage
## About
This directory contains the necessary artifacts for building a prototype
monkey_island AppImage using appimage-builder.
## Building an AppImage
1. Create a clean VM or LXC (not docker!) based on Ubuntu 18.04.
1. Copy the `deployment_scripts/appimage` directory to `$HOME/` in the VM.
1. Run `sudo -v`.
1. On the VM, `cd $HOME/appimage`
1. Execute `./build_appimage.sh`. This will pull all necessary dependencies
and build the AppImage.
NOTE: This script is intended to be run from a clean VM. You can also manually
remove build artifacts by removing the following files and directories.
- $HOME/.monkey_island (optional)
- $HOME/monkey-appdir
- $HOME/git/monkey
- $HOME/appimage/appimage-builder-cache
- $HOME/appimage/"Monkey\ Island-\*-x86-64.Appimage"
After removing the above files and directories, you can again execute `bash
build_appimage.sh`.
## Running the AppImage
The build script will produce an AppImage executible named something like
`Monkey Island-VERSION-x86-64.AppImage`. Simply execute this file and you're
off to the races.
A new directory, `$HOME/.monkey_island` will be created to store runtime
artifacts.

View File

@ -0,0 +1,233 @@
#!/bin/bash
python_cmd="python3.7"
APPDIR="$HOME/monkey-appdir"
INSTALL_DIR="$APPDIR/usr/src"
GIT=$HOME/git
REPO_MONKEY_HOME=$GIT/monkey
REPO_MONKEY_SRC=$REPO_MONKEY_HOME/monkey
ISLAND_PATH="$INSTALL_DIR/monkey_island"
MONGO_PATH="$ISLAND_PATH/bin/mongodb"
ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries"
is_root() {
return "$(id -u)"
}
has_sudo() {
# 0 true, 1 false
sudo -nv > /dev/null 2>&1
return $?
}
handle_error() {
echo "Fix the errors above and rerun the script"
exit 1
}
log_message() {
echo -e "\n\n"
echo -e "DEPLOYMENT SCRIPT: $1"
}
setup_appdir() {
rm -rf "$APPDIR" || true
mkdir -p "$INSTALL_DIR"
}
install_pip_37() {
pip_url=https://bootstrap.pypa.io/get-pip.py
curl $pip_url -o get-pip.py
${python_cmd} get-pip.py
rm get-pip.py
}
install_nodejs() {
NODE_SRC=https://deb.nodesource.com/setup_12.x
log_message "Installing nodejs"
curl -sL $NODE_SRC | sudo -E bash -
sudo apt-get install -y nodejs
}
install_build_prereqs() {
sudo apt update
sudo apt upgrade
# appimage-builder prereqs
sudo apt install -y python3 python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace
#monkey island prereqs
sudo apt install -y curl libcurl4 python3.7 python3.7-dev openssl git build-essential moreutils
install_pip_37
install_nodejs
}
install_appimage_builder() {
sudo pip3 install appimage-builder
install_appimage_tool
}
install_appimage_tool() {
APP_TOOL_BIN=$HOME/bin/appimagetool
APP_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage
mkdir "$HOME"/bin
curl -L -o "$APP_TOOL_BIN" "$APP_TOOL_URL"
chmod u+x "$APP_TOOL_BIN"
PATH=$PATH:$HOME/bin
}
load_monkey_binary_config() {
tmpfile=$(mktemp)
log_message "downloading configuration"
curl -L -s -o "$tmpfile" "$config_url"
log_message "loading configuration"
source "$tmpfile"
}
clone_monkey_repo() {
if [[ ! -d ${GIT} ]]; then
mkdir -p "${GIT}"
fi
log_message "Cloning files from git"
branch=${2:-"develop"}
git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${REPO_MONKEY_HOME}" 2>&1 || handle_error
chmod 774 -R "${MONKEY_HOME}"
}
copy_monkey_island_to_appdir() {
cp "$REPO_MONKEY_SRC"/__init__.py "$INSTALL_DIR"
cp "$REPO_MONKEY_SRC"/monkey_island.py "$INSTALL_DIR"
cp -r "$REPO_MONKEY_SRC"/common "$INSTALL_DIR"
cp -r "$REPO_MONKEY_SRC"/monkey_island "$INSTALL_DIR"
cp ./run_appimage.sh "$INSTALL_DIR"/monkey_island/linux/
cp ./island_logger_config.json "$INSTALL_DIR"/
cp ./server_config.json.standard "$INSTALL_DIR"/monkey_island/cc/
# TODO: This is a workaround that may be able to be removed after PR #848 is
# merged. See monkey_island/cc/environment_singleton.py for more information.
cp ./server_config.json.standard "$INSTALL_DIR"/monkey_island/cc/server_config.json
}
install_monkey_island_python_dependencies() {
log_message "Installing island requirements"
requirements_island="$ISLAND_PATH/requirements.txt"
# TODO: This is an ugly hack. PyInstaller and VirtualEnv are build-time
# dependencies and should not be installed as a runtime requirement.
cat "$requirements_island" | grep -Piv "virtualenv|pyinstaller" | sponge "$requirements_island"
${python_cmd} -m pip install -r "${requirements_island}" --ignore-installed --prefix /usr --root="$APPDIR" || handle_error
}
download_monkey_agent_binaries() {
log_message "Downloading monkey agent binaries to ${ISLAND_BINARIES_PATH}"
mkdir -p "${ISLAND_BINARIES_PATH}" || handle_error
curl -L -o "${ISLAND_BINARIES_PATH}/${LINUX_32_BINARY_NAME}" "${LINUX_32_BINARY_URL}"
curl -L -o "${ISLAND_BINARIES_PATH}/${LINUX_64_BINARY_NAME}" "${LINUX_64_BINARY_URL}"
curl -L -o "${ISLAND_BINARIES_PATH}/${WINDOWS_32_BINARY_NAME}" "${WINDOWS_32_BINARY_URL}"
curl -L -o "${ISLAND_BINARIES_PATH}/${WINDOWS_64_BINARY_NAME}" "${WINDOWS_64_BINARY_URL}"
# Allow them to be executed
chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_32_BINARY_NAME"
chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_64_BINARY_NAME"
}
install_mongodb() {
log_message "Installing MongoDB"
mkdir -p "$MONGO_PATH"
"${ISLAND_PATH}"/linux/install_mongo.sh "${MONGO_PATH}" || handle_error
}
generate_ssl_cert() {
log_message "Generating certificate"
chmod u+x "${ISLAND_PATH}"/linux/create_certificate.sh
"${ISLAND_PATH}"/linux/create_certificate.sh "${ISLAND_PATH}"/cc
}
build_frontend() {
pushd "$ISLAND_PATH/cc/ui" || handle_error
npm install sass-loader node-sass webpack --save-dev
npm update
log_message "Generating front end"
npm run dist
popd || handle_error
}
build_appimage() {
log_message "Building AppImage"
appimage-builder --recipe monkey_island_builder.yml --log DEBUG --skip-appimage
# There is a bug or unwanted behavior in appimage-builder that causes issues
# if 32-bit binaries are present in the appimage. To work around this, we:
# 1. Build the AppDir with appimage-builder and skip building the appimage
# 2. Add the 32-bit binaries to the AppDir
# 3. Build the AppImage with appimage-builder from the already-built AppDir
#
# Note that appimage-builder replaces the interpreter on the monkey agent binaries
# when building the AppDir. This is unwanted as the monkey agents may execute in
# environments where the AppImage isn't loaded.
#
# See https://github.com/AppImageCrafters/appimage-builder/issues/93 for more info.
download_monkey_agent_binaries
appimage-builder --recipe monkey_island_builder.yml --log DEBUG --skip-build
}
if is_root; then
log_message "Please don't run this script as root"
exit 1
fi
if ! has_sudo; then
log_message "You need root permissions for some of this script operations. \
Run \`sudo -v\`, enter your password, and then re-run this script."
exit 1
fi
config_url="https://raw.githubusercontent.com/mssalvatore/monkey/linux-deploy-binaries/deployment_scripts/config"
setup_appdir
install_build_prereqs
install_appimage_builder
load_monkey_binary_config
clone_monkey_repo "$@"
copy_monkey_island_to_appdir
# Create folders
log_message "Creating island dirs under $ISLAND_PATH"
mkdir -p "${MONGO_PATH}" || handle_error
install_monkey_island_python_dependencies
install_mongodb
generate_ssl_cert
build_frontend
mkdir -p "$APPDIR"/usr/share/icons
cp "$REPO_MONKEY_SRC"/monkey_island/cc/ui/src/images/monkey-icon.svg "$APPDIR"/usr/share/icons/monkey-icon.svg
build_appimage
log_message "Deployment script finished."
exit 0

View File

@ -0,0 +1,33 @@
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"simple": {
"format": "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)10s() - %(levelname)s - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple",
"stream": "ext://sys.stdout"
},
"info_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "simple",
"filename": "~/.monkey_island/info.log",
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8"
}
},
"root": {
"level": "DEBUG",
"handlers": [
"console",
"info_file_handler"
]
}
}

View File

@ -0,0 +1,40 @@
version: 1
AppDir:
path: '../monkey-appdir'
app_info:
id: org.guardicore.monkey-island
name: Monkey Island
icon: monkey-icon
version: 1.10.0
exec: bin/bash
exec_args: "$APPDIR/usr/src/monkey_island/linux/run_appimage.sh"
apt:
arch: amd64
sources:
- sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic main restricted
key_url: http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3B4FE6ACC0B21F32
- sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic universe
- sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-security main restricted
- sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-security universe
- sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted
- sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-updates universe
include:
- bash
- python3.7
runtime:
env:
PATH: '${APPDIR}/usr/bin:${PATH}'
PYTHONHOME: '${APPDIR}/usr'
PYTHONPATH: '${APPDIR}/usr/lib/python3.7/site-packages'
AppImage:
update-information: None
sign-key: None
arch: x86_64

View File

@ -0,0 +1,41 @@
#!/bin/bash
DOT_MONKEY=$HOME/.monkey_island/
configure_default_logging() {
if [ ! -f $DOT_MONKEY/island_logger_config.json ]; then
cp $APPDIR/usr/src/island_logger_config.json $DOT_MONKEY
fi
}
configure_default_server() {
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
}
# Detecting command that calls python 3.7
python_cmd=""
if [[ $(python --version 2>&1) == *"Python 3.7"* ]]; then
python_cmd="python"
fi
if [[ $(python37 --version 2>&1) == *"Python 3.7"* ]]; then
python_cmd="python37"
fi
if [[ $(python3.7 --version 2>&1) == *"Python 3.7"* ]]; then
python_cmd="python3.7"
fi
mkdir --mode=0700 --parents $DOT_MONKEY
DB_DIR=$DOT_MONKEY/db
mkdir -p $DB_DIR
configure_default_logging
configure_default_server
cd $APPDIR/usr/src
./monkey_island/bin/mongodb/bin/mongod --dbpath $DB_DIR &
${python_cmd} ./monkey_island.py --server-config $DOT_MONKEY/server_config.json --logger-config $DOT_MONKEY/island_logger_config.json

View File

@ -0,0 +1,5 @@
{
"server_config": "password",
"deployment": "standard",
"data_dir": "~/.monkey_island"
}

View File

@ -1,21 +1,25 @@
from gevent import monkey as gevent_monkey from gevent import monkey as gevent_monkey
from monkey_island.cc.arg_parser import parse_cli_args
gevent_monkey.patch_all() gevent_monkey.patch_all()
from monkey_island.cc.main import main import json # noqa: E402
from monkey_island.cc.server_utils.island_logger import json_setup_logging # noqa: E402
def parse_cli_args():
import argparse
parser = argparse.ArgumentParser(description="Infection Monkey Island CnC Server. See https://infectionmonkey.com")
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.")
args = parser.parse_args()
return args.setup_only
if "__main__" == __name__: if "__main__" == __name__:
is_setup_only = parse_cli_args() island_args = parse_cli_args()
main(is_setup_only)
# This is here in order to catch EVERYTHING, some functions are being called on
# imports, so the log init needs to be first.
try:
json_setup_logging(island_args.logger_config)
except json.JSONDecodeError as ex:
print(f"Error loading logging config: {ex}")
exit(1)
from monkey_island.cc.main import main # noqa: E402
main(island_args.setup_only, island_args.server_config)

View File

@ -0,0 +1,42 @@
from dataclasses import dataclass
from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH, DEFAULT_LOGGER_CONFIG_PATH
@dataclass
class IslandArgs:
setup_only: bool
server_config: str
logger_config: str
def parse_cli_args() -> IslandArgs:
import argparse
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(
"--server-config",
action="store",
help="The path to the server configuration file.",
default=DEFAULT_SERVER_CONFIG_PATH,
)
parser.add_argument(
"--logger-config",
action="store",
help="The path to the logging configuration file.",
default=DEFAULT_LOGGER_CONFIG_PATH,
)
args = parser.parse_args()
return IslandArgs(args.setup_only, args.server_config, args.logger_config)

View File

@ -6,62 +6,66 @@ from pathlib import Path
from typing import Dict, List from typing import Dict, List
import monkey_island.cc.environment.server_config_generator as server_config_generator import monkey_island.cc.environment.server_config_generator as server_config_generator
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR
from monkey_island.cc.environment.user_creds import UserCreds 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.auth_user import User
from monkey_island.cc.resources.auth.user_store import UserStore from monkey_island.cc.resources.auth.user_store import UserStore
SERVER_CONFIG_FILENAME = "server_config.json"
class EnvironmentConfig: class EnvironmentConfig:
def __init__(self, def __init__(self, file_path):
server_config: str, self._server_config_path = os.path.expanduser(file_path)
deployment: str, self.server_config = None
user_creds: UserCreds, self.deployment = None
aws=None): self.user_creds = None
self.server_config = server_config self.aws = None
self.deployment = deployment self.data_dir = None
self.user_creds = user_creds
self.aws = aws
@staticmethod self._load_from_file(self._server_config_path)
def get_from_json(config_json: str) -> EnvironmentConfig:
data = json.loads(config_json)
return EnvironmentConfig.get_from_dict(data)
@staticmethod def _load_from_file(self, file_path):
def get_from_dict(dict_data: Dict) -> EnvironmentConfig: file_path = os.path.expanduser(file_path)
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)
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))
@staticmethod
def get_from_file() -> EnvironmentConfig:
file_path = EnvironmentConfig.get_config_file_path()
if not Path(file_path).is_file(): if not Path(file_path).is_file():
server_config_generator.create_default_config_file(file_path) server_config_generator.create_default_config_file(file_path)
with open(file_path, 'r') as f: with open(file_path, "r") as f:
config_content = f.read() config_content = f.read()
return EnvironmentConfig.get_from_json(config_content)
@staticmethod self._load_from_json(config_content)
def get_config_file_path() -> str:
return os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', SERVER_CONFIG_FILENAME) 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
data_dir = (
dict_data["data_dir"] if "data_dir" in dict_data else DEFAULT_DATA_DIR
)
self.server_config = dict_data["server_config"]
self.deployment = dict_data["deployment"]
self.user_creds = user_creds
self.aws = aws
self.data_dir = data_dir
@property
def data_dir_abs_path(self):
return os.path.abspath(os.path.expanduser(os.path.expandvars(self.data_dir)))
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: def to_dict(self) -> Dict:
config_dict = {'server_config': self.server_config, config_dict = {
'deployment': self.deployment} "server_config": self.server_config,
"deployment": self.deployment,
"data_dir": self.data_dir,
}
if self.aws: if self.aws:
config_dict.update({'aws': self.aws}) config_dict.update({"aws": self.aws})
config_dict.update(self.user_creds.to_dict()) config_dict.update(self.user_creds.to_dict())
return config_dict return config_dict

View File

@ -1,7 +1,9 @@
import logging import logging
import monkey_island.cc.resources.auth.user_store as user_store 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.environment import (EnvironmentConfig, aws, password,
standard, testing)
from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
@ -36,8 +38,10 @@ def set_to_standard():
user_store.UserStore.set_users(env.get_auth_users()) user_store.UserStore.set_users(env.get_auth_users())
def initialize_from_file(file_path):
try: try:
config = EnvironmentConfig.get_from_file() config = EnvironmentConfig(file_path)
__env_type = config.server_config __env_type = config.server_config
set_env(__env_type, config) set_env(__env_type, config)
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
@ -45,3 +49,8 @@ try:
except Exception: except Exception:
logger.error('Failed initializing environment', exc_info=True) logger.error('Failed initializing environment', exc_info=True)
raise 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)

View File

@ -1,7 +1,8 @@
from pathlib import Path from pathlib import Path
from monkey_island.cc.server_utils.consts import DEFAULT_DEVELOP_SERVER_CONFIG_PATH
def create_default_config_file(path): def create_default_config_file(path):
default_config_path = f"{path}.default" default_config = Path(DEFAULT_DEVELOP_SERVER_CONFIG_PATH).read_text()
default_config = Path(default_config_path).read_text()
Path(path).write_text(default_config) Path(path).write_text(default_config)

View File

@ -14,7 +14,7 @@ def add_monkey_dir_to_sys_path():
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.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 isort:skip
SERVER_CONFIG = "server_config" SERVER_CONFIG = "server_config"
BACKUP_CONFIG_FILENAME = "./server_config.backup" BACKUP_CONFIG_FILENAME = "./server_config.backup"
@ -26,7 +26,7 @@ logger.setLevel(logging.DEBUG)
def main(): def main():
args = parse_args() args = parse_args()
file_path = EnvironmentConfig.get_config_file_path() file_path = DEFAULT_SERVER_CONFIG_PATH
if args.server_config == "restore": if args.server_config == "restore":
restore_previous_config(file_path) restore_previous_config(file_path)

View File

@ -1,13 +1,42 @@
import json
import os import os
import tempfile
from typing import Dict from typing import Dict
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import monkey_island.cc.test_common.environment.server_config_mocks as config_mocks from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
from common.utils.exceptions import (AlreadyRegisteredError, CredentialsNotRequiredError, from common.utils.exceptions import (AlreadyRegisteredError,
InvalidRegistrationCredentialsError, RegistrationNotNeededError) CredentialsNotRequiredError,
from monkey_island.cc.environment import Environment, EnvironmentConfig, UserCreds 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(): def get_server_config_file_path_test_version():
@ -18,7 +47,7 @@ class TestEnvironment(TestCase):
class EnvironmentCredentialsNotRequired(Environment): class EnvironmentCredentialsNotRequired(Environment):
def __init__(self): def __init__(self):
config = EnvironmentConfig('test', 'test', UserCreds()) config = StubEnvironmentConfig('test', 'test', UserCreds())
super().__init__(config) super().__init__(config)
_credentials_required = False _credentials_required = False
@ -28,7 +57,7 @@ class TestEnvironment(TestCase):
class EnvironmentCredentialsRequired(Environment): class EnvironmentCredentialsRequired(Environment):
def __init__(self): def __init__(self):
config = EnvironmentConfig('test', 'test', UserCreds()) config = StubEnvironmentConfig('test', 'test', UserCreds())
super().__init__(config) super().__init__(config)
_credentials_required = True _credentials_required = True
@ -38,7 +67,7 @@ class TestEnvironment(TestCase):
class EnvironmentAlreadyRegistered(Environment): class EnvironmentAlreadyRegistered(Environment):
def __init__(self): def __init__(self):
config = EnvironmentConfig('test', 'test', UserCreds('test_user', 'test_secret')) config = StubEnvironmentConfig('test', 'test', UserCreds('test_user', 'test_secret'))
super().__init__(config) super().__init__(config)
_credentials_required = True _credentials_required = True
@ -75,35 +104,35 @@ class TestEnvironment(TestCase):
def test_needs_registration(self): def test_needs_registration(self):
env = TestEnvironment.EnvironmentCredentialsRequired() 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, 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, 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, PARTIAL_CREDENTIALS, True)
env = TestEnvironment.EnvironmentCredentialsNotRequired() 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, 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_WITH_CREDENTIALS, False)
def test_is_registered(self): def test_is_registered(self):
env = TestEnvironment.EnvironmentCredentialsRequired() 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, 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, 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, PARTIAL_CREDENTIALS, False)
env = TestEnvironment.EnvironmentCredentialsNotRequired() 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, 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_WITH_CREDENTIALS, False)
def test_is_credentials_set_up(self): def test_is_credentials_set_up(self):
env = TestEnvironment.EnvironmentCredentialsRequired() 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, 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, 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, PARTIAL_CREDENTIALS, False)
env = TestEnvironment.EnvironmentCredentialsNotRequired() 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): 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) method = getattr(env, method_name)
if expected_result: if expected_result:
self.assertTrue(method()) self.assertTrue(method())

View File

@ -1,99 +1,138 @@
import json import json
import os import os
import platform import shutil
from typing import Dict
from unittest import TestCase
from unittest.mock import MagicMock, patch
import monkey_island.cc.test_common.environment.server_config_mocks as config_mocks import pytest
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.environment.environment_config import EnvironmentConfig from monkey_island.cc.environment.environment_config import EnvironmentConfig
from monkey_island.cc.environment.user_creds import UserCreds from monkey_island.cc.environment.user_creds import UserCreds
def get_server_config_file_path_test_version(): TEST_RESOURCES_DIR = os.path.join(
return os.path.join(os.getcwd(), 'test_config.json') 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"
)
WITH_DATA_DIR = os.path.join(TEST_RESOURCES_DIR, "server_config_with_data_dir.json")
WITH_DATA_DIR_HOME = os.path.join(TEST_RESOURCES_DIR, "server_config_with_data_dir_home.json")
class TestEnvironmentConfig(TestCase): @pytest.fixture
def config_file(tmpdir):
return os.path.join(tmpdir, "test_config.json")
def test_get_from_json(self):
self._test_get_from_json(config_mocks.CONFIG_WITH_CREDENTIALS)
self._test_get_from_json(config_mocks.CONFIG_NO_CREDENTIALS)
self._test_get_from_json(config_mocks.CONFIG_PARTIAL_CREDENTIALS)
def _test_get_from_json(self, config: Dict): def test_get_with_credentials():
config_json = json.dumps(config) config_dict = EnvironmentConfig(WITH_CREDENTIALS).to_dict()
env_config_object = EnvironmentConfig.get_from_json(config_json)
self.assertEqual(config['server_config'], env_config_object.server_config)
self.assertEqual(config['deployment'], env_config_object.deployment)
if 'user' in config:
self.assertEqual(config['user'], env_config_object.user_creds.username)
if 'password_hash' in config:
self.assertEqual(config['password_hash'], env_config_object.user_creds.password_hash)
if 'aws' in config:
self.assertEqual(config['aws'], env_config_object.aws)
def test_save_to_file(self): assert len(config_dict.keys()) == 5
self._test_save_to_file(config_mocks.CONFIG_WITH_CREDENTIALS) assert config_dict["server_config"] == "password"
self._test_save_to_file(config_mocks.CONFIG_NO_CREDENTIALS) assert config_dict["deployment"] == "develop"
self._test_save_to_file(config_mocks.CONFIG_PARTIAL_CREDENTIALS) assert config_dict["user"] == "test"
assert config_dict["password_hash"] == "abcdef"
assert config_dict["data_dir"] == DEFAULT_DATA_DIR
@patch.object(target=EnvironmentConfig, attribute="get_config_file_path",
new=MagicMock(return_value=get_server_config_file_path_test_version()))
def _test_save_to_file(self, config: Dict):
user_creds = UserCreds.get_from_dict(config)
env_config = EnvironmentConfig(server_config=config['server_config'],
deployment=config['deployment'],
user_creds=user_creds)
env_config.save_to_file() def test_get_with_no_credentials():
file_path = get_server_config_file_path_test_version() config_dict = EnvironmentConfig(NO_CREDENTIALS).to_dict()
with open(file_path, 'r') as f:
content_from_file = f.read()
os.remove(file_path)
self.assertDictEqual(config, json.loads(content_from_file)) assert len(config_dict.keys()) == 3
assert config_dict["server_config"] == "password"
assert config_dict["deployment"] == "develop"
assert config_dict["data_dir"] == DEFAULT_DATA_DIR
def test_get_server_config_file_path(self):
if platform.system() == "Windows":
server_file_path = MONKEY_ISLAND_ABS_PATH + r"\cc\server_config.json"
else:
server_file_path = MONKEY_ISLAND_ABS_PATH + "/cc/server_config.json"
self.assertEqual(EnvironmentConfig.get_config_file_path(), server_file_path)
def test_get_from_dict(self): def test_get_with_partial_credentials():
config_dict = config_mocks.CONFIG_WITH_CREDENTIALS config_dict = EnvironmentConfig(PARTIAL_CREDENTIALS).to_dict()
env_conf = EnvironmentConfig.get_from_dict(config_dict)
self.assertEqual(env_conf.server_config, config_dict['server_config'])
self.assertEqual(env_conf.deployment, config_dict['deployment'])
self.assertEqual(env_conf.user_creds.username, config_dict['user'])
self.assertEqual(env_conf.aws, None)
config_dict = config_mocks.CONFIG_BOGUS_VALUES assert len(config_dict.keys()) == 4
env_conf = EnvironmentConfig.get_from_dict(config_dict) assert config_dict["server_config"] == "password"
self.assertEqual(env_conf.server_config, config_dict['server_config']) assert config_dict["deployment"] == "develop"
self.assertEqual(env_conf.deployment, config_dict['deployment']) assert config_dict["user"] == "test"
self.assertEqual(env_conf.user_creds.username, config_dict['user']) assert config_dict["data_dir"] == DEFAULT_DATA_DIR
self.assertEqual(env_conf.aws, config_dict['aws'])
def test_to_dict(self):
conf_json1 = json.dumps(config_mocks.CONFIG_WITH_CREDENTIALS)
self._test_to_dict(EnvironmentConfig.get_from_json(conf_json1))
conf_json2 = json.dumps(config_mocks.CONFIG_NO_CREDENTIALS) def test_save_to_file(config_file):
self._test_to_dict(EnvironmentConfig.get_from_json(conf_json2)) shutil.copyfile(STANDARD_WITH_CREDENTIALS, config_file)
conf_json3 = json.dumps(config_mocks.CONFIG_PARTIAL_CREDENTIALS) environment_config = EnvironmentConfig(config_file)
self._test_to_dict(EnvironmentConfig.get_from_json(conf_json3)) environment_config.aws = "test_aws"
environment_config.save_to_file()
def _test_to_dict(self, env_config_object: EnvironmentConfig): with open(config_file, "r") as f:
test_dict = {'server_config': env_config_object.server_config, from_file = json.load(f)
'deployment': env_config_object.deployment}
user_creds = env_config_object.user_creds
if user_creds.username:
test_dict.update({'user': user_creds.username})
if user_creds.password_hash:
test_dict.update({'password_hash': user_creds.password_hash})
self.assertDictEqual(test_dict, env_config_object.to_dict()) assert len(from_file.keys()) == 6
assert from_file["server_config"] == "standard"
assert from_file["deployment"] == "develop"
assert from_file["user"] == "test"
assert from_file["password_hash"] == "abcdef"
assert from_file["aws"] == "test_aws"
assert from_file["data_dir"] == DEFAULT_DATA_DIR
def test_add_user(config_file):
new_user = "new_user"
new_password_hash = "fedcba"
new_user_creds = UserCreds(new_user, new_password_hash)
shutil.copyfile(STANDARD_WITH_CREDENTIALS, config_file)
environment_config = EnvironmentConfig(config_file)
environment_config.add_user(new_user_creds)
with open(config_file, "r") as f:
from_file = json.load(f)
assert len(from_file.keys()) == 5
assert from_file["user"] == new_user
assert from_file["password_hash"] == new_password_hash
def test_get_users():
environment_config = EnvironmentConfig(STANDARD_WITH_CREDENTIALS)
users = environment_config.get_users()
assert len(users) == 1
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
assert environment_config.data_dir == DEFAULT_DATA_DIR
def test_data_dir():
environment_config = EnvironmentConfig(WITH_DATA_DIR)
assert environment_config.data_dir == "/test/data/dir"
def set_home_env(monkeypatch, tmpdir):
monkeypatch.setenv("HOME", str(tmpdir))
def test_data_dir_abs_path_from_file(monkeypatch, tmpdir):
set_home_env(monkeypatch, tmpdir)
config = EnvironmentConfig(WITH_DATA_DIR_HOME)
assert config.data_dir_abs_path == os.path.join(tmpdir, "data_dir")

View File

@ -13,19 +13,17 @@ if str(MONKEY_ISLAND_DIR_BASE_PATH) not in sys.path:
sys.path.insert(0, MONKEY_ISLAND_DIR_BASE_PATH) sys.path.insert(0, MONKEY_ISLAND_DIR_BASE_PATH)
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH # noqa: E402 from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH # noqa: E402
from monkey_island.cc.server_utils.island_logger import json_setup_logging # noqa: E402
# This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top.
json_setup_logging(default_path=Path(MONKEY_ISLAND_ABS_PATH, 'cc', 'island_logger_default_config.json'),
default_level=logging.DEBUG)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402
from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH # noqa: E402
from common.version import get_version # noqa: E402 from common.version import get_version # noqa: E402
from monkey_island.cc.app import init_app # noqa: E402 from monkey_island.cc.app import init_app # noqa: E402
from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402
from monkey_island.cc.database import get_db_version # noqa: E402 from monkey_island.cc.database import get_db_version # noqa: E402
from monkey_island.cc.database import is_db_server_up # noqa: E402 from monkey_island.cc.database import is_db_server_up # noqa: E402
from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402
from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402
from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402
from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402
@ -34,8 +32,11 @@ from monkey_island.cc.setup import setup # noqa: E402
MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0"
def main(should_setup_only=False): def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_PATH):
logger.info("Starting bootloader server") logger.info("Starting bootloader server")
env_singleton.initialize_from_file(server_config_filename)
initialize_encryptor(env_singleton.env.get_config().data_dir_abs_path)
mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url()) mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url())
bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True) bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True)

View File

@ -32,7 +32,7 @@ def run_local_monkey():
return False, "OS Type not found" return False, "OS Type not found"
monkey_path = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'binaries', result['filename']) monkey_path = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'binaries', result['filename'])
target_path = os.path.join(MONKEY_ISLAND_ABS_PATH, result['filename']) target_path = os.path.join(env_singleton.env.get_config().data_dir_abs_path, result['filename'])
# copy the executable to temp path (don't run the monkey from its current location as it may delete itself) # copy the executable to temp path (don't run the monkey from its current location as it may delete itself)
try: try:

View File

@ -1,6 +1,20 @@
import os 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_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"
)
DEFAULT_LOGGER_CONFIG_PATH = os.path.join(
MONKEY_ISLAND_ABS_PATH, "cc", "island_logger_default_config.json"
)
DEFAULT_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc")

View File

@ -6,36 +6,36 @@ import os
from Crypto import Random # noqa: DUO133 # nosec: B413 from Crypto import Random # noqa: DUO133 # nosec: B413
from Crypto.Cipher import AES # noqa: DUO133 # nosec: B413 from Crypto.Cipher import AES # noqa: DUO133 # nosec: B413
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
__author__ = "itay.mizeretz" __author__ = "itay.mizeretz"
_encryptor = None
class Encryptor: class Encryptor:
_BLOCK_SIZE = 32 _BLOCK_SIZE = 32
_DB_PASSWORD_FILENAME = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc/mongo_key.bin') _PASSWORD_FILENAME = "mongo_key.bin"
def __init__(self): def __init__(self, password_file_dir):
self._load_key() password_file = os.path.join(password_file_dir, self._PASSWORD_FILENAME)
def _init_key(self): if os.path.exists(password_file):
self._load_existing_key(password_file)
else:
self._init_key(password_file)
def _init_key(self, password_file):
self._cipher_key = Random.new().read(self._BLOCK_SIZE) self._cipher_key = Random.new().read(self._BLOCK_SIZE)
with open(self._DB_PASSWORD_FILENAME, 'wb') as f: with open(password_file, "wb") as f:
f.write(self._cipher_key) f.write(self._cipher_key)
def _load_existing_key(self): def _load_existing_key(self, password_file):
with open(self._DB_PASSWORD_FILENAME, 'rb') as f: with open(password_file, "rb") as f:
self._cipher_key = f.read() self._cipher_key = f.read()
def _load_key(self):
if os.path.exists(self._DB_PASSWORD_FILENAME):
self._load_existing_key()
else:
self._init_key()
def _pad(self, message): def _pad(self, message):
return message + (self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) * chr( return message + (self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) * chr(
self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)
)
def _unpad(self, message: str): def _unpad(self, message: str):
return message[0:-ord(message[len(message) - 1])] return message[0:-ord(message[len(message) - 1])]
@ -43,7 +43,9 @@ class Encryptor:
def enc(self, message: str): def enc(self, message: str):
cipher_iv = Random.new().read(AES.block_size) cipher_iv = Random.new().read(AES.block_size)
cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv)
return base64.b64encode(cipher_iv + cipher.encrypt(self._pad(message).encode())).decode() return base64.b64encode(
cipher_iv + cipher.encrypt(self._pad(message).encode())
).decode()
def dec(self, enc_message): def dec(self, enc_message):
enc_message = base64.b64decode(enc_message) enc_message = base64.b64decode(enc_message)
@ -52,4 +54,11 @@ class Encryptor:
return self._unpad(cipher.decrypt(enc_message[AES.block_size:]).decode()) return self._unpad(cipher.decrypt(enc_message[AES.block_size:]).decode())
encryptor = Encryptor() def initialize_encryptor(password_file_dir):
global _encryptor
_encryptor = Encryptor(password_file_dir)
def get_encryptor():
return _encryptor

View File

@ -1,11 +1,18 @@
import json import json
import logging.config import logging.config
import os import os
from typing import Dict
__author__ = 'Maor.Rayzin' from monkey_island.cc.server_utils.consts import DEFAULT_LOGGER_CONFIG_PATH
__author__ = "Maor.Rayzin"
def json_setup_logging(default_path='logging.json', default_level=logging.INFO, env_key='LOG_CFG'): def json_setup_logging(
default_path=DEFAULT_LOGGER_CONFIG_PATH,
default_level=logging.INFO,
env_key="LOG_CFG",
):
""" """
Setup the logging configuration Setup the logging configuration
:param default_path: the default log configuration file path :param default_path: the default log configuration file path
@ -13,13 +20,26 @@ def json_setup_logging(default_path='logging.json', default_level=logging.INFO,
:param env_key: SYS ENV key to use for external configuration file path :param env_key: SYS ENV key to use for external configuration file path
:return: :return:
""" """
path = default_path path = os.path.expanduser(default_path)
value = os.getenv(env_key, None) value = os.getenv(env_key, None)
if value: if value:
path = value path = value
if os.path.exists(path): if os.path.exists(path):
with open(path, 'rt') as f: with open(path, "rt") as f:
config = json.load(f) config = json.load(f)
_expanduser_log_file_paths(config)
logging.config.dictConfig(config) logging.config.dictConfig(config)
else: else:
logging.basicConfig(level=default_level) logging.basicConfig(level=default_level)
def _expanduser_log_file_paths(config: Dict):
handlers = config.get("handlers", {})
for handler_settings in handlers.values():
if "filename" in handler_settings:
handler_settings["filename"] = os.path.expanduser(
handler_settings["filename"]
)

View File

@ -0,0 +1,32 @@
import logging
import os
import pytest
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.server_utils.island_logger import json_setup_logging
TEST_LOGGER_CONFIG_PATH = os.path.join(
MONKEY_ISLAND_ABS_PATH, "cc", "testing", "logger_config.json"
)
# TODO move into monkey/monkey_island/cc/test_common/fixtures after rebase/backmerge
@pytest.fixture
def mock_home_env(monkeypatch, tmpdir):
monkeypatch.setenv("HOME", str(tmpdir))
def test_expanduser_filename(mock_home_env, tmpdir):
INFO_LOG = os.path.join(tmpdir, "info.log")
TEST_STRING = "Hello, Monkey!"
json_setup_logging(TEST_LOGGER_CONFIG_PATH)
logger = logging.getLogger("TestLogger")
logger.info(TEST_STRING)
assert os.path.isfile(INFO_LOG)
with open(INFO_LOG, "r") as f:
line = f.readline()
assert TEST_STRING in line

View File

@ -1,4 +1,4 @@
from monkey_island.cc.server_utils.encryptor import encryptor from monkey_island.cc.server_utils.encryptor import get_encryptor
def parse_creds(attempt): def parse_creds(attempt):
@ -29,7 +29,7 @@ def censor_password(password, plain_chars=3, secret_chars=5):
""" """
if not password: if not password:
return "" return ""
password = encryptor.dec(password) password = get_encryptor().dec(password)
return password[0:plain_chars] + '*' * secret_chars return password[0:plain_chars] + '*' * secret_chars
@ -42,5 +42,5 @@ def censor_hash(hash_, plain_chars=5):
""" """
if not hash_: if not hash_:
return "" return ""
hash_ = encryptor.dec(hash_) hash_ = get_encryptor().dec(hash_)
return hash_[0: plain_chars] + ' ...' return hash_[0: plain_chars] + ' ...'

View File

@ -8,7 +8,7 @@ from jsonschema import Draft4Validator, validators
import monkey_island.cc.environment.environment_singleton as env_singleton import monkey_island.cc.environment.environment_singleton as env_singleton
import monkey_island.cc.services.post_breach_files import monkey_island.cc.services.post_breach_files
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
from monkey_island.cc.server_utils.encryptor import encryptor from monkey_island.cc.server_utils.encryptor import get_encryptor
from monkey_island.cc.services.utils.network_utils import local_ip_addresses from monkey_island.cc.services.utils.network_utils import local_ip_addresses
from monkey_island.cc.services.config_schema.config_schema import SCHEMA from monkey_island.cc.services.config_schema.config_schema import SCHEMA
@ -75,9 +75,9 @@ class ConfigService:
if should_decrypt: if should_decrypt:
if config_key_as_arr in ENCRYPTED_CONFIG_VALUES: if config_key_as_arr in ENCRYPTED_CONFIG_VALUES:
if isinstance(config, str): if isinstance(config, str):
config = encryptor.dec(config) config = get_encryptor().dec(config)
elif isinstance(config, list): elif isinstance(config, list):
config = [encryptor.dec(x) for x in config] config = [get_encryptor().dec(x) for x in config]
return config return config
@staticmethod @staticmethod
@ -112,7 +112,7 @@ class ConfigService:
if item_value in items_from_config: if item_value in items_from_config:
return return
if should_encrypt: if should_encrypt:
item_value = encryptor.enc(item_value) item_value = get_encryptor().enc(item_value)
mongo.db.config.update( mongo.db.config.update(
{'name': 'newconfig'}, {'name': 'newconfig'},
{'$addToSet': {item_key: item_value}}, {'$addToSet': {item_key: item_value}},
@ -297,9 +297,9 @@ class ConfigService:
if flat_config[key] and isinstance(flat_config[key][0], dict) and 'public_key' in flat_config[key][0]: if flat_config[key] and isinstance(flat_config[key][0], dict) and 'public_key' in flat_config[key][0]:
flat_config[key] = [ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key]] flat_config[key] = [ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key]]
else: else:
flat_config[key] = [encryptor.dec(item) for item in flat_config[key]] flat_config[key] = [get_encryptor().dec(item) for item in flat_config[key]]
else: else:
flat_config[key] = encryptor.dec(flat_config[key]) flat_config[key] = get_encryptor().dec(flat_config[key])
return flat_config return flat_config
@staticmethod @staticmethod
@ -320,19 +320,19 @@ class ConfigService:
config_arr[i] = ConfigService.decrypt_ssh_key_pair(config_arr[i]) if is_decrypt else \ config_arr[i] = ConfigService.decrypt_ssh_key_pair(config_arr[i]) if is_decrypt else \
ConfigService.decrypt_ssh_key_pair(config_arr[i], True) ConfigService.decrypt_ssh_key_pair(config_arr[i], True)
else: else:
config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i]) config_arr[i] = get_encryptor().dec(config_arr[i]) if is_decrypt else get_encryptor().enc(config_arr[i])
else: else:
parent_config_arr[config_arr_as_array[-1]] = \ parent_config_arr[config_arr_as_array[-1]] = \
encryptor.dec(config_arr) if is_decrypt else encryptor.enc(config_arr) get_encryptor().dec(config_arr) if is_decrypt else get_encryptor().enc(config_arr)
@staticmethod @staticmethod
def decrypt_ssh_key_pair(pair, encrypt=False): def decrypt_ssh_key_pair(pair, encrypt=False):
if encrypt: if encrypt:
pair['public_key'] = encryptor.enc(pair['public_key']) pair['public_key'] = get_encryptor().enc(pair['public_key'])
pair['private_key'] = encryptor.enc(pair['private_key']) pair['private_key'] = get_encryptor().enc(pair['private_key'])
else: else:
pair['public_key'] = encryptor.dec(pair['public_key']) pair['public_key'] = get_encryptor().dec(pair['public_key'])
pair['private_key'] = encryptor.dec(pair['private_key']) pair['private_key'] = get_encryptor().dec(pair['private_key'])
return pair return pair
@staticmethod @staticmethod

View File

@ -2,7 +2,7 @@ import copy
import dateutil import dateutil
from monkey_island.cc.server_utils.encryptor import encryptor from monkey_island.cc.server_utils.encryptor import get_encryptor
from monkey_island.cc.models import Monkey from monkey_island.cc.models import Monkey
from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.edge.displayed_edge import EdgeService from monkey_island.cc.services.edge.displayed_edge import EdgeService
@ -66,4 +66,4 @@ def encrypt_exploit_creds(telemetry_json):
for field in ['password', 'lm_hash', 'ntlm_hash']: for field in ['password', 'lm_hash', 'ntlm_hash']:
credential = attempts[i][field] credential = attempts[i][field]
if len(credential) > 0: if len(credential) > 0:
attempts[i][field] = encryptor.enc(credential) attempts[i][field] = get_encryptor().enc(credential)

View File

@ -1,6 +1,6 @@
import logging import logging
from monkey_island.cc.server_utils.encryptor import encryptor from monkey_island.cc.server_utils.encryptor import get_encryptor
from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.node import NodeService
from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \ from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \
@ -63,7 +63,7 @@ def encrypt_system_info_ssh_keys(ssh_info):
for idx, user in enumerate(ssh_info): for idx, user in enumerate(ssh_info):
for field in ['public_key', 'private_key', 'known_hosts']: for field in ['public_key', 'private_key', 'known_hosts']:
if ssh_info[idx][field]: if ssh_info[idx][field]:
ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field]) ssh_info[idx][field] = get_encryptor().enc(ssh_info[idx][field])
def process_credential_info(telemetry_json): def process_credential_info(telemetry_json):

View File

@ -4,7 +4,7 @@ from ScoutSuite.providers.base.authentication_strategy import AuthenticationExce
from common.cloud.scoutsuite_consts import CloudProviders from common.cloud.scoutsuite_consts import CloudProviders
from common.utils.exceptions import InvalidAWSKeys from common.utils.exceptions import InvalidAWSKeys
from monkey_island.cc.server_utils.encryptor import encryptor from monkey_island.cc.server_utils.encryptor import get_encryptor
from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.config import ConfigService
from common.config_value_paths import AWS_KEYS_PATH from common.config_value_paths import AWS_KEYS_PATH
@ -37,7 +37,7 @@ def set_aws_keys(access_key_id: str, secret_access_key: str, session_token: str)
def _set_aws_key(key_type: str, key_value: str): def _set_aws_key(key_type: str, key_value: str):
path_to_keys = AWS_KEYS_PATH path_to_keys = AWS_KEYS_PATH
encrypted_key = encryptor.enc(key_value) encrypted_key = get_encryptor().enc(key_value)
ConfigService.set_config_value(path_to_keys + [key_type], encrypted_key) ConfigService.set_config_value(path_to_keys + [key_type], encrypted_key)

View File

@ -4,7 +4,7 @@ import pytest
import dpath.util import dpath.util
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
from monkey_island.cc.server_utils import encryptor from monkey_island.cc.server_utils.encryptor import initialize_encryptor, get_encryptor
from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.config import ConfigService
from common.config_value_paths import AWS_KEYS_PATH from common.config_value_paths import AWS_KEYS_PATH
from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import is_aws_keys_setup from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import is_aws_keys_setup
@ -16,7 +16,7 @@ class MockObject:
@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE)
def test_is_aws_keys_setup(): def test_is_aws_keys_setup(tmp_path):
# Mock default configuration # Mock default configuration
ConfigService.init_default_config() ConfigService.init_default_config()
mongo.db = MockObject() mongo.db = MockObject()
@ -26,7 +26,8 @@ def test_is_aws_keys_setup():
assert not is_aws_keys_setup() assert not is_aws_keys_setup()
# Make sure noone changed config path and broke this function # Make sure noone changed config path and broke this function
bogus_key_value = encryptor.encryptor.enc('bogus_aws_key') initialize_encryptor(tmp_path)
bogus_key_value = get_encryptor().enc('bogus_aws_key')
dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_secret_access_key'], bogus_key_value) dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_secret_access_key'], bogus_key_value)
dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_access_key_id'], bogus_key_value) dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_access_key_id'], bogus_key_value)

View File

@ -1,41 +0,0 @@
# Username:test Password:test
CONFIG_WITH_CREDENTIALS = {
"server_config": "password",
"deployment": "develop",
"user": "test",
"password_hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a"
"4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14"
}
CONFIG_NO_CREDENTIALS = {
"server_config": "password",
"deployment": "develop"
}
CONFIG_PARTIAL_CREDENTIALS = {
"server_config": "password",
"deployment": "develop",
"user": "test"
}
CONFIG_BOGUS_VALUES = {
"server_config": "password",
"deployment": "develop",
"user": "test",
"aws": "test",
"test": "test",
"test2": "test2"
}
CONFIG_STANDARD_ENV = {
"server_config": "standard",
"deployment": "develop"
}
CONFIG_STANDARD_WITH_CREDENTIALS = {
"server_config": "standard",
"deployment": "develop",
"user": "test",
"password_hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a"
"4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14"
}

View File

@ -0,0 +1,11 @@
import platform
import monkey_island.cc.server_utils.consts as consts
def test_default_server_config_file_path():
if platform.system() == "Windows":
server_file_path = consts.MONKEY_ISLAND_ABS_PATH + r"\cc\server_config.json"
else:
server_file_path = consts.MONKEY_ISLAND_ABS_PATH + "/cc/server_config.json"
assert consts.DEFAULT_SERVER_CONFIG_PATH == server_file_path

View File

@ -0,0 +1,35 @@
import os
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.server_utils.encryptor import initialize_encryptor, get_encryptor
TEST_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing")
PASSWORD_FILENAME = "mongo_key.bin"
PLAINTEXT = "Hello, Monkey!"
CYPHERTEXT = "vKgvD6SjRyIh1dh2AM/rnTa0NI/vjfwnbZLbMocWtE4e42WJmSUz2ordtbQrH1Fq"
def test_aes_cbc_encryption():
initialize_encryptor(TEST_DATA_DIR)
assert get_encryptor().enc(PLAINTEXT) != PLAINTEXT
def test_aes_cbc_decryption():
initialize_encryptor(TEST_DATA_DIR)
assert get_encryptor().dec(CYPHERTEXT) == PLAINTEXT
def test_aes_cbc_enc_dec():
initialize_encryptor(TEST_DATA_DIR)
assert get_encryptor().dec(get_encryptor().enc(PLAINTEXT)) == PLAINTEXT
def test_create_new_password_file(tmpdir):
initialize_encryptor(tmpdir)
assert os.path.isfile(os.path.join(tmpdir, PASSWORD_FILENAME))

View File

@ -0,0 +1,4 @@
{
"server_config": "password",
"deployment": "develop"
}

View File

@ -0,0 +1,5 @@
{
"server_config": "password",
"deployment": "develop",
"user": "test"
}

View File

@ -0,0 +1,4 @@
{
"server_config": "standard",
"deployment": "develop"
}

View File

@ -0,0 +1,6 @@
{
"server_config": "standard",
"deployment": "develop",
"user": "test",
"password_hash": "abcdef"
}

View File

@ -0,0 +1,6 @@
{
"server_config": "password",
"deployment": "develop",
"user": "test",
"password_hash": "abcdef"
}

View File

@ -0,0 +1,7 @@
{
"server_config": "password",
"deployment": "develop",
"user": "test",
"password_hash": "abcdef",
"data_dir": "/test/data/dir"
}

View File

@ -0,0 +1,7 @@
{
"server_config": "password",
"deployment": "develop",
"user": "test",
"password_hash": "abcdef",
"data_dir": "~/data_dir"
}

View File

@ -0,0 +1,33 @@
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"simple": {
"format": "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)10s() - %(levelname)s - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple",
"stream": "ext://sys.stdout"
},
"info_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "simple",
"filename": "~/info.log",
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8"
}
},
"root": {
"level": "DEBUG",
"handlers": [
"console",
"info_file_handler"
]
}
}

View File

@ -0,0 +1,2 @@
+ù­Æõ RO
ý)ê<>ž¾T“|ÄRSíÞ&Cá™â