forked from p15670423/monkey
Merge pull request #1506 from guardicore/mongo_key_encryption
Mongo key encryption
This commit is contained in:
commit
af99482a4a
|
@ -49,6 +49,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Generate a random password when creating a new user for CommunicateAsNewUser
|
- Generate a random password when creating a new user for CommunicateAsNewUser
|
||||||
PBA. #1434
|
PBA. #1434
|
||||||
- Credentials gathered from victim machines are no longer stored plaintext in the database. #1454
|
- Credentials gathered from victim machines are no longer stored plaintext in the database. #1454
|
||||||
|
- Encrypt the database key with user's credentials. #1463
|
||||||
|
|
||||||
|
|
||||||
## [1.11.0] - 2021-08-13
|
## [1.11.0] - 2021-08-13
|
||||||
|
|
|
@ -54,7 +54,6 @@ from monkey_island.cc.resources.zero_trust.scoutsuite_auth.scoutsuite_auth impor
|
||||||
from monkey_island.cc.resources.zero_trust.zero_trust_report import ZeroTrustReport
|
from monkey_island.cc.resources.zero_trust.zero_trust_report import ZeroTrustReport
|
||||||
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
|
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
|
||||||
from monkey_island.cc.server_utils.custom_json_encoder import CustomJSONEncoder
|
from monkey_island.cc.server_utils.custom_json_encoder import CustomJSONEncoder
|
||||||
from monkey_island.cc.services.database import Database
|
|
||||||
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
||||||
from monkey_island.cc.services.representations import output_json
|
from monkey_island.cc.services.representations import output_json
|
||||||
|
|
||||||
|
@ -108,7 +107,6 @@ def init_app_services(app):
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
database.init()
|
database.init()
|
||||||
Database.init_db()
|
|
||||||
|
|
||||||
# If running on AWS, this will initialize the instance data, which is used "later" in the
|
# If running on AWS, this will initialize the instance data, which is used "later" in the
|
||||||
# execution of the island.
|
# execution of the island.
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
|
@ -9,8 +8,12 @@ from flask_jwt_extended.exceptions import JWTExtendedException
|
||||||
from jwt import PyJWTError
|
from jwt import PyJWTError
|
||||||
|
|
||||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||||
import monkey_island.cc.resources.auth.password_utils as password_utils
|
|
||||||
import monkey_island.cc.resources.auth.user_store as user_store
|
import monkey_island.cc.resources.auth.user_store as user_store
|
||||||
|
from monkey_island.cc.resources.auth.credential_utils import (
|
||||||
|
get_username_password_from_request,
|
||||||
|
password_matches_hash,
|
||||||
|
)
|
||||||
|
from monkey_island.cc.services.authentication import AuthenticationService
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -38,28 +41,20 @@ class Authenticate(flask_restful.Resource):
|
||||||
"password": "my_password"
|
"password": "my_password"
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
(username, password) = _get_credentials_from_request(request)
|
username, password = get_username_password_from_request(request)
|
||||||
|
|
||||||
if _credentials_match_registered_user(username, password):
|
if _credentials_match_registered_user(username, password):
|
||||||
|
AuthenticationService.ensure_datastore_encryptor(username, password)
|
||||||
access_token = _create_access_token(username)
|
access_token = _create_access_token(username)
|
||||||
return make_response({"access_token": access_token, "error": ""}, 200)
|
return make_response({"access_token": access_token, "error": ""}, 200)
|
||||||
else:
|
else:
|
||||||
return make_response({"error": "Invalid credentials"}, 401)
|
return make_response({"error": "Invalid credentials"}, 401)
|
||||||
|
|
||||||
|
|
||||||
def _get_credentials_from_request(request):
|
def _credentials_match_registered_user(username: str, password: str) -> bool:
|
||||||
credentials = json.loads(request.data)
|
|
||||||
|
|
||||||
username = credentials["username"]
|
|
||||||
password = credentials["password"]
|
|
||||||
|
|
||||||
return (username, password)
|
|
||||||
|
|
||||||
|
|
||||||
def _credentials_match_registered_user(username, password):
|
|
||||||
user = user_store.UserStore.username_table.get(username, None)
|
user = user_store.UserStore.username_table.get(username, None)
|
||||||
|
|
||||||
if user and password_utils.password_matches_hash(password, user.secret):
|
if user and password_matches_hash(password, user.secret):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import json
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
import bcrypt
|
||||||
|
from flask import Request, request
|
||||||
|
|
||||||
|
from monkey_island.cc.environment.user_creds import UserCreds
|
||||||
|
|
||||||
|
|
||||||
|
def hash_password(plaintext_password):
|
||||||
|
salt = bcrypt.gensalt()
|
||||||
|
password_hash = bcrypt.hashpw(plaintext_password.encode("utf-8"), salt)
|
||||||
|
|
||||||
|
return password_hash.decode()
|
||||||
|
|
||||||
|
|
||||||
|
def password_matches_hash(plaintext_password, password_hash):
|
||||||
|
return bcrypt.checkpw(plaintext_password.encode("utf-8"), password_hash.encode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_credentials_from_request(_request) -> UserCreds:
|
||||||
|
username, password = get_username_password_from_request(_request)
|
||||||
|
password_hash = hash_password(password)
|
||||||
|
|
||||||
|
return UserCreds(username, password_hash)
|
||||||
|
|
||||||
|
|
||||||
|
def get_username_password_from_request(_request: Request) -> Tuple[str, str]:
|
||||||
|
cred_dict = json.loads(request.data)
|
||||||
|
username = cred_dict.get("username", "")
|
||||||
|
password = cred_dict.get("password", "")
|
||||||
|
return username, password
|
|
@ -1,12 +0,0 @@
|
||||||
import bcrypt
|
|
||||||
|
|
||||||
|
|
||||||
def hash_password(plaintext_password):
|
|
||||||
salt = bcrypt.gensalt()
|
|
||||||
password_hash = bcrypt.hashpw(plaintext_password.encode("utf-8"), salt)
|
|
||||||
|
|
||||||
return password_hash.decode()
|
|
||||||
|
|
||||||
|
|
||||||
def password_matches_hash(plaintext_password, password_hash):
|
|
||||||
return bcrypt.checkpw(plaintext_password.encode("utf-8"), password_hash.encode("utf-8"))
|
|
|
@ -1,13 +1,15 @@
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import flask_restful
|
import flask_restful
|
||||||
from flask import make_response, request
|
from flask import make_response, request
|
||||||
|
|
||||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||||
import monkey_island.cc.resources.auth.password_utils as password_utils
|
|
||||||
from common.utils.exceptions import InvalidRegistrationCredentialsError, RegistrationNotNeededError
|
from common.utils.exceptions import InvalidRegistrationCredentialsError, RegistrationNotNeededError
|
||||||
from monkey_island.cc.environment.user_creds import UserCreds
|
from monkey_island.cc.resources.auth.credential_utils import (
|
||||||
|
get_user_credentials_from_request,
|
||||||
|
get_username_password_from_request,
|
||||||
|
)
|
||||||
|
from monkey_island.cc.services.authentication import AuthenticationService
|
||||||
from monkey_island.cc.setup.mongo.database_initializer import reset_database
|
from monkey_island.cc.setup.mongo.database_initializer import reset_database
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -19,21 +21,13 @@ class Registration(flask_restful.Resource):
|
||||||
return {"needs_registration": is_registration_needed}
|
return {"needs_registration": is_registration_needed}
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
credentials = _get_user_credentials_from_request(request)
|
credentials = get_user_credentials_from_request(request)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
env_singleton.env.try_add_user(credentials)
|
env_singleton.env.try_add_user(credentials)
|
||||||
|
username, password = get_username_password_from_request(request)
|
||||||
|
AuthenticationService.reset_datastore_encryptor(username, password)
|
||||||
reset_database()
|
reset_database()
|
||||||
return make_response({"error": ""}, 200)
|
return make_response({"error": ""}, 200)
|
||||||
except (InvalidRegistrationCredentialsError, RegistrationNotNeededError) as e:
|
except (InvalidRegistrationCredentialsError, RegistrationNotNeededError) as e:
|
||||||
return make_response({"error": str(e)}, 400)
|
return make_response({"error": str(e)}, 400)
|
||||||
|
|
||||||
|
|
||||||
def _get_user_credentials_from_request(request):
|
|
||||||
cred_dict = json.loads(request.data)
|
|
||||||
|
|
||||||
username = cred_dict.get("user", "")
|
|
||||||
password = cred_dict.get("password", "")
|
|
||||||
password_hash = password_utils.hash_password(password)
|
|
||||||
|
|
||||||
return UserCreds(username, password_hash)
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import flask_restful
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||||
from monkey_island.cc.server_utils.encryption import PasswordBasedEncryptor
|
from monkey_island.cc.server_utils.encryption import PasswordBasedStringEncryptor
|
||||||
from monkey_island.cc.services.config import ConfigService
|
from monkey_island.cc.services.config import ConfigService
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class ConfigurationExport(flask_restful.Resource):
|
||||||
password = data["password"]
|
password = data["password"]
|
||||||
plaintext_config = json.dumps(plaintext_config)
|
plaintext_config = json.dumps(plaintext_config)
|
||||||
|
|
||||||
pb_encryptor = PasswordBasedEncryptor(password)
|
pb_encryptor = PasswordBasedStringEncryptor(password)
|
||||||
config_export = pb_encryptor.encrypt(plaintext_config)
|
config_export = pb_encryptor.encrypt(plaintext_config)
|
||||||
|
|
||||||
return {"config_export": config_export, "encrypted": should_encrypt}
|
return {"config_export": config_export, "encrypted": should_encrypt}
|
||||||
|
|
|
@ -11,7 +11,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required
|
||||||
from monkey_island.cc.server_utils.encryption import (
|
from monkey_island.cc.server_utils.encryption import (
|
||||||
InvalidCiphertextError,
|
InvalidCiphertextError,
|
||||||
InvalidCredentialsError,
|
InvalidCredentialsError,
|
||||||
PasswordBasedEncryptor,
|
PasswordBasedStringEncryptor,
|
||||||
is_encrypted,
|
is_encrypted,
|
||||||
)
|
)
|
||||||
from monkey_island.cc.services.config import ConfigService
|
from monkey_island.cc.services.config import ConfigService
|
||||||
|
@ -72,7 +72,7 @@ class ConfigurationImport(flask_restful.Resource):
|
||||||
try:
|
try:
|
||||||
config = request_contents["config"]
|
config = request_contents["config"]
|
||||||
if ConfigurationImport.is_config_encrypted(request_contents["config"]):
|
if ConfigurationImport.is_config_encrypted(request_contents["config"]):
|
||||||
pb_encryptor = PasswordBasedEncryptor(request_contents["password"])
|
pb_encryptor = PasswordBasedStringEncryptor(request_contents["password"])
|
||||||
config = pb_encryptor.decrypt(config)
|
config = pb_encryptor.decrypt(config)
|
||||||
return json.loads(config)
|
return json.loads(config)
|
||||||
except (JSONDecodeError, InvalidCiphertextError):
|
except (JSONDecodeError, InvalidCiphertextError):
|
||||||
|
|
|
@ -27,7 +27,6 @@ from monkey_island.cc.server_utils.consts import ( # noqa: E402
|
||||||
GEVENT_EXCEPTION_LOG,
|
GEVENT_EXCEPTION_LOG,
|
||||||
MONGO_CONNECTION_TIMEOUT,
|
MONGO_CONNECTION_TIMEOUT,
|
||||||
)
|
)
|
||||||
from monkey_island.cc.server_utils.encryption import initialize_datastore_encryptor # noqa: E402
|
|
||||||
from monkey_island.cc.server_utils.island_logger import reset_logger, setup_logging # 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.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.reporting.exporter_init import populate_exporter_list # noqa: E402
|
||||||
|
@ -87,7 +86,6 @@ def _configure_logging(config_options):
|
||||||
def _initialize_globals(config_options: IslandConfigOptions, server_config_path: str):
|
def _initialize_globals(config_options: IslandConfigOptions, server_config_path: str):
|
||||||
env_singleton.initialize_from_file(server_config_path)
|
env_singleton.initialize_from_file(server_config_path)
|
||||||
|
|
||||||
initialize_datastore_encryptor(config_options.data_dir)
|
|
||||||
initialize_services(config_options.data_dir)
|
initialize_services(config_options.data_dir)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
from monkey_island.cc.server_utils.encryption.i_encryptor import IEncryptor
|
from monkey_island.cc.server_utils.encryption.encryptors.i_encryptor import IEncryptor
|
||||||
from monkey_island.cc.server_utils.encryption.key_based_encryptor import KeyBasedEncryptor
|
from monkey_island.cc.server_utils.encryption.encryptors.key_based_encryptor import (
|
||||||
from monkey_island.cc.server_utils.encryption.password_based_encryption import (
|
KeyBasedEncryptor,
|
||||||
InvalidCiphertextError,
|
)
|
||||||
InvalidCredentialsError,
|
from monkey_island.cc.server_utils.encryption.encryptors.password_based_string_encryptior import (
|
||||||
PasswordBasedEncryptor,
|
PasswordBasedStringEncryptor,
|
||||||
is_encrypted,
|
is_encrypted,
|
||||||
)
|
)
|
||||||
from monkey_island.cc.server_utils.encryption.data_store_encryptor import (
|
from monkey_island.cc.server_utils.encryption.encryptors.password_based_bytes_encryption import (
|
||||||
DataStoreEncryptor,
|
PasswordBasedBytesEncryptor,
|
||||||
get_datastore_encryptor,
|
InvalidCredentialsError,
|
||||||
|
InvalidCiphertextError,
|
||||||
|
)
|
||||||
|
from .data_store_encryptor import (
|
||||||
initialize_datastore_encryptor,
|
initialize_datastore_encryptor,
|
||||||
|
get_datastore_encryptor,
|
||||||
|
remove_old_datastore_key,
|
||||||
)
|
)
|
||||||
from .dict_encryption.dict_encryptor import (
|
from .dict_encryption.dict_encryptor import (
|
||||||
SensitiveField,
|
SensitiveField,
|
||||||
|
|
|
@ -1,50 +1,59 @@
|
||||||
import os
|
import os
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
# PyCrypto is deprecated, but we use pycryptodome, which uses the exact same imports but
|
|
||||||
# is maintained.
|
|
||||||
from Crypto import Random # noqa: DUO133 # nosec: B413
|
from Crypto import Random # noqa: DUO133 # nosec: B413
|
||||||
|
|
||||||
from monkey_island.cc.server_utils.encryption import KeyBasedEncryptor
|
from monkey_island.cc.server_utils.encryption import (
|
||||||
|
IEncryptor,
|
||||||
|
KeyBasedEncryptor,
|
||||||
|
PasswordBasedBytesEncryptor,
|
||||||
|
)
|
||||||
from monkey_island.cc.server_utils.file_utils import open_new_securely_permissioned_file
|
from monkey_island.cc.server_utils.file_utils import open_new_securely_permissioned_file
|
||||||
|
|
||||||
_encryptor = None
|
|
||||||
|
|
||||||
|
|
||||||
class DataStoreEncryptor:
|
|
||||||
_BLOCK_SIZE = 32
|
|
||||||
_KEY_FILENAME = "mongo_key.bin"
|
_KEY_FILENAME = "mongo_key.bin"
|
||||||
|
_BLOCK_SIZE = 32
|
||||||
|
|
||||||
def __init__(self, key_file_dir):
|
_encryptor: Union[None, IEncryptor] = None
|
||||||
key_file = os.path.join(key_file_dir, self._KEY_FILENAME)
|
|
||||||
|
|
||||||
if os.path.exists(key_file):
|
|
||||||
self._load_existing_key(key_file)
|
|
||||||
else:
|
|
||||||
self._init_key(key_file)
|
|
||||||
|
|
||||||
self._key_base_encryptor = KeyBasedEncryptor(self._cipher_key)
|
|
||||||
|
|
||||||
def _init_key(self, password_file_path: str):
|
|
||||||
self._cipher_key = Random.new().read(self._BLOCK_SIZE)
|
|
||||||
with open_new_securely_permissioned_file(password_file_path, "wb") as f:
|
|
||||||
f.write(self._cipher_key)
|
|
||||||
|
|
||||||
def _load_existing_key(self, key_file):
|
|
||||||
with open(key_file, "rb") as f:
|
|
||||||
self._cipher_key = f.read()
|
|
||||||
|
|
||||||
def enc(self, message: str):
|
|
||||||
return self._key_base_encryptor.encrypt(message)
|
|
||||||
|
|
||||||
def dec(self, enc_message: str):
|
|
||||||
return self._key_base_encryptor.decrypt(enc_message)
|
|
||||||
|
|
||||||
|
|
||||||
def initialize_datastore_encryptor(key_file_dir):
|
def _load_existing_key(key_file_path: str, secret: str) -> KeyBasedEncryptor:
|
||||||
|
with open(key_file_path, "rb") as f:
|
||||||
|
encrypted_key = f.read()
|
||||||
|
cipher_key = PasswordBasedBytesEncryptor(secret).decrypt(encrypted_key)
|
||||||
|
return KeyBasedEncryptor(cipher_key)
|
||||||
|
|
||||||
|
|
||||||
|
def _create_new_key(key_file_path: str, secret: str) -> KeyBasedEncryptor:
|
||||||
|
cipher_key = _get_random_bytes()
|
||||||
|
encrypted_key = PasswordBasedBytesEncryptor(secret).encrypt(cipher_key)
|
||||||
|
with open_new_securely_permissioned_file(key_file_path, "wb") as f:
|
||||||
|
f.write(encrypted_key)
|
||||||
|
return KeyBasedEncryptor(cipher_key)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_random_bytes() -> bytes:
|
||||||
|
return Random.new().read(_BLOCK_SIZE)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_old_datastore_key(key_file_dir: str):
|
||||||
|
key_file_path = _get_key_file_path(key_file_dir)
|
||||||
|
if os.path.isfile(key_file_path):
|
||||||
|
os.remove(key_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_datastore_encryptor(key_file_dir: str, secret: str):
|
||||||
global _encryptor
|
global _encryptor
|
||||||
|
|
||||||
_encryptor = DataStoreEncryptor(key_file_dir)
|
key_file_path = _get_key_file_path(key_file_dir)
|
||||||
|
if os.path.exists(key_file_path):
|
||||||
|
_encryptor = _load_existing_key(key_file_path, secret)
|
||||||
|
else:
|
||||||
|
_encryptor = _create_new_key(key_file_path, secret)
|
||||||
|
|
||||||
|
|
||||||
def get_datastore_encryptor():
|
def _get_key_file_path(key_file_dir: str) -> str:
|
||||||
|
return os.path.join(key_file_dir, _KEY_FILENAME)
|
||||||
|
|
||||||
|
|
||||||
|
def get_datastore_encryptor() -> IEncryptor:
|
||||||
return _encryptor
|
return _encryptor
|
||||||
|
|
|
@ -17,7 +17,7 @@ class MimikatzResultsEncryptor(IFieldEncryptor):
|
||||||
for _, credentials in results.items():
|
for _, credentials in results.items():
|
||||||
for secret_type in MimikatzResultsEncryptor.secret_types:
|
for secret_type in MimikatzResultsEncryptor.secret_types:
|
||||||
try:
|
try:
|
||||||
credentials[secret_type] = get_datastore_encryptor().enc(
|
credentials[secret_type] = get_datastore_encryptor().encrypt(
|
||||||
credentials[secret_type]
|
credentials[secret_type]
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
@ -25,12 +25,14 @@ class MimikatzResultsEncryptor(IFieldEncryptor):
|
||||||
f"Failed encrypting sensitive field for "
|
f"Failed encrypting sensitive field for "
|
||||||
f"user {credentials['username']}! Error: {e}"
|
f"user {credentials['username']}! Error: {e}"
|
||||||
)
|
)
|
||||||
credentials[secret_type] = get_datastore_encryptor().enc("")
|
credentials[secret_type] = get_datastore_encryptor().encrypt("")
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decrypt(results: dict) -> dict:
|
def decrypt(results: dict) -> dict:
|
||||||
for _, credentials in results.items():
|
for _, credentials in results.items():
|
||||||
for secret_type in MimikatzResultsEncryptor.secret_types:
|
for secret_type in MimikatzResultsEncryptor.secret_types:
|
||||||
credentials[secret_type] = get_datastore_encryptor().dec(credentials[secret_type])
|
credentials[secret_type] = get_datastore_encryptor().decrypt(
|
||||||
|
credentials[secret_type]
|
||||||
|
)
|
||||||
return results
|
return results
|
||||||
|
|
|
@ -9,8 +9,8 @@ from monkey_island.cc.server_utils.encryption.dict_encryption.field_encryptors i
|
||||||
class StringListEncryptor(IFieldEncryptor):
|
class StringListEncryptor(IFieldEncryptor):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def encrypt(value: List[str]):
|
def encrypt(value: List[str]):
|
||||||
return [get_datastore_encryptor().enc(string) for string in value]
|
return [get_datastore_encryptor().encrypt(string) for string in value]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decrypt(value: List[str]):
|
def decrypt(value: List[str]):
|
||||||
return [get_datastore_encryptor().dec(string) for string in value]
|
return [get_datastore_encryptor().decrypt(string) for string in value]
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import base64
|
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -17,36 +16,30 @@ logger = logging.getLogger(__name__)
|
||||||
# Note: password != key
|
# Note: password != key
|
||||||
|
|
||||||
|
|
||||||
class PasswordBasedEncryptor(IEncryptor):
|
class PasswordBasedBytesEncryptor(IEncryptor):
|
||||||
|
|
||||||
_BUFFER_SIZE = pyAesCrypt.crypto.bufferSizeDef
|
_BUFFER_SIZE = pyAesCrypt.crypto.bufferSizeDef
|
||||||
|
|
||||||
def __init__(self, password: str):
|
def __init__(self, password: str):
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
||||||
def encrypt(self, plaintext: str) -> str:
|
def encrypt(self, plaintext: bytes) -> bytes:
|
||||||
plaintext_stream = io.BytesIO(plaintext.encode())
|
|
||||||
ciphertext_stream = io.BytesIO()
|
ciphertext_stream = io.BytesIO()
|
||||||
|
|
||||||
pyAesCrypt.encryptStream(
|
pyAesCrypt.encryptStream(
|
||||||
plaintext_stream, ciphertext_stream, self.password, self._BUFFER_SIZE
|
io.BytesIO(plaintext), ciphertext_stream, self.password, self._BUFFER_SIZE
|
||||||
)
|
)
|
||||||
|
|
||||||
ciphertext_b64 = base64.b64encode(ciphertext_stream.getvalue())
|
return ciphertext_stream.getvalue()
|
||||||
logger.info("String encrypted.")
|
|
||||||
|
|
||||||
return ciphertext_b64.decode()
|
def decrypt(self, ciphertext: bytes) -> bytes:
|
||||||
|
|
||||||
def decrypt(self, ciphertext: str):
|
|
||||||
ciphertext = base64.b64decode(ciphertext)
|
|
||||||
ciphertext_stream = io.BytesIO(ciphertext)
|
|
||||||
plaintext_stream = io.BytesIO()
|
plaintext_stream = io.BytesIO()
|
||||||
|
|
||||||
ciphertext_stream_len = len(ciphertext_stream.getvalue())
|
ciphertext_stream_len = len(ciphertext)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pyAesCrypt.decryptStream(
|
pyAesCrypt.decryptStream(
|
||||||
ciphertext_stream,
|
io.BytesIO(ciphertext),
|
||||||
plaintext_stream,
|
plaintext_stream,
|
||||||
self.password,
|
self.password,
|
||||||
self._BUFFER_SIZE,
|
self._BUFFER_SIZE,
|
||||||
|
@ -59,7 +52,7 @@ class PasswordBasedEncryptor(IEncryptor):
|
||||||
else:
|
else:
|
||||||
logger.info("The corrupt ciphertext provided.")
|
logger.info("The corrupt ciphertext provided.")
|
||||||
raise InvalidCiphertextError
|
raise InvalidCiphertextError
|
||||||
return plaintext_stream.getvalue().decode("utf-8")
|
return plaintext_stream.getvalue()
|
||||||
|
|
||||||
|
|
||||||
class InvalidCredentialsError(Exception):
|
class InvalidCredentialsError(Exception):
|
||||||
|
@ -68,8 +61,3 @@ class InvalidCredentialsError(Exception):
|
||||||
|
|
||||||
class InvalidCiphertextError(Exception):
|
class InvalidCiphertextError(Exception):
|
||||||
""" Raised when ciphertext is corrupted """
|
""" Raised when ciphertext is corrupted """
|
||||||
|
|
||||||
|
|
||||||
def is_encrypted(ciphertext: str) -> bool:
|
|
||||||
ciphertext = base64.b64decode(ciphertext)
|
|
||||||
return ciphertext.startswith(b"AES")
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import base64
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pyAesCrypt
|
||||||
|
|
||||||
|
from monkey_island.cc.server_utils.encryption import IEncryptor
|
||||||
|
from monkey_island.cc.server_utils.encryption.encryptors.password_based_bytes_encryption import (
|
||||||
|
PasswordBasedBytesEncryptor,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordBasedStringEncryptor(IEncryptor):
|
||||||
|
|
||||||
|
_BUFFER_SIZE = pyAesCrypt.crypto.bufferSizeDef
|
||||||
|
|
||||||
|
def __init__(self, password: str):
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
def encrypt(self, plaintext: str) -> str:
|
||||||
|
ciphertext = PasswordBasedBytesEncryptor(self.password).encrypt(plaintext.encode())
|
||||||
|
|
||||||
|
return base64.b64encode(ciphertext).decode()
|
||||||
|
|
||||||
|
def decrypt(self, ciphertext: str) -> str:
|
||||||
|
ciphertext = base64.b64decode(ciphertext)
|
||||||
|
|
||||||
|
plaintext_stream = PasswordBasedBytesEncryptor(self.password).decrypt(ciphertext)
|
||||||
|
return plaintext_stream.decode()
|
||||||
|
|
||||||
|
|
||||||
|
def is_encrypted(ciphertext: str) -> bool:
|
||||||
|
ciphertext = base64.b64decode(ciphertext)
|
||||||
|
return ciphertext.startswith(b"AES")
|
|
@ -29,7 +29,7 @@ def censor_password(password, plain_chars=3, secret_chars=5):
|
||||||
"""
|
"""
|
||||||
if not password:
|
if not password:
|
||||||
return ""
|
return ""
|
||||||
password = get_datastore_encryptor().dec(password)
|
password = get_datastore_encryptor().decrypt(password)
|
||||||
return password[0:plain_chars] + "*" * secret_chars
|
return password[0:plain_chars] + "*" * secret_chars
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,5 +42,5 @@ def censor_hash(hash_, plain_chars=5):
|
||||||
"""
|
"""
|
||||||
if not hash_:
|
if not hash_:
|
||||||
return ""
|
return ""
|
||||||
hash_ = get_datastore_encryptor().dec(hash_)
|
hash_ = get_datastore_encryptor().decrypt(hash_)
|
||||||
return hash_[0:plain_chars] + " ..."
|
return hash_[0:plain_chars] + " ..."
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
from monkey_island.cc.server_utils.encryption import (
|
||||||
|
get_datastore_encryptor,
|
||||||
|
initialize_datastore_encryptor,
|
||||||
|
remove_old_datastore_key,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticationService:
|
||||||
|
KEY_FILE_DIRECTORY = None
|
||||||
|
|
||||||
|
# TODO: A number of these services should be instance objects instead of
|
||||||
|
# static/singleton hybrids. At the moment, this requires invasive refactoring that's
|
||||||
|
# not a priority.
|
||||||
|
@classmethod
|
||||||
|
def initialize(cls, key_file_directory):
|
||||||
|
cls.KEY_FILE_DIRECTORY = key_file_directory
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ensure_datastore_encryptor(username: str, password: str):
|
||||||
|
if not get_datastore_encryptor():
|
||||||
|
AuthenticationService._init_encryptor_from_credentials(username, password)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def reset_datastore_encryptor(username: str, password: str):
|
||||||
|
remove_old_datastore_key(AuthenticationService.KEY_FILE_DIRECTORY)
|
||||||
|
AuthenticationService._init_encryptor_from_credentials(username, password)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _init_encryptor_from_credentials(username: str, password: str):
|
||||||
|
secret = AuthenticationService._get_secret_from_credentials(username, password)
|
||||||
|
initialize_datastore_encryptor(AuthenticationService.KEY_FILE_DIRECTORY, secret)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_secret_from_credentials(username: str, password: str) -> str:
|
||||||
|
return f"{username}:{password}"
|
|
@ -90,9 +90,9 @@ class ConfigService:
|
||||||
if should_decrypt:
|
if should_decrypt:
|
||||||
if config_key_as_arr in ENCRYPTED_CONFIG_VALUES:
|
if config_key_as_arr in ENCRYPTED_CONFIG_VALUES:
|
||||||
if isinstance(config, str):
|
if isinstance(config, str):
|
||||||
config = get_datastore_encryptor().dec(config)
|
config = get_datastore_encryptor().decrypt(config)
|
||||||
elif isinstance(config, list):
|
elif isinstance(config, list):
|
||||||
config = [get_datastore_encryptor().dec(x) for x in config]
|
config = [get_datastore_encryptor().decrypt(x) for x in config]
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -130,7 +130,7 @@ class ConfigService:
|
||||||
if item_value in items_from_config:
|
if item_value in items_from_config:
|
||||||
return
|
return
|
||||||
if should_encrypt:
|
if should_encrypt:
|
||||||
item_value = get_datastore_encryptor().enc(item_value)
|
item_value = get_datastore_encryptor().encrypt(item_value)
|
||||||
mongo.db.config.update(
|
mongo.db.config.update(
|
||||||
{"name": "newconfig"}, {"$addToSet": {item_key: item_value}}, upsert=False
|
{"name": "newconfig"}, {"$addToSet": {item_key: item_value}}, upsert=False
|
||||||
)
|
)
|
||||||
|
@ -350,10 +350,10 @@ class ConfigService:
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
flat_config[key] = [
|
flat_config[key] = [
|
||||||
get_datastore_encryptor().dec(item) for item in flat_config[key]
|
get_datastore_encryptor().decrypt(item) for item in flat_config[key]
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
flat_config[key] = get_datastore_encryptor().dec(flat_config[key])
|
flat_config[key] = get_datastore_encryptor().decrypt(flat_config[key])
|
||||||
return flat_config
|
return flat_config
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -379,25 +379,25 @@ class ConfigService:
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
config_arr[i] = (
|
config_arr[i] = (
|
||||||
get_datastore_encryptor().dec(config_arr[i])
|
get_datastore_encryptor().decrypt(config_arr[i])
|
||||||
if is_decrypt
|
if is_decrypt
|
||||||
else get_datastore_encryptor().enc(config_arr[i])
|
else get_datastore_encryptor().encrypt(config_arr[i])
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
parent_config_arr[config_arr_as_array[-1]] = (
|
parent_config_arr[config_arr_as_array[-1]] = (
|
||||||
get_datastore_encryptor().dec(config_arr)
|
get_datastore_encryptor().decrypt(config_arr)
|
||||||
if is_decrypt
|
if is_decrypt
|
||||||
else get_datastore_encryptor().enc(config_arr)
|
else get_datastore_encryptor().encrypt(config_arr)
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decrypt_ssh_key_pair(pair, encrypt=False):
|
def decrypt_ssh_key_pair(pair, encrypt=False):
|
||||||
if encrypt:
|
if encrypt:
|
||||||
pair["public_key"] = get_datastore_encryptor().enc(pair["public_key"])
|
pair["public_key"] = get_datastore_encryptor().encrypt(pair["public_key"])
|
||||||
pair["private_key"] = get_datastore_encryptor().enc(pair["private_key"])
|
pair["private_key"] = get_datastore_encryptor().encrypt(pair["private_key"])
|
||||||
else:
|
else:
|
||||||
pair["public_key"] = get_datastore_encryptor().dec(pair["public_key"])
|
pair["public_key"] = get_datastore_encryptor().decrypt(pair["public_key"])
|
||||||
pair["private_key"] = get_datastore_encryptor().dec(pair["private_key"])
|
pair["private_key"] = get_datastore_encryptor().decrypt(pair["private_key"])
|
||||||
return pair
|
return pair
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -33,11 +33,6 @@ class Database(object):
|
||||||
mongo.db[collection_name].drop()
|
mongo.db[collection_name].drop()
|
||||||
logger.info("Dropped collection {}".format(collection_name))
|
logger.info("Dropped collection {}".format(collection_name))
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def init_db():
|
|
||||||
if not mongo.db.collection_names():
|
|
||||||
Database.reset_db()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_mitigations_missing() -> bool:
|
def is_mitigations_missing() -> bool:
|
||||||
return bool(AttackMitigations.COLLECTION_NAME not in mongo.db.list_collection_names())
|
return bool(AttackMitigations.COLLECTION_NAME not in mongo.db.list_collection_names())
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from monkey_island.cc.services.authentication import AuthenticationService
|
||||||
from monkey_island.cc.services.post_breach_files import PostBreachFilesService
|
from monkey_island.cc.services.post_breach_files import PostBreachFilesService
|
||||||
from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService
|
from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService
|
||||||
|
|
||||||
|
@ -5,3 +6,4 @@ from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService
|
||||||
def initialize_services(data_dir):
|
def initialize_services(data_dir):
|
||||||
PostBreachFilesService.initialize(data_dir)
|
PostBreachFilesService.initialize(data_dir)
|
||||||
LocalMonkeyRunService.initialize(data_dir)
|
LocalMonkeyRunService.initialize(data_dir)
|
||||||
|
AuthenticationService.initialize(key_file_directory=data_dir)
|
||||||
|
|
|
@ -76,4 +76,4 @@ def encrypt_exploit_creds(telemetry_json):
|
||||||
credential = attempts[i][field]
|
credential = attempts[i][field]
|
||||||
if credential: # PowerShell exploiter's telem may have `None` here
|
if credential: # PowerShell exploiter's telem may have `None` here
|
||||||
if len(credential) > 0:
|
if len(credential) > 0:
|
||||||
attempts[i][field] = get_datastore_encryptor().enc(credential)
|
attempts[i][field] = get_datastore_encryptor().encrypt(credential)
|
||||||
|
|
|
@ -70,7 +70,7 @@ def encrypt_system_info_ssh_keys(ssh_info):
|
||||||
for idx, user in enumerate(ssh_info):
|
for idx, user in enumerate(ssh_info):
|
||||||
for field in ["public_key", "private_key", "known_hosts"]:
|
for field in ["public_key", "private_key", "known_hosts"]:
|
||||||
if ssh_info[idx][field]:
|
if ssh_info[idx][field]:
|
||||||
ssh_info[idx][field] = get_datastore_encryptor().enc(ssh_info[idx][field])
|
ssh_info[idx][field] = get_datastore_encryptor().encrypt(ssh_info[idx][field])
|
||||||
|
|
||||||
|
|
||||||
def process_credential_info(telemetry_json):
|
def process_credential_info(telemetry_json):
|
||||||
|
|
|
@ -41,7 +41,7 @@ def set_aws_keys(access_key_id: str, secret_access_key: str, session_token: str)
|
||||||
|
|
||||||
def _set_aws_key(key_type: str, key_value: str):
|
def _set_aws_key(key_type: str, key_value: str):
|
||||||
path_to_keys = AWS_KEYS_PATH
|
path_to_keys = AWS_KEYS_PATH
|
||||||
encrypted_key = get_datastore_encryptor().enc(key_value)
|
encrypted_key = get_datastore_encryptor().encrypt(key_value)
|
||||||
ConfigService.set_config_value(path_to_keys + [key_type], encrypted_key)
|
ConfigService.set_config_value(path_to_keys + [key_type], encrypted_key)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ export default class AuthService {
|
||||||
return this._authFetch(this.REGISTRATION_API_ENDPOINT, {
|
return this._authFetch(this.REGISTRATION_API_ENDPOINT, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
'user': username,
|
'username': username,
|
||||||
'password': password
|
'password': password
|
||||||
})
|
})
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
|
Binary file not shown.
|
@ -11,6 +11,7 @@ from tests.unit_tests.monkey_island.cc.server_utils.encryption.test_password_bas
|
||||||
)
|
)
|
||||||
|
|
||||||
from monkey_island.cc.server_utils.encryption import initialize_datastore_encryptor
|
from monkey_island.cc.server_utils.encryption import initialize_datastore_encryptor
|
||||||
|
from monkey_island.cc.services.authentication import AuthenticationService
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -27,6 +28,11 @@ def monkey_config_json(monkey_config):
|
||||||
return json.dumps(monkey_config)
|
return json.dumps(monkey_config)
|
||||||
|
|
||||||
|
|
||||||
|
MOCK_USERNAME = "m0nk3y_u53r"
|
||||||
|
MOCK_PASSWORD = "3cr3t_p455w0rd"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def uses_encryptor(data_for_tests_dir):
|
def uses_encryptor(data_for_tests_dir):
|
||||||
initialize_datastore_encryptor(data_for_tests_dir)
|
secret = AuthenticationService._get_secret_from_credentials(MOCK_USERNAME, MOCK_PASSWORD)
|
||||||
|
initialize_datastore_encryptor(data_for_tests_dir, secret)
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
from monkey_island.cc.server_utils.encryption import initialize_datastore_encryptor
|
|
||||||
from monkey_island.cc.server_utils.encryption.dict_encryption.field_encryptors import (
|
from monkey_island.cc.server_utils.encryption.dict_encryption.field_encryptors import (
|
||||||
StringListEncryptor,
|
StringListEncryptor,
|
||||||
)
|
)
|
||||||
|
@ -9,11 +6,6 @@ MOCK_STRING_LIST = ["test_1", "test_2"]
|
||||||
EMPTY_LIST = []
|
EMPTY_LIST = []
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def uses_encryptor(data_for_tests_dir):
|
|
||||||
initialize_datastore_encryptor(data_for_tests_dir)
|
|
||||||
|
|
||||||
|
|
||||||
def test_encryption_and_decryption(uses_encryptor):
|
def test_encryption_and_decryption(uses_encryptor):
|
||||||
encrypted_list = StringListEncryptor.encrypt(MOCK_STRING_LIST)
|
encrypted_list = StringListEncryptor.encrypt(MOCK_STRING_LIST)
|
||||||
assert not encrypted_list == MOCK_STRING_LIST
|
assert not encrypted_list == MOCK_STRING_LIST
|
||||||
|
|
|
@ -8,7 +8,7 @@ from tests.unit_tests.monkey_island.cc.services.utils.ciphertexts_for_encryption
|
||||||
|
|
||||||
from common.utils.exceptions import InvalidConfigurationError
|
from common.utils.exceptions import InvalidConfigurationError
|
||||||
from monkey_island.cc.resources.configuration_import import ConfigurationImport
|
from monkey_island.cc.resources.configuration_import import ConfigurationImport
|
||||||
from monkey_island.cc.server_utils.encryption import PasswordBasedEncryptor
|
from monkey_island.cc.server_utils.encryption import PasswordBasedStringEncryptor
|
||||||
|
|
||||||
|
|
||||||
def test_is_config_encrypted__json(monkey_config_json):
|
def test_is_config_encrypted__json(monkey_config_json):
|
||||||
|
@ -17,7 +17,7 @@ def test_is_config_encrypted__json(monkey_config_json):
|
||||||
|
|
||||||
@pytest.mark.slow
|
@pytest.mark.slow
|
||||||
def test_is_config_encrypted__ciphertext(monkey_config_json):
|
def test_is_config_encrypted__ciphertext(monkey_config_json):
|
||||||
pb_encryptor = PasswordBasedEncryptor(PASSWORD)
|
pb_encryptor = PasswordBasedStringEncryptor(PASSWORD)
|
||||||
encrypted_config = pb_encryptor.encrypt(monkey_config_json)
|
encrypted_config = pb_encryptor.encrypt(monkey_config_json)
|
||||||
assert ConfigurationImport.is_config_encrypted(encrypted_config)
|
assert ConfigurationImport.is_config_encrypted(encrypted_config)
|
||||||
|
|
||||||
|
|
|
@ -1,35 +1,61 @@
|
||||||
import os
|
import pytest
|
||||||
|
|
||||||
from monkey_island.cc.server_utils.encryption import (
|
from monkey_island.cc.server_utils.encryption import (
|
||||||
|
data_store_encryptor,
|
||||||
get_datastore_encryptor,
|
get_datastore_encryptor,
|
||||||
initialize_datastore_encryptor,
|
initialize_datastore_encryptor,
|
||||||
|
remove_old_datastore_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
PASSWORD_FILENAME = "mongo_key.bin"
|
|
||||||
|
|
||||||
PLAINTEXT = "Hello, Monkey!"
|
PLAINTEXT = "Hello, Monkey!"
|
||||||
CYPHERTEXT = "vKgvD6SjRyIh1dh2AM/rnTa0NI/vjfwnbZLbMocWtE4e42WJmSUz2ordtbQrH1Fq"
|
MOCK_SECRET = "53CR31"
|
||||||
|
|
||||||
|
|
||||||
def test_aes_cbc_encryption(data_for_tests_dir):
|
@pytest.mark.usefixtures("uses_encryptor")
|
||||||
initialize_datastore_encryptor(data_for_tests_dir)
|
def test_encryption(data_for_tests_dir):
|
||||||
|
encrypted_data = get_datastore_encryptor().encrypt(PLAINTEXT)
|
||||||
|
assert encrypted_data != PLAINTEXT
|
||||||
|
|
||||||
assert get_datastore_encryptor().enc(PLAINTEXT) != PLAINTEXT
|
decrypted_data = get_datastore_encryptor().decrypt(encrypted_data)
|
||||||
|
assert decrypted_data == PLAINTEXT
|
||||||
|
|
||||||
|
|
||||||
def test_aes_cbc_decryption(data_for_tests_dir):
|
@pytest.fixture
|
||||||
initialize_datastore_encryptor(data_for_tests_dir)
|
def cleanup_encryptor():
|
||||||
|
yield
|
||||||
assert get_datastore_encryptor().dec(CYPHERTEXT) == PLAINTEXT
|
data_store_encryptor._encryptor = None
|
||||||
|
|
||||||
|
|
||||||
def test_aes_cbc_enc_dec(data_for_tests_dir):
|
@pytest.mark.usefixtures("cleanup_encryptor")
|
||||||
initialize_datastore_encryptor(data_for_tests_dir)
|
@pytest.fixture
|
||||||
|
def initialized_encryptor_dir(tmpdir):
|
||||||
assert get_datastore_encryptor().dec(get_datastore_encryptor().enc(PLAINTEXT)) == PLAINTEXT
|
initialize_datastore_encryptor(tmpdir, MOCK_SECRET)
|
||||||
|
return tmpdir
|
||||||
|
|
||||||
|
|
||||||
def test_create_new_password_file(tmpdir):
|
def test_key_creation(initialized_encryptor_dir):
|
||||||
initialize_datastore_encryptor(tmpdir)
|
assert (initialized_encryptor_dir / data_store_encryptor._KEY_FILENAME).isfile()
|
||||||
|
|
||||||
assert os.path.isfile(os.path.join(tmpdir, PASSWORD_FILENAME))
|
|
||||||
|
def test_key_removal(initialized_encryptor_dir):
|
||||||
|
remove_old_datastore_key(initialized_encryptor_dir)
|
||||||
|
assert not (initialized_encryptor_dir / data_store_encryptor._KEY_FILENAME).isfile()
|
||||||
|
|
||||||
|
|
||||||
|
def test_key_removal__no_key(tmpdir):
|
||||||
|
assert not (tmpdir / data_store_encryptor._KEY_FILENAME).isfile()
|
||||||
|
# Make sure no error thrown when we try to remove an non-existing key
|
||||||
|
remove_old_datastore_key(tmpdir)
|
||||||
|
data_store_encryptor._factory = None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("cleanup_encryptor")
|
||||||
|
def test_key_file_encryption(tmpdir, monkeypatch):
|
||||||
|
monkeypatch.setattr(data_store_encryptor, "_get_random_bytes", lambda: PLAINTEXT.encode())
|
||||||
|
initialize_datastore_encryptor(tmpdir, MOCK_SECRET)
|
||||||
|
key_file_path = data_store_encryptor._get_key_file_path(tmpdir)
|
||||||
|
key_file_contents = open(key_file_path, "rb").read()
|
||||||
|
assert not key_file_contents == PLAINTEXT.encode()
|
||||||
|
|
||||||
|
key_based_encryptor = data_store_encryptor._load_existing_key(key_file_path, MOCK_SECRET)
|
||||||
|
assert key_based_encryptor._key == PLAINTEXT.encode()
|
||||||
|
|
|
@ -4,7 +4,10 @@ from tests.unit_tests.monkey_island.cc.services.utils.ciphertexts_for_encryption
|
||||||
VALID_CIPHER_TEXT,
|
VALID_CIPHER_TEXT,
|
||||||
)
|
)
|
||||||
|
|
||||||
from monkey_island.cc.server_utils.encryption import InvalidCredentialsError, PasswordBasedEncryptor
|
from monkey_island.cc.server_utils.encryption import (
|
||||||
|
InvalidCredentialsError,
|
||||||
|
PasswordBasedStringEncryptor,
|
||||||
|
)
|
||||||
|
|
||||||
MONKEY_CONFIGS_DIR_PATH = "monkey_configs"
|
MONKEY_CONFIGS_DIR_PATH = "monkey_configs"
|
||||||
STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME = "monkey_config_standard.json"
|
STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME = "monkey_config_standard.json"
|
||||||
|
@ -14,27 +17,27 @@ INCORRECT_PASSWORD = "goodbye321"
|
||||||
|
|
||||||
@pytest.mark.slow
|
@pytest.mark.slow
|
||||||
def test_encrypt_decrypt_string(monkey_config_json):
|
def test_encrypt_decrypt_string(monkey_config_json):
|
||||||
pb_encryptor = PasswordBasedEncryptor(PASSWORD)
|
pb_encryptor = PasswordBasedStringEncryptor(PASSWORD)
|
||||||
encrypted_config = pb_encryptor.encrypt(monkey_config_json)
|
encrypted_config = pb_encryptor.encrypt(monkey_config_json)
|
||||||
assert pb_encryptor.decrypt(encrypted_config) == monkey_config_json
|
assert pb_encryptor.decrypt(encrypted_config) == monkey_config_json
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow
|
@pytest.mark.slow
|
||||||
def test_decrypt_string__wrong_password(monkey_config_json):
|
def test_decrypt_string__wrong_password(monkey_config_json):
|
||||||
pb_encryptor = PasswordBasedEncryptor(INCORRECT_PASSWORD)
|
pb_encryptor = PasswordBasedStringEncryptor(INCORRECT_PASSWORD)
|
||||||
with pytest.raises(InvalidCredentialsError):
|
with pytest.raises(InvalidCredentialsError):
|
||||||
pb_encryptor.decrypt(VALID_CIPHER_TEXT)
|
pb_encryptor.decrypt(VALID_CIPHER_TEXT)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow
|
@pytest.mark.slow
|
||||||
def test_decrypt_string__malformed_corrupted():
|
def test_decrypt_string__malformed_corrupted():
|
||||||
pb_encryptor = PasswordBasedEncryptor(PASSWORD)
|
pb_encryptor = PasswordBasedStringEncryptor(PASSWORD)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
pb_encryptor.decrypt(MALFORMED_CIPHER_TEXT_CORRUPTED)
|
pb_encryptor.decrypt(MALFORMED_CIPHER_TEXT_CORRUPTED)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow
|
@pytest.mark.slow
|
||||||
def test_decrypt_string__no_password(monkey_config_json):
|
def test_decrypt_string__no_password(monkey_config_json):
|
||||||
pb_encryptor = PasswordBasedEncryptor("")
|
pb_encryptor = PasswordBasedStringEncryptor("")
|
||||||
with pytest.raises(InvalidCredentialsError):
|
with pytest.raises(InvalidCredentialsError):
|
||||||
pb_encryptor.decrypt(VALID_CIPHER_TEXT)
|
pb_encryptor.decrypt(VALID_CIPHER_TEXT)
|
||||||
|
|
|
@ -156,7 +156,7 @@ def test_get_stolen_creds_exploit(fake_mongo):
|
||||||
assert expected_stolen_creds_exploit == stolen_creds_exploit
|
assert expected_stolen_creds_exploit == stolen_creds_exploit
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("uses_database")
|
@pytest.mark.usefixtures("uses_database", "uses_encryptor")
|
||||||
def test_get_stolen_creds_system_info(fake_mongo):
|
def test_get_stolen_creds_system_info(fake_mongo):
|
||||||
fake_mongo.db.monkey.insert_one(MONKEY_TELEM)
|
fake_mongo.db.monkey.insert_one(MONKEY_TELEM)
|
||||||
save_telemetry(SYSTEM_INFO_TELEMETRY_TELEM)
|
save_telemetry(SYSTEM_INFO_TELEMETRY_TELEM)
|
||||||
|
|
|
@ -18,12 +18,14 @@ def mock_port_in_env_singleton(monkeypatch, PORT):
|
||||||
monkeypatch.setattr("monkey_island.cc.services.config.env_singleton", mock_singleton)
|
monkeypatch.setattr("monkey_island.cc.services.config.env_singleton", mock_singleton)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("uses_encryptor")
|
||||||
def test_set_server_ips_in_config_command_servers(config, IPS, PORT):
|
def test_set_server_ips_in_config_command_servers(config, IPS, PORT):
|
||||||
ConfigService.set_server_ips_in_config(config)
|
ConfigService.set_server_ips_in_config(config)
|
||||||
expected_config_command_servers = [f"{ip}:{PORT}" for ip in IPS]
|
expected_config_command_servers = [f"{ip}:{PORT}" for ip in IPS]
|
||||||
assert config["internal"]["island_server"]["command_servers"] == expected_config_command_servers
|
assert config["internal"]["island_server"]["command_servers"] == expected_config_command_servers
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("uses_encryptor")
|
||||||
def test_set_server_ips_in_config_current_server(config, IPS, PORT):
|
def test_set_server_ips_in_config_current_server(config, IPS, PORT):
|
||||||
ConfigService.set_server_ips_in_config(config)
|
ConfigService.set_server_ips_in_config(config)
|
||||||
expected_config_current_server = f"{IPS[0]}:{PORT}"
|
expected_config_current_server = f"{IPS[0]}:{PORT}"
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from monkey_island.cc.services.config_manipulator import update_config_on_mode_set
|
from monkey_island.cc.services.config_manipulator import update_config_on_mode_set
|
||||||
from monkey_island.cc.services.mode.mode_enum import IslandModeEnum
|
from monkey_island.cc.services.mode.mode_enum import IslandModeEnum
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("uses_encryptor")
|
||||||
def test_update_config_on_mode_set_advanced(config, monkeypatch):
|
def test_update_config_on_mode_set_advanced(config, monkeypatch):
|
||||||
monkeypatch.setattr("monkey_island.cc.services.config.ConfigService.get_config", lambda: config)
|
monkeypatch.setattr("monkey_island.cc.services.config.ConfigService.get_config", lambda: config)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
|
@ -14,6 +17,7 @@ def test_update_config_on_mode_set_advanced(config, monkeypatch):
|
||||||
assert manipulated_config == config
|
assert manipulated_config == config
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("uses_encryptor")
|
||||||
def test_update_config_on_mode_set_ransomware(config, monkeypatch):
|
def test_update_config_on_mode_set_ransomware(config, monkeypatch):
|
||||||
monkeypatch.setattr("monkey_island.cc.services.config.ConfigService.get_config", lambda: config)
|
monkeypatch.setattr("monkey_island.cc.services.config.ConfigService.get_config", lambda: config)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
|
|
|
@ -5,10 +5,7 @@ import pytest
|
||||||
|
|
||||||
from common.config_value_paths import AWS_KEYS_PATH
|
from common.config_value_paths import AWS_KEYS_PATH
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
from monkey_island.cc.server_utils.encryption import (
|
from monkey_island.cc.server_utils.encryption import get_datastore_encryptor
|
||||||
get_datastore_encryptor,
|
|
||||||
initialize_datastore_encryptor,
|
|
||||||
)
|
|
||||||
from monkey_island.cc.services.config import ConfigService
|
from monkey_island.cc.services.config import ConfigService
|
||||||
from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import (
|
from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import (
|
||||||
is_aws_keys_setup,
|
is_aws_keys_setup,
|
||||||
|
@ -19,7 +16,7 @@ class MockObject:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("uses_database")
|
@pytest.mark.usefixtures("uses_database", "uses_encryptor")
|
||||||
def test_is_aws_keys_setup(tmp_path):
|
def test_is_aws_keys_setup(tmp_path):
|
||||||
# Mock default configuration
|
# Mock default configuration
|
||||||
ConfigService.init_default_config()
|
ConfigService.init_default_config()
|
||||||
|
@ -29,9 +26,7 @@ def test_is_aws_keys_setup(tmp_path):
|
||||||
mongo.db.config.find_one = MagicMock(return_value=ConfigService.default_config)
|
mongo.db.config.find_one = MagicMock(return_value=ConfigService.default_config)
|
||||||
assert not is_aws_keys_setup()
|
assert not is_aws_keys_setup()
|
||||||
|
|
||||||
# Make sure noone changed config path and broke this function
|
bogus_key_value = get_datastore_encryptor().encrypt("bogus_aws_key")
|
||||||
initialize_datastore_encryptor(tmp_path)
|
|
||||||
bogus_key_value = get_datastore_encryptor().enc("bogus_aws_key")
|
|
||||||
dpath.util.set(
|
dpath.util.set(
|
||||||
ConfigService.default_config, AWS_KEYS_PATH + ["aws_secret_access_key"], bogus_key_value
|
ConfigService.default_config, AWS_KEYS_PATH + ["aws_secret_access_key"], bogus_key_value
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue