forked from p15670423/monkey
Merge branch 'custom-certificate' into develop
This commit is contained in:
commit
1c5daf168c
|
@ -7,27 +7,125 @@ weight: 4
|
|||
tags: ["setup", "docker", "linux", "windows"]
|
||||
---
|
||||
|
||||
## Supported operating systems
|
||||
|
||||
The Infection Monkey Docker container works on Linux only. It is not compatible with Docker for Windows or Docker for Mac.
|
||||
|
||||
## Deployment
|
||||
|
||||
### Linux
|
||||
### 1. Load the docker images
|
||||
1. Pull the MongoDB v4.2 Docker image:
|
||||
|
||||
To extract the `tar.gz` file, run `tar -xvzf monkey-island-docker.tar.gz`.
|
||||
```bash
|
||||
sudo docker pull mongo:4.2
|
||||
```
|
||||
|
||||
Once you've extracted the container from the tar.gz file, run the following commands:
|
||||
1. Extract the Monkey Island Docker tarball:
|
||||
|
||||
```sh
|
||||
sudo docker load -i dk.monkeyisland.1.10.0.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:1.10.0
|
||||
```
|
||||
```bash
|
||||
tar -xvzf monkey-island-docker.tar.gz
|
||||
```
|
||||
|
||||
Wait until the Island is done setting up and it will be available on https://localhost:5000
|
||||
1. Load the Monkey Island Docker image:
|
||||
|
||||
### Windows and Mac OS X
|
||||
```bash
|
||||
sudo docker load -i dk.monkeyisland.1.10.0.tar
|
||||
```
|
||||
|
||||
Not supported yet, since docker doesn't support `--network=host` parameter on these OS's.
|
||||
### 2. Start MongoDB
|
||||
|
||||
1. Start a MongoDB Docker container:
|
||||
|
||||
```bash
|
||||
sudo docker run \
|
||||
--name monkey-mongo \
|
||||
--network=host \
|
||||
--volume db:/data/db \
|
||||
--detach mongo:4.2
|
||||
```
|
||||
|
||||
### 3a. Start Monkey Island with default certificate
|
||||
|
||||
By default, Infection Monkey comes with a [self-signed SSL certificate](https://aboutssl.org/what-is-self-sign-certificate/). In
|
||||
enterprise or other security-sensitive environments, it is recommended that the
|
||||
user [provide Infection Monkey with a
|
||||
certificate](#3b-start-monkey-island-with-user-provided-certificate) that has
|
||||
been signed by a private certificate authority.
|
||||
|
||||
1. Run the Monkey Island server
|
||||
```bash
|
||||
sudo docker run \
|
||||
--name monkey-island \
|
||||
--network=host \
|
||||
guardicore/monkey-island:1.10.0
|
||||
```
|
||||
|
||||
### 3b. Start Monkey Island with user-provided certificate
|
||||
|
||||
1. Create a directory named `monkey_island_data`. This will serve as the
|
||||
location where Infection Monkey stores its configuration and runtime
|
||||
artifacts.
|
||||
|
||||
```bash
|
||||
mkdir ./monkey_island_data
|
||||
```
|
||||
|
||||
1. Run Monkey Island with the `--setup-only` flag to populate the `./monkey_island_data` directory with a default `server_config.json` file.
|
||||
|
||||
```bash
|
||||
sudo docker run \
|
||||
--rm \
|
||||
--name monkey-island \
|
||||
--network=host \
|
||||
--user $(id -u ${USER}):$(id -g ${USER}) \
|
||||
--volume "$(realpath ./monkey_island_data)":/monkey_island_data \
|
||||
guardicore/monkey-island:1.10.0 --setup-only
|
||||
```
|
||||
|
||||
1. (Optional but recommended) Move your `.crt` and `.key` files to `./monkey_island_data`.
|
||||
|
||||
1. Make sure that your `.crt` and `.key` files are read-only and readable only by you.
|
||||
|
||||
```bash
|
||||
chmod 400 <PATH_TO_KEY_FILE>
|
||||
chmod 400 <PATH_TO_CRT_FILE>
|
||||
```
|
||||
|
||||
1. Edit `./monkey_island_data/server_config.json` to configure Monkey Island
|
||||
to use your certificate. Your config should look something like this:
|
||||
|
||||
```json {linenos=inline,hl_lines=["11-14"]}
|
||||
{
|
||||
"data_dir": "/monkey_island_data",
|
||||
"log_level": "DEBUG",
|
||||
"environment": {
|
||||
"server_config": "password",
|
||||
"deployment": "docker"
|
||||
},
|
||||
"mongodb": {
|
||||
"start_mongodb": false
|
||||
},
|
||||
"ssl_certificate": {
|
||||
"ssl_certificate_file": "<PATH_TO_CRT_FILE>",
|
||||
"ssl_certificate_key_file": "<PATH_TO_KEY_FILE>",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. Start the Monkey Island server:
|
||||
|
||||
```bash
|
||||
sudo docker run \
|
||||
--name monkey-island \
|
||||
--network=host \
|
||||
--user $(id -u ${USER}):$(id -g ${USER}) \
|
||||
--volume "$(realpath ./monkey_island_data)":/monkey_island_data \
|
||||
guardicore/monkey-island:1.10.0
|
||||
```
|
||||
|
||||
### 4. Accessing Monkey Island
|
||||
|
||||
After the Monkey Island docker container starts, you can access Monkey Island by pointing your browser at `https://localhost:5000`.
|
||||
|
||||
## Upgrading
|
||||
|
||||
|
@ -43,12 +141,27 @@ using the *Export config* button and then import it to the new Monkey Island.
|
|||
## Troubleshooting
|
||||
|
||||
### The Monkey Island container crashes due to a 'UnicodeDecodeError'
|
||||
`UnicodeDecodeError: 'utf-8' codec can't decode byte 0xee in position 0: invalid continuation byte`
|
||||
|
||||
You may encounter this error because of the existence of different MongoDB keys in the `monkey-island` and `monkey-mongo` containers.
|
||||
You will encounter a `UnicodeDecodeError` if the `monkey-island` container is
|
||||
using a different secret key to encrypt sensitive data than was initially used
|
||||
to store data in the `monkey-mongo` container.
|
||||
|
||||
Starting a new container from the `guardicore/monkey-island:1.10.0` image generates a new secret key for storing sensitive information in MongoDB. If you have an old database instance running (from a previous run of Monkey), the key in the `monkey-mongo` container is different than the newly generated key in the `monkey-island` container. Since encrypted data (obtained from the previous run) is stored in MongoDB with the old key, decryption fails and you get this error.
|
||||
```
|
||||
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xee in position 0: invalid continuation byte
|
||||
```
|
||||
|
||||
You can fix this in two ways:
|
||||
Starting a new container from the `guardicore/monkey-island:1.10.0` image
|
||||
generates a new secret key for storing sensitive information in MongoDB. If you
|
||||
have an old database instance running (from a previous instance of Infection
|
||||
Monkey), the data stored in the `monkey-mongo` container has been encrypted
|
||||
with a key that is different from the one that Monkey Island is currently
|
||||
using. When MongoDB attempts to decrypt its data with the new key, decryption
|
||||
fails and you get this error.
|
||||
|
||||
You can fix this in one of three ways:
|
||||
1. Instead of starting a new container for the Monkey Island, you can run `docker container start -a monkey-island` to restart the existing container, which will contain the correct key material.
|
||||
2. Kill and remove the existing MongoDB container, and start a new one. This will remove the old database entirely. Then, start the new Monkey Island container.
|
||||
1. Kill and remove the existing MongoDB container, and start a new one. This will remove the old database entirely. Then, start the new Monkey Island container.
|
||||
1. When you start the Monkey Island container, use `--volume
|
||||
monkey_island_data:/monkey_island_data`. This will store all of Monkey
|
||||
Island's runtime artifacts (including the encryption key file) in a docker
|
||||
volume that can be reused by subsequent Monkey Island containers.
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
---
|
||||
title: "Linux"
|
||||
date: 2020-05-26T20:57:28+03:00
|
||||
draft: false
|
||||
pre: '<i class="fab fa-linux"></i> '
|
||||
weight: 4
|
||||
tags: ["setup", "AppImage", "linux"]
|
||||
---
|
||||
|
||||
## Supported operating systems
|
||||
|
||||
## Deployment
|
||||
|
||||
1. Make the AppImage package executable:
|
||||
```bash
|
||||
chmod u+x Infection_Monkey_v1.11.0.AppImage
|
||||
```
|
||||
1. Start Monkey Island by running the Infection Monkey AppImage package:
|
||||
```bash
|
||||
./Infection_Monkey_v1.11.0.AppImage
|
||||
```
|
||||
1. Access the Monkey Island web UI by pointing your browser at
|
||||
`https://localhost:5000`.
|
||||
|
||||
### Start Monkey Island with user-provided certificate
|
||||
|
||||
By default, Infection Monkey comes with a [self-signed SSL
|
||||
certificate](https://aboutssl.org/what-is-self-sign-certificate/). In
|
||||
enterprise or other security-sensitive environments, it is recommended that the
|
||||
user provide Infection Monkey with a certificate that has been signed by a
|
||||
private certificate authority.
|
||||
|
||||
1. Run the Infection Monkey AppImage package with the `--setup-only` flag to
|
||||
populate the `$HOME/.monkey_island` directory with a default
|
||||
`server_config.json` file.
|
||||
|
||||
```bash
|
||||
./Infection_Monkey_v1.11.0.AppImage --setup-only
|
||||
```
|
||||
|
||||
1. (Optional but recommended) Move your `.crt` and `.key` files to
|
||||
`$HOME/.monkey_island`.
|
||||
|
||||
1. Make sure that your `.crt` and `.key` files are read-only and readable only
|
||||
by you.
|
||||
|
||||
```bash
|
||||
chmod 400 <PATH_TO_KEY_FILE>
|
||||
chmod 400 <PATH_TO_CRT_FILE>
|
||||
```
|
||||
|
||||
1. Edit `$HOME/.monkey_island/server_config.json` to configure Monkey Island
|
||||
to use your certificate. Your config should look something like this:
|
||||
|
||||
```json {linenos=inline,hl_lines=["11-14"]}
|
||||
{
|
||||
"data_dir": "~/.monkey_island",
|
||||
"log_level": "DEBUG",
|
||||
"environment": {
|
||||
"server_config": "password",
|
||||
"deployment": "linux"
|
||||
},
|
||||
"mongodb": {
|
||||
"start_mongodb": true
|
||||
},
|
||||
"ssl_certificate": {
|
||||
"ssl_certificate_file": "<PATH_TO_CRT_FILE>",
|
||||
"ssl_certificate_key_file": "<PATH_TO_KEY_FILE>",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. Start Monkey Island by running the Infection Monkey AppImage package:
|
||||
```bash
|
||||
./Infection_Monkey_v1.11.0.AppImage
|
||||
```
|
||||
|
||||
1. Access the Monkey Island web UI by pointing your browser at
|
||||
`https://localhost:5000`.
|
||||
|
||||
## Upgrading
|
|
@ -52,3 +52,7 @@ class FindingWithoutDetailsError(Exception):
|
|||
|
||||
class DomainControllerNameFetchError(FailedExploitationError):
|
||||
""" Raise on failed attempt to extract domain controller's name """
|
||||
|
||||
|
||||
class InsecurePermissionsError(Exception):
|
||||
""" Raise when a file does not have permissions that are secure enough """
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
|
@ -22,12 +21,12 @@ from monkey_island.cc.arg_parser import IslandCmdArgs # noqa: E402
|
|||
from monkey_island.cc.arg_parser import parse_cli_args # noqa: E402
|
||||
from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402
|
||||
from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402
|
||||
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH # noqa: E402
|
||||
from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402
|
||||
from monkey_island.cc.server_utils.island_logger import reset_logger, setup_logging # noqa: E402
|
||||
from monkey_island.cc.services.initialize import initialize_services # noqa: E402
|
||||
from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402
|
||||
from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402
|
||||
from monkey_island.cc.setup import island_config_options_validator # noqa: E402
|
||||
from monkey_island.cc.setup.island_config_options import IslandConfigOptions # noqa: E402
|
||||
from monkey_island.cc.setup.mongo.database_initializer import init_collections # noqa: E402
|
||||
from monkey_island.cc.setup.mongo.mongo_setup import ( # noqa: E402
|
||||
|
@ -44,6 +43,8 @@ def run_monkey_island():
|
|||
island_args = parse_cli_args()
|
||||
config_options, server_config_path = _setup_data_dir(island_args)
|
||||
|
||||
_exit_on_invalid_config_options(config_options)
|
||||
|
||||
_configure_logging(config_options)
|
||||
_initialize_globals(config_options, server_config_path)
|
||||
|
||||
|
@ -67,6 +68,14 @@ def _setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, st
|
|||
exit(1)
|
||||
|
||||
|
||||
def _exit_on_invalid_config_options(config_options: IslandConfigOptions):
|
||||
try:
|
||||
island_config_options_validator.raise_on_invalid_options(config_options)
|
||||
except Exception as ex:
|
||||
print(f"Configuration error: {ex}")
|
||||
exit(1)
|
||||
|
||||
|
||||
def _configure_logging(config_options):
|
||||
reset_logger()
|
||||
setup_logging(config_options.data_dir, config_options.log_level)
|
||||
|
@ -83,9 +92,6 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions)
|
|||
populate_exporter_list()
|
||||
app = init_app(MONGO_URL)
|
||||
|
||||
crt_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt"))
|
||||
key_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.key"))
|
||||
|
||||
init_collections()
|
||||
|
||||
if should_setup_only:
|
||||
|
@ -94,14 +100,23 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions)
|
|||
|
||||
bootloader_server_thread = _start_bootloader_server()
|
||||
|
||||
logger.info(
|
||||
f"Using certificate path: {config_options.crt_path}, and key path: "
|
||||
"{config_options.key_path}."
|
||||
)
|
||||
|
||||
if env_singleton.env.is_debug():
|
||||
app.run(host="0.0.0.0", debug=True, ssl_context=(crt_path, key_path))
|
||||
app.run(
|
||||
host="0.0.0.0",
|
||||
debug=True,
|
||||
ssl_context=(config_options.crt_path, config_options.key_path),
|
||||
)
|
||||
else:
|
||||
http_server = WSGIServer(
|
||||
("0.0.0.0", env_singleton.env.get_island_port()),
|
||||
app,
|
||||
certfile=os.environ.get("SERVER_CRT", crt_path),
|
||||
keyfile=os.environ.get("SERVER_KEY", key_path),
|
||||
certfile=config_options.crt_path,
|
||||
keyfile=config_options.key_path,
|
||||
)
|
||||
_log_init_info()
|
||||
http_server.serve_forever()
|
||||
|
|
|
@ -2,6 +2,7 @@ import os
|
|||
from pathlib import Path
|
||||
|
||||
from monkey_island.cc.environment.utils import is_windows_os
|
||||
from monkey_island.cc.server_utils import file_utils
|
||||
|
||||
__author__ = "itay.mizeretz"
|
||||
|
||||
|
@ -25,7 +26,7 @@ SERVER_CONFIG_FILENAME = "server_config.json"
|
|||
|
||||
MONKEY_ISLAND_ABS_PATH = _get_monkey_island_abs_path()
|
||||
|
||||
DEFAULT_DATA_DIR = os.path.expandvars(get_default_data_dir())
|
||||
DEFAULT_DATA_DIR = file_utils.expand_path(get_default_data_dir())
|
||||
|
||||
DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5
|
||||
|
||||
|
@ -36,9 +37,18 @@ MONGO_EXECUTABLE_PATH = (
|
|||
_MONGO_EXECUTABLE_PATH_WIN if is_windows_os() else _MONGO_EXECUTABLE_PATH_LINUX
|
||||
)
|
||||
|
||||
DEFAULT_SERVER_CONFIG_PATH = os.path.expandvars(
|
||||
DEFAULT_SERVER_CONFIG_PATH = file_utils.expand_path(
|
||||
os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", SERVER_CONFIG_FILENAME)
|
||||
)
|
||||
|
||||
DEFAULT_LOG_LEVEL = "INFO"
|
||||
|
||||
DEFAULT_START_MONGO_DB = True
|
||||
|
||||
DEFAULT_CRT_PATH = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt"))
|
||||
DEFAULT_KEY_PATH = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.key"))
|
||||
|
||||
DEFAULT_CERTIFICATE_PATHS = {
|
||||
"ssl_certificate_file": DEFAULT_CRT_PATH,
|
||||
"ssl_certificate_key_file": DEFAULT_KEY_PATH,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import os
|
||||
|
||||
from monkey_island.cc.environment.utils import is_windows_os
|
||||
|
||||
|
||||
def expand_path(path: str) -> str:
|
||||
return os.path.expandvars(os.path.expanduser(path))
|
||||
|
||||
|
||||
def has_expected_permissions(path: str, expected_permissions: int) -> bool:
|
||||
if is_windows_os():
|
||||
return _has_expected_windows_permissions(path, expected_permissions)
|
||||
|
||||
return _has_expected_linux_permissions(path, expected_permissions)
|
||||
|
||||
|
||||
def _has_expected_linux_permissions(path: str, expected_permissions: int) -> bool:
|
||||
file_mode = os.stat(path).st_mode
|
||||
file_permissions = file_mode & 0o777
|
||||
|
||||
return file_permissions == expected_permissions
|
||||
|
||||
|
||||
def _has_expected_windows_permissions(path: str, expected_permissions: int) -> bool:
|
||||
import win32api # noqa: E402
|
||||
import win32security # noqa: E402
|
||||
|
||||
FULL_CONTROL = 2032127
|
||||
ACE_TYPE_ALLOW = 0
|
||||
ACE_TYPE_DENY = 1
|
||||
|
||||
admins_sid, _, _ = win32security.LookupAccountName("", "Administrators")
|
||||
user_sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName())
|
||||
|
||||
security_descriptor = win32security.GetNamedSecurityInfo(
|
||||
path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION
|
||||
)
|
||||
|
||||
acl = security_descriptor.GetSecurityDescriptorDacl()
|
||||
|
||||
for i in range(acl.GetAceCount()):
|
||||
ace = acl.GetAce(i)
|
||||
ace_type, _ = ace[0] # 0 for allow, 1 for deny
|
||||
permissions = ace[1]
|
||||
sid = ace[-1]
|
||||
|
||||
if sid == user_sid:
|
||||
if not (permissions == expected_permissions and ace_type == ACE_TYPE_ALLOW):
|
||||
return False
|
||||
elif sid == admins_sid:
|
||||
continue
|
||||
# TODO: consider removing; so many system accounts/groups exist, it's likely to fail
|
||||
else:
|
||||
if not (permissions == FULL_CONTROL and ace_type == ACE_TYPE_DENY):
|
||||
return False
|
||||
|
||||
return True
|
|
@ -1,9 +1,9 @@
|
|||
import os
|
||||
from typing import Tuple
|
||||
|
||||
from monkey_island.cc.arg_parser import IslandCmdArgs
|
||||
from monkey_island.cc.environment import server_config_handler
|
||||
from monkey_island.cc.environment.utils import create_secure_directory
|
||||
from monkey_island.cc.server_utils import file_utils
|
||||
from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH
|
||||
from monkey_island.cc.setup.island_config_options import IslandConfigOptions
|
||||
|
||||
|
@ -16,14 +16,19 @@ def setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, str
|
|||
|
||||
|
||||
def _setup_config_by_cmd_arg(server_config_path) -> Tuple[IslandConfigOptions, str]:
|
||||
server_config_path = os.path.expandvars(os.path.expanduser(server_config_path))
|
||||
server_config_path = file_utils.expand_path(server_config_path)
|
||||
config = server_config_handler.load_server_config_from_file(server_config_path)
|
||||
create_secure_directory(config.data_dir, create_parent_dirs=True)
|
||||
return config, server_config_path
|
||||
|
||||
|
||||
def _setup_default_config() -> Tuple[IslandConfigOptions, str]:
|
||||
config = server_config_handler.load_server_config_from_file(DEFAULT_SERVER_CONFIG_PATH)
|
||||
create_secure_directory(config.data_dir, create_parent_dirs=False)
|
||||
server_config_path = server_config_handler.create_default_server_config_file(config.data_dir)
|
||||
default_config = server_config_handler.load_server_config_from_file(DEFAULT_SERVER_CONFIG_PATH)
|
||||
default_data_dir = default_config.data_dir
|
||||
|
||||
create_secure_directory(default_data_dir, create_parent_dirs=False)
|
||||
|
||||
server_config_path = server_config_handler.create_default_server_config_file(default_data_dir)
|
||||
config = server_config_handler.load_server_config_from_file(server_config_path)
|
||||
|
||||
return config, server_config_path
|
||||
|
|
|
@ -1,21 +1,33 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from monkey_island.cc.server_utils.consts import (
|
||||
DEFAULT_CERTIFICATE_PATHS,
|
||||
DEFAULT_CRT_PATH,
|
||||
DEFAULT_DATA_DIR,
|
||||
DEFAULT_KEY_PATH,
|
||||
DEFAULT_LOG_LEVEL,
|
||||
DEFAULT_START_MONGO_DB,
|
||||
)
|
||||
from monkey_island.cc.server_utils.file_utils import expand_path
|
||||
|
||||
|
||||
class IslandConfigOptions:
|
||||
def __init__(self, config_contents: dict):
|
||||
self.data_dir = os.path.expandvars(
|
||||
os.path.expanduser(config_contents.get("data_dir", DEFAULT_DATA_DIR))
|
||||
)
|
||||
self.data_dir = expand_path(config_contents.get("data_dir", DEFAULT_DATA_DIR))
|
||||
|
||||
self.log_level = config_contents.get("log_level", DEFAULT_LOG_LEVEL)
|
||||
|
||||
self.start_mongodb = config_contents.get(
|
||||
"mongodb", {"start_mongodb": DEFAULT_START_MONGO_DB}
|
||||
).get("start_mongodb", DEFAULT_START_MONGO_DB)
|
||||
|
||||
self.crt_path = expand_path(
|
||||
config_contents.get("ssl_certificate", DEFAULT_CERTIFICATE_PATHS).get(
|
||||
"ssl_certificate_file", DEFAULT_CRT_PATH
|
||||
)
|
||||
)
|
||||
self.key_path = expand_path(
|
||||
config_contents.get("ssl_certificate", DEFAULT_CERTIFICATE_PATHS).get(
|
||||
"ssl_certificate_key_file", DEFAULT_KEY_PATH
|
||||
)
|
||||
)
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import os
|
||||
|
||||
from common.utils.exceptions import InsecurePermissionsError
|
||||
from monkey_island.cc.environment.utils import is_windows_os
|
||||
from monkey_island.cc.server_utils.file_utils import has_expected_permissions
|
||||
from monkey_island.cc.setup.island_config_options import IslandConfigOptions
|
||||
|
||||
|
||||
def raise_on_invalid_options(options: IslandConfigOptions):
|
||||
LINUX_READ_ONLY_BY_USER = 0o400
|
||||
WINDOWS_READ_ONLY = 1179817
|
||||
|
||||
_raise_if_not_isfile(options.crt_path)
|
||||
_raise_if_incorrect_permissions(options.crt_path, LINUX_READ_ONLY_BY_USER, WINDOWS_READ_ONLY)
|
||||
|
||||
_raise_if_not_isfile(options.key_path)
|
||||
_raise_if_incorrect_permissions(options.key_path, LINUX_READ_ONLY_BY_USER, WINDOWS_READ_ONLY)
|
||||
|
||||
|
||||
def _raise_if_not_isfile(f: str):
|
||||
if not os.path.isfile(f):
|
||||
raise FileNotFoundError(f"{f} does not exist or is not a regular file.")
|
||||
|
||||
|
||||
def _raise_if_incorrect_permissions(
|
||||
f: str, linux_expected_permissions: int, windows_expected_permissions: int
|
||||
):
|
||||
expected_permissions = (
|
||||
windows_expected_permissions if is_windows_os() else linux_expected_permissions
|
||||
)
|
||||
if not has_expected_permissions(f, expected_permissions):
|
||||
raise InsecurePermissionsError(
|
||||
f"The file {f} has incorrect permissions. Expected: {expected_permissions}"
|
||||
)
|
|
@ -21,10 +21,16 @@ umask 377
|
|||
|
||||
echo "Generating key in $server_root/server.key..."
|
||||
openssl genrsa -out "$server_root"/server.key 2048
|
||||
chmod 400 "$server_root"/server.key
|
||||
|
||||
echo "Generating csr in $server_root/server.csr..."
|
||||
openssl req -new -key "$server_root"/server.key -out "$server_root"/server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com"
|
||||
chmod 400 "$server_root"/server.csr
|
||||
|
||||
echo "Generating certificate in $server_root/server.crt..."
|
||||
openssl x509 -req -days 366 -in "$server_root"/server.csr -signkey "$server_root"/server.key -out "$server_root"/server.crt
|
||||
chmod 400 "$server_root"/server.crt
|
||||
|
||||
|
||||
# Shove some new random data into the file to override the original seed we put in.
|
||||
if [ "$CREATED_RND_FILE" = true ] ; then
|
||||
|
|
|
@ -16,3 +16,17 @@ copy "%mydir%windows\openssl.cfg" "%mydir%bin\openssl\openssl.cfg"
|
|||
"%mydir%bin\openssl\openssl.exe" genrsa -out "%mydir%cc\server.key" 1024
|
||||
"%mydir%bin\openssl\openssl.exe" req -new -config "%mydir%bin\openssl\openssl.cfg" -key "%mydir%cc\server.key" -out "%mydir%cc\server.csr" -subj "/OU=Monkey Department/CN=monkey.com"
|
||||
"%mydir%bin\openssl\openssl.exe" x509 -req -days 366 -in "%mydir%cc\server.csr" -signkey "%mydir%cc\server.key" -out "%mydir%cc\server.crt"
|
||||
|
||||
|
||||
:: Change file permissions
|
||||
SET adminsIdentity="BUILTIN\Administrators"
|
||||
FOR /f %%O IN ('whoami') DO SET ownIdentity=%%O
|
||||
|
||||
FOR %%F IN ("%mydir%cc\server.key", "%mydir%cc\server.csr", "%mydir%cc\server.crt") DO (
|
||||
|
||||
:: Remove all others and add admins rule (with full control)
|
||||
echo y| cacls %%F" /p %adminsIdentity%:F
|
||||
|
||||
:: Add user rule (with read)
|
||||
echo y| cacls %%F /e /p %ownIdentity%:R
|
||||
)
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import os
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
from monkey_island.cc.server_utils import file_utils
|
||||
|
||||
|
||||
def test_expand_user(patched_home_env):
|
||||
input_path = os.path.join("~", "test")
|
||||
expected_path = os.path.join(patched_home_env, "test")
|
||||
|
||||
assert file_utils.expand_path(input_path) == expected_path
|
||||
|
||||
|
||||
def test_expand_vars(patched_home_env):
|
||||
input_path = os.path.join("$HOME", "test")
|
||||
expected_path = os.path.join(patched_home_env, "test")
|
||||
|
||||
assert file_utils.expand_path(input_path) == expected_path
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.")
|
||||
def test_has_expected_permissions_true_linux(tmpdir, create_empty_tmp_file):
|
||||
file_name = create_empty_tmp_file("test")
|
||||
os.chmod(file_name, 0o754)
|
||||
|
||||
assert file_utils.has_expected_permissions(file_name, 0o754)
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.")
|
||||
def test_has_expected_permissions_false_linux(tmpdir, create_empty_tmp_file):
|
||||
file_name = create_empty_tmp_file("test")
|
||||
os.chmod(file_name, 0o755)
|
||||
|
||||
assert not file_utils.has_expected_permissions(file_name, 0o700)
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.")
|
||||
def test_has_expected_permissions_true_windows(tmpdir, create_empty_tmp_file):
|
||||
file_name = create_empty_tmp_file("test")
|
||||
subprocess.run(f"echo y| cacls {file_name} /p %USERNAME%:F", shell=True) # noqa: DUO116
|
||||
|
||||
assert file_utils.has_expected_permissions(file_name, 2032127)
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.")
|
||||
def test_has_expected_permissions_false_windows(tmpdir, create_empty_tmp_file):
|
||||
file_name = create_empty_tmp_file("test")
|
||||
subprocess.run(f"echo y| cacls {file_name} /p %USERNAME%:R", shell=True) # noqa: DUO116
|
||||
|
||||
assert not file_utils.has_expected_permissions(file_name, 2032127)
|
|
@ -1,7 +1,9 @@
|
|||
import os
|
||||
|
||||
from monkey_island.cc.server_utils.consts import (
|
||||
DEFAULT_CRT_PATH,
|
||||
DEFAULT_DATA_DIR,
|
||||
DEFAULT_KEY_PATH,
|
||||
DEFAULT_LOG_LEVEL,
|
||||
DEFAULT_START_MONGO_DB,
|
||||
)
|
||||
|
@ -11,6 +13,10 @@ TEST_CONFIG_FILE_CONTENTS_SPECIFIED = {
|
|||
"data_dir": "/tmp",
|
||||
"log_level": "test",
|
||||
"mongodb": {"start_mongodb": False},
|
||||
"ssl_certificate": {
|
||||
"ssl_certificate_file": "/tmp/test.crt",
|
||||
"ssl_certificate_key_file": "/tmp/test.key",
|
||||
},
|
||||
}
|
||||
|
||||
TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED = {}
|
||||
|
@ -18,54 +24,122 @@ TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED = {}
|
|||
TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO = {"mongodb": {}}
|
||||
|
||||
|
||||
def test_island_config_options__data_dir_specified():
|
||||
assert_island_config_options_data_dir_equals(TEST_CONFIG_FILE_CONTENTS_SPECIFIED, "/tmp")
|
||||
def test_data_dir_specified():
|
||||
assert_data_dir_equals(TEST_CONFIG_FILE_CONTENTS_SPECIFIED, "/tmp")
|
||||
|
||||
|
||||
def test_island_config_options__data_dir_uses_default():
|
||||
assert_island_config_options_data_dir_equals(
|
||||
TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED, DEFAULT_DATA_DIR
|
||||
)
|
||||
def test_data_dir_uses_default():
|
||||
assert_data_dir_equals(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED, DEFAULT_DATA_DIR)
|
||||
|
||||
|
||||
def test_island_config_options__data_dir_expanduser(monkeypatch, tmpdir):
|
||||
set_home_env(monkeypatch, tmpdir)
|
||||
def test_data_dir_expanduser(patched_home_env):
|
||||
DATA_DIR_NAME = "test_data_dir"
|
||||
|
||||
assert_island_config_options_data_dir_equals(
|
||||
{"data_dir": os.path.join("~", DATA_DIR_NAME)}, os.path.join(tmpdir, DATA_DIR_NAME)
|
||||
assert_data_dir_equals(
|
||||
{"data_dir": os.path.join("~", DATA_DIR_NAME)},
|
||||
os.path.join(patched_home_env, DATA_DIR_NAME),
|
||||
)
|
||||
|
||||
|
||||
def test_island_config_options__data_dir_expandvars(monkeypatch, tmpdir):
|
||||
set_home_env(monkeypatch, tmpdir)
|
||||
def test_data_dir_expandvars(patched_home_env):
|
||||
DATA_DIR_NAME = "test_data_dir"
|
||||
|
||||
assert_island_config_options_data_dir_equals(
|
||||
{"data_dir": os.path.join("$HOME", DATA_DIR_NAME)}, os.path.join(tmpdir, DATA_DIR_NAME)
|
||||
assert_data_dir_equals(
|
||||
{"data_dir": os.path.join("$HOME", DATA_DIR_NAME)},
|
||||
os.path.join(patched_home_env, DATA_DIR_NAME),
|
||||
)
|
||||
|
||||
|
||||
def set_home_env(monkeypatch, tmpdir):
|
||||
monkeypatch.setenv("HOME", str(tmpdir))
|
||||
def assert_data_dir_equals(config_file_contents, expected_data_dir):
|
||||
assert_island_config_option_equals(config_file_contents, "data_dir", expected_data_dir)
|
||||
|
||||
|
||||
def assert_island_config_options_data_dir_equals(config_file_contents, expected_data_dir):
|
||||
options = IslandConfigOptions(config_file_contents)
|
||||
assert options.data_dir == expected_data_dir
|
||||
|
||||
|
||||
def test_island_config_options__log_level():
|
||||
def test_log_level():
|
||||
options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_SPECIFIED)
|
||||
assert options.log_level == "test"
|
||||
options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED)
|
||||
assert options.log_level == DEFAULT_LOG_LEVEL
|
||||
|
||||
|
||||
def test_island_config_options__mongodb():
|
||||
def test_mongodb():
|
||||
options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_SPECIFIED)
|
||||
assert not options.start_mongodb
|
||||
options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED)
|
||||
assert options.start_mongodb == DEFAULT_START_MONGO_DB
|
||||
options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO)
|
||||
assert options.start_mongodb == DEFAULT_START_MONGO_DB
|
||||
|
||||
|
||||
def test_crt_path_uses_default():
|
||||
assert_ssl_certificate_file_equals(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED, DEFAULT_CRT_PATH)
|
||||
|
||||
|
||||
def test_crt_path_specified():
|
||||
assert_ssl_certificate_file_equals(
|
||||
TEST_CONFIG_FILE_CONTENTS_SPECIFIED,
|
||||
TEST_CONFIG_FILE_CONTENTS_SPECIFIED["ssl_certificate"]["ssl_certificate_file"],
|
||||
)
|
||||
|
||||
|
||||
def test_crt_path_expanduser(patched_home_env):
|
||||
FILE_NAME = "test.crt"
|
||||
|
||||
assert_ssl_certificate_file_equals(
|
||||
{"ssl_certificate": {"ssl_certificate_file": os.path.join("~", FILE_NAME)}},
|
||||
os.path.join(patched_home_env, FILE_NAME),
|
||||
)
|
||||
|
||||
|
||||
def test_crt_path_expandvars(patched_home_env):
|
||||
FILE_NAME = "test.crt"
|
||||
|
||||
assert_ssl_certificate_file_equals(
|
||||
{"ssl_certificate": {"ssl_certificate_file": os.path.join("$HOME", FILE_NAME)}},
|
||||
os.path.join(patched_home_env, FILE_NAME),
|
||||
)
|
||||
|
||||
|
||||
def assert_ssl_certificate_file_equals(config_file_contents, expected_ssl_certificate_file):
|
||||
assert_island_config_option_equals(
|
||||
config_file_contents, "crt_path", expected_ssl_certificate_file
|
||||
)
|
||||
|
||||
|
||||
def test_key_path_uses_default():
|
||||
assert_ssl_certificate_key_file_equals(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED, DEFAULT_KEY_PATH)
|
||||
|
||||
|
||||
def test_key_path_specified():
|
||||
assert_ssl_certificate_key_file_equals(
|
||||
TEST_CONFIG_FILE_CONTENTS_SPECIFIED,
|
||||
TEST_CONFIG_FILE_CONTENTS_SPECIFIED["ssl_certificate"]["ssl_certificate_key_file"],
|
||||
)
|
||||
|
||||
|
||||
def test_key_path_expanduser(patched_home_env):
|
||||
FILE_NAME = "test.key"
|
||||
|
||||
assert_ssl_certificate_key_file_equals(
|
||||
{"ssl_certificate": {"ssl_certificate_key_file": os.path.join("~", FILE_NAME)}},
|
||||
os.path.join(patched_home_env, FILE_NAME),
|
||||
)
|
||||
|
||||
|
||||
def test_key_path_expandvars(patched_home_env):
|
||||
FILE_NAME = "test.key"
|
||||
|
||||
assert_ssl_certificate_key_file_equals(
|
||||
{"ssl_certificate": {"ssl_certificate_key_file": os.path.join("$HOME", FILE_NAME)}},
|
||||
os.path.join(patched_home_env, FILE_NAME),
|
||||
)
|
||||
|
||||
|
||||
def assert_ssl_certificate_key_file_equals(config_file_contents, expected_ssl_certificate_file):
|
||||
assert_island_config_option_equals(
|
||||
config_file_contents, "key_path", expected_ssl_certificate_file
|
||||
)
|
||||
|
||||
|
||||
def assert_island_config_option_equals(config_file_contents, option_name, expected_value):
|
||||
options = IslandConfigOptions(config_file_contents)
|
||||
assert getattr(options, option_name) == expected_value
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
import os
|
||||
import subprocess
|
||||
from collections.abc import Callable
|
||||
|
||||
import pytest
|
||||
|
||||
from common.utils.exceptions import InsecurePermissionsError
|
||||
from monkey_island.cc.setup.island_config_options import IslandConfigOptions
|
||||
from monkey_island.cc.setup.island_config_options_validator import raise_on_invalid_options
|
||||
|
||||
LINUX_READ_ONLY_BY_USER = 0o400
|
||||
LINUX_RWX_BY_ALL = 0o777
|
||||
|
||||
|
||||
def certificate_test_island_config_options(crt_file, key_file):
|
||||
return IslandConfigOptions(
|
||||
{
|
||||
"ssl_certificate": {
|
||||
"ssl_certificate_file": crt_file,
|
||||
"ssl_certificate_key_file": key_file,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def linux_island_config_options(create_read_only_linux_file: Callable):
|
||||
crt_file = create_read_only_linux_file("test.crt")
|
||||
key_file = create_read_only_linux_file("test.key")
|
||||
|
||||
return certificate_test_island_config_options(crt_file, key_file)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def create_read_only_linux_file(tmpdir: str, create_empty_tmp_file: Callable) -> Callable:
|
||||
def inner(file_name: str) -> str:
|
||||
full_file_path = create_empty_tmp_file(file_name)
|
||||
os.chmod(full_file_path, LINUX_READ_ONLY_BY_USER)
|
||||
|
||||
return full_file_path
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.")
|
||||
def test_linux_valid_crt_and_key_paths(linux_island_config_options):
|
||||
try:
|
||||
raise_on_invalid_options(linux_island_config_options)
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
assert False
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.")
|
||||
def test_linux_crt_path_does_not_exist(linux_island_config_options):
|
||||
os.remove(linux_island_config_options.crt_path)
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
raise_on_invalid_options(linux_island_config_options)
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.")
|
||||
def test_linux_crt_path_insecure_permissions(linux_island_config_options):
|
||||
os.chmod(linux_island_config_options.crt_path, LINUX_RWX_BY_ALL)
|
||||
|
||||
with pytest.raises(InsecurePermissionsError):
|
||||
raise_on_invalid_options(linux_island_config_options)
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.")
|
||||
def test_linux_key_path_does_not_exist(linux_island_config_options):
|
||||
os.remove(linux_island_config_options.key_path)
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
raise_on_invalid_options(linux_island_config_options)
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.")
|
||||
def test_linux_key_path_insecure_permissions(linux_island_config_options):
|
||||
os.chmod(linux_island_config_options.key_path, LINUX_RWX_BY_ALL)
|
||||
|
||||
with pytest.raises(InsecurePermissionsError):
|
||||
raise_on_invalid_options(linux_island_config_options)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def windows_island_config_options(tmpdir: str, create_read_only_windows_file: Callable):
|
||||
crt_file = create_read_only_windows_file("test.crt")
|
||||
key_file = create_read_only_windows_file("test.key")
|
||||
|
||||
return certificate_test_island_config_options(crt_file, key_file)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def create_read_only_windows_file(tmpdir: str, create_empty_tmp_file: Callable) -> Callable:
|
||||
def inner(file_name: str) -> str:
|
||||
full_file_path = create_empty_tmp_file(file_name)
|
||||
cmd_to_change_permissions = get_windows_cmd_to_change_permissions(full_file_path, "R")
|
||||
subprocess.run(cmd_to_change_permissions, shell=True) # noqa DUO116
|
||||
|
||||
return full_file_path
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
def get_windows_cmd_to_change_permissions(file_name, permissions):
|
||||
"""
|
||||
:param file_name: name of file
|
||||
:param permissions: can be: N (None), R (Read), W (Write), C (Change (write)), F (Full control)
|
||||
"""
|
||||
return f"echo y| cacls {file_name} /p %USERNAME%:{permissions}"
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.")
|
||||
def test_windows_valid_crt_and_key_paths(windows_island_config_options):
|
||||
try:
|
||||
raise_on_invalid_options(windows_island_config_options)
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
assert False
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.")
|
||||
def test_windows_crt_path_does_not_exist(windows_island_config_options):
|
||||
os.remove(windows_island_config_options.crt_path)
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
raise_on_invalid_options(windows_island_config_options)
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.")
|
||||
def test_windows_crt_path_insecure_permissions(windows_island_config_options):
|
||||
cmd_to_change_permissions = get_windows_cmd_to_change_permissions(
|
||||
windows_island_config_options.crt_path, "W"
|
||||
)
|
||||
subprocess.run(cmd_to_change_permissions, shell=True) # noqa DUO116
|
||||
|
||||
with pytest.raises(InsecurePermissionsError):
|
||||
raise_on_invalid_options(windows_island_config_options)
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.")
|
||||
def test_windows_key_path_does_not_exist(windows_island_config_options):
|
||||
os.remove(windows_island_config_options.key_path)
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
raise_on_invalid_options(windows_island_config_options)
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.")
|
||||
def test_windows_key_path_insecure_permissions(windows_island_config_options):
|
||||
cmd_to_change_permissions = get_windows_cmd_to_change_permissions(
|
||||
windows_island_config_options.key_path, "W"
|
||||
)
|
||||
subprocess.run(cmd_to_change_permissions, shell=True) # noqa DUO116
|
||||
|
||||
with pytest.raises(InsecurePermissionsError):
|
||||
raise_on_invalid_options(windows_island_config_options)
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
from collections.abc import Callable
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -6,3 +7,22 @@ import pytest
|
|||
@pytest.fixture(scope="module")
|
||||
def server_configs_dir(data_for_tests_dir):
|
||||
return os.path.join(data_for_tests_dir, "server_configs")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_home_env(monkeypatch, tmpdir):
|
||||
monkeypatch.setenv("HOME", str(tmpdir))
|
||||
|
||||
return tmpdir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def create_empty_tmp_file(tmpdir: str) -> Callable:
|
||||
def inner(file_name: str):
|
||||
new_file = os.path.join(tmpdir, file_name)
|
||||
with open(new_file, "w"):
|
||||
pass
|
||||
|
||||
return new_file
|
||||
|
||||
return inner
|
||||
|
|
Loading…
Reference in New Issue