diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ad91472..e21e31019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - The capability for a user to specify their own SSL certificate. #1208 - API endpoint for ransomware report. #1297 - Add ransomware report. #1240 +- A script to build a docker image locally. #1140 ### Changed - server_config.json can be selected at runtime. #963 diff --git a/appimage/README.md b/appimage/README.md index dfa4731a0..bbf6a61e8 100644 --- a/appimage/README.md +++ b/appimage/README.md @@ -9,25 +9,17 @@ Monkey AppImage 1. Create a clean VM or LXC (not docker!) based on Ubuntu 18.04. 1. Copy the `deployment_scripts/appimage` directory to `$HOME/` in the VM. -1. Run `sudo -v`. 1. On the VM, `cd $HOME/appimage` +1. Run `sudo -v`. 1. Execute `./build_appimage.sh`. This will pull all necessary dependencies and build the AppImage. NOTE: This script is intended to be run from a clean VM. You can also manually -remove build artifacts by removing the following files and directories. - -- $HOME/.monkey_island (optional) -- $HOME/appimage/squashfs-root -- $HOME/git/monkey -- $HOME/appimage/Infection_Monkey*x86_64.AppImage - -After removing the above files and directories, you can again execute `bash -build_appimage.sh`. +remove build artifacts by running `appimage/clean.sh` ## Running the AppImage -The build script will produce an AppImage executible named +The build script will produce an AppImage executable named `Infection_Monkey-x86_64.AppImage`. Simply execute this file and you're off to the races. diff --git a/docker/DOCKER_README.md b/docker/DOCKER_README.md new file mode 100644 index 000000000..e2b1f97d0 --- /dev/null +++ b/docker/DOCKER_README.md @@ -0,0 +1,21 @@ +# Infection Monkey + +How to run Monkey Island from the docker file: + +Note: Ports 5000 and 5001 must be available for the island to work. + +## Setup + +Run the following commands: + +```sh +sudo docker load -i dk.monkeyisland.MONKEY_VER_PLACEHOLDER.tar +sudo docker pull mongo:4.2 +sudo mkdir -p /var/monkey-mongo/data/db +sudo docker run --name monkey-mongo --network=host -v /var/monkey-mongo/data/db:/data/db -d mongo:4.2 +sudo docker run --name monkey-island --network=host -d guardicore/monkey-island:MONKEY_VER_PLACEHOLDER +``` + +## Start Infecting + +Open `https://:5000` using Google Chrome and follow the instructions. You can also visit [the Infection Monkey website](https://infectionmonkey.com) and read the in-depth Getting Started guides. diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..2637d3725 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,27 @@ +# Install python dependencies using the bitnami/python:3.7 image, which includes +# development dependencies. +FROM bitnami/python:3.7 as builder +COPY ./monkey /monkey +WORKDIR /monkey +RUN virtualenv . +RUN . bin/activate && \ + cd monkey_island && \ + pip install pipenv && \ + pipenv sync + + +# Build the final application using the bitnami/python:3.7-prod image, which +# does not include development dependencies. +FROM bitnami/python:3.7-prod +RUN apt-get update && apt-get install -y iputils-ping && apt-get clean +COPY --from=builder /monkey /monkey +WORKDIR /monkey +EXPOSE 5000 +EXPOSE 5001 +RUN groupadd -r monkey-island && useradd --no-log-init -r -g monkey-island monkey-island +RUN chmod 444 /monkey/monkey_island/cc/server.key +RUN chmod 444 /monkey/monkey_island/cc/server.csr +RUN chmod 444 /monkey/monkey_island/cc/server.crt +RUN mkdir /monkey_island_data && chmod 700 /monkey_island_data && chown -R monkey-island:monkey-island /monkey_island_data +USER monkey-island +ENTRYPOINT ["/monkey/entrypoint.sh"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..d01890b23 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,20 @@ +# Monkey Island Docker Image + +## About + +This directory contains the necessary artifacts for building an Infection +Monkey Docker image. + +## Building a Docker image +1. Create a clean Ubuntu 18.04 VM (not WSL). +1. Copy the `docker/` directory to `$HOME/` in the VM. +1. On the VM, `cd $HOME/docker` +1. Run `sudo -v`. +1. Execute `./build_docker.sh`. This will pull all necessary dependencies + and build the Docker image. + +NOTE: This script is intended to be run from a clean VM. You can also manually +remove build rtifacts by running `docker/clean.sh` + +## Running the Docker Image +See `docker/DOCKER_README.md` for instructions on running the docker image. diff --git a/docker/build_docker.sh b/docker/build_docker.sh new file mode 100755 index 000000000..797e8d06d --- /dev/null +++ b/docker/build_docker.sh @@ -0,0 +1,316 @@ +WORKSPACE=${WORKSPACE:-$HOME} + +BUILD_DIR="$PWD/monkey" + +GIT=$WORKSPACE/git + +DEFAULT_REPO_MONKEY_HOME=$GIT/monkey + +ISLAND_PATH="$BUILD_DIR/monkey_island" +ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries" + +MONKEY_ORIGIN_URL="https://github.com/guardicore/monkey.git" +CONFIG_URL="https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/config" +NODE_SRC=https://deb.nodesource.com/setup_12.x +ISLAND_DIR_COPY_TIMEOUT=60 #Seconds + +OUTPUT_NAME_TGZ="$PWD/infection_monkey_docker_$(date +%Y%m%d_%H%M%S).tgz" + +exit_if_missing_argument() { + if [ -z "$2" ] || [ "${2:0:1}" == "-" ]; then + echo "Error: Argument for $1 is missing" >&2 + exit 1 + fi +} + +echo_help() { + echo "usage: build_appimage.sh [--help] [--agent-binary-dir ] [--branch ]" + echo " [--monkey-repo ] [--version ]" + echo "" + echo "Creates an AppImage package for Infection Monkey." + echo "" + echo "--agent-binary-dir A directory containing the agent binaries that" + echo " you'd like to include with the AppImage. If this" + echo " parameter is unspecified, the latest release" + echo " binaries will be downloaded from GitHub." + echo "" + echo "--as-root Throw caution to the wind and allow this script" + echo " to be run as root." + echo "" + echo "--branch The git branch you'd like the AppImage to be" + echo " built from. (Default: develop)" + echo "" + echo "--monkey-repo A directory containing the Infection Monkey git" + echo " repository. If the directory is empty or does" + echo " not exist, a new repo will be cloned from GitHub." + echo " If the directory is already a valid GitHub repo," + echo " it will be used as-is and the --branch parameter" + echo " will have no effect." + echo " (Default: $DEFAULT_REPO_MONKEY_HOME)" + echo "" + echo "--version A version number for the AppImage package." + echo " (Default: dev)" + + exit 0 +} + +is_root() { + return "$(id -u)" +} + +has_sudo() { + # 0 true, 1 false + sudo -nv > /dev/null 2>&1 + return $? +} + +log_message() { + echo -e "\n\n" + echo -e "DOCKER IMAGE BUILDER: $1" +} + +handle_error() { + echo "Fix the errors above and rerun the script" + exit 1 +} + +install_nodejs() { + log_message "Installing nodejs" + + curl -sL $NODE_SRC | sudo -E bash - + sudo apt-get install -y nodejs +} + +install_build_prereqs() { + sudo apt-get update + sudo apt-get upgrade -y + + # monkey island prereqs + sudo apt-get install -y curl libcurl4 openssl git build-essential moreutils + install_nodejs +} + +install_docker() { + sudo apt-get install -y docker.io +} + +clone_monkey_repo() { + local repo_dir=$1 + local branch=$2 + + if [[ ! -d "$repo_dir" ]]; then + mkdir -p "$repo_dir" + fi + + log_message "Cloning files from git" + git clone -c core.autocrlf=false --single-branch --recurse-submodules -b "$branch" "$MONKEY_ORIGIN_URL" "$repo_dir" 2>&1 || handle_error +} + +is_valid_git_repo() { + pushd "$1" 2>/dev/null || return 1 + git status >/dev/null 2>&1 + success="$?" + popd || exit 1 + + return $success +} + +setup_build_dir() { + local agent_binary_dir=$1 + local monkey_repo=$2 + + mkdir "$BUILD_DIR" + + copy_entrypoint_to_build_dir + + copy_monkey_island_to_build_dir "$monkey_repo/monkey" + add_agent_binaries_to_build_dir "$agent_binary_dir" + + generate_ssl_cert + + build_frontend +} + +copy_entrypoint_to_build_dir() { + cp ./entrypoint.sh "$BUILD_DIR" + chmod 755 "$BUILD_DIR/entrypoint.sh" +} + +copy_monkey_island_to_build_dir() { + local src=$1 + + cp "$src"/__init__.py "$BUILD_DIR" + cp "$src"/monkey_island.py "$BUILD_DIR" + cp -v -r "$src"/common "$BUILD_DIR/" + + rsync \ + -avr \ + --exclude=monkey_island/cc/ui/node_modules \ + --exclude=monkey_island/cc/ui/.npm \ + "$src"/monkey_island "$BUILD_DIR/" + + cp ./server_config.json "$BUILD_DIR"/monkey_island/cc/ +} + +add_agent_binaries_to_build_dir() { + local agent_binary_dir=$1 + + if [ -z "$agent_binary_dir" ]; then + download_monkey_agent_binaries + else + copy_agent_binaries_to_appdir "$agent_binary_dir" + fi + + make_linux_binaries_executable +} + +download_monkey_agent_binaries() { + log_message "Downloading monkey agent binaries to ${ISLAND_BINARIES_PATH}" + + load_monkey_binary_config + + mkdir -p "${ISLAND_BINARIES_PATH}" || handle_error + curl -L -o "${ISLAND_BINARIES_PATH}/${LINUX_32_BINARY_NAME}" "${LINUX_32_BINARY_URL}" + curl -L -o "${ISLAND_BINARIES_PATH}/${LINUX_64_BINARY_NAME}" "${LINUX_64_BINARY_URL}" + curl -L -o "${ISLAND_BINARIES_PATH}/${WINDOWS_32_BINARY_NAME}" "${WINDOWS_32_BINARY_URL}" + curl -L -o "${ISLAND_BINARIES_PATH}/${WINDOWS_64_BINARY_NAME}" "${WINDOWS_64_BINARY_URL}" +} + +load_monkey_binary_config() { + tmpfile=$(mktemp) + + log_message "Downloading prebuilt binary configuration" + curl -L -s -o "$tmpfile" "$CONFIG_URL" + + log_message "Loading configuration" + source "$tmpfile" +} + +copy_agent_binaries_to_appdir() { + cp "$1"/* "$ISLAND_BINARIES_PATH/" +} + +make_linux_binaries_executable() { + chmod a+x "$ISLAND_BINARIES_PATH"/monkey-linux-* +} + +generate_ssl_cert() { + log_message "Generating certificate" + + chmod u+x "${ISLAND_PATH}"/linux/create_certificate.sh + "${ISLAND_PATH}"/linux/create_certificate.sh "${ISLAND_PATH}"/cc +} + +build_frontend() { + pushd "$ISLAND_PATH/cc/ui" || handle_error + + log_message "Generating front end" + npm ci + npm run dist + + popd || handle_error + + remove_node_modules +} + +remove_node_modules() { + # Node has served its purpose. We don't need to deliver the node modules with + # the AppImage. + rm -rf "$ISLAND_PATH"/cc/ui/node_modules + rm -rf "$ISLAND_PATH"/cc/ui/.npm +} + +build_docker_image() { + local version=$1 + + docker_image_name=guardicore/monkey-island:$version + tar_name=./dk.monkeyisland.$version.tar + + build_docker_image_tar "$docker_image_name" "$tar_name" + build_docker_image_tgz "$tar_name" "$version" +} + +build_docker_image_tar() { + sudo docker build . -t "$1" + sudo docker save "$1" > "$2" +} + +build_docker_image_tgz() { + mkdir tgz + cp "$1" ./tgz + cp ./DOCKER_README.md ./tgz/README.md + tar -C ./tgz -cvf "$OUTPUT_NAME_TGZ" --gzip . +} + +agent_binary_dir="" +as_root=false +branch="develop" +monkey_repo="$DEFAULT_REPO_MONKEY_HOME" +monkey_version="dev" + + +while (( "$#" )); do + case "$1" in + --agent-binary-dir) + exit_if_missing_argument "$1" "$2" + + agent_binary_dir=$2 + shift 2 + ;; + --as-root) + as_root=true + shift + ;; + --branch) + exit_if_missing_argument "$1" "$2" + + branch=$2 + shift 2 + ;; + -h|--help) + echo_help + ;; + --monkey-repo) + exit_if_missing_argument "$1" "$2" + + monkey_repo=$2 + shift 2 + ;; + --version) + exit_if_missing_argument "$1" "$2" + + monkey_version=$2 + shift 2 + ;; + *) + echo "Error: Unsupported parameter $1" >&2 + exit 1 + ;; + esac +done + +log_message "Building Monkey Island Docker image." + +if ! $as_root && is_root; then + log_message "Please don't run this script as root" + exit 1 +fi + +if ! has_sudo; then + log_message "You need root permissions for some of this script operations. \ +Run \`sudo -v\`, enter your password, and then re-run this script." + exit 1 +fi + +install_build_prereqs +install_docker + +if ! is_valid_git_repo "$monkey_repo"; then + clone_monkey_repo "$monkey_repo" "$branch" +fi + +setup_build_dir "$agent_binary_dir" "$monkey_repo" +build_docker_image "$monkey_version" + +log_message "Docker build script finished." +exit 0 diff --git a/docker/clean.sh b/docker/clean.sh new file mode 100755 index 000000000..f5cfacc28 --- /dev/null +++ b/docker/clean.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# This is a utility script to clean up after a failed or successful Docker +# image build in order to speed up development and debugging + +BUILD_DIR=$HOME/docker + +rm -rf $HOME/git/monkey +rm -rf $BUILD_DIR/monkey +rm -rf $BUILD_DIR/tgz +rm $BUILD_DIR/dk.monkeyisland.*.tar +rm $BUILD_DIR/infection_monkey_docker*.tgz diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 000000000..069d3619c --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +echo "$@" + +source /monkey/bin/activate +python /monkey/monkey_island.py "$@" diff --git a/docker/server_config.json b/docker/server_config.json new file mode 100644 index 000000000..77e5d9855 --- /dev/null +++ b/docker/server_config.json @@ -0,0 +1,11 @@ +{ + "data_dir": "/monkey_island_data", + "log_level": "DEBUG", + "environment": { + "server_config": "password", + "deployment": "docker" + }, + "mongodb": { + "start_mongodb": false + } +}