forked from p34709852/monkey
Merge branch 'appimage' into develop
This commit is contained in:
commit
e616fcdf50
|
@ -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
|
||||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -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
|
||||||
|
|
|
@ -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.
|
|
@ -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
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"server_config": "password",
|
||||||
|
"deployment": "standard",
|
||||||
|
"data_dir": "~/.monkey_island"
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"]
|
||||||
|
)
|
||||||
|
|
|
@ -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
|
|
@ -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] + ' ...'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
|
|
@ -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
|
|
@ -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))
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"server_config": "password",
|
||||||
|
"deployment": "develop"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"server_config": "password",
|
||||||
|
"deployment": "develop",
|
||||||
|
"user": "test"
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"server_config": "standard",
|
||||||
|
"deployment": "develop"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"server_config": "standard",
|
||||||
|
"deployment": "develop",
|
||||||
|
"user": "test",
|
||||||
|
"password_hash": "abcdef"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"server_config": "password",
|
||||||
|
"deployment": "develop",
|
||||||
|
"user": "test",
|
||||||
|
"password_hash": "abcdef"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"server_config": "password",
|
||||||
|
"deployment": "develop",
|
||||||
|
"user": "test",
|
||||||
|
"password_hash": "abcdef",
|
||||||
|
"data_dir": "/test/data/dir"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"server_config": "password",
|
||||||
|
"deployment": "develop",
|
||||||
|
"user": "test",
|
||||||
|
"password_hash": "abcdef",
|
||||||
|
"data_dir": "~/data_dir"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
+ùÆõ
RO
|
||||||
|
ý)ê<>ž¾T“|ÄRSíÞ&Cá™â
|
Loading…
Reference in New Issue