From 34b47a07d44f67a957aa4182c0174abfd8965525 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 26 Jul 2021 12:04:40 -0400 Subject: [PATCH] Docker: Add a script to build monkey island docker locally --- docker/DOCKER_README.md | 21 +++ docker/Dockerfile | 27 ++++ docker/build_docker.sh | 310 ++++++++++++++++++++++++++++++++++++++ docker/clean.sh | 12 ++ docker/entrypoint.sh | 6 + docker/server_config.json | 11 ++ 6 files changed, 387 insertions(+) create mode 100644 docker/DOCKER_README.md create mode 100644 docker/Dockerfile create mode 100755 docker/build_docker.sh create mode 100755 docker/clean.sh create mode 100755 docker/entrypoint.sh create mode 100644 docker/server_config.json 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/build_docker.sh b/docker/build_docker.sh new file mode 100755 index 000000000..6aad00bed --- /dev/null +++ b/docker/build_docker.sh @@ -0,0 +1,310 @@ +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 -r "$src"/common "$BUILD_DIR/" + if ! timeout "${ISLAND_DIR_COPY_TIMEOUT}" cp -r "$src"/monkey_island "$BUILD_DIR/"; then + log_message "Copying island files takes too long. Maybe you're copying a dev folder instead of a fresh repository?" + exit 1 + fi + 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 +} + +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 + } +}