diff --git a/.travis.yml b/.travis.yml
index 6abeb59b1..4400f7e9e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,6 +15,7 @@ install:
# Python
- pip install -r monkey/monkey_island/requirements.txt # for unit tests
- pip install flake8 pytest dlint # for next stages
+- pip install coverage # for code coverage
- pip install -r monkey/infection_monkey/requirements.txt # for unit tests
before_script:
@@ -23,24 +24,28 @@ before_script:
script:
# Check Python code
-# Check syntax errors and fail the build if any are found.
+## Check syntax errors and fail the build if any are found.
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
-# Warn about linter issues.
-# --exit-zero forces Flake8 to use the exit status code 0 even if there are errors, which means this will NOT fail the build.
-# --count will print the total number of errors.
-# --statistics Count the number of occurrences of each error/warning code and print a report.
-# The output is redirected to a file.
+## Warn about linter issues.
+### --exit-zero forces Flake8 to use the exit status code 0 even if there are errors, which means this will NOT fail the build.
+### --count will print the total number of errors.
+### --statistics Count the number of occurrences of each error/warning code and print a report.
+### The output is redirected to a file.
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics > flake8_warnings.txt
-# Display the linter issues
+## Display the linter issues
- cat flake8_warnings.txt
-# Make sure that we haven't increased the amount of warnings.
+## Make sure that we haven't increased the amount of warnings.
- PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT=190
-- if [ $(tail -n 1 flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi
+- if [ $(tail -n 1 flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi
+## Run unit tests
- cd monkey # This is our source dir
- python -m pytest # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path.
+## Calculate Code Coverage
+- coverage run -m pytest
+
# Check JS code. The npm install must happen AFTER the flake8 because the node_modules folder will cause a lot of errors.
- cd monkey_island/cc/ui
- npm i
@@ -51,6 +56,10 @@ script:
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=37
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT
+after_success:
+ # Upload code coverage results to codecov.io, see https://github.com/codecov/codecov-bash for more information
+ - bash <(curl -s https://codecov.io/bash)
+
notifications:
slack: # Notify to slack
rooms:
diff --git a/README.md b/README.md
index dd1d7982b..83fd878ad 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,9 @@
# Infection Monkey
-[![Build Status](https://travis-ci.com/guardicore/monkey.svg?branch=develop)](https://travis-ci.com/guardicore/monkey)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/guardicore/monkey)](https://github.com/guardicore/monkey/releases)
+
+[![Build Status](https://travis-ci.com/guardicore/monkey.svg?branch=develop)](https://travis-ci.com/guardicore/monkey)
+[![codecov](https://codecov.io/gh/guardicore/monkey/branch/develop/graph/badge.svg)](https://codecov.io/gh/guardicore/monkey)
+
![GitHub stars](https://img.shields.io/github/stars/guardicore/monkey)
![GitHub commit activity](https://img.shields.io/github/commit-activity/m/guardicore/monkey)
@@ -14,14 +17,15 @@ The Infection Monkey is an open source security tool for testing a data center's
-
The Infection Monkey is comprised of two parts:
-* Monkey - A tool which infects other machines and propagates to them
-* Monkey Island - A dedicated server to control and visualize the Infection Monkey's progress inside the data center
-To read more about the Monkey, visit http://infectionmonkey.com
+* **Monkey** - A tool which infects other machines and propagates to them.
+* **Monkey Island** - A dedicated server to control and visualize the Infection Monkey's progress inside the data center.
+
+To read more about the Monkey, visit [infectionmonkey.com](https://infectionmonkey.com).
## Main Features
+
The Infection Monkey uses the following techniques and exploits to propagate to other machines.
* Multiple propagation techniques:
@@ -42,12 +46,10 @@ Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in t
The Infection Monkey supports a variety of platforms, documented [in the wiki](https://github.com/guardicore/monkey/wiki/OS-compatibility).
-
## Building the Monkey from source
To deploy development version of monkey you should refer to readme in the [deployment scripts](deployment_scripts) folder.
If you only want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/Setup#compile-it-yourself)
-and follow the instructions at the readme files under [infection_monkey](infection_monkey) and [monkey_island](monkey_island).
-
+and follow the instructions at the readme files under [infection_monkey](monkey/infection_monkey) and [monkey_island](monkey/monkey_island).
### Build status
| Branch | Status |
@@ -56,13 +58,21 @@ and follow the instructions at the readme files under [infection_monkey](infecti
| Master | [![Build Status](https://travis-ci.com/guardicore/monkey.svg?branch=master)](https://travis-ci.com/guardicore/monkey) |
## Tests
+
### Unit Tests
+
In order to run all of the Unit Tests, run the command `python -m pytest` in the `monkey` directory.
+To get a coverage report, first make sure the `coverage` package is installed using `pip install coverage`. Run the command
+`coverage run -m unittest` in the `monkey` directory and then `coverage html`. The coverage report can be found in
+`htmlcov.index`.
+
### Blackbox tests
+
In order to run the Blackbox tests, refer to `envs/monkey_zoo/blackbox/README.md`.
# License
+
Copyright (c) Guardicore Ltd
See the [LICENSE](LICENSE) file for license rights and limitations (GPLv3).
diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md
index f69a48b77..16b150852 100644
--- a/deployment_scripts/README.md
+++ b/deployment_scripts/README.md
@@ -1,25 +1,55 @@
-# Files used to deploy development version of infection monkey
-## Windows
+# Deployment guide for a development environemnt
-Before running the script you must have git installed.
-Cd to scripts directory and use the scripts.
-First argument is an empty directory (script can create one) and second is branch you want to clone.
-Example usages:
-./run_script.bat (Sets up monkey in current directory under .\infection_monkey)
-./run_script.bat "C:\test" (Sets up monkey in C:\test)
-powershell -ExecutionPolicy ByPass -Command ". .\deploy_windows.ps1; Deploy-Windows -monkey_home C:\test" (Same as above)
-./run_script.bat "" "master"(Sets up master branch instead of develop in current dir)
-Don't forget to add python to PATH or do so while installing it via this script.
+This guide is for you if you wish to develop for Infection Monkey. If you only want to use it, please download the relevant version from [our website](https://infectionmonkey.com).
-## Linux
+## Prerequisites
-Linux deployment script is meant for Ubuntu 16.x machines.
-You must have root permissions, but don't run the script as root.
-Launch deploy_linux.sh from scripts directory.
-First argument should be an absolute path of an empty directory (script will create one if doesn't exist, default is ./infection_monkey).
-Second parameter is the branch you want to clone (develop by default).
-Example usages:
-./deploy_linux.sh (deploys under ./infection_monkey)
-./deploy_linux.sh "/home/test/monkey" (deploys under /home/test/monkey)
-./deploy_linux.sh "" "master" (deploys master branch in script directory)
-./deploy_linux.sh "/home/user/new" "master" (if directory "new" is not found creates it and clones master branch into it)
+Before running the script you must have `git` installed. If you don't have `git` installed, please follow [this guide](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
+
+## Deploy on Windows
+
+Run the following command in powershell:
+
+```powershell
+Invoke-WebRequest https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/deploy_windows.ps1 -OutFile deploy_windows.ps1
+```
+
+This will download our deploy script. It's a good idea to read it quickly before executing it!
+
+After downloading that script, execute it in `powershell`.
+
+The first argument is an empty directory (script can create one). The second argument is which branch you want to clone - by default, the script will check out the `develop` branch. Some example usages:
+
+- `.\deploy_windows.ps1` (Sets up monkey in current directory under .\infection_monkey)
+- `.\deploy_windows.ps1 -monkey_home "C:\test"` (Sets up monkey in C:\test)
+- `.\deploy_windows.ps1 -branch "master"` (Sets up master branch instead of develop in current dir)
+
+You may also pass in an optional `agents=$false` parameter to disable downloading the latest agent binaries.
+
+### Troubleshooting
+
+- If you run into Execution Policy warnings, you can disable them by prefixing the following snippet: `powershell -ExecutionPolicy ByPass -Command "[original command here]"`
+- Don't forget to add python to PATH or do so while installing it via this script.
+
+## Deploy on Linux
+
+Linux deployment script is meant for Ubuntu 16 and Ubuntu 18 machines.
+
+Your user must have root permissions; however, don't run the script as root!
+
+```sh
+wget https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/deploy_linux.sh
+```
+
+This will download our deploy script. It's a good idea to read it quickly before executing it!
+
+Then execute the resulting script with your shell.
+
+After downloading that script, execute it in a shell. The first argument should be an absolute path of an empty directory (the script will create one if doesn't exist, default is ./infection_monkey). The second parameter is the branch you want to clone (develop by default). Some example usages:
+
+- `./deploy_linux.sh` (deploys under ./infection_monkey)
+- `./deploy_linux.sh "/home/test/monkey"` (deploys under /home/test/monkey)
+- `./deploy_linux.sh "" "master"` (deploys master branch in script directory)
+- `./deploy_linux.sh "/home/user/new" "master"` (if directory "new" is not found creates it and clones master branch into it)
+
+You may also pass in an optional third `false` parameter to disable downloading the latest agent binaries.
\ No newline at end of file
diff --git a/deployment_scripts/config b/deployment_scripts/config
index fb7a3d5b6..5607d37fd 100644
--- a/deployment_scripts/config
+++ b/deployment_scripts/config
@@ -5,21 +5,17 @@ MONKEY_FOLDER_NAME="infection_monkey"
MONKEY_GIT_URL="https://github.com/guardicore/monkey"
# Monkey binaries
-LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-32"
+LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/monkey-linux-32"
LINUX_32_BINARY_NAME="monkey-linux-32"
-LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-64"
+LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/monkey-linux-64"
LINUX_64_BINARY_NAME="monkey-linux-64"
-WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-32.exe"
+WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/monkey-windows-32.exe"
WINDOWS_32_BINARY_NAME="monkey-windows-32.exe"
-WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-64.exe"
+WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/monkey-windows-64.exe"
WINDOWS_64_BINARY_NAME="monkey-windows-64.exe"
# Other binaries for monkey
-TRACEROUTE_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/traceroute64"
-TRACEROUTE_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/traceroute32"
-SAMBACRY_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/sc_monkey_runner64.so"
-SAMBACRY_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/sc_monkey_runner32.so"
-
-# Mongo url's
-MONGO_DEBIAN_URL="https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz"
-MONGO_UBUNTU_URL="https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz"
+TRACEROUTE_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/traceroute64"
+TRACEROUTE_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/traceroute32"
+SAMBACRY_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/sc_monkey_runner64.so"
+SAMBACRY_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/sc_monkey_runner32.so"
\ No newline at end of file
diff --git a/deployment_scripts/config.ps1 b/deployment_scripts/config.ps1
index 095f7b899..b18b7c63c 100644
--- a/deployment_scripts/config.ps1
+++ b/deployment_scripts/config.ps1
@@ -2,28 +2,33 @@
$MONKEY_FOLDER_NAME = "infection_monkey"
# Url of public git repository that contains monkey's source code
$MONKEY_GIT_URL = "https://github.com/guardicore/monkey"
+$MONKEY_RELEASES_URL = $MONKEY_GIT_URL + "/releases"
+$MONKEY_LATEST_VERSION = "v1.7.0"
+$MONKEY_DOWNLOAD_URL = $MONKEY_RELEASES_URL + "/download/" + $MONKEY_LATEST_VERSION + "/"
# Link to the latest python download or install it manually
-$PYTHON_URL = "https://www.python.org/ftp/python/3.7.4/python-3.7.4-amd64.exe"
+$PYTHON_URL = "https://www.python.org/ftp/python/3.7.6/python-3.7.6-amd64.exe"
+
# Monkey binaries
-$LINUX_32_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-32"
+$LINUX_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-linux-32"
$LINUX_32_BINARY_PATH = "monkey-linux-32"
-$LINUX_64_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-64"
+$LINUX_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-linux-64"
$LINUX_64_BINARY_PATH = "monkey-linux-64"
-$WINDOWS_32_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-32.exe"
+$WINDOWS_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-windows-32.exe"
$WINDOWS_32_BINARY_PATH = "monkey-windows-32.exe"
-$WINDOWS_64_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-64.exe"
+$WINDOWS_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-windows-64.exe"
$WINDOWS_64_BINARY_PATH = "monkey-windows-64.exe"
-$SAMBA_32_BINARY_URL = "https://github.com/VakarisZ/tempBinaries/raw/master/sc_monkey_runner32.so"
-$SAMBA_32_BINARY_NAME= "sc_monkey_runner32.so"
-$SAMBA_64_BINARY_URL = "https://github.com/VakarisZ/tempBinaries/raw/master/sc_monkey_runner64.so"
+$SAMBA_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "sc_monkey_runner32.so"
+$SAMBA_32_BINARY_NAME = "sc_monkey_runner32.so"
+$SAMBA_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "sc_monkey_runner64.so"
$SAMBA_64_BINARY_NAME = "sc_monkey_runner64.so"
+$TRACEROUTE_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "traceroute64"
+$TRACEROUTE_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "traceroute32"
# Other directories and paths ( most likely you dont need to configure)
-$MONKEY_ISLAND_DIR = "\monkey\monkey_island"
-$MONKEY_DIR = "\monkey\infection_monkey"
+$MONKEY_ISLAND_DIR = Join-Path "\monkey" -ChildPath "monkey_island"
+$MONKEY_DIR = Join-Path "\monkey" -ChildPath "infection_monkey"
$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\bin"
-$PYTHON_DLL = "C:\Windows\System32\python27.dll"
$MK32_DLL = "mk32.zip"
$MK64_DLL = "mk64.zip"
$TEMP_PYTHON_INSTALLER = ".\python.exe"
@@ -31,16 +36,14 @@ $TEMP_MONGODB_ZIP = ".\mongodb.zip"
$TEMP_OPEN_SSL_ZIP = ".\openssl.zip"
$TEMP_CPP_INSTALLER = "cpp.exe"
$TEMP_NPM_INSTALLER = "node.msi"
-$TEMP_PYWIN32_INSTALLER = "pywin32.exe"
$TEMP_UPX_ZIP = "upx.zip"
-$UPX_FOLDER = "upx394w"
+$UPX_FOLDER = "upx-3.96-win64"
# Other url's
-$MONGODB_URL = "https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip"
-$OPEN_SSL_URL = "https://indy.fulgan.com/SSL/Archive/openssl-1.0.2l-i386-win32.zip"
+$MONGODB_URL = "https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2012plus-v4.2-latest.zip"
+$OPEN_SSL_URL = "https://indy.fulgan.com/SSL/openssl-1.0.2u-x64_86-win64.zip"
$CPP_URL = "https://go.microsoft.com/fwlink/?LinkId=746572"
-$NPM_URL = "https://nodejs.org/dist/v10.13.0/node-v10.13.0-x64.msi"
-$PYWIN32_URL = "https://github.com/mhammond/pywin32/releases/download/b225/pywin32-225.win-amd64-py3.7.exe"
+$NPM_URL = "https://nodejs.org/dist/v12.14.1/node-v12.14.1-x64.msi"
$MK32_DLL_URL = "https://github.com/guardicore/mimikatz/releases/download/1.1.0/mk32.zip"
$MK64_DLL_URL = "https://github.com/guardicore/mimikatz/releases/download/1.1.0/mk64.zip"
-$UPX_URL = "https://github.com/upx/upx/releases/download/v3.94/upx394w.zip"
+$UPX_URL = "https://github.com/upx/upx/releases/download/v3.96/upx-3.96-win64.zip"
diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh
index 834d811a7..65fdd48e6 100755
--- a/deployment_scripts/deploy_linux.sh
+++ b/deployment_scripts/deploy_linux.sh
@@ -1,10 +1,54 @@
#!/bin/bash
-source config
exists() {
command -v "$1" >/dev/null 2>&1
}
+is_root() {
+ return $(id -u)
+}
+
+has_sudo() {
+ # 0 true, 1 false
+ timeout 1 sudo id && return 0 || return 1
+}
+
+handle_error() {
+ echo "Fix the errors above and rerun the script"
+ exit 1
+}
+
+log_message() {
+ echo -e "\n\n"
+ echo -e "DEPLOYMENT SCRIPT: $1"
+}
+
+config_branch=${2:-"develop"}
+config_url="https://raw.githubusercontent.com/guardicore/monkey/${config_branch}/deployment_scripts/config"
+
+if (! exists curl) && (! exists wget); then
+ log_message 'Your system does not have curl or wget, exiting'
+ exit 1
+fi
+
+file=$(mktemp)
+# shellcheck disable=SC2086
+if exists wget; then
+ # shellcheck disable=SC2086
+ wget --output-document=$file "$config_url"
+else
+ # shellcheck disable=SC2086
+ curl -s -o $file "$config_url"
+fi
+
+log_message "downloaded configuration"
+# shellcheck source=deployment_scripts/config
+# shellcheck disable=SC2086
+source $file
+log_message "loaded configuration"
+# shellcheck disable=SC2086
+# rm $file
+
# Setup monkey either in dir required or current dir
monkey_home=${1:-$(pwd)}
if [[ $monkey_home == $(pwd) ]]; then
@@ -13,26 +57,19 @@ fi
# We can set main paths after we know the home dir
ISLAND_PATH="$monkey_home/monkey/monkey_island"
-MONKEY_COMMON_PATH="$monkey_home/monkey/common/"
MONGO_PATH="$ISLAND_PATH/bin/mongodb"
ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries"
INFECTION_MONKEY_DIR="$monkey_home/monkey/infection_monkey"
MONKEY_BIN_DIR="$INFECTION_MONKEY_DIR/bin"
-handle_error() {
- echo "Fix the errors above and rerun the script"
+if is_root; then
+ log_message "Please don't run this script as root"
exit 1
-}
+fi
-log_message() {
- echo -e "\n\n-------------------------------------------"
- echo -e "DEPLOYMENT SCRIPT: $1"
- echo -e "-------------------------------------------\n"
-}
-
-sudo -v
-if [[ $? != 0 ]]; then
- echo "You need root permissions for some of this script operations. Quiting."
+HAS_SUDO=$(has_sudo)
+if [[ ! $HAS_SUDO ]]; then
+ log_message "You need root permissions for some of this script operations. Quiting."
exit 1
fi
@@ -41,15 +78,10 @@ if [[ ! -d ${monkey_home} ]]; then
fi
if ! exists git; then
- echo "Please install git and re-run this script"
+ log_message "Please install git and re-run this script"
exit 1
fi
-if ! exists wget; then
- echo 'Your system does have wget, please install and re-run this script'
- exit 1
-fi
-
log_message "Cloning files from git"
branch=${2:-"develop"}
if [[ ! -d "$monkey_home/monkey" ]]; then # If not already cloned
@@ -59,7 +91,7 @@ fi
# Create folders
log_message "Creating island dirs under $ISLAND_PATH"
-mkdir -p "${MONGO_PATH}"
+mkdir -p "${MONGO_PATH}" || handle_error
mkdir -p "${ISLAND_BINARIES_PATH}" || handle_error
# Detecting command that calls python 3.7
@@ -78,87 +110,114 @@ if [[ ${python_cmd} == "" ]]; then
log_message "Python 3.7 command not found. Installing python 3.7."
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
- sudo apt install python3.7
+ sudo apt install python3.7 python3.7-dev
log_message "Python 3.7 is now available with command 'python3.7'."
python_cmd="python3.7"
fi
-log_message "Updating package list"
-sudo apt-get update
+log_message "Installing build-essential"
+sudo apt install build-essential
-log_message "Installing pip"
-sudo apt install python3-pip
-${python_cmd} -m pip install pip
-
-log_message "Install python3.7-dev"
-sudo apt-get install python3.7-dev
+log_message "Installing or updating pip"
+# shellcheck disable=SC2086
+pip_url=https://bootstrap.pypa.io/get-pip.py
+if exists wget; then
+ wget --output-document=get-pip.py $pip_url
+else
+ curl $pip_url -o get-pip.py
+fi
+${python_cmd} get-pip.py
+rm get-pip.py
log_message "Installing island requirements"
-requirements="$ISLAND_PATH/requirements.txt"
-${python_cmd} -m pip install --user --upgrade -r ${requirements} || handle_error
+requirements_island="$ISLAND_PATH/requirements.txt"
+${python_cmd} -m pip install -r "${requirements_island}" --user --upgrade || handle_error
log_message "Installing monkey requirements"
sudo apt-get install libffi-dev upx libssl-dev libc++1
-cd "${monkey_home}"/monkey/infection_monkey || handle_error
-${python_cmd} -m pip install -r requirements.txt --user --upgrade || handle_error
+requirements_monkey="$INFECTION_MONKEY_DIR/requirements.txt"
+${python_cmd} -m pip install -r "${requirements_monkey}" --user --upgrade || handle_error
+
+agents=${3:-true}
# Download binaries
-log_message "Downloading binaries"
-wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_32_BINARY_URL}
-wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_64_BINARY_URL}
-wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_32_BINARY_URL}
-wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_64_BINARY_URL}
+if [ "$agents" = true ] ; then
+ log_message "Downloading binaries"
+ if exists wget; then
+ wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_32_BINARY_URL}
+ wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_64_BINARY_URL}
+ wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_32_BINARY_URL}
+ wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_64_BINARY_URL}
+ else
+ curl -o ${ISLAND_BINARIES_PATH}\monkey-linux-32 ${LINUX_32_BINARY_URL}
+ curl -o ${ISLAND_BINARIES_PATH}\monkey-linux-64 ${LINUX_64_BINARY_URL}
+ curl -o ${ISLAND_BINARIES_PATH}\monkey-windows-32.exe ${WINDOWS_32_BINARY_URL}
+ curl -o ${ISLAND_BINARIES_PATH}\monkey-windows-64.exe ${WINDOWS_64_BINARY_URL}
+ fi
+fi
+
# 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"
-# Get machine type/kernel version
-kernel=$(uname -m)
-linux_dist=$(lsb_release -a 2>/dev/null)
-
# If a user haven't installed mongo manually check if we can install it with our script
-log_message "Installing MongoDB"
-"${ISLAND_PATH}"/linux/install_mongo.sh ${MONGO_PATH} || handle_error
-
+if ! exists mongod; then
+ log_message "Installing MongoDB"
+ "${ISLAND_PATH}"/linux/install_mongo.sh ${MONGO_PATH} || handle_error
+fi
log_message "Installing openssl"
sudo apt-get install openssl
# Generate SSL certificate
log_message "Generating certificate"
-cd "${ISLAND_PATH}" || {
- echo "cd failed"
- exit 1
-}
-openssl genrsa -out cc/server.key 2048
-openssl req -new -key cc/server.key -out cc/server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com"
-openssl x509 -req -days 366 -in cc/server.csr -signkey cc/server.key -out cc/server.crt
+
+"${ISLAND_PATH}"/linux/create_certificate.sh ${ISLAND_PATH}/cc
# Update node
-log_message "Installing nodejs"
-cd "$ISLAND_PATH/cc/ui" || handle_error
-sudo apt-get install curl
-curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
-sudo apt-get install -y nodejs
+if ! exists npm; then
+ log_message "Installing nodejs"
+ node_src=https://deb.nodesource.com/setup_12.x
+ if exists curl; then
+ curl -sL $node_src | sudo -E bash -
+ else
+ wget -q -O - $node_src | sudo -E bash -
+ fi
+ sudo apt-get install -y nodejs
+fi
+
+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
# Making dir for binaries
mkdir "${MONKEY_BIN_DIR}"
# Download sambacry binaries
log_message "Downloading sambacry binaries"
-wget -c -N -P "${MONKEY_BIN_DIR}" "${SAMBACRY_64_BINARY_URL}"
-wget -c -N -P "${MONKEY_BIN_DIR}" "${SAMBACRY_32_BINARY_URL}"
-
+# shellcheck disable=SC2086
+if exists wget; then
+ wget -c -N -P "${MONKEY_BIN_DIR}" ${SAMBACRY_64_BINARY_URL}
+ wget -c -N -P "${MONKEY_BIN_DIR}" ${SAMBACRY_32_BINARY_URL}
+else
+ curl -o ${MONKEY_BIN_DIR}/sc_monkey_runner64.so ${SAMBACRY_64_BINARY_URL}
+ curl -o ${MONKEY_BIN_DIR}/sc_monkey_runner32.so ${SAMBACRY_32_BINARY_URL}
+fi
# Download traceroute binaries
log_message "Downloading traceroute binaries"
-wget -c -N -P "${MONKEY_BIN_DIR}" "${TRACEROUTE_64_BINARY_URL}"
-wget -c -N -P "${MONKEY_BIN_DIR}" "${TRACEROUTE_32_BINARY_URL}"
+# shellcheck disable=SC2086
+if exists wget; then
+ wget -c -N -P "${MONKEY_BIN_DIR}" ${TRACEROUTE_64_BINARY_URL}
+ wget -c -N -P "${MONKEY_BIN_DIR}" ${TRACEROUTE_32_BINARY_URL}
+else
+ curl -o ${MONKEY_BIN_DIR}/traceroute64 ${TRACEROUTE_64_BINARY_URL}
+ curl -o ${MONKEY_BIN_DIR}/traceroute32 ${TRACEROUTE_32_BINARY_URL}
+fi
-sudo chmod +x "${monkey_home}"/monkey/infection_monkey/build_linux.sh
+sudo chmod +x "${INFECTION_MONKEY_DIR}/build_linux.sh"
log_message "Deployment script finished."
exit 0
diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1
index dd602e199..003fdd061 100644
--- a/deployment_scripts/deploy_windows.ps1
+++ b/deployment_scripts/deploy_windows.ps1
@@ -1,17 +1,40 @@
-function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, [String] $branch = "develop"){
- # Import the config variables
- . ./config.ps1
- "Config variables from config.ps1 imported"
-
- # If we want monkey in current dir we need to create an empty folder for source files
- if ( (Join-Path $monkey_home '') -eq (Join-Path (Get-Item -Path ".\").FullName '') ){
- $monkey_home = Join-Path -Path $monkey_home -ChildPath $MONKEY_FOLDER_NAME
- }
+param(
+ [Parameter(Mandatory = $false, Position = 0)]
+ [String] $monkey_home = (Get-Item -Path ".\").FullName,
+ [Parameter(Mandatory = $false, Position = 1)]
+ [System.String]
+ $branch = "develop",
+ [Parameter(Mandatory = $false, Position = 2)]
+ [Bool]
+ $agents = $true
+)
+function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, [String] $branch = "develop")
+{
+ Write-Output "Downloading to $monkey_home"
+ Write-Output "Branch $branch"
# Set variables for script execution
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webClient = New-Object System.Net.WebClient
+
+ # Import the config variables
+ $config_filename = New-TemporaryFile
+ $config_filename = "config.ps1"
+ $config_url = "https://raw.githubusercontent.com/guardicore/monkey/" + $branch + "/deployment_scripts/config.ps1"
+ $webClient.DownloadFile($config_url, $config_filename)
+ . ./config.ps1
+ "Config variables from config.ps1 imported"
+ Remove-Item $config_filename
+
+
+ # If we want monkey in current dir we need to create an empty folder for source files
+ if ((Join-Path $monkey_home '') -eq (Join-Path (Get-Item -Path ".\").FullName ''))
+ {
+ $monkey_home = Join-Path -Path $monkey_home -ChildPath $MONKEY_FOLDER_NAME
+ }
+
+
# We check if git is installed
try
{
@@ -25,15 +48,22 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
}
# Download the monkey
- $output = cmd.exe /c "git clone --single-branch -b $branch $MONKEY_GIT_URL $monkey_home 2>&1"
+ $command = "git clone --single-branch -b $branch $MONKEY_GIT_URL $monkey_home 2>&1"
+ Write-Output $command
+ $output = cmd.exe /c $command
$binDir = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\bin")
- if ( $output -like "*already exists and is not an empty directory.*"){
+ if ($output -like "*already exists and is not an empty directory.*")
+ {
"Assuming you already have the source directory. If not, make sure to set an empty directory as monkey's home directory."
- } elseif ($output -like "fatal:*"){
+ }
+ elseif ($output -like "fatal:*")
+ {
"Error while cloning monkey from the repository:"
$output
return
- } else {
+ }
+ else
+ {
"Monkey cloned from the repository"
# Create bin directory
New-Item -ItemType directory -path $binDir
@@ -44,9 +74,12 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
try
{
$version = cmd.exe /c '"python" --version 2>&1'
- if ( $version -like 'Python 3.*' ) {
+ if ($version -like 'Python 3.*')
+ {
"Python 3.* was found, installing dependencies"
- } else {
+ }
+ else
+ {
throw System.Management.Automation.CommandNotFoundException
}
}
@@ -56,11 +89,12 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
"Select 'add to PATH' when installing"
$webClient.DownloadFile($PYTHON_URL, $TEMP_PYTHON_INSTALLER)
Start-Process -Wait $TEMP_PYTHON_INSTALLER -ErrorAction Stop
- $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
Remove-Item $TEMP_PYTHON_INSTALLER
# Check if installed correctly
$version = cmd.exe /c '"python" --version 2>&1'
- if ( $version -like '* is not recognized*' ) {
+ if ($version -like '* is not recognized*')
+ {
"Python is not found in PATH. Add it to PATH and relaunch the script."
return
}
@@ -69,7 +103,8 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
"Upgrading pip..."
$output = cmd.exe /c 'python -m pip install --user --upgrade pip 2>&1'
$output
- if ( $output -like '*No module named pip*' ) {
+ if ($output -like '*No module named pip*')
+ {
"Make sure pip module is installed and re-run this script."
return
}
@@ -83,20 +118,24 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
$user_python_dir = cmd.exe /c 'py -m site --user-site'
$user_python_dir = Join-Path (Split-Path $user_python_dir) -ChildPath "\Scripts"
- if(!($ENV:PATH | Select-String -SimpleMatch $user_python_dir)){
+ if (!($ENV:Path | Select-String -SimpleMatch $user_python_dir))
+ {
"Adding python scripts path to user's env"
- $env:Path += ";"+$user_python_dir
- [Environment]::SetEnvironmentVariable("Path",$env:Path,"User")
+ $env:Path += ";" + $user_python_dir
+ [Environment]::SetEnvironmentVariable("Path", $env:Path, "User")
}
# Download mongodb
- if(!(Test-Path -Path (Join-Path -Path $binDir -ChildPath "mongodb") )){
+ if (!(Test-Path -Path (Join-Path -Path $binDir -ChildPath "mongodb")))
+ {
"Downloading mongodb ..."
$webClient.DownloadFile($MONGODB_URL, $TEMP_MONGODB_ZIP)
"Unzipping mongodb"
Expand-Archive $TEMP_MONGODB_ZIP -DestinationPath $binDir
# Get unzipped folder's name
- $mongodb_folder = Get-ChildItem -Path $binDir | Where-Object -FilterScript {($_.Name -like "mongodb*")} | Select-Object -ExpandProperty Name
+ $mongodb_folder = Get-ChildItem -Path $binDir | Where-Object -FilterScript {
+ ($_.Name -like "mongodb*")
+ } | Select-Object -ExpandProperty Name
# Move all files from extracted folder to mongodb folder
New-Item -ItemType directory -Path (Join-Path -Path $binDir -ChildPath "mongodb")
New-Item -ItemType directory -Path (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "db")
@@ -127,23 +166,30 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
. .\windows\create_certificate.bat
Pop-Location
- # Adding binaries
- "Adding binaries"
- $binaries = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\binaries")
- New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue
- $webClient.DownloadFile($LINUX_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_32_BINARY_PATH))
- $webClient.DownloadFile($LINUX_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_64_BINARY_PATH))
- $webClient.DownloadFile($WINDOWS_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_32_BINARY_PATH))
- $webClient.DownloadFile($WINDOWS_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_64_BINARY_PATH))
+ if ($agents)
+ {
+ # Adding binaries
+ "Adding binaries"
+ $binaries = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\binaries")
+ New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue
+ $webClient.DownloadFile($LINUX_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_32_BINARY_PATH))
+ $webClient.DownloadFile($LINUX_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_64_BINARY_PATH))
+ $webClient.DownloadFile($WINDOWS_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_32_BINARY_PATH))
+ $webClient.DownloadFile($WINDOWS_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_64_BINARY_PATH))
+ }
+
# Check if NPM installed
"Installing npm"
try
{
$version = cmd.exe /c '"npm" --version 2>&1'
- if ( $version -like "*is not recognized*"){
+ if ($version -like "*is not recognized*")
+ {
throw System.Management.Automation.CommandNotFoundException
- } else {
+ }
+ else
+ {
"Npm already installed"
}
}
@@ -152,7 +198,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
"Downloading npm ..."
$webClient.DownloadFile($NPM_URL, $TEMP_NPM_INSTALLER)
Start-Process -Wait $TEMP_NPM_INSTALLER
- $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
Remove-Item $TEMP_NPM_INSTALLER
}
@@ -162,18 +208,13 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
& npm run dist
Pop-Location
- # Install pywin32
- "Downloading pywin32"
- $webClient.DownloadFile($PYWIN32_URL, $TEMP_PYWIN32_INSTALLER)
- Start-Process -Wait $TEMP_PYWIN32_INSTALLER -ErrorAction Stop
- Remove-Item $TEMP_PYWIN32_INSTALLER
-
# Create infection_monkey/bin directory if not already present
$binDir = (Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\bin")
New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue
# Download upx
- if(!(Test-Path -Path (Join-Path -Path $binDir -ChildPath "upx.exe") )){
+ if (!(Test-Path -Path (Join-Path -Path $binDir -ChildPath "upx.exe")))
+ {
"Downloading upx ..."
$webClient.DownloadFile($UPX_URL, $TEMP_UPX_ZIP)
"Unzipping upx"
@@ -187,12 +228,14 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
# Download mimikatz binaries
$mk32_path = Join-Path -Path $binDir -ChildPath $MK32_DLL
- if(!(Test-Path -Path $mk32_path )){
+ if (!(Test-Path -Path $mk32_path))
+ {
"Downloading mimikatz 32 binary"
$webClient.DownloadFile($MK32_DLL_URL, $mk32_path)
}
$mk64_path = Join-Path -Path $binDir -ChildPath $MK64_DLL
- if(!(Test-Path -Path $mk64_path )){
+ if (!(Test-Path -Path $mk64_path))
+ {
"Downloading mimikatz 64 binary"
$webClient.DownloadFile($MK64_DLL_URL, $mk64_path)
}
@@ -200,12 +243,14 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
# Download sambacry binaries
$samba_path = Join-Path -Path $monkey_home -ChildPath $SAMBA_BINARIES_DIR
$samba32_path = Join-Path -Path $samba_path -ChildPath $SAMBA_32_BINARY_NAME
- if(!(Test-Path -Path $samba32_path )){
+ if (!(Test-Path -Path $samba32_path))
+ {
"Downloading sambacry 32 binary"
$webClient.DownloadFile($SAMBA_32_BINARY_URL, $samba32_path)
}
$samba64_path = Join-Path -Path $samba_path -ChildPath $SAMBA_64_BINARY_NAME
- if(!(Test-Path -Path $samba64_path )){
+ if (!(Test-Path -Path $samba64_path))
+ {
"Downloading sambacry 64 binary"
$webClient.DownloadFile($SAMBA_64_BINARY_URL, $samba64_path)
}
@@ -213,3 +258,4 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
"Script finished"
}
+Deploy-Windows -monkey_home $monkey_home -branch $branch
diff --git a/deployment_scripts/run_script.bat b/deployment_scripts/run_script.bat
deleted file mode 100644
index 3dcd62760..000000000
--- a/deployment_scripts/run_script.bat
+++ /dev/null
@@ -1,8 +0,0 @@
-SET command=. .\deploy_windows.ps1; Deploy-Windows
-if NOT "%~1" == "" (
- SET "command=%command% -monkey_home %~1"
-)
-if NOT "%~2" == "" (
- SET "command=%command% -branch %~2"
-)
-powershell -ExecutionPolicy ByPass -Command %command%
\ No newline at end of file
diff --git a/monkey/codecov.yml b/monkey/codecov.yml
new file mode 100644
index 000000000..8d5127230
--- /dev/null
+++ b/monkey/codecov.yml
@@ -0,0 +1,20 @@
+codecov:
+ require_ci_to_pass: yes
+
+coverage:
+ precision: 2
+ round: down
+ range: "50...90"
+
+parsers:
+ gcov:
+ branch_detection:
+ conditional: yes
+ loop: yes
+ method: no
+ macro: no
+
+comment:
+ layout: "reach,diff,flags,tree"
+ behavior: default
+ require_changes: no
diff --git a/monkey/common/BUILD b/monkey/common/BUILD
new file mode 100644
index 000000000..90012116c
--- /dev/null
+++ b/monkey/common/BUILD
@@ -0,0 +1 @@
+dev
\ No newline at end of file
diff --git a/monkey/common/cloud/all_instances.py b/monkey/common/cloud/all_instances.py
new file mode 100644
index 000000000..6387730f6
--- /dev/null
+++ b/monkey/common/cloud/all_instances.py
@@ -0,0 +1,12 @@
+from typing import List
+
+from common.cloud.aws.aws_instance import AwsInstance
+from common.cloud.azure.azure_instance import AzureInstance
+from common.cloud.gcp.gcp_instance import GcpInstance
+from common.cloud.instance import CloudInstance
+
+all_cloud_instances = [AwsInstance(), AzureInstance(), GcpInstance()]
+
+
+def get_all_cloud_instances() -> List[CloudInstance]:
+ return all_cloud_instances
diff --git a/monkey/common/cloud/aws/__init__.py b/monkey/common/cloud/aws/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/common/cloud/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py
similarity index 92%
rename from monkey/common/cloud/aws_instance.py
rename to monkey/common/cloud/aws/aws_instance.py
index 4339fbcf4..03c5482ba 100644
--- a/monkey/common/cloud/aws_instance.py
+++ b/monkey/common/cloud/aws/aws_instance.py
@@ -6,6 +6,9 @@ import logging
__author__ = 'itay.mizeretz'
+from common.cloud.environment_names import Environment
+from common.cloud.instance import CloudInstance
+
AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS = "169.254.169.254"
AWS_LATEST_METADATA_URI_PREFIX = 'http://{0}/latest/'.format(AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS)
ACCOUNT_ID_KEY = "accountId"
@@ -13,10 +16,15 @@ ACCOUNT_ID_KEY = "accountId"
logger = logging.getLogger(__name__)
-class AwsInstance(object):
+class AwsInstance(CloudInstance):
"""
Class which gives useful information about the current instance you're on.
"""
+ def is_instance(self):
+ return self.instance_id is not None
+
+ def get_cloud_provider_name(self) -> Environment:
+ return Environment.AWS
def __init__(self):
self.instance_id = None
@@ -57,9 +65,6 @@ class AwsInstance(object):
def get_region(self):
return self.region
- def is_aws_instance(self):
- return self.instance_id is not None
-
@staticmethod
def _extract_account_id(instance_identity_document_response):
"""
diff --git a/monkey/common/cloud/aws_service.py b/monkey/common/cloud/aws/aws_service.py
similarity index 98%
rename from monkey/common/cloud/aws_service.py
rename to monkey/common/cloud/aws/aws_service.py
index 6ef385542..a42c2e1dd 100644
--- a/monkey/common/cloud/aws_service.py
+++ b/monkey/common/cloud/aws/aws_service.py
@@ -4,7 +4,7 @@ import boto3
import botocore
from botocore.exceptions import ClientError
-from common.cloud.aws_instance import AwsInstance
+from common.cloud.aws.aws_instance import AwsInstance
__author__ = ['itay.mizeretz', 'shay.nehmad']
diff --git a/monkey/common/cloud/aws_service_test.py b/monkey/common/cloud/aws/aws_service_test.py
similarity index 100%
rename from monkey/common/cloud/aws_service_test.py
rename to monkey/common/cloud/aws/aws_service_test.py
diff --git a/monkey/common/cloud/azure/__init__.py b/monkey/common/cloud/azure/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/common/cloud/azure/azure_instance.py b/monkey/common/cloud/azure/azure_instance.py
new file mode 100644
index 000000000..ec910fb98
--- /dev/null
+++ b/monkey/common/cloud/azure/azure_instance.py
@@ -0,0 +1,55 @@
+import logging
+import requests
+
+from common.cloud.environment_names import Environment
+from common.cloud.instance import CloudInstance
+
+LATEST_AZURE_METADATA_API_VERSION = "2019-04-30"
+AZURE_METADATA_SERVICE_URL = "http://169.254.169.254/metadata/instance?api-version=%s" % LATEST_AZURE_METADATA_API_VERSION
+
+logger = logging.getLogger(__name__)
+
+
+class AzureInstance(CloudInstance):
+ """
+ Access to useful information about the current machine if it's an Azure VM.
+ Based on Azure metadata service: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
+ """
+ def is_instance(self):
+ return self.on_azure
+
+ def get_cloud_provider_name(self) -> Environment:
+ return Environment.AZURE
+
+ def __init__(self):
+ """
+ Determines if on Azure and if so, gets some basic metadata on this instance.
+ """
+ self.instance_name = None
+ self.instance_id = None
+ self.location = None
+ self.on_azure = False
+
+ try:
+ response = requests.get(AZURE_METADATA_SERVICE_URL, headers={"Metadata": "true"})
+ self.on_azure = True
+
+ # If not on cloud, the metadata URL is non-routable and the connection will fail.
+ # If on AWS, should get 404 since the metadata service URL is different, so bool(response) will be false.
+ if response:
+ logger.debug("On Azure. Trying to parse metadata.")
+ self.try_parse_response(response)
+ else:
+ logger.warning("On Azure, but metadata response not ok: {}".format(response.status_code))
+ except requests.RequestException:
+ logger.debug("Failed to get response from Azure metadata service: This instance is not on Azure.")
+ self.on_azure = False
+
+ def try_parse_response(self, response):
+ try:
+ response_data = response.json()
+ self.instance_name = response_data["compute"]["name"]
+ self.instance_id = response_data["compute"]["vmId"]
+ self.location = response_data["compute"]["location"]
+ except KeyError:
+ logger.exception("Error while parsing response from Azure metadata service.")
diff --git a/monkey/common/cloud/environment_names.py b/monkey/common/cloud/environment_names.py
new file mode 100644
index 000000000..945d438ce
--- /dev/null
+++ b/monkey/common/cloud/environment_names.py
@@ -0,0 +1,15 @@
+from enum import Enum
+
+
+class Environment(Enum):
+ UNKNOWN = "Unknown"
+ ON_PREMISE = "On Premise"
+ AZURE = "Azure"
+ AWS = "AWS"
+ GCP = "GCP"
+ ALIBABA = "Alibaba Cloud"
+ IBM = "IBM Cloud"
+ DigitalOcean = "Digital Ocean"
+
+
+ALL_ENVIRONMENTS_NAMES = [x.value for x in Environment]
diff --git a/monkey/common/cloud/gcp/__init__.py b/monkey/common/cloud/gcp/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/common/cloud/gcp/gcp_instance.py b/monkey/common/cloud/gcp/gcp_instance.py
new file mode 100644
index 000000000..184465bf5
--- /dev/null
+++ b/monkey/common/cloud/gcp/gcp_instance.py
@@ -0,0 +1,43 @@
+import logging
+import requests
+
+from common.cloud.environment_names import Environment
+from common.cloud.instance import CloudInstance
+
+logger = logging.getLogger(__name__)
+
+
+GCP_METADATA_SERVICE_URL = "http://metadata.google.internal/"
+
+
+class GcpInstance(CloudInstance):
+ """
+ Used to determine if on GCP. See https://cloud.google.com/compute/docs/storing-retrieving-metadata#runninggce
+ """
+ def is_instance(self):
+ return self.on_gcp
+
+ def get_cloud_provider_name(self) -> Environment:
+ return Environment.GCP
+
+ def __init__(self):
+ self.on_gcp = False
+
+ try:
+ # If not on GCP, this domain shouldn't resolve.
+ response = requests.get(GCP_METADATA_SERVICE_URL)
+
+ if response:
+ logger.debug("Got ok metadata response: on GCP")
+ self.on_gcp = True
+
+ if "Metadata-Flavor" not in response.headers:
+ logger.warning("Got unexpected GCP Metadata format")
+ else:
+ if not response.headers["Metadata-Flavor"] == "Google":
+ logger.warning("Got unexpected Metadata flavor: {}".format(response.headers["Metadata-Flavor"]))
+ else:
+ logger.warning("On GCP, but metadata response not ok: {}".format(response.status_code))
+ except requests.RequestException:
+ logger.debug("Failed to get response from GCP metadata service: This instance is not on GCP")
+ self.on_gcp = False
diff --git a/monkey/common/cloud/instance.py b/monkey/common/cloud/instance.py
new file mode 100644
index 000000000..abe0c7910
--- /dev/null
+++ b/monkey/common/cloud/instance.py
@@ -0,0 +1,14 @@
+from common.cloud.environment_names import Environment
+
+
+class CloudInstance(object):
+ """
+ This is an abstract class which represents a cloud instance.
+
+ The current machine can be a cloud instance (for example EC2 instance or Azure VM).
+ """
+ def is_instance(self) -> bool:
+ raise NotImplementedError()
+
+ def get_cloud_provider_name(self) -> Environment:
+ raise NotImplementedError()
diff --git a/monkey/common/cmd/aws/aws_cmd_runner.py b/monkey/common/cmd/aws/aws_cmd_runner.py
index 459a42129..1ab680c4d 100644
--- a/monkey/common/cmd/aws/aws_cmd_runner.py
+++ b/monkey/common/cmd/aws/aws_cmd_runner.py
@@ -1,6 +1,6 @@
import logging
-from common.cloud.aws_service import AwsService
+from common.cloud.aws.aws_service import AwsService
from common.cmd.aws.aws_cmd_result import AwsCmdResult
from common.cmd.cmd_runner import CmdRunner
from common.cmd.cmd_status import CmdStatus
diff --git a/monkey/common/data/system_info_collectors_names.py b/monkey/common/data/system_info_collectors_names.py
new file mode 100644
index 000000000..831bbe142
--- /dev/null
+++ b/monkey/common/data/system_info_collectors_names.py
@@ -0,0 +1,4 @@
+AWS_COLLECTOR = "AwsCollector"
+HOSTNAME_COLLECTOR = "HostnameCollector"
+ENVIRONMENT_COLLECTOR = "EnvironmentCollector"
+PROCESS_LIST_COLLECTOR = "ProcessListCollector"
diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py
index e39da1d03..15e04f893 100644
--- a/monkey/common/network/network_range.py
+++ b/monkey/common/network/network_range.py
@@ -46,6 +46,7 @@ class NetworkRange(object, metaclass=ABCMeta):
def get_range_obj(address_str):
if not address_str: # Empty string
return None
+ address_str = address_str.strip()
if NetworkRange.check_if_range(address_str):
return IpRange(ip_range=address_str)
if -1 != address_str.find('/'):
diff --git a/monkey/common/version.py b/monkey/common/version.py
new file mode 100644
index 000000000..9d60e636c
--- /dev/null
+++ b/monkey/common/version.py
@@ -0,0 +1,25 @@
+# To get the version from shell, run `python ./version.py` (see `python ./version.py -h` for details).
+import argparse
+from pathlib import Path
+
+MAJOR = "1"
+MINOR = "8"
+PATCH = "0"
+build_file_path = Path(__file__).parent.joinpath("BUILD")
+with open(build_file_path, "r") as build_file:
+ BUILD = build_file.read()
+
+
+def get_version(build=BUILD):
+ return f"{MAJOR}.{MINOR}.{PATCH}+{build}"
+
+
+def print_version():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-b", "--build", default=BUILD, help="Choose the build string for this version.", type=str)
+ args = parser.parse_args()
+ print(get_version(args.build))
+
+
+if __name__ == '__main__':
+ print_version()
diff --git a/monkey/infection_monkey/build_linux.sh b/monkey/infection_monkey/build_linux.sh
index fcaf4c75d..68abd4758 100644
--- a/monkey/infection_monkey/build_linux.sh
+++ b/monkey/infection_monkey/build_linux.sh
@@ -1,2 +1,17 @@
#!/bin/bash
+
+# Allow custom build ID
+# If the first argument is not empty...
+if [[ -n "$1" ]]
+then
+ # Validate argument is a valid build string
+ if [[ "$1" =~ ^[\da-zA-Z]*$ ]]
+ then
+ # And put it in the BUILD file
+ echo "$1" > ../common/BUILD
+ else
+ echo "Build ID $1 invalid!"
+ fi
+fi
+
pyinstaller -F --log-level=DEBUG --clean monkey.spec
diff --git a/monkey/infection_monkey/build_windows.bat b/monkey/infection_monkey/build_windows.bat
index f763bda6b..93e4e4a42 100644
--- a/monkey/infection_monkey/build_windows.bat
+++ b/monkey/infection_monkey/build_windows.bat
@@ -1 +1,12 @@
+REM Check if build ID was passed to the build script.
+if "%1"=="" GOTO START_BUILD
+
+REM Validate build ID
+echo %1|findstr /r "^[0-9a-zA-Z]*$"
+if %errorlevel% neq 0 (exit /b %errorlevel%)
+
+REM replace build ID
+echo %1> ../common/BUILD
+
+:START_BUILD
pyinstaller -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec
diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py
index 1a3cfc78c..5c5b5a392 100644
--- a/monkey/infection_monkey/config.py
+++ b/monkey/infection_monkey/config.py
@@ -1,6 +1,6 @@
import hashlib
-import os
import json
+import os
import sys
import uuid
from abc import ABCMeta
@@ -125,6 +125,7 @@ class Configuration(object):
finger_classes = []
exploiter_classes = []
+ system_info_collectors_classes = []
# how many victims to look for in a single scan iteration
victims_max_find = 100
diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py
index 928425535..21871d857 100644
--- a/monkey/infection_monkey/main.py
+++ b/monkey/infection_monkey/main.py
@@ -12,6 +12,7 @@ from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE
from infection_monkey.dropper import MonkeyDrops
from infection_monkey.model import MONKEY_ARG, DROPPER_ARG
from infection_monkey.monkey import InfectionMonkey
+from common.version import get_version
# noinspection PyUnresolvedReferences
import infection_monkey.post_breach # dummy import for pyinstaller
@@ -117,6 +118,8 @@ def main():
LOG.info(">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<",
monkey_cls.__name__, os.getpid())
+ LOG.info(f"version: {get_version()}")
+
monkey = monkey_cls(monkey_args)
monkey.initialize()
diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py
index 80d2d8642..6b8803a9f 100644
--- a/monkey/infection_monkey/monkey.py
+++ b/monkey/infection_monkey/monkey.py
@@ -30,13 +30,20 @@ from infection_monkey.network.tools import get_interface_to_target
from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from common.utils.attack_utils import ScanStatus, UsageEnum
+from common.version import get_version
from infection_monkey.exploit.HostExploiter import HostExploiter
+MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, shutting down"
+
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
+class PlannedShutdownException(Exception):
+ pass
+
+
class InfectionMonkey(object):
def __init__(self, args):
self._keep_running = False
@@ -87,143 +94,158 @@ class InfectionMonkey(object):
LOG.debug("Default server: %s is already in command servers list" % self._default_server)
def start(self):
- LOG.info("Monkey is running...")
+ try:
+ LOG.info("Monkey is starting...")
- # Sets island's IP and port for monkey to communicate to
- if not self.set_default_server():
- return
- self.set_default_port()
+ LOG.debug("Starting the setup phase.")
+ # Sets island's IP and port for monkey to communicate to
+ self.set_default_server()
+ self.set_default_port()
- # Create a dir for monkey files if there isn't one
- create_monkey_dir()
+ # Create a dir for monkey files if there isn't one
+ create_monkey_dir()
- if WindowsUpgrader.should_upgrade():
- self._upgrading_to_64 = True
- self._singleton.unlock()
- LOG.info("32bit monkey running on 64bit Windows. Upgrading.")
- WindowsUpgrader.upgrade(self._opts)
- return
+ self.upgrade_to_64_if_needed()
- ControlClient.wakeup(parent=self._parent)
- ControlClient.load_control_config()
+ ControlClient.wakeup(parent=self._parent)
+ ControlClient.load_control_config()
- if is_windows_os():
- T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send()
+ if is_windows_os():
+ T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send()
- if not WormConfiguration.alive:
- LOG.info("Marked not alive from configuration")
- return
+ self.shutdown_by_not_alive_config()
- if firewall.is_enabled():
- firewall.add_firewall_rule()
+ if firewall.is_enabled():
+ firewall.add_firewall_rule()
- monkey_tunnel = ControlClient.create_control_tunnel()
- if monkey_tunnel:
- monkey_tunnel.start()
+ monkey_tunnel = ControlClient.create_control_tunnel()
+ if monkey_tunnel:
+ monkey_tunnel.start()
- StateTelem(is_done=False).send()
- TunnelTelem().send()
+ StateTelem(is_done=False, version=get_version()).send()
+ TunnelTelem().send()
+ LOG.debug("Starting the post-breach phase.")
+ self.collect_system_info_if_configured()
+ PostBreach().execute_all_configured()
+
+ LOG.debug("Starting the propagation phase.")
+ self.shutdown_by_max_depth_reached()
+
+ for iteration_index in range(WormConfiguration.max_iterations):
+ ControlClient.keepalive()
+ ControlClient.load_control_config()
+
+ self._network.initialize()
+
+ self._fingerprint = HostFinger.get_instances()
+
+ self._exploiters = HostExploiter.get_classes()
+
+ if not self._keep_running or not WormConfiguration.alive:
+ break
+
+ machines = self._network.get_victim_machines(max_find=WormConfiguration.victims_max_find,
+ stop_callback=ControlClient.check_for_stop)
+ is_empty = True
+ for machine in machines:
+ if ControlClient.check_for_stop():
+ break
+
+ is_empty = False
+ for finger in self._fingerprint:
+ LOG.info("Trying to get OS fingerprint from %r with module %s",
+ machine, finger.__class__.__name__)
+ finger.get_host_fingerprint(machine)
+
+ ScanTelem(machine).send()
+
+ # skip machines that we've already exploited
+ if machine in self._exploited_machines:
+ LOG.debug("Skipping %r - already exploited",
+ machine)
+ continue
+ elif machine in self._fail_exploitation_machines:
+ if WormConfiguration.retry_failed_explotation:
+ LOG.debug("%r - exploitation failed before, trying again", machine)
+ else:
+ LOG.debug("Skipping %r - exploitation failed before", machine)
+ continue
+
+ if monkey_tunnel:
+ monkey_tunnel.set_tunnel_for_host(machine)
+ if self._default_server:
+ if self._network.on_island(self._default_server):
+ machine.set_default_server(get_interface_to_target(machine.ip_addr) +
+ (':' + self._default_server_port if self._default_server_port else ''))
+ else:
+ machine.set_default_server(self._default_server)
+ LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server))
+
+ # Order exploits according to their type
+ if WormConfiguration.should_exploit:
+ self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value)
+ host_exploited = False
+ for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
+ if self.try_exploiting(machine, exploiter):
+ host_exploited = True
+ VictimHostTelem('T1210', ScanStatus.USED, machine=machine).send()
+ break
+ if not host_exploited:
+ self._fail_exploitation_machines.add(machine)
+ VictimHostTelem('T1210', ScanStatus.SCANNED, machine=machine).send()
+ if not self._keep_running:
+ break
+
+ if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1):
+ time_to_sleep = WormConfiguration.timeout_between_iterations
+ LOG.info("Sleeping %d seconds before next life cycle iteration", time_to_sleep)
+ time.sleep(time_to_sleep)
+
+ if self._keep_running and WormConfiguration.alive:
+ LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations)
+ elif not WormConfiguration.alive:
+ LOG.info("Marked not alive from configuration")
+
+ # if host was exploited, before continue to closing the tunnel ensure the exploited host had its chance to
+ # connect to the tunnel
+ if len(self._exploited_machines) > 0:
+ time_to_sleep = WormConfiguration.keep_tunnel_open_time
+ LOG.info("Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep)
+ time.sleep(time_to_sleep)
+
+ if monkey_tunnel:
+ monkey_tunnel.stop()
+ monkey_tunnel.join()
+ except PlannedShutdownException:
+ LOG.info("A planned shutdown of the Monkey occurred. Logging the reason and finishing execution.")
+ LOG.exception("Planned shutdown, reason:")
+
+ def shutdown_by_max_depth_reached(self):
+ if 0 == WormConfiguration.depth:
+ TraceTelem(MAX_DEPTH_REACHED_MESSAGE).send()
+ raise PlannedShutdownException(MAX_DEPTH_REACHED_MESSAGE)
+ else:
+ LOG.debug("Running with depth: %d" % WormConfiguration.depth)
+
+ def collect_system_info_if_configured(self):
if WormConfiguration.collect_system_info:
LOG.debug("Calling system info collection")
system_info_collector = SystemInfoCollector()
system_info = system_info_collector.get_info()
SystemInfoTelem(system_info).send()
- # Executes post breach actions
- PostBreach().execute()
+ def shutdown_by_not_alive_config(self):
+ if not WormConfiguration.alive:
+ raise PlannedShutdownException("Marked 'not alive' from configuration.")
- if 0 == WormConfiguration.depth:
- TraceTelem("Reached max depth, shutting down").send()
- return
- else:
- LOG.debug("Running with depth: %d" % WormConfiguration.depth)
-
- for iteration_index in range(WormConfiguration.max_iterations):
- ControlClient.keepalive()
- ControlClient.load_control_config()
-
- self._network.initialize()
-
- self._fingerprint = HostFinger.get_instances()
-
- self._exploiters = HostExploiter.get_classes()
-
- if not self._keep_running or not WormConfiguration.alive:
- break
-
- machines = self._network.get_victim_machines(max_find=WormConfiguration.victims_max_find,
- stop_callback=ControlClient.check_for_stop)
- is_empty = True
- for machine in machines:
- if ControlClient.check_for_stop():
- break
-
- is_empty = False
- for finger in self._fingerprint:
- LOG.info("Trying to get OS fingerprint from %r with module %s",
- machine, finger.__class__.__name__)
- finger.get_host_fingerprint(machine)
-
- ScanTelem(machine).send()
-
- # skip machines that we've already exploited
- if machine in self._exploited_machines:
- LOG.debug("Skipping %r - already exploited",
- machine)
- continue
- elif machine in self._fail_exploitation_machines:
- if WormConfiguration.retry_failed_explotation:
- LOG.debug("%r - exploitation failed before, trying again", machine)
- else:
- LOG.debug("Skipping %r - exploitation failed before", machine)
- continue
-
- if monkey_tunnel:
- monkey_tunnel.set_tunnel_for_host(machine)
- if self._default_server:
- if self._network.on_island(self._default_server):
- machine.set_default_server(get_interface_to_target(machine.ip_addr) +
- (':' + self._default_server_port if self._default_server_port else ''))
- else:
- machine.set_default_server(self._default_server)
- LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server))
-
- # Order exploits according to their type
- if WormConfiguration.should_exploit:
- self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value)
- host_exploited = False
- for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
- if self.try_exploiting(machine, exploiter):
- host_exploited = True
- VictimHostTelem('T1210', ScanStatus.USED, machine=machine).send()
- break
- if not host_exploited:
- self._fail_exploitation_machines.add(machine)
- VictimHostTelem('T1210', ScanStatus.SCANNED, machine=machine).send()
- if not self._keep_running:
- break
-
- if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1):
- time_to_sleep = WormConfiguration.timeout_between_iterations
- LOG.info("Sleeping %d seconds before next life cycle iteration", time_to_sleep)
- time.sleep(time_to_sleep)
-
- if self._keep_running and WormConfiguration.alive:
- LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations)
- elif not WormConfiguration.alive:
- LOG.info("Marked not alive from configuration")
-
- # if host was exploited, before continue to closing the tunnel ensure the exploited host had its chance to
- # connect to the tunnel
- if len(self._exploited_machines) > 0:
- time_to_sleep = WormConfiguration.keep_tunnel_open_time
- LOG.info("Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep)
- time.sleep(time_to_sleep)
-
- if monkey_tunnel:
- monkey_tunnel.stop()
- monkey_tunnel.join()
+ def upgrade_to_64_if_needed(self):
+ if WindowsUpgrader.should_upgrade():
+ self._upgrading_to_64 = True
+ self._singleton.unlock()
+ LOG.info("32bit monkey running on 64bit Windows. Upgrading.")
+ WindowsUpgrader.upgrade(self._opts)
+ raise PlannedShutdownException("Finished upgrading from 32bit to 64bit.")
def cleanup(self):
LOG.info("Monkey cleanup started")
@@ -233,7 +255,7 @@ class InfectionMonkey(object):
InfectionMonkey.close_tunnel()
firewall.close()
else:
- StateTelem(is_done=True).send() # Signal the server (before closing the tunnel)
+ StateTelem(is_done=True, version=get_version()).send() # Signal the server (before closing the tunnel)
InfectionMonkey.close_tunnel()
firewall.close()
if WormConfiguration.send_log_to_server:
@@ -346,9 +368,11 @@ class InfectionMonkey(object):
self._default_server_port = ''
def set_default_server(self):
+ """
+ Sets the default server for the Monkey to communicate back to.
+ :raises PlannedShutdownException if couldn't find the server.
+ """
if not ControlClient.find_server(default_tunnel=self._default_tunnel):
- LOG.info("Monkey couldn't find server. Going down.")
- return False
+ raise PlannedShutdownException("Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel))
self._default_server = WormConfiguration.current_server
LOG.debug("default server set to: %s" % self._default_server)
- return True
diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec
index 9c5fa9a18..e5873c9c5 100644
--- a/monkey/infection_monkey/monkey.spec
+++ b/monkey/infection_monkey/monkey.spec
@@ -1,5 +1,6 @@
# -*- mode: python -*-
import os
+import sys
import platform
@@ -18,7 +19,9 @@ def main():
hookspath=['./pyinstaller_hooks'],
runtime_hooks=None,
binaries=None,
- datas=None,
+ datas=[
+ ("../common/BUILD", "/common")
+ ],
excludes=None,
win_no_prefer_redirects=None,
win_private_assemblies=None,
@@ -48,7 +51,7 @@ def is_windows():
def is_32_bit():
- return platform.architecture()[0] == "32bit"
+ return sys.maxsize <= 2**32
def get_bin_folder():
@@ -93,7 +96,18 @@ def get_traceroute_binaries():
def get_monkey_filename():
- return 'monkey.exe' if is_windows() else 'monkey'
+ name = 'monkey-'
+ if is_windows():
+ name = name+"windows-"
+ else:
+ name = name+"linux-"
+ if is_32_bit():
+ name = name+"32"
+ else:
+ name = name+"64"
+ if is_windows():
+ name = name+".exe"
+ return name
def get_exe_strip():
diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py
index 7474c8ef1..d700bac62 100644
--- a/monkey/infection_monkey/post_breach/post_breach_handler.py
+++ b/monkey/infection_monkey/post_breach/post_breach_handler.py
@@ -20,7 +20,7 @@ class PostBreach(object):
self.os_is_linux = not is_windows_os()
self.pba_list = self.config_to_pba_list()
- def execute(self):
+ def execute_all_configured(self):
"""
Executes all post breach actions.
"""
diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py
new file mode 100644
index 000000000..97cf81bfb
--- /dev/null
+++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py
@@ -0,0 +1,6 @@
+from PyInstaller.utils.hooks import collect_submodules, collect_data_files
+
+# Import all actions as modules
+hiddenimports = collect_submodules('infection_monkey.system_info.collectors')
+# Add action files that we enumerate
+datas = (collect_data_files('infection_monkey.system_info.collectors', include_py_files=True))
diff --git a/monkey/infection_monkey/readme.md b/monkey/infection_monkey/readme.md
index d6e17acdb..da865c35f 100644
--- a/monkey/infection_monkey/readme.md
+++ b/monkey/infection_monkey/readme.md
@@ -34,7 +34,7 @@ The monkey is composed of three separate parts.
6. To build the final exe:
- `cd monkey\infection_monkey`
- `build_windows.bat`
- - `output is placed under dist\monkey.exe`
+ - output is placed under `dist\monkey32.exe` or `dist\monkey64.exe` depending on your version of Python
## Linux
@@ -55,18 +55,18 @@ Tested on Ubuntu 16.04.
3. Build Sambacry binaries
- Build/Download according to sections at the end of this readme.
- - Place the binaries under [code location]\infection_monkey\bin, under the names 'sc_monkey_runner32.so', 'sc_monkey_runner64.so'
+ - Place the binaries under [code location]/infection_monkey/bin, under the names 'sc_monkey_runner32.so', 'sc_monkey_runner64.so'
4. Build Traceroute binaries
- Build/Download according to sections at the end of this readme.
- - Place the binaries under [code location]\infection_monkey\bin, under the names 'traceroute32', 'traceroute64'
+ - Place the binaries under [code location]/infection_monkey/bin, under the names 'traceroute32', 'traceroute64'
5. To build, run in terminal:
- `cd [code location]/infection_monkey`
- `chmod +x build_linux.sh`
- `./build_linux.sh`
- output is placed under dist/monkey
+ output is placed under `dist/monkey32` or `dist/monkey64` depending on your version of python
### Sambacry
diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py
index 7d4395af7..d9107e7bf 100644
--- a/monkey/infection_monkey/system_info/__init__.py
+++ b/monkey/infection_monkey/system_info/__init__.py
@@ -6,9 +6,9 @@ import psutil
from enum import IntEnum
from infection_monkey.network.info import get_host_subnets
-from infection_monkey.system_info.aws_collector import AwsCollector
from infection_monkey.system_info.azure_cred_collector import AzureCollector
from infection_monkey.system_info.netstat_collector import NetstatCollector
+from infection_monkey.system_info.system_info_collectors_handler import SystemInfoCollectorsHandler
LOG = logging.getLogger(__name__)
@@ -61,50 +61,12 @@ class InfoCollector(object):
self.info = {}
def get_info(self):
- self.get_hostname()
- self.get_process_list()
+ # Collect all hardcoded
self.get_network_info()
self.get_azure_info()
- self.get_aws_info()
- def get_hostname(self):
- """
- Adds the fully qualified computer hostname to the system information.
- :return: None. Updates class information
- """
- LOG.debug("Reading hostname")
- self.info['hostname'] = socket.getfqdn()
-
- def get_process_list(self):
- """
- Adds process information from the host to the system information.
- Currently lists process name, ID, parent ID, command line
- and the full image path of each process.
- :return: None. Updates class information
- """
- LOG.debug("Reading process list")
- processes = {}
- for process in psutil.process_iter():
- try:
- processes[process.pid] = {"name": process.name(),
- "pid": process.pid,
- "ppid": process.ppid(),
- "cmdline": " ".join(process.cmdline()),
- "full_image_path": process.exe(),
- }
- except (psutil.AccessDenied, WindowsError):
- # we may be running as non root
- # and some processes are impossible to acquire in Windows/Linux
- # in this case we'll just add what we can
- processes[process.pid] = {"name": "null",
- "pid": process.pid,
- "ppid": process.ppid(),
- "cmdline": "ACCESS DENIED",
- "full_image_path": "null",
- }
- continue
-
- self.info['process_list'] = processes
+ # Collect all plugins
+ SystemInfoCollectorsHandler().execute_all_configured()
def get_network_info(self):
"""
@@ -150,11 +112,3 @@ class InfoCollector(object):
except Exception:
# If we failed to collect azure info, no reason to fail all the collection. Log and continue.
LOG.error("Failed collecting Azure info.", exc_info=True)
-
- def get_aws_info(self):
- # noinspection PyBroadException
- try:
- self.info['aws'] = AwsCollector().get_aws_info()
- except Exception:
- # If we failed to collect aws info, no reason to fail all the collection. Log and continue.
- LOG.error("Failed collecting AWS info.", exc_info=True)
diff --git a/monkey/infection_monkey/system_info/aws_collector.py b/monkey/infection_monkey/system_info/aws_collector.py
deleted file mode 100644
index df90e5913..000000000
--- a/monkey/infection_monkey/system_info/aws_collector.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import logging
-
-from common.cloud.aws_instance import AwsInstance
-
-__author__ = 'itay.mizeretz'
-
-LOG = logging.getLogger(__name__)
-
-
-class AwsCollector(object):
- """
- Extract info from AWS machines
- """
-
- @staticmethod
- def get_aws_info():
- LOG.info("Collecting AWS info")
- aws = AwsInstance()
- info = {}
- if aws.is_aws_instance():
- LOG.info("Machine is an AWS instance")
- info = \
- {
- 'instance_id': aws.get_instance_id()
- }
- else:
- LOG.info("Machine is NOT an AWS instance")
-
- return info
diff --git a/monkey/infection_monkey/system_info/collectors/__init__.py b/monkey/infection_monkey/system_info/collectors/__init__.py
new file mode 100644
index 000000000..f5b7166e9
--- /dev/null
+++ b/monkey/infection_monkey/system_info/collectors/__init__.py
@@ -0,0 +1,3 @@
+"""
+This package holds all the dynamic (plugin) collectors
+"""
diff --git a/monkey/infection_monkey/system_info/collectors/aws_collector.py b/monkey/infection_monkey/system_info/collectors/aws_collector.py
new file mode 100644
index 000000000..68d125279
--- /dev/null
+++ b/monkey/infection_monkey/system_info/collectors/aws_collector.py
@@ -0,0 +1,31 @@
+import logging
+
+from common.cloud.aws.aws_instance import AwsInstance
+from common.data.system_info_collectors_names import AWS_COLLECTOR
+from infection_monkey.system_info.system_info_collector import SystemInfoCollector
+
+
+logger = logging.getLogger(__name__)
+
+
+class AwsCollector(SystemInfoCollector):
+ """
+ Extract info from AWS machines.
+ """
+ def __init__(self):
+ super().__init__(name=AWS_COLLECTOR)
+
+ def collect(self) -> dict:
+ logger.info("Collecting AWS info")
+ aws = AwsInstance()
+ info = {}
+ if aws.is_instance():
+ logger.info("Machine is an AWS instance")
+ info = \
+ {
+ 'instance_id': aws.get_instance_id()
+ }
+ else:
+ logger.info("Machine is NOT an AWS instance")
+
+ return info
diff --git a/monkey/infection_monkey/system_info/collectors/environment_collector.py b/monkey/infection_monkey/system_info/collectors/environment_collector.py
new file mode 100644
index 000000000..100d23175
--- /dev/null
+++ b/monkey/infection_monkey/system_info/collectors/environment_collector.py
@@ -0,0 +1,24 @@
+from common.cloud.all_instances import get_all_cloud_instances
+from common.cloud.environment_names import Environment
+from common.data.system_info_collectors_names import ENVIRONMENT_COLLECTOR
+from infection_monkey.system_info.system_info_collector import SystemInfoCollector
+
+
+def get_monkey_environment() -> str:
+ """
+ Get the Monkey's running environment.
+ :return: One of the cloud providers if on cloud; otherwise, assumes "on premise".
+ """
+ for instance in get_all_cloud_instances():
+ if instance.is_instance():
+ return instance.get_cloud_provider_name().value
+
+ return Environment.ON_PREMISE.value
+
+
+class EnvironmentCollector(SystemInfoCollector):
+ def __init__(self):
+ super().__init__(name=ENVIRONMENT_COLLECTOR)
+
+ def collect(self) -> dict:
+ return {"environment": get_monkey_environment()}
diff --git a/monkey/infection_monkey/system_info/collectors/hostname_collector.py b/monkey/infection_monkey/system_info/collectors/hostname_collector.py
new file mode 100644
index 000000000..21d03aac7
--- /dev/null
+++ b/monkey/infection_monkey/system_info/collectors/hostname_collector.py
@@ -0,0 +1,16 @@
+import logging
+import socket
+
+from common.data.system_info_collectors_names import HOSTNAME_COLLECTOR
+from infection_monkey.system_info.system_info_collector import SystemInfoCollector
+
+
+logger = logging.getLogger(__name__)
+
+
+class HostnameCollector(SystemInfoCollector):
+ def __init__(self):
+ super().__init__(name=HOSTNAME_COLLECTOR)
+
+ def collect(self) -> dict:
+ return {"hostname": socket.getfqdn()}
diff --git a/monkey/infection_monkey/system_info/collectors/process_list_collector.py b/monkey/infection_monkey/system_info/collectors/process_list_collector.py
new file mode 100644
index 000000000..c0610cc74
--- /dev/null
+++ b/monkey/infection_monkey/system_info/collectors/process_list_collector.py
@@ -0,0 +1,50 @@
+import logging
+import psutil
+
+from common.data.system_info_collectors_names import PROCESS_LIST_COLLECTOR
+from infection_monkey.system_info.system_info_collector import SystemInfoCollector
+
+logger = logging.getLogger(__name__)
+
+# Linux doesn't have WindowsError
+try:
+ WindowsError
+except NameError:
+ # noinspection PyShadowingBuiltins
+ WindowsError = psutil.AccessDenied
+
+
+class ProcessListCollector(SystemInfoCollector):
+ def __init__(self):
+ super().__init__(name=PROCESS_LIST_COLLECTOR)
+
+ def collect(self) -> dict:
+ """
+ Adds process information from the host to the system information.
+ Currently lists process name, ID, parent ID, command line
+ and the full image path of each process.
+ """
+ logger.debug("Reading process list")
+ processes = {}
+ for process in psutil.process_iter():
+ try:
+ processes[process.pid] = {
+ "name": process.name(),
+ "pid": process.pid,
+ "ppid": process.ppid(),
+ "cmdline": " ".join(process.cmdline()),
+ "full_image_path": process.exe(),
+ }
+ except (psutil.AccessDenied, WindowsError):
+ # we may be running as non root and some processes are impossible to acquire in Windows/Linux.
+ # In this case we'll just add what we know.
+ processes[process.pid] = {
+ "name": "null",
+ "pid": process.pid,
+ "ppid": process.ppid(),
+ "cmdline": "ACCESS DENIED",
+ "full_image_path": "null",
+ }
+ continue
+
+ return {'process_list': processes}
diff --git a/monkey/infection_monkey/system_info/system_info_collector.py b/monkey/infection_monkey/system_info/system_info_collector.py
new file mode 100644
index 000000000..8c0b6aa65
--- /dev/null
+++ b/monkey/infection_monkey/system_info/system_info_collector.py
@@ -0,0 +1,38 @@
+from infection_monkey.config import WormConfiguration
+from infection_monkey.utils.plugins.plugin import Plugin
+from abc import ABCMeta, abstractmethod
+
+import infection_monkey.system_info.collectors
+
+
+class SystemInfoCollector(Plugin, metaclass=ABCMeta):
+ """
+ ABC for system info collection. See system_info_collector_handler for more info. Basically, to implement a new system info
+ collector, inherit from this class in an implementation in the infection_monkey.system_info.collectors class, and override
+ the 'collect' method. Don't forget to parse your results in the Monkey Island and to add the collector to the configuration
+ as well - see monkey_island.cc.services.processing.system_info_collectors for examples.
+
+ See the Wiki page "How to add a new System Info Collector to the Monkey?" for a detailed guide.
+ """
+ def __init__(self, name="unknown"):
+ self.name = name
+
+ @staticmethod
+ def should_run(class_name) -> bool:
+ return class_name in WormConfiguration.system_info_collectors_classes
+
+ @staticmethod
+ def base_package_file():
+ return infection_monkey.system_info.collectors.__file__
+
+ @staticmethod
+ def base_package_name():
+ return infection_monkey.system_info.collectors.__package__
+
+ @abstractmethod
+ def collect(self) -> dict:
+ """
+ Collect the relevant information and return it in a dictionary.
+ To be implemented by each collector.
+ """
+ raise NotImplementedError()
diff --git a/monkey/infection_monkey/system_info/system_info_collectors_handler.py b/monkey/infection_monkey/system_info/system_info_collectors_handler.py
new file mode 100644
index 000000000..cc007ff86
--- /dev/null
+++ b/monkey/infection_monkey/system_info/system_info_collectors_handler.py
@@ -0,0 +1,33 @@
+import logging
+from typing import Sequence
+
+from infection_monkey.system_info.system_info_collector import SystemInfoCollector
+from infection_monkey.telemetry.system_info_telem import SystemInfoTelem
+
+LOG = logging.getLogger(__name__)
+
+
+class SystemInfoCollectorsHandler(object):
+ def __init__(self):
+ self.collectors_list = self.config_to_collectors_list()
+
+ def execute_all_configured(self):
+ successful_collections = 0
+ system_info_telemetry = {}
+ for collector in self.collectors_list:
+ try:
+ LOG.debug("Executing system info collector: '{}'".format(collector.name))
+ collected_info = collector.collect()
+ system_info_telemetry[collector.name] = collected_info
+ successful_collections += 1
+ except Exception as e:
+ # If we failed one collector, no need to stop execution. Log and continue.
+ LOG.error("Collector {} failed. Error info: {}".format(collector.name, e))
+ LOG.info("All system info collectors executed. Total {} executed, out of which {} collected successfully.".
+ format(len(self.collectors_list), successful_collections))
+
+ SystemInfoTelem({"collectors": system_info_telemetry}).send()
+
+ @staticmethod
+ def config_to_collectors_list() -> Sequence[SystemInfoCollector]:
+ return SystemInfoCollector.get_instances()
diff --git a/monkey/infection_monkey/telemetry/state_telem.py b/monkey/infection_monkey/telemetry/state_telem.py
index 3bd63d2f9..4d4224288 100644
--- a/monkey/infection_monkey/telemetry/state_telem.py
+++ b/monkey/infection_monkey/telemetry/state_telem.py
@@ -5,15 +5,19 @@ __author__ = "itay.mizeretz"
class StateTelem(BaseTelem):
- def __init__(self, is_done):
+ def __init__(self, is_done, version="Unknown"):
"""
Default state telemetry constructor
:param is_done: Whether the state of monkey is done.
"""
super(StateTelem, self).__init__()
self.is_done = is_done
+ self.version = version
telem_category = 'state'
def get_data(self):
- return {'done': self.is_done}
+ return {
+ 'done': self.is_done,
+ 'version': self.version
+ }
diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py
index 26d33f78c..ec7c7a0f4 100644
--- a/monkey/monkey_island/cc/environment/__init__.py
+++ b/monkey/monkey_island/cc/environment/__init__.py
@@ -26,8 +26,6 @@ class Environment(object, metaclass=ABCMeta):
def testing(self, value):
self._testing = value
- _MONKEY_VERSION = "1.7.0"
-
def __init__(self):
self.config = None
self._testing = False # Assume env is not for unit testing.
@@ -58,9 +56,6 @@ class Environment(object, metaclass=ABCMeta):
def is_develop(self):
return self.get_deployment() == 'develop'
- def get_version(self):
- return self._MONKEY_VERSION + ('-dev' if self.is_develop() else '')
-
def _get_from_config(self, key, default_value=None):
val = default_value
if self.config is not None:
diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py
index 18db5c376..5608bddcd 100644
--- a/monkey/monkey_island/cc/environment/aws.py
+++ b/monkey/monkey_island/cc/environment/aws.py
@@ -1,6 +1,6 @@
import monkey_island.cc.auth
from monkey_island.cc.environment import Environment
-from common.cloud.aws_instance import AwsInstance
+from common.cloud.aws.aws_instance import AwsInstance
__author__ = 'itay.mizeretz'
diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py
index 508479287..f02aebafa 100644
--- a/monkey/monkey_island/cc/main.py
+++ b/monkey/monkey_island/cc/main.py
@@ -26,6 +26,7 @@ from monkey_island.cc.utils import local_ip_addresses
from monkey_island.cc.environment.environment import env
from monkey_island.cc.database import is_db_server_up, get_db_version
from monkey_island.cc.resources.monkey_download import MonkeyDownload
+from common.version import get_version
from monkey_island.cc.bootloader_server import BootloaderHttpServer
@@ -66,8 +67,9 @@ def start_island_server():
def log_init_info():
- logger.info(
- 'Monkey Island Server is running. Listening on the following URLs: {}'.format(
+ logger.info('Monkey Island Server is running!')
+ logger.info(f"version: {get_version()}")
+ logger.info('Listening on the following URLs: {}'.format(
", ".join(["https://{}:{}".format(x, env.get_island_port()) for x in local_ip_addresses()])
)
)
diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py
index 07b5ba3fe..f658a3d06 100644
--- a/monkey/monkey_island/cc/models/monkey.py
+++ b/monkey/monkey_island/cc/models/monkey.py
@@ -9,6 +9,7 @@ from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_docu
from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
from monkey_island.cc.models.command_control_channel import CommandControlChannel
from monkey_island.cc.utils import local_ip_addresses
+from common.cloud import environment_names
MAX_MONKEYS_AMOUNT_TO_CACHE = 100
@@ -42,6 +43,10 @@ class Monkey(Document):
ttl_ref = ReferenceField(MonkeyTtl)
tunnel = ReferenceField("self")
command_control_channel = EmbeddedDocumentField(CommandControlChannel)
+
+ # Environment related fields
+ environment = StringField(default=environment_names.Environment.UNKNOWN.value,
+ choices=environment_names.ALL_ENVIRONMENTS_NAMES)
aws_instance_id = StringField(required=False) # This field only exists when the monkey is running on an AWS
# instance. See https://github.com/guardicore/monkey/issues/426.
@@ -55,7 +60,8 @@ class Monkey(Document):
raise MonkeyNotFoundError("info: {0} | id: {1}".format(ex, str(db_id)))
@staticmethod
- def get_single_monkey_by_guid(monkey_guid):
+ # See https://www.python.org/dev/peps/pep-0484/#forward-references
+ def get_single_monkey_by_guid(monkey_guid) -> 'Monkey':
try:
return Monkey.objects.get(guid=monkey_guid)
except DoesNotExist as ex:
diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py
index c41699add..98d3694bf 100644
--- a/monkey/monkey_island/cc/resources/remote_run.py
+++ b/monkey/monkey_island/cc/resources/remote_run.py
@@ -6,7 +6,7 @@ import flask_restful
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
-from common.cloud.aws_service import AwsService
+from common.cloud.aws.aws_service import AwsService
CLIENT_ERROR_FORMAT = "ClientError, error message: '{}'. Probably, the IAM role that has been associated with the " \
"instance doesn't permit SSM calls. "
diff --git a/monkey/monkey_island/cc/resources/version_update.py b/monkey/monkey_island/cc/resources/version_update.py
index b1fbfdf82..a88f8830c 100644
--- a/monkey/monkey_island/cc/resources/version_update.py
+++ b/monkey/monkey_island/cc/resources/version_update.py
@@ -1,7 +1,7 @@
import flask_restful
import logging
-from monkey_island.cc.environment.environment import env
+from common.version import get_version
from monkey_island.cc.services.version_update import VersionUpdateService
__author__ = 'itay.mizeretz'
@@ -17,7 +17,7 @@ class VersionUpdate(flask_restful.Resource):
# even when not authenticated
def get(self):
return {
- 'current_version': env.get_version(),
+ 'current_version': get_version(),
'newer_version': VersionUpdateService.get_newer_version(),
'download_link': VersionUpdateService.get_download_link()
}
diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py
index fd2ed5b8d..96c59cad6 100644
--- a/monkey/monkey_island/cc/services/config.py
+++ b/monkey/monkey_island/cc/services/config.py
@@ -153,9 +153,18 @@ class ConfigService:
def ssh_key_exists(keys, user, ip):
return [key for key in keys if key['user'] == user and key['ip'] == ip]
+ def _filter_none_values(data):
+ if isinstance(data, dict):
+ return {k: ConfigService._filter_none_values(v) for k, v in data.items() if k is not None and v is not None}
+ elif isinstance(data, list):
+ return [ConfigService._filter_none_values(item) for item in data if item is not None]
+ else:
+ return data
+
@staticmethod
def update_config(config_json, should_encrypt):
# PBA file upload happens on pba_file_upload endpoint and corresponding config options are set there
+ config_json = ConfigService._filter_none_values(config_json)
monkey_island.cc.services.post_breach_files.set_config_PBA_files(config_json)
if should_encrypt:
try:
diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py
index 32ee13b12..3d0220ee2 100644
--- a/monkey/monkey_island/cc/services/config_schema.py
+++ b/monkey/monkey_island/cc/services/config_schema.py
@@ -1,3 +1,6 @@
+from common.data.system_info_collectors_names \
+ import AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR
+
WARNING_SIGN = " \u26A0"
SCHEMA = {
@@ -99,6 +102,44 @@ SCHEMA = {
}
]
},
+ "system_info_collectors_classes": {
+ "title": "System Information Collectors",
+ "type": "string",
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ ENVIRONMENT_COLLECTOR
+ ],
+ "title": "Collect which environment this machine is on (on prem/cloud)",
+ "attack_techniques": []
+ },
+ {
+ "type": "string",
+ "enum": [
+ AWS_COLLECTOR
+ ],
+ "title": "If on AWS, collect more information about the instance",
+ "attack_techniques": []
+ },
+ {
+ "type": "string",
+ "enum": [
+ HOSTNAME_COLLECTOR
+ ],
+ "title": "Collect the machine's hostname",
+ "attack_techniques": []
+ },
+{
+ "type": "string",
+ "enum": [
+ PROCESS_LIST_COLLECTOR
+ ],
+ "title": "Collect running processes on the machine",
+ "attack_techniques": []
+ },
+ ],
+ },
"post_breach_acts": {
"title": "Post breach actions",
"type": "string",
@@ -433,6 +474,21 @@ SCHEMA = {
"attack_techniques": ["T1003"],
"description": "Determines whether to use Mimikatz"
},
+ "system_info_collectors_classes": {
+ "title": "System info collectors",
+ "type": "array",
+ "uniqueItems": True,
+ "items": {
+ "$ref": "#/definitions/system_info_collectors_classes"
+ },
+ "default": [
+ ENVIRONMENT_COLLECTOR,
+ AWS_COLLECTOR,
+ HOSTNAME_COLLECTOR,
+ PROCESS_LIST_COLLECTOR
+ ],
+ "description": "Determines which system information collectors will collect information."
+ },
}
},
"life_cycle": {
diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py
index 9627bf74c..dfaa0e327 100644
--- a/monkey/monkey_island/cc/services/remote_run_aws.py
+++ b/monkey/monkey_island/cc/services/remote_run_aws.py
@@ -1,7 +1,7 @@
import logging
-from common.cloud.aws_instance import AwsInstance
-from common.cloud.aws_service import AwsService
+from common.cloud.aws.aws_instance import AwsInstance
+from common.cloud.aws.aws_service import AwsService
from common.cmd.aws.aws_cmd_runner import AwsCmdRunner
from common.cmd.cmd import Cmd
from common.cmd.cmd_runner import CmdRunner
@@ -54,7 +54,7 @@ class RemoteRunAwsService:
@staticmethod
def is_running_on_aws():
- return RemoteRunAwsService.aws_instance.is_aws_instance()
+ return RemoteRunAwsService.aws_instance.is_instance()
@staticmethod
def update_aws_region_authless():
@@ -130,7 +130,8 @@ class RemoteRunAwsService:
return r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" \
r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" + island_ip + \
r":5000/api/monkey/download/monkey-windows-" + bit_text + r".exe','.\\monkey.exe'); " \
- r";Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s " + island_ip + r":5000'; "
+ r";Start-Process -FilePath '.\\monkey.exe' " \
+ r"-ArgumentList 'm0nk3y -s " + island_ip + r":5000'; "
@staticmethod
def _get_run_monkey_cmd_line(is_linux, is_64bit, island_ip):
diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py
index 1df12e2eb..86486b9ba 100644
--- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py
+++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py
@@ -5,7 +5,7 @@ from datetime import datetime
import boto3
from botocore.exceptions import UnknownServiceError
-from common.cloud.aws_instance import AwsInstance
+from common.cloud.aws.aws_instance import AwsInstance
from monkey_island.cc.environment.environment import load_server_configuration_from_file
from monkey_island.cc.services.reporting.exporter import Exporter
diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py
index 6a44679a4..97e8fa4f1 100644
--- a/monkey/monkey_island/cc/services/reporting/report.py
+++ b/monkey/monkey_island/cc/services/reporting/report.py
@@ -386,10 +386,11 @@ class ReportService:
@staticmethod
def get_monkey_subnets(monkey_guid):
network_info = mongo.db.telemetry.find_one(
- {'telem_category': 'system_info', 'monkey_guid': monkey_guid},
+ {'telem_category': 'system_info',
+ 'monkey_guid': monkey_guid},
{'data.network_info.networks': 1}
)
- if network_info is None:
+ if network_info is None or not network_info["data"]:
return []
return \
diff --git a/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py b/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py
index 9025ff68f..1a041bb3b 100644
--- a/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py
+++ b/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py
@@ -15,28 +15,34 @@ __regular_report_generating_lock = threading.Semaphore()
def safe_generate_reports():
# Entering the critical section; Wait until report generation is available.
__report_generating_lock.acquire()
- report = safe_generate_regular_report()
- attack_report = safe_generate_attack_report()
- # Leaving the critical section.
- __report_generating_lock.release()
+ try:
+ report = safe_generate_regular_report()
+ attack_report = safe_generate_attack_report()
+ finally:
+ # Leaving the critical section.
+ __report_generating_lock.release()
return report, attack_report
def safe_generate_regular_report():
# Local import to avoid circular imports
from monkey_island.cc.services.reporting.report import ReportService
- __regular_report_generating_lock.acquire()
- report = ReportService.generate_report()
- __regular_report_generating_lock.release()
+ try:
+ __regular_report_generating_lock.acquire()
+ report = ReportService.generate_report()
+ finally:
+ __regular_report_generating_lock.release()
return report
def safe_generate_attack_report():
# Local import to avoid circular imports
from monkey_island.cc.services.attack.attack_report import AttackReportService
- __attack_report_generating_lock.acquire()
- attack_report = AttackReportService.generate_new_report()
- __attack_report_generating_lock.release()
+ try:
+ __attack_report_generating_lock.acquire()
+ attack_report = AttackReportService.generate_new_report()
+ finally:
+ __attack_report_generating_lock.release()
return attack_report
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/state.py b/monkey/monkey_island/cc/services/telemetry/processing/state.py
index 4e164e900..b7e341483 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/state.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/state.py
@@ -1,9 +1,14 @@
+import logging
+
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.node import NodeService
from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import \
test_passed_findings_for_unreached_segments
+logger = logging.getLogger(__name__)
+
+
def process_state_telemetry(telemetry_json):
monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
NodeService.add_communication_info(monkey, telemetry_json['command_control_channel'])
@@ -15,3 +20,6 @@ def process_state_telemetry(telemetry_json):
if telemetry_json['data']['done']:
current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
test_passed_findings_for_unreached_segments(current_monkey)
+
+ if telemetry_json['data']['version']:
+ logger.info(f"monkey {telemetry_json['monkey_guid']} has version {telemetry_json['data']['version']}")
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py
index e43581c43..75c806ea5 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py
@@ -1,25 +1,23 @@
import logging
-from monkey_island.cc.database import mongo
-from monkey_island.cc.models import Monkey
-from monkey_island.cc.services import mimikatz_utils
-from monkey_island.cc.services.node import NodeService
-from monkey_island.cc.services.config import ConfigService
-from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence
-from monkey_island.cc.services.wmi_handler import WMIHandler
from monkey_island.cc.encryptor import encryptor
+from monkey_island.cc.services import mimikatz_utils
+from monkey_island.cc.services.config import ConfigService
+from monkey_island.cc.services.node import NodeService
+from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \
+ SystemInfoTelemetryDispatcher
+from monkey_island.cc.services.wmi_handler import WMIHandler
logger = logging.getLogger(__name__)
def process_system_info_telemetry(telemetry_json):
+ dispatcher = SystemInfoTelemetryDispatcher()
telemetry_processing_stages = [
process_ssh_info,
process_credential_info,
process_mimikatz_and_wmi_info,
- process_aws_data,
- update_db_with_new_hostname,
- test_antivirus_existence,
+ dispatcher.dispatch_collector_results_to_relevant_processors
]
# Calling safe_process_telemetry so if one of the stages fail, we log and move on instead of failing the rest of
@@ -34,7 +32,7 @@ def safe_process_telemetry(processing_function, telemetry_json):
processing_function(telemetry_json)
except Exception as err:
logger.error(
- "Error {} while in {} stage of processing telemetry.".format(str(err), processing_function.func_name),
+ "Error {} while in {} stage of processing telemetry.".format(str(err), processing_function.__name__),
exc_info=True)
@@ -104,14 +102,3 @@ def process_mimikatz_and_wmi_info(telemetry_json):
wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets)
wmi_handler.process_and_handle_wmi_info()
-
-def process_aws_data(telemetry_json):
- if 'aws' in telemetry_json['data']:
- if 'instance_id' in telemetry_json['data']['aws']:
- monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id')
- mongo.db.monkey.update_one({'_id': monkey_id},
- {'$set': {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}})
-
-
-def update_db_with_new_hostname(telemetry_json):
- Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']).set_hostname(telemetry_json['data']['hostname'])
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/__init__.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py
new file mode 100644
index 000000000..2b4d8085e
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py
@@ -0,0 +1,15 @@
+import logging
+
+from monkey_island.cc.models.monkey import Monkey
+
+logger = logging.getLogger(__name__)
+
+
+def process_aws_telemetry(collector_results, monkey_guid):
+ relevant_monkey = Monkey.get_single_monkey_by_guid(monkey_guid)
+
+ if "instance_id" in collector_results:
+ instance_id = collector_results["instance_id"]
+ relevant_monkey.aws_instance_id = instance_id
+ relevant_monkey.save()
+ logger.debug("Updated Monkey {} with aws instance id {}".format(str(relevant_monkey), instance_id))
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py
new file mode 100644
index 000000000..4c685a01b
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py
@@ -0,0 +1,12 @@
+import logging
+
+from monkey_island.cc.models.monkey import Monkey
+
+logger = logging.getLogger(__name__)
+
+
+def process_environment_telemetry(collector_results, monkey_guid):
+ relevant_monkey = Monkey.get_single_monkey_by_guid(monkey_guid)
+ relevant_monkey.environment = collector_results["environment"]
+ relevant_monkey.save()
+ logger.debug("Updated Monkey {} with env {}".format(str(relevant_monkey), collector_results))
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py
new file mode 100644
index 000000000..e2de4519c
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py
@@ -0,0 +1,9 @@
+import logging
+
+from monkey_island.cc.models.monkey import Monkey
+
+logger = logging.getLogger(__name__)
+
+
+def process_hostname_telemetry(collector_results, monkey_guid):
+ Monkey.get_single_monkey_by_guid(monkey_guid).set_hostname(collector_results["hostname"])
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py
new file mode 100644
index 000000000..b5f2d24ea
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py
@@ -0,0 +1,64 @@
+import logging
+import typing
+
+from common.data.system_info_collectors_names \
+ import AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR
+from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry
+from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import process_environment_telemetry
+from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry
+from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence
+
+logger = logging.getLogger(__name__)
+
+SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = {
+ AWS_COLLECTOR: [process_aws_telemetry],
+ ENVIRONMENT_COLLECTOR: [process_environment_telemetry],
+ HOSTNAME_COLLECTOR: [process_hostname_telemetry],
+ PROCESS_LIST_COLLECTOR: [test_antivirus_existence]
+}
+
+
+class SystemInfoTelemetryDispatcher(object):
+ def __init__(self, collector_to_parsing_functions: typing.Mapping[str, typing.List[typing.Callable]] = None):
+ """
+ :param collector_to_parsing_functions: Map between collector names and a list of functions that process the output of
+ that collector. If `None` is supplied, uses the default one; This should be the normal flow, overriding the
+ collector->functions mapping is useful mostly for testing.
+ """
+ if collector_to_parsing_functions is None:
+ collector_to_parsing_functions = SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS
+ self.collector_to_processing_functions = collector_to_parsing_functions
+
+ def dispatch_collector_results_to_relevant_processors(self, telemetry_json):
+ """
+ If the telemetry has collectors' results, dispatches the results to the relevant processing functions.
+ :param telemetry_json: Telemetry sent from the Monkey
+ """
+ if "collectors" in telemetry_json["data"]:
+ self.dispatch_single_result_to_relevant_processor(telemetry_json)
+
+ def dispatch_single_result_to_relevant_processor(self, telemetry_json):
+ relevant_monkey_guid = telemetry_json['monkey_guid']
+
+ for collector_name, collector_results in telemetry_json["data"]["collectors"].items():
+ self.dispatch_result_of_single_collector_to_processing_functions(
+ collector_name,
+ collector_results,
+ relevant_monkey_guid)
+
+ def dispatch_result_of_single_collector_to_processing_functions(
+ self,
+ collector_name,
+ collector_results,
+ relevant_monkey_guid):
+ if collector_name in self.collector_to_processing_functions:
+ for processing_function in self.collector_to_processing_functions[collector_name]:
+ # noinspection PyBroadException
+ try:
+ processing_function(collector_results, relevant_monkey_guid)
+ except Exception as e:
+ logger.error(
+ "Error {} while processing {} system info telemetry".format(str(e), collector_name),
+ exc_info=True)
+ else:
+ logger.warning("Unknown system info collector name: {}".format(collector_name))
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py
new file mode 100644
index 000000000..f85b2b88c
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py
@@ -0,0 +1,31 @@
+import uuid
+
+from monkey_island.cc.models import Monkey
+from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \
+ SystemInfoTelemetryDispatcher
+from monkey_island.cc.testing.IslandTestCase import IslandTestCase
+
+
+class TestEnvironmentTelemetryProcessing(IslandTestCase):
+ def test_process_environment_telemetry(self):
+ self.fail_if_not_testing_env()
+ self.clean_monkey_db()
+
+ # Arrange
+ monkey_guid = str(uuid.uuid4())
+ a_monkey = Monkey(guid=monkey_guid)
+ a_monkey.save()
+ dispatcher = SystemInfoTelemetryDispatcher()
+
+ on_premise = "On Premise"
+ telem_json = {
+ "data": {
+ "collectors": {
+ "EnvironmentCollector": {"environment": on_premise},
+ }
+ },
+ "monkey_guid": monkey_guid
+ }
+ dispatcher.dispatch_collector_results_to_relevant_processors(telem_json)
+
+ self.assertEqual(Monkey.get_single_monkey_by_guid(monkey_guid).environment, on_premise)
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py
new file mode 100644
index 000000000..c5cc7aca2
--- /dev/null
+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py
@@ -0,0 +1,57 @@
+import uuid
+
+from monkey_island.cc.models import Monkey
+from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \
+ SystemInfoTelemetryDispatcher
+from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \
+ process_aws_telemetry
+from monkey_island.cc.testing.IslandTestCase import IslandTestCase
+
+TEST_SYS_INFO_TO_PROCESSING = {
+ "AwsCollector": [process_aws_telemetry],
+}
+
+
+class SystemInfoTelemetryDispatcherTest(IslandTestCase):
+ def test_dispatch_to_relevant_collector_bad_inputs(self):
+ self.fail_if_not_testing_env()
+
+ dispatcher = SystemInfoTelemetryDispatcher(TEST_SYS_INFO_TO_PROCESSING)
+
+ # Bad format telem JSONs - throws
+ bad_empty_telem_json = {}
+ self.assertRaises(KeyError, dispatcher.dispatch_collector_results_to_relevant_processors, bad_empty_telem_json)
+ bad_no_data_telem_json = {"monkey_guid": "bla"}
+ self.assertRaises(KeyError, dispatcher.dispatch_collector_results_to_relevant_processors, bad_no_data_telem_json)
+ bad_no_monkey_telem_json = {"data": {"collectors": {"AwsCollector": "Bla"}}}
+ self.assertRaises(KeyError, dispatcher.dispatch_collector_results_to_relevant_processors, bad_no_monkey_telem_json)
+
+ # Telem JSON with no collectors - nothing gets dispatched
+ good_telem_no_collectors = {"monkey_guid": "bla", "data": {"bla": "bla"}}
+ good_telem_empty_collectors = {"monkey_guid": "bla", "data": {"bla": "bla", "collectors": {}}}
+
+ dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_no_collectors)
+ dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_empty_collectors)
+
+ def test_dispatch_to_relevant_collector(self):
+ self.fail_if_not_testing_env()
+ self.clean_monkey_db()
+
+ a_monkey = Monkey(guid=str(uuid.uuid4()))
+ a_monkey.save()
+
+ dispatcher = SystemInfoTelemetryDispatcher()
+
+ # JSON with results - make sure functions are called
+ instance_id = "i-0bd2c14bd4c7d703f"
+ telem_json = {
+ "data": {
+ "collectors": {
+ "AwsCollector": {"instance_id": instance_id},
+ }
+ },
+ "monkey_guid": a_monkey.guid
+ }
+ dispatcher.dispatch_collector_results_to_relevant_processors(telem_json)
+
+ self.assertEquals(Monkey.get_single_monkey_by_guid(a_monkey.guid).aws_instance_id, instance_id)
diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py
index ddc1af65b..1916291e2 100644
--- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py
+++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py
@@ -7,36 +7,36 @@ from monkey_island.cc.models.zero_trust.event import Event
from monkey_island.cc.services.telemetry.zero_trust_tests.known_anti_viruses import ANTI_VIRUS_KNOWN_PROCESS_NAMES
-def test_antivirus_existence(telemetry_json):
- current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
- if 'process_list' in telemetry_json['data']:
- process_list_event = Event.create_event(
- title="Process list",
- message="Monkey on {} scanned the process list".format(current_monkey.hostname),
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL)
- events = [process_list_event]
+def test_antivirus_existence(process_list_json, monkey_guid):
+ current_monkey = Monkey.get_single_monkey_by_guid(monkey_guid)
- av_processes = filter_av_processes(telemetry_json)
+ process_list_event = Event.create_event(
+ title="Process list",
+ message="Monkey on {} scanned the process list".format(current_monkey.hostname),
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL)
+ events = [process_list_event]
- for process in av_processes:
- events.append(Event.create_event(
- title="Found AV process",
- message="The process '{}' was recognized as an Anti Virus process. Process "
- "details: {}".format(process[1]['name'], json.dumps(process[1])),
- event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL
- ))
+ av_processes = filter_av_processes(process_list_json["process_list"])
- if len(av_processes) > 0:
- test_status = zero_trust_consts.STATUS_PASSED
- else:
- test_status = zero_trust_consts.STATUS_FAILED
- AggregateFinding.create_or_add_to_existing(
- test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events
- )
+ for process in av_processes:
+ events.append(Event.create_event(
+ title="Found AV process",
+ message="The process '{}' was recognized as an Anti Virus process. Process "
+ "details: {}".format(process[1]['name'], json.dumps(process[1])),
+ event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL
+ ))
+
+ if len(av_processes) > 0:
+ test_status = zero_trust_consts.STATUS_PASSED
+ else:
+ test_status = zero_trust_consts.STATUS_FAILED
+ AggregateFinding.create_or_add_to_existing(
+ test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events
+ )
-def filter_av_processes(telemetry_json):
- all_processes = list(telemetry_json['data']['process_list'].items())
+def filter_av_processes(process_list):
+ all_processes = list(process_list.items())
av_processes = []
for process in all_processes:
process_name = process[1]['name']
diff --git a/monkey/monkey_island/cc/services/version_update.py b/monkey/monkey_island/cc/services/version_update.py
index c1dab52a9..ddd60d5c0 100644
--- a/monkey/monkey_island/cc/services/version_update.py
+++ b/monkey/monkey_island/cc/services/version_update.py
@@ -2,6 +2,7 @@ import logging
import requests
+from common.version import get_version
from monkey_island.cc.environment.environment import env
__author__ = "itay.mizeretz"
@@ -39,7 +40,7 @@ class VersionUpdateService:
Checks if newer monkey version is available
:return: False if not, version in string format ('1.6.2') otherwise
"""
- url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (env.get_deployment(), env.get_version())
+ url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (env.get_deployment(), get_version())
reply = requests.get(url, timeout=15)
@@ -53,4 +54,4 @@ class VersionUpdateService:
@staticmethod
def get_download_link():
- return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % (env.get_deployment(), env.get_version())
+ return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % (env.get_deployment(), get_version())
diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js
index 2c54822a5..b4325e55c 100644
--- a/monkey/monkey_island/cc/ui/src/components/Main.js
+++ b/monkey/monkey_island/cc/ui/src/components/Main.js
@@ -30,7 +30,7 @@ let infectionMonkeyImage = require('../images/infection-monkey.svg');
let guardicoreLogoImage = require('../images/guardicore-logo.png');
let notificationIcon = require('../images/notification-logo-512x512.png');
-const reportZeroTrustRoute = '/report/zero_trust';
+const reportZeroTrustRoute = '/report/zeroTrust';
class AppComponent extends AuthComponent {
updateStatus = () => {
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
index 6d9325487..a49e198a6 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
@@ -30,7 +30,7 @@ class ConfigurePageComponent extends AuthComponent {
lastAction: 'none',
sections: [],
selectedSection: 'attack',
- allMonkeysAreDead: true,
+ monkeysRan: false,
PBAwinFile: [],
PBAlinuxFile: [],
showAttackAlert: false
@@ -363,13 +363,7 @@ class ConfigurePageComponent extends AuthComponent {
this.authFetch('/api')
.then(res => res.json())
.then(res => {
- // This check is used to prevent unnecessary re-rendering
- let allMonkeysAreDead = (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']);
- if (allMonkeysAreDead !== this.state.allMonkeysAreDead) {
- this.setState({
- allMonkeysAreDead: allMonkeysAreDead
- });
- }
+ this.setState({monkeysRan: res['completed_steps']['run_monkey']});
});
};
@@ -470,15 +464,15 @@ class ConfigurePageComponent extends AuthComponent {
)
};
- renderRunningMonkeysWarning = () => {
+ renderConfigWontChangeWarning = () => {
return (