Merge pull request #2083 from guardicore/1965-encrypt-credentials

1965 encrypt credentials
This commit is contained in:
Mike Salvatore 2022-07-13 06:07:50 -04:00 committed by GitHub
commit 07a8d6194c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 52 deletions

View File

@ -1,10 +1,11 @@
from typing import Sequence from typing import Any, Dict, Mapping, Sequence
from pymongo import MongoClient from pymongo import MongoClient
from common.credentials import Credentials from common.credentials import Credentials
from monkey_island.cc.repository import RemovalError, RetrievalError, StorageError from monkey_island.cc.repository import RemovalError, RetrievalError, StorageError
from monkey_island.cc.repository.i_credentials_repository import ICredentialsRepository from monkey_island.cc.repository.i_credentials_repository import ICredentialsRepository
from monkey_island.cc.server_utils.encryption import ILockableEncryptor
class MongoCredentialsRepository(ICredentialsRepository): class MongoCredentialsRepository(ICredentialsRepository):
@ -12,18 +13,15 @@ class MongoCredentialsRepository(ICredentialsRepository):
Store credentials in a mongo database that can be used to propagate around the network. Store credentials in a mongo database that can be used to propagate around the network.
""" """
def __init__(self, mongo: MongoClient): def __init__(self, mongo: MongoClient, repository_encryptor: ILockableEncryptor):
self._mongo = mongo self._mongo = mongo
self._repository_encryptor = repository_encryptor
def get_configured_credentials(self) -> Sequence[Credentials]: def get_configured_credentials(self) -> Sequence[Credentials]:
return MongoCredentialsRepository._get_credentials_from_collection( return self._get_credentials_from_collection(self._mongo.db.configured_credentials)
self._mongo.db.configured_credentials
)
def get_stolen_credentials(self) -> Sequence[Credentials]: def get_stolen_credentials(self) -> Sequence[Credentials]:
return MongoCredentialsRepository._get_credentials_from_collection( return self._get_credentials_from_collection(self._mongo.db.stolen_credentials)
self._mongo.db.stolen_credentials
)
def get_all_credentials(self) -> Sequence[Credentials]: def get_all_credentials(self) -> Sequence[Credentials]:
configured_credentials = self.get_configured_credentials() configured_credentials = self.get_configured_credentials()
@ -33,14 +31,10 @@ class MongoCredentialsRepository(ICredentialsRepository):
def save_configured_credentials(self, credentials: Sequence[Credentials]): def save_configured_credentials(self, credentials: Sequence[Credentials]):
# TODO: Fix deduplication of Credentials in mongo # TODO: Fix deduplication of Credentials in mongo
MongoCredentialsRepository._save_credentials_to_collection( self._save_credentials_to_collection(credentials, self._mongo.db.configured_credentials)
credentials, self._mongo.db.configured_credentials
)
def save_stolen_credentials(self, credentials: Sequence[Credentials]): def save_stolen_credentials(self, credentials: Sequence[Credentials]):
MongoCredentialsRepository._save_credentials_to_collection( self._save_credentials_to_collection(credentials, self._mongo.db.stolen_credentials)
credentials, self._mongo.db.stolen_credentials
)
def remove_configured_credentials(self): def remove_configured_credentials(self):
MongoCredentialsRepository._remove_credentials_fom_collection( MongoCredentialsRepository._remove_credentials_fom_collection(
@ -56,27 +50,61 @@ class MongoCredentialsRepository(ICredentialsRepository):
self.remove_configured_credentials() self.remove_configured_credentials()
self.remove_stolen_credentials() self.remove_stolen_credentials()
@staticmethod def _get_credentials_from_collection(self, collection) -> Sequence[Credentials]:
def _get_credentials_from_collection(collection) -> Sequence[Credentials]:
try: try:
collection_result = [] collection_result = []
list_collection_result = list(collection.find({})) list_collection_result = list(collection.find({}))
for c in list_collection_result: for encrypted_credentials in list_collection_result:
del c["_id"] del encrypted_credentials["_id"]
collection_result.append(Credentials.from_mapping(c)) plaintext_credentials = self._decrypt_credentials_mapping(encrypted_credentials)
collection_result.append(Credentials.from_mapping(plaintext_credentials))
return collection_result return collection_result
except Exception as err: except Exception as err:
raise RetrievalError(err) raise RetrievalError(err)
@staticmethod def _save_credentials_to_collection(self, credentials: Sequence[Credentials], collection):
def _save_credentials_to_collection(credentials: Sequence[Credentials], collection):
try: try:
for c in credentials: for c in credentials:
collection.insert_one(Credentials.to_mapping(c)) encrypted_credentials = self._encrypt_credentials_mapping(Credentials.to_mapping(c))
collection.insert_one(encrypted_credentials)
except Exception as err: except Exception as err:
raise StorageError(err) raise StorageError(err)
# NOTE: The encryption/decryption is complicated and also full of mostly duplicated code. Rather
# than spend the effort to improve them now, we can revisit them when we resolve #2072.
# Resolving #2072 will make it easier to simplify these methods and remove duplication.
#
# If possible, implement the encryption/decryption as a decorator so it can be reused with
# different ICredentialsRepository implementations
def _encrypt_credentials_mapping(self, mapping: Mapping[str, Any]) -> Mapping[str, Any]:
encrypted_mapping: Dict[str, Any] = {}
for secret_or_identity, credentials_components in mapping.items():
encrypted_mapping[secret_or_identity] = []
for component in credentials_components:
encrypted_component = {}
for key, value in component.items():
encrypted_component[key] = self._repository_encryptor.encrypt(value.encode())
encrypted_mapping[secret_or_identity].append(encrypted_component)
return encrypted_mapping
def _decrypt_credentials_mapping(self, mapping: Mapping[str, Any]) -> Mapping[str, Any]:
encrypted_mapping: Dict[str, Any] = {}
for secret_or_identity, credentials_components in mapping.items():
encrypted_mapping[secret_or_identity] = []
for component in credentials_components:
encrypted_component = {}
for key, value in component.items():
encrypted_component[key] = self._repository_encryptor.decrypt(value).decode()
encrypted_mapping[secret_or_identity].append(encrypted_component)
return encrypted_mapping
@staticmethod @staticmethod
def _remove_credentials_fom_collection(collection): def _remove_credentials_fom_collection(collection):
try: try:

View File

@ -60,12 +60,12 @@ class RepositoryEncryptor(ILockableEncryptor):
def encrypt(self, plaintext: bytes) -> bytes: def encrypt(self, plaintext: bytes) -> bytes:
if self._key_based_encryptor is None: if self._key_based_encryptor is None:
raise LockedKeyError("Cannot encrypt while the encryptor is locked)") raise LockedKeyError("Cannot encrypt while the encryptor is locked")
return self._key_based_encryptor.encrypt(plaintext) return self._key_based_encryptor.encrypt(plaintext)
def decrypt(self, ciphertext: bytes) -> bytes: def decrypt(self, ciphertext: bytes) -> bytes:
if self._key_based_encryptor is None: if self._key_based_encryptor is None:
raise LockedKeyError("Cannot decrypt while the encryptor is locked)") raise LockedKeyError("Cannot decrypt while the encryptor is locked")
return self._key_based_encryptor.decrypt(ciphertext) return self._key_based_encryptor.decrypt(ciphertext)

View File

@ -1,8 +1,11 @@
from unittest.mock import MagicMock
import mongomock import mongomock
import pytest import pytest
from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username
from monkey_island.cc.repository import MongoCredentialsRepository from monkey_island.cc.repository import MongoCredentialsRepository
from monkey_island.cc.server_utils.encryption import ILockableEncryptor
USER1 = "test_user_1" USER1 = "test_user_1"
USER2 = "test_user_2" USER2 = "test_user_2"
@ -36,80 +39,115 @@ STOLEN_CREDENTIALS = [CREDENTIALS_OBJECT_2]
CREDENTIALS_LIST = [CREDENTIALS_OBJECT_1, CREDENTIALS_OBJECT_2] CREDENTIALS_LIST = [CREDENTIALS_OBJECT_1, CREDENTIALS_OBJECT_2]
@pytest.fixture def reverse(data: bytes) -> bytes:
def mongo_repository(): return bytes(reversed(data))
mongo = mongomock.MongoClient()
return MongoCredentialsRepository(mongo)
@pytest.fixture
def repository_encryptor():
repository_encryptor = MagicMock(spec=ILockableEncryptor)
repository_encryptor.encrypt = MagicMock(side_effect=reverse)
repository_encryptor.decrypt = MagicMock(side_effect=reverse)
return repository_encryptor
@pytest.fixture
def mongo_client():
return mongomock.MongoClient()
@pytest.fixture
def mongo_repository(mongo_client, repository_encryptor):
return MongoCredentialsRepository(mongo_client, repository_encryptor)
def test_mongo_repository_get_configured(mongo_repository): def test_mongo_repository_get_configured(mongo_repository):
actual_configured_credentials = mongo_repository.get_configured_credentials() actual_configured_credentials = mongo_repository.get_configured_credentials()
assert actual_configured_credentials == [] assert actual_configured_credentials == []
def test_mongo_repository_get_stolen(mongo_repository): def test_mongo_repository_get_stolen(mongo_repository):
actual_stolen_credentials = mongo_repository.get_stolen_credentials() actual_stolen_credentials = mongo_repository.get_stolen_credentials()
assert actual_stolen_credentials == [] assert actual_stolen_credentials == []
def test_mongo_repository_get_all(mongo_repository): def test_mongo_repository_get_all(mongo_repository):
actual_credentials = mongo_repository.get_all_credentials() actual_credentials = mongo_repository.get_all_credentials()
assert actual_credentials == [] assert actual_credentials == []
def test_mongo_repository_configured(mongo_repository): def test_mongo_repository_configured(mongo_repository):
mongo_repository.save_configured_credentials(CREDENTIALS_LIST) mongo_repository.save_configured_credentials(CREDENTIALS_LIST)
actual_configured_credentials = mongo_repository.get_configured_credentials() actual_configured_credentials = mongo_repository.get_configured_credentials()
assert actual_configured_credentials == CREDENTIALS_LIST assert actual_configured_credentials == CREDENTIALS_LIST
mongo_repository.remove_configured_credentials() mongo_repository.remove_configured_credentials()
actual_configured_credentials = mongo_repository.get_configured_credentials() actual_configured_credentials = mongo_repository.get_configured_credentials()
assert actual_configured_credentials == [] assert actual_configured_credentials == []
def test_mongo_repository_stolen(mongo_repository): def test_mongo_repository_stolen(mongo_repository):
mongo_repository.save_configured_credentials(CONFIGURED_CREDENTIALS)
mongo_repository.save_stolen_credentials(STOLEN_CREDENTIALS) mongo_repository.save_stolen_credentials(STOLEN_CREDENTIALS)
actual_stolen_credentials = mongo_repository.get_stolen_credentials() actual_stolen_credentials = mongo_repository.get_stolen_credentials()
assert sorted(actual_stolen_credentials) == sorted(STOLEN_CREDENTIALS)
assert actual_stolen_credentials == STOLEN_CREDENTIALS
mongo_repository.remove_stolen_credentials() mongo_repository.remove_stolen_credentials()
actual_stolen_credentials = mongo_repository.get_stolen_credentials() actual_stolen_credentials = mongo_repository.get_stolen_credentials()
assert actual_stolen_credentials == [] assert actual_stolen_credentials == []
def test_mongo_repository_all(mongo_repository): def test_mongo_repository_all(mongo_repository):
mongo_repository.save_configured_credentials(CONFIGURED_CREDENTIALS) mongo_repository.save_configured_credentials(CONFIGURED_CREDENTIALS)
mongo_repository.save_stolen_credentials(STOLEN_CREDENTIALS) mongo_repository.save_stolen_credentials(STOLEN_CREDENTIALS)
actual_credentials = mongo_repository.get_all_credentials() actual_credentials = mongo_repository.get_all_credentials()
assert actual_credentials == CREDENTIALS_LIST assert actual_credentials == CREDENTIALS_LIST
mongo_repository.remove_all_credentials() mongo_repository.remove_all_credentials()
actual_credentials = mongo_repository.get_all_credentials() assert mongo_repository.get_all_credentials() == []
actual_stolen_credentials = mongo_repository.get_stolen_credentials() assert mongo_repository.get_stolen_credentials() == []
actual_configured_credentials = mongo_repository.get_configured_credentials() assert mongo_repository.get_configured_credentials() == []
assert actual_credentials == []
assert actual_stolen_credentials == [] # NOTE: The following tests are complicated, but they work. Rather than spend the effort to improve
assert actual_configured_credentials == [] # them now, we can revisit them when we resolve #2072. Resolving #2072 will make it easier to
# simplify these tests.
def test_configured_secrets_encrypted(mongo_repository, mongo_client):
mongo_repository.save_configured_credentials([CREDENTIALS_OBJECT_2])
check_if_stored_credentials_encrypted(mongo_client, CREDENTIALS_OBJECT_2)
def test_stolen_secrets_encrypted(mongo_repository, mongo_client):
mongo_repository.save_stolen_credentials([CREDENTIALS_OBJECT_2])
check_if_stored_credentials_encrypted(mongo_client, CREDENTIALS_OBJECT_2)
def check_if_stored_credentials_encrypted(mongo_client, original_credentials):
raw_credentials = get_all_credentials_in_mongo(mongo_client)
original_credentials_mapping = Credentials.to_mapping(original_credentials)
for rc in raw_credentials:
for identity_or_secret, credentials_components in rc.items():
for component in credentials_components:
for key, value in component.items():
assert (
original_credentials_mapping[identity_or_secret][0].get(key, None) != value
)
def get_all_credentials_in_mongo(mongo_client):
encrypted_credentials = []
# Loop through all databases and collections and search for credentials. We don't want the tests
# to assume anything about the internal workings of the repository.
for db in mongo_client.list_database_names():
for collection in mongo_client[db].list_collection_names():
mongo_credentials = mongo_client[db][collection].find({})
for mc in mongo_credentials:
del mc["_id"]
encrypted_credentials.append(mc)
return encrypted_credentials