diff --git a/monkey/common/common_consts/telem_categories.py b/monkey/common/common_consts/telem_categories.py index c9d3f82bd..d1e931721 100644 --- a/monkey/common/common_consts/telem_categories.py +++ b/monkey/common/common_consts/telem_categories.py @@ -9,3 +9,4 @@ class TelemCategoryEnum: ATTACK = "attack" FILE_ENCRYPTION = "file_encryption" AWS_INFO = "aws_info" + CREDENTIALS = "credentials" diff --git a/monkey/infection_monkey/telemetry/credentials_telem.py b/monkey/infection_monkey/telemetry/credentials_telem.py new file mode 100644 index 000000000..5da7040d5 --- /dev/null +++ b/monkey/infection_monkey/telemetry/credentials_telem.py @@ -0,0 +1,40 @@ +import enum +import json +from typing import Dict, Iterable + +from common.common_consts.telem_categories import TelemCategoryEnum +from infection_monkey.i_puppet.credential_collection import Credentials, ICredentialComponent +from infection_monkey.telemetry.base_telem import BaseTelem + + +class CredentialsTelem(BaseTelem): + telem_category = TelemCategoryEnum.CREDENTIALS + + def __init__(self, credentials: Iterable[Credentials]): + """ + Used to send information about stolen or discovered credentials to the Island. + :param credentials: An iterable containing credentials to be sent to the Island. + """ + self._credentials = credentials + + def get_data(self) -> Dict: + # TODO: At a later time we can consider factoring this into a Serializer class or similar. + return json.loads(json.dumps(self._credentials, default=_serialize)) + + +def _serialize(obj): + if isinstance(obj, enum.Enum): + return obj.name + + if isinstance(obj, ICredentialComponent): + # This is a workaround for ICredentialComponents that are implemented as dataclasses. If the + # credential_type attribute is populated with `field(init=False, ...)`, then credential_type + # is not added to the object's __dict__ attribute. The biggest risk of this workaround is + # that we might change the name of the credential_type field in ICredentialComponents, but + # automated refactoring tools would not detect that this string needs to change. This is + # mittigated by the call to getattr() below, which will raise an AttributeException if the + # attribute name changes and a unit test will fail under these conditions. + credential_type = getattr(obj, "credential_type") + return dict(obj.__dict__, **{"credential_type": credential_type}) + + return getattr(obj, "__dict__", str(obj)) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py new file mode 100644 index 000000000..a3d1e3f6f --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py @@ -0,0 +1,37 @@ +import json + +from infection_monkey.credential_collectors import Password, SSHKeypair, Username +from infection_monkey.i_puppet import Credentials +from infection_monkey.telemetry.credentials_telem import CredentialsTelem + + +def test_credential_telem_send(spy_send_telemetry): + username = "m0nkey" + password = "mmm" + public_key = "pub_key" + private_key = "priv_key" + + expected_data = [ + { + "identities": [{"username": username, "credential_type": "USERNAME"}], + "secrets": [ + {"password": password, "credential_type": "PASSWORD"}, + { + "private_key": "pub_key", + "public_key": "priv_key", + "credential_type": "SSH_KEYPAIR", + }, + ], + } + ] + + credentials = Credentials( + [Username(username)], [Password(password), SSHKeypair(public_key, private_key)] + ) + + telem = CredentialsTelem([credentials]) + telem.send() + + expected_data = json.dumps(expected_data, cls=telem.json_encoder) + assert spy_send_telemetry.data == expected_data + assert spy_send_telemetry.telem_category == "credentials"