From 3ac60988a87aea25e1a53d4759fae1ac038cb4b4 Mon Sep 17 00:00:00 2001
From: vakarisz
Date: Tue, 30 Aug 2022 13:38:59 +0300
Subject: [PATCH 01/29] Common: Replace credentials dataclasses with pydantic
dataclasses
For pydantic to work properly, the models need to either use pydantic version of dataclass or inherit BaseModel, but not both. https://pydantic-docs.helpmanual.io/usage/dataclasses/
---
monkey/common/credentials/i_credential_component.py | 3 +++
monkey/common/credentials/lm_hash.py | 8 +++++---
monkey/common/credentials/nt_hash.py | 8 +++++---
monkey/common/credentials/password.py | 8 +++++---
monkey/common/credentials/ssh_keypair.py | 12 ++++++++----
monkey/common/credentials/username.py | 8 +++++---
.../common/credentials/test_credentials.py | 2 +-
...aggregating_propagation_credentials_repository.py | 4 ++--
.../cc/services/reporting/test_format_credentials.py | 12 ++++++------
9 files changed, 40 insertions(+), 25 deletions(-)
diff --git a/monkey/common/credentials/i_credential_component.py b/monkey/common/credentials/i_credential_component.py
index 03dc75604..ba55b3cce 100644
--- a/monkey/common/credentials/i_credential_component.py
+++ b/monkey/common/credentials/i_credential_component.py
@@ -1,8 +1,11 @@
from abc import ABC, abstractmethod
+from pydantic.dataclasses import dataclass
+
from . import CredentialComponentType
+@dataclass
class ICredentialComponent(ABC):
@property
@abstractmethod
diff --git a/monkey/common/credentials/lm_hash.py b/monkey/common/credentials/lm_hash.py
index 5a04e8bae..df59beeb3 100644
--- a/monkey/common/credentials/lm_hash.py
+++ b/monkey/common/credentials/lm_hash.py
@@ -1,6 +1,8 @@
-from dataclasses import dataclass, field
+from dataclasses import field
+from typing import ClassVar
from marshmallow import fields
+from pydantic.dataclasses import dataclass
from . import CredentialComponentType, ICredentialComponent
from .credential_component_schema import CredentialComponentSchema, CredentialTypeField
@@ -12,9 +14,9 @@ class LMHashSchema(CredentialComponentSchema):
lm_hash = fields.Str(validate=ntlm_hash_validator)
-@dataclass(frozen=True)
+@dataclass
class LMHash(ICredentialComponent):
- credential_type: CredentialComponentType = field(
+ credential_type: ClassVar[CredentialComponentType] = field(
default=CredentialComponentType.LM_HASH, init=False
)
lm_hash: str
diff --git a/monkey/common/credentials/nt_hash.py b/monkey/common/credentials/nt_hash.py
index a7145a5a0..e7b84449a 100644
--- a/monkey/common/credentials/nt_hash.py
+++ b/monkey/common/credentials/nt_hash.py
@@ -1,6 +1,8 @@
-from dataclasses import dataclass, field
+from dataclasses import field
+from typing import ClassVar
from marshmallow import fields
+from pydantic.dataclasses import dataclass
from . import CredentialComponentType, ICredentialComponent
from .credential_component_schema import CredentialComponentSchema, CredentialTypeField
@@ -12,9 +14,9 @@ class NTHashSchema(CredentialComponentSchema):
nt_hash = fields.Str(validate=ntlm_hash_validator)
-@dataclass(frozen=True)
+@dataclass
class NTHash(ICredentialComponent):
- credential_type: CredentialComponentType = field(
+ credential_type: ClassVar[CredentialComponentType] = field(
default=CredentialComponentType.NT_HASH, init=False
)
nt_hash: str
diff --git a/monkey/common/credentials/password.py b/monkey/common/credentials/password.py
index b7bd1b84c..8fac2a37f 100644
--- a/monkey/common/credentials/password.py
+++ b/monkey/common/credentials/password.py
@@ -1,6 +1,8 @@
-from dataclasses import dataclass, field
+from dataclasses import field
+from typing import ClassVar
from marshmallow import fields
+from pydantic.dataclasses import dataclass
from . import CredentialComponentType, ICredentialComponent
from .credential_component_schema import CredentialComponentSchema, CredentialTypeField
@@ -11,9 +13,9 @@ class PasswordSchema(CredentialComponentSchema):
password = fields.Str()
-@dataclass(frozen=True)
+@dataclass
class Password(ICredentialComponent):
- credential_type: CredentialComponentType = field(
+ credential_type: ClassVar[CredentialComponentType] = field(
default=CredentialComponentType.PASSWORD, init=False
)
password: str
diff --git a/monkey/common/credentials/ssh_keypair.py b/monkey/common/credentials/ssh_keypair.py
index 6b8dcded2..3183adbd4 100644
--- a/monkey/common/credentials/ssh_keypair.py
+++ b/monkey/common/credentials/ssh_keypair.py
@@ -1,22 +1,26 @@
-from dataclasses import dataclass, field
+from dataclasses import field
+from typing import ClassVar
from marshmallow import fields
+from pydantic.dataclasses import dataclass
from . import CredentialComponentType, ICredentialComponent
from .credential_component_schema import CredentialComponentSchema, CredentialTypeField
class SSHKeypairSchema(CredentialComponentSchema):
- credential_type = CredentialTypeField(CredentialComponentType.SSH_KEYPAIR)
+ credential_type: ClassVar[CredentialComponentType] = CredentialTypeField(
+ CredentialComponentType.SSH_KEYPAIR
+ )
# TODO: Find a list of valid formats for ssh keys and add validators.
# See https://github.com/nemchik/ssh-key-regex
private_key = fields.Str()
public_key = fields.Str()
-@dataclass(frozen=True)
+@dataclass
class SSHKeypair(ICredentialComponent):
- credential_type: CredentialComponentType = field(
+ credential_type: ClassVar[CredentialComponentType] = field(
default=CredentialComponentType.SSH_KEYPAIR, init=False
)
private_key: str
diff --git a/monkey/common/credentials/username.py b/monkey/common/credentials/username.py
index 86fde05ff..115b205a6 100644
--- a/monkey/common/credentials/username.py
+++ b/monkey/common/credentials/username.py
@@ -1,6 +1,8 @@
-from dataclasses import dataclass, field
+from dataclasses import field
+from typing import ClassVar
from marshmallow import fields
+from pydantic.dataclasses import dataclass
from . import CredentialComponentType, ICredentialComponent
from .credential_component_schema import CredentialComponentSchema, CredentialTypeField
@@ -11,9 +13,9 @@ class UsernameSchema(CredentialComponentSchema):
username = fields.Str()
-@dataclass(frozen=True)
+@dataclass
class Username(ICredentialComponent):
- credential_type: CredentialComponentType = field(
+ credential_type: ClassVar[CredentialComponentType] = field(
default=CredentialComponentType.USERNAME, init=False
)
username: str
diff --git a/monkey/tests/unit_tests/common/credentials/test_credentials.py b/monkey/tests/unit_tests/common/credentials/test_credentials.py
index 4eeb8647f..d4e14fbad 100644
--- a/monkey/tests/unit_tests/common/credentials/test_credentials.py
+++ b/monkey/tests/unit_tests/common/credentials/test_credentials.py
@@ -45,7 +45,7 @@ SECRET_DICTS = [
]
CREDENTIALS = [
- Credentials(identity, secret)
+ Credentials(identity=identity, secret=secret)
for identity, secret in product(IDENTITIES, SECRETS)
if not (identity is None and secret is None)
]
diff --git a/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py
index c992aaf85..074095cb6 100644
--- a/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py
+++ b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py
@@ -60,8 +60,8 @@ STOLEN_CREDENTIALS = [
STOLEN_SSH_KEYS_CREDENTIALS = [
Credentials(
- Username(USERNAME),
- SSHKeypair(public_key=STOLEN_PUBLIC_KEY_2, private_key=STOLEN_PRIVATE_KEY_2),
+ identity=Username(USERNAME),
+ secret=SSHKeypair(public_key=STOLEN_PUBLIC_KEY_2, private_key=STOLEN_PRIVATE_KEY_2),
)
]
diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_format_credentials.py b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_format_credentials.py
index bb51b89dd..079cbd4e3 100644
--- a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_format_credentials.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_format_credentials.py
@@ -24,12 +24,12 @@ identities = (fake_username,)
secrets = (fake_nt_hash, fake_lm_hash, fake_password, fake_ssh_key)
fake_credentials = [
- Credentials(fake_username, fake_nt_hash),
- Credentials(fake_username, fake_lm_hash),
- Credentials(fake_username, fake_password),
- Credentials(fake_username, fake_ssh_key),
- Credentials(None, fake_ssh_key),
- Credentials(fake_username, None),
+ Credentials(identity=fake_username, secret=fake_nt_hash),
+ Credentials(identity=fake_username, secret=fake_lm_hash),
+ Credentials(identity=fake_username, secret=fake_password),
+ Credentials(identity=fake_username, secret=fake_ssh_key),
+ Credentials(identity=None, secret=fake_ssh_key),
+ Credentials(identity=fake_username, secret=None),
]
From f868f03ea7eb363507add24241f6489b4c3dc4f0 Mon Sep 17 00:00:00 2001
From: vakarisz
Date: Thu, 1 Sep 2022 12:56:05 +0300
Subject: [PATCH 02/29] Common: Refactor credentials from marshmallow to
pydantic
---
monkey/common/credentials/__init__.py | 5 -
.../credential_component_schema.py | 20 --
.../credentials/credential_component_type.py | 9 -
monkey/common/credentials/credentials.py | 192 +-----------------
.../credentials/i_credential_component.py | 13 --
monkey/common/credentials/lm_hash.py | 29 +--
monkey/common/credentials/nt_hash.py | 29 +--
monkey/common/credentials/password.py | 20 +-
monkey/common/credentials/ssh_keypair.py | 25 +--
monkey/common/credentials/username.py | 20 +-
monkey/common/credentials/validators.py | 49 +----
.../data_for_tests/propagation_credentials.py | 61 ++++--
.../credentials/test_credential_components.py | 148 --------------
.../common/credentials/test_credentials.py | 118 +----------
.../common/credentials/test_lm_hash.py | 14 ++
vulture_allowlist.py | 8 +-
16 files changed, 101 insertions(+), 659 deletions(-)
delete mode 100644 monkey/common/credentials/credential_component_schema.py
delete mode 100644 monkey/common/credentials/credential_component_type.py
delete mode 100644 monkey/common/credentials/i_credential_component.py
delete mode 100644 monkey/tests/unit_tests/common/credentials/test_credential_components.py
create mode 100644 monkey/tests/unit_tests/common/credentials/test_lm_hash.py
diff --git a/monkey/common/credentials/__init__.py b/monkey/common/credentials/__init__.py
index 6275e0985..66b91971d 100644
--- a/monkey/common/credentials/__init__.py
+++ b/monkey/common/credentials/__init__.py
@@ -1,8 +1,3 @@
-from .credential_component_type import CredentialComponentType
-from .i_credential_component import ICredentialComponent
-
-from .validators import InvalidCredentialComponentError, InvalidCredentialsError
-
from .lm_hash import LMHash
from .nt_hash import NTHash
from .password import Password
diff --git a/monkey/common/credentials/credential_component_schema.py b/monkey/common/credentials/credential_component_schema.py
deleted file mode 100644
index ff3e657be..000000000
--- a/monkey/common/credentials/credential_component_schema.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from marshmallow import Schema, post_load, validate
-from marshmallow_enum import EnumField
-
-from common.utils.code_utils import del_key
-
-from . import CredentialComponentType
-
-
-class CredentialTypeField(EnumField):
- def __init__(self, credential_component_type: CredentialComponentType):
- super().__init__(
- CredentialComponentType, validate=validate.Equal(credential_component_type)
- )
-
-
-class CredentialComponentSchema(Schema):
- @post_load
- def _strip_credential_type(self, data, **kwargs):
- del_key(data, "credential_type")
- return data
diff --git a/monkey/common/credentials/credential_component_type.py b/monkey/common/credentials/credential_component_type.py
deleted file mode 100644
index 25bd3a168..000000000
--- a/monkey/common/credentials/credential_component_type.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from enum import Enum, auto
-
-
-class CredentialComponentType(Enum):
- USERNAME = auto()
- PASSWORD = auto()
- NT_HASH = auto()
- LM_HASH = auto()
- SSH_KEYPAIR = auto()
diff --git a/monkey/common/credentials/credentials.py b/monkey/common/credentials/credentials.py
index 93390f212..053d7f33d 100644
--- a/monkey/common/credentials/credentials.py
+++ b/monkey/common/credentials/credentials.py
@@ -1,190 +1,14 @@
from __future__ import annotations
-from dataclasses import dataclass
-from typing import Any, Mapping, Optional, Type
+from typing import Optional, Union
-from marshmallow import Schema, fields, post_load, pre_dump
-from marshmallow.exceptions import MarshmallowError
+from ..base_models import InfectionMonkeyBaseModel
+from . import LMHash, NTHash, Password, SSHKeypair, Username
-from ..utils import IJSONSerializable
-from . import (
- CredentialComponentType,
- InvalidCredentialComponentError,
- InvalidCredentialsError,
- LMHash,
- NTHash,
- Password,
- SSHKeypair,
- Username,
-)
-from .i_credential_component import ICredentialComponent
-from .lm_hash import LMHashSchema
-from .nt_hash import NTHashSchema
-from .password import PasswordSchema
-from .ssh_keypair import SSHKeypairSchema
-from .username import UsernameSchema
-
-CREDENTIAL_COMPONENT_TYPE_TO_CLASS: Mapping[CredentialComponentType, Type[ICredentialComponent]] = {
- CredentialComponentType.LM_HASH: LMHash,
- CredentialComponentType.NT_HASH: NTHash,
- CredentialComponentType.PASSWORD: Password,
- CredentialComponentType.SSH_KEYPAIR: SSHKeypair,
- CredentialComponentType.USERNAME: Username,
-}
-
-CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA: Mapping[CredentialComponentType, Schema] = {
- CredentialComponentType.LM_HASH: LMHashSchema(),
- CredentialComponentType.NT_HASH: NTHashSchema(),
- CredentialComponentType.PASSWORD: PasswordSchema(),
- CredentialComponentType.SSH_KEYPAIR: SSHKeypairSchema(),
- CredentialComponentType.USERNAME: UsernameSchema(),
-}
-
-CredentialComponentMapping = Optional[Mapping[str, Any]]
-CredentialsMapping = Mapping[str, CredentialComponentMapping]
+Secret = Union[Password, LMHash, NTHash, SSHKeypair]
+Identity = Username
-class CredentialsSchema(Schema):
- identity = fields.Mapping(allow_none=True)
- secret = fields.Mapping(allow_none=True)
-
- @post_load
- def _make_credentials(
- self,
- credentials: CredentialsMapping,
- **kwargs: Mapping[str, Any],
- ) -> Mapping[str, Optional[ICredentialComponent]]:
- if not any(credentials.values()):
- raise InvalidCredentialsError("At least one credentials component must be defined")
-
- return {
- key: CredentialsSchema._build_credential_component(credential_component_mapping)
- for key, credential_component_mapping in credentials.items()
- }
-
- @staticmethod
- def _build_credential_component(
- credential_component: CredentialComponentMapping,
- ) -> Optional[ICredentialComponent]:
- if credential_component is None:
- return None
-
- try:
- credential_component_type = CredentialComponentType[
- credential_component["credential_type"]
- ]
- except KeyError as err:
- raise InvalidCredentialsError(f"Unknown credential component type {err}")
-
- credential_component_class = CREDENTIAL_COMPONENT_TYPE_TO_CLASS[credential_component_type]
- credential_component_schema = CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA[
- credential_component_type
- ]
-
- try:
- return credential_component_class(
- **credential_component_schema.load(credential_component)
- )
- except MarshmallowError as err:
- raise InvalidCredentialComponentError(credential_component_class, str(err))
-
- @pre_dump
- def _serialize_credentials(self, credentials: Credentials, **kwargs) -> CredentialsMapping:
- return {
- "identity": CredentialsSchema._serialize_credential_component(credentials.identity),
- "secret": CredentialsSchema._serialize_credential_component(credentials.secret),
- }
-
- @staticmethod
- def _serialize_credential_component(
- credential_component: Optional[ICredentialComponent],
- ) -> CredentialComponentMapping:
- if credential_component is None:
- return None
-
- credential_component_schema = CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA[
- credential_component.credential_type
- ]
-
- return credential_component_schema.dump(credential_component)
-
-
-@dataclass(frozen=True)
-class Credentials(IJSONSerializable):
- identity: Optional[ICredentialComponent]
- secret: Optional[ICredentialComponent]
-
- def __post_init__(self):
- schema = CredentialsSchema()
- try:
- serialized_data = schema.dump(self)
-
- # This will raise an exception if the object is invalid. Calling this in __post__init()
- # makes it impossible to construct an invalid object
- schema.load(serialized_data)
- except Exception as err:
- raise InvalidCredentialsError(err)
-
- @staticmethod
- def from_mapping(credentials: CredentialsMapping) -> Credentials:
- """
- Construct a Credentials object from a Mapping
-
- :param credentials: A mapping that represents a Credentials object
- :return: A Credentials object
- :raises InvalidCredentialsError: If the provided Mapping does not represent a valid
- Credentials object
- :raises InvalidCredentialComponentError: If any of the contents of `identities` or `secrets`
- are not a valid ICredentialComponent
- """
-
- try:
- deserialized_data = CredentialsSchema().load(credentials)
- return Credentials(**deserialized_data)
- except (InvalidCredentialsError, InvalidCredentialComponentError) as err:
- raise err
- except MarshmallowError as err:
- raise InvalidCredentialsError(str(err))
-
- @classmethod
- def from_json(cls, credentials: str) -> Credentials:
- """
- Construct a Credentials object from a JSON string
-
- :param credentials: A JSON string that represents a Credentials object
- :return: A Credentials object
- :raises InvalidCredentialsError: If the provided JSON does not represent a valid
- Credentials object
- :raises InvalidCredentialComponentError: If any of the contents of `identities` or `secrets`
- are not a valid ICredentialComponent
- """
-
- try:
- deserialized_data = CredentialsSchema().loads(credentials)
- return Credentials(**deserialized_data)
- except (InvalidCredentialsError, InvalidCredentialComponentError) as err:
- raise err
- except MarshmallowError as err:
- raise InvalidCredentialsError(str(err))
-
- @staticmethod
- def to_mapping(credentials: Credentials) -> CredentialsMapping:
- """
- Serialize a Credentials object to a Mapping
-
- :param credentials: A Credentials object
- :return: A mapping representing a Credentials object
- """
-
- return CredentialsSchema().dump(credentials)
-
- @classmethod
- def to_json(cls, credentials: Credentials) -> str:
- """
- Serialize a Credentials object to JSON
-
- :param credentials: A Credentials object
- :return: A JSON string representing a Credentials object
- """
-
- return CredentialsSchema().dumps(credentials)
+class Credentials(InfectionMonkeyBaseModel):
+ identity: Optional[Identity]
+ secret: Optional[Secret]
diff --git a/monkey/common/credentials/i_credential_component.py b/monkey/common/credentials/i_credential_component.py
deleted file mode 100644
index ba55b3cce..000000000
--- a/monkey/common/credentials/i_credential_component.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from abc import ABC, abstractmethod
-
-from pydantic.dataclasses import dataclass
-
-from . import CredentialComponentType
-
-
-@dataclass
-class ICredentialComponent(ABC):
- @property
- @abstractmethod
- def credential_type(self) -> CredentialComponentType:
- pass
diff --git a/monkey/common/credentials/lm_hash.py b/monkey/common/credentials/lm_hash.py
index df59beeb3..02525d386 100644
--- a/monkey/common/credentials/lm_hash.py
+++ b/monkey/common/credentials/lm_hash.py
@@ -1,25 +1,16 @@
-from dataclasses import field
-from typing import ClassVar
+import re
-from marshmallow import fields
-from pydantic.dataclasses import dataclass
+from pydantic import validator
+from pydantic.main import BaseModel
-from . import CredentialComponentType, ICredentialComponent
-from .credential_component_schema import CredentialComponentSchema, CredentialTypeField
-from .validators import credential_component_validator, ntlm_hash_validator
+from .validators import ntlm_hash_regex
-class LMHashSchema(CredentialComponentSchema):
- credential_type = CredentialTypeField(CredentialComponentType.LM_HASH)
- lm_hash = fields.Str(validate=ntlm_hash_validator)
-
-
-@dataclass
-class LMHash(ICredentialComponent):
- credential_type: ClassVar[CredentialComponentType] = field(
- default=CredentialComponentType.LM_HASH, init=False
- )
+class LMHash(BaseModel):
lm_hash: str
- def __post_init__(self):
- credential_component_validator(LMHashSchema(), self)
+ @validator("lm_hash")
+ def validate_hash_format(cls, nt_hash):
+ if not re.match(ntlm_hash_regex, nt_hash):
+ raise ValueError(f"Invalid lm hash provided: {nt_hash}")
+ return nt_hash
diff --git a/monkey/common/credentials/nt_hash.py b/monkey/common/credentials/nt_hash.py
index e7b84449a..dd288afba 100644
--- a/monkey/common/credentials/nt_hash.py
+++ b/monkey/common/credentials/nt_hash.py
@@ -1,25 +1,16 @@
-from dataclasses import field
-from typing import ClassVar
+import re
-from marshmallow import fields
-from pydantic.dataclasses import dataclass
+from pydantic import validator
-from . import CredentialComponentType, ICredentialComponent
-from .credential_component_schema import CredentialComponentSchema, CredentialTypeField
-from .validators import credential_component_validator, ntlm_hash_validator
+from ..base_models import InfectionMonkeyBaseModel
+from .validators import ntlm_hash_regex
-class NTHashSchema(CredentialComponentSchema):
- credential_type = CredentialTypeField(CredentialComponentType.NT_HASH)
- nt_hash = fields.Str(validate=ntlm_hash_validator)
-
-
-@dataclass
-class NTHash(ICredentialComponent):
- credential_type: ClassVar[CredentialComponentType] = field(
- default=CredentialComponentType.NT_HASH, init=False
- )
+class NTHash(InfectionMonkeyBaseModel):
nt_hash: str
- def __post_init__(self):
- credential_component_validator(NTHashSchema(), self)
+ @validator("nt_hash")
+ def validate_hash_format(cls, nt_hash):
+ if not re.match(ntlm_hash_regex, nt_hash):
+ raise ValueError(f"Invalid nt hash provided: {nt_hash}")
+ return nt_hash
diff --git a/monkey/common/credentials/password.py b/monkey/common/credentials/password.py
index 8fac2a37f..a9c1f2016 100644
--- a/monkey/common/credentials/password.py
+++ b/monkey/common/credentials/password.py
@@ -1,21 +1,5 @@
-from dataclasses import field
-from typing import ClassVar
-
-from marshmallow import fields
-from pydantic.dataclasses import dataclass
-
-from . import CredentialComponentType, ICredentialComponent
-from .credential_component_schema import CredentialComponentSchema, CredentialTypeField
+from ..base_models import InfectionMonkeyBaseModel
-class PasswordSchema(CredentialComponentSchema):
- credential_type = CredentialTypeField(CredentialComponentType.PASSWORD)
- password = fields.Str()
-
-
-@dataclass
-class Password(ICredentialComponent):
- credential_type: ClassVar[CredentialComponentType] = field(
- default=CredentialComponentType.PASSWORD, init=False
- )
+class Password(InfectionMonkeyBaseModel):
password: str
diff --git a/monkey/common/credentials/ssh_keypair.py b/monkey/common/credentials/ssh_keypair.py
index 3183adbd4..49d886b8b 100644
--- a/monkey/common/credentials/ssh_keypair.py
+++ b/monkey/common/credentials/ssh_keypair.py
@@ -1,27 +1,6 @@
-from dataclasses import field
-from typing import ClassVar
-
-from marshmallow import fields
-from pydantic.dataclasses import dataclass
-
-from . import CredentialComponentType, ICredentialComponent
-from .credential_component_schema import CredentialComponentSchema, CredentialTypeField
+from ..base_models import InfectionMonkeyBaseModel
-class SSHKeypairSchema(CredentialComponentSchema):
- credential_type: ClassVar[CredentialComponentType] = CredentialTypeField(
- CredentialComponentType.SSH_KEYPAIR
- )
- # TODO: Find a list of valid formats for ssh keys and add validators.
- # See https://github.com/nemchik/ssh-key-regex
- private_key = fields.Str()
- public_key = fields.Str()
-
-
-@dataclass
-class SSHKeypair(ICredentialComponent):
- credential_type: ClassVar[CredentialComponentType] = field(
- default=CredentialComponentType.SSH_KEYPAIR, init=False
- )
+class SSHKeypair(InfectionMonkeyBaseModel):
private_key: str
public_key: str
diff --git a/monkey/common/credentials/username.py b/monkey/common/credentials/username.py
index 115b205a6..2bfc0b25d 100644
--- a/monkey/common/credentials/username.py
+++ b/monkey/common/credentials/username.py
@@ -1,21 +1,5 @@
-from dataclasses import field
-from typing import ClassVar
-
-from marshmallow import fields
-from pydantic.dataclasses import dataclass
-
-from . import CredentialComponentType, ICredentialComponent
-from .credential_component_schema import CredentialComponentSchema, CredentialTypeField
+from ..base_models import InfectionMonkeyBaseModel
-class UsernameSchema(CredentialComponentSchema):
- credential_type = CredentialTypeField(CredentialComponentType.USERNAME)
- username = fields.Str()
-
-
-@dataclass
-class Username(ICredentialComponent):
- credential_type: ClassVar[CredentialComponentType] = field(
- default=CredentialComponentType.USERNAME, init=False
- )
+class Username(InfectionMonkeyBaseModel):
username: str
diff --git a/monkey/common/credentials/validators.py b/monkey/common/credentials/validators.py
index 2e0e2e93c..1d94575d4 100644
--- a/monkey/common/credentials/validators.py
+++ b/monkey/common/credentials/validators.py
@@ -1,50 +1,3 @@
import re
-from typing import Type
-from marshmallow import Schema, validate
-
-from . import ICredentialComponent
-
-_ntlm_hash_regex = re.compile(r"^[a-fA-F0-9]{32}$")
-ntlm_hash_validator = validate.Regexp(regex=_ntlm_hash_regex)
-
-
-class InvalidCredentialComponentError(Exception):
- def __init__(self, credential_component_class: Type[ICredentialComponent], message: str):
- self._credential_component_name = credential_component_class.__name__
- self._message = message
-
- def __str__(self) -> str:
- return (
- f"Cannot construct a {self._credential_component_name} object with the supplied, "
- f"invalid data: {self._message}"
- )
-
-
-class InvalidCredentialsError(Exception):
- def __init__(self, message: str):
- self._message = message
-
- def __str__(self) -> str:
- return (
- f"Cannot construct a Credentials object with the supplied, "
- f"invalid data: {self._message}"
- )
-
-
-def credential_component_validator(schema: Schema, credential_component: ICredentialComponent):
- """
- Validate a credential component
-
- :param schema: A marshmallow schema used for validating the component
- :param credential_component: A credential component to be validated
- :raises InvalidCredentialComponent: if the credential_component contains invalid data
- """
- try:
- serialized_data = schema.dump(credential_component)
-
- # This will raise an exception if the object is invalid. Calling this in __post__init()
- # makes it impossible to construct an invalid object
- schema.load(serialized_data)
- except Exception as err:
- raise InvalidCredentialComponentError(credential_component.__class__, err)
+ntlm_hash_regex = re.compile(r"^[a-fA-F0-9]{32}$")
diff --git a/monkey/tests/data_for_tests/propagation_credentials.py b/monkey/tests/data_for_tests/propagation_credentials.py
index 6efe9b7af..a1d783f78 100644
--- a/monkey/tests/data_for_tests/propagation_credentials.py
+++ b/monkey/tests/data_for_tests/propagation_credentials.py
@@ -1,3 +1,5 @@
+from itertools import product
+
from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username
USERNAME = "m0nk3y_user"
@@ -10,22 +12,47 @@ PASSWORD_3 = "rubberbabybuggybumpers"
PUBLIC_KEY = "MY_PUBLIC_KEY"
PRIVATE_KEY = "MY_PRIVATE_KEY"
-PASSWORD_CREDENTIALS_1 = Credentials(identity=Username(USERNAME), secret=Password(PASSWORD_1))
-PASSWORD_CREDENTIALS_2 = Credentials(identity=Username(USERNAME), secret=Password(PASSWORD_2))
-LM_HASH_CREDENTIALS = Credentials(identity=Username(SPECIAL_USERNAME), secret=LMHash(LM_HASH))
-NT_HASH_CREDENTIALS = Credentials(identity=Username(USERNAME), secret=NTHash(NT_HASH))
-SSH_KEY_CREDENTIALS = Credentials(
- identity=Username(USERNAME), secret=SSHKeypair(PRIVATE_KEY, PUBLIC_KEY)
-)
-EMPTY_SECRET_CREDENTIALS = Credentials(identity=Username(USERNAME), secret=None)
-EMPTY_IDENTITY_CREDENTIALS = Credentials(identity=None, secret=Password(PASSWORD_3))
+IDENTITIES = [Username(username=USERNAME), None, Username(username=SPECIAL_USERNAME)]
+IDENTITY_DICTS = [{"username": USERNAME}, None]
-PROPAGATION_CREDENTIALS = [
- PASSWORD_CREDENTIALS_1,
- LM_HASH_CREDENTIALS,
- NT_HASH_CREDENTIALS,
- PASSWORD_CREDENTIALS_2,
- SSH_KEY_CREDENTIALS,
- EMPTY_SECRET_CREDENTIALS,
- EMPTY_IDENTITY_CREDENTIALS,
+SECRETS = (
+ Password(password=PASSWORD_1),
+ Password(password=PASSWORD_2),
+ Password(password=PASSWORD_3),
+ LMHash(lm_hash=LM_HASH),
+ NTHash(nt_hash=NT_HASH),
+ SSHKeypair(private_key=PRIVATE_KEY, public_key=PUBLIC_KEY),
+ None,
+)
+SECRET_DICTS = [
+ {"password": PASSWORD_1},
+ {"lm_hash": LM_HASH},
+ {"nt_hash": NT_HASH},
+ {
+ "public_key": PUBLIC_KEY,
+ "private_key": PRIVATE_KEY,
+ },
+ None,
+]
+
+CREDENTIALS = [
+ Credentials(identity=identity, secret=secret)
+ for identity, secret in product(IDENTITIES, SECRETS)
+]
+
+FULL_CREDENTIALS = [
+ credentials
+ for credentials in CREDENTIALS
+ if not (credentials.identity is None and credentials.secret is None)
+]
+
+CREDENTIALS_DICTS = [
+ {"identity": identity, "secret": secret}
+ for identity, secret in product(IDENTITY_DICTS, SECRET_DICTS)
+]
+
+FULL_CREDENTIALS_DICTS = [
+ credentials
+ for credentials in CREDENTIALS_DICTS
+ if not (credentials["identity"] is None and credentials["secret"] is None)
]
diff --git a/monkey/tests/unit_tests/common/credentials/test_credential_components.py b/monkey/tests/unit_tests/common/credentials/test_credential_components.py
deleted file mode 100644
index 55a2b9279..000000000
--- a/monkey/tests/unit_tests/common/credentials/test_credential_components.py
+++ /dev/null
@@ -1,148 +0,0 @@
-from typing import Any, Mapping
-
-import pytest
-from marshmallow.exceptions import ValidationError
-
-from common.credentials import (
- CredentialComponentType,
- LMHash,
- NTHash,
- Password,
- SSHKeypair,
- Username,
-)
-from common.credentials.lm_hash import LMHashSchema
-from common.credentials.nt_hash import NTHashSchema
-from common.credentials.password import PasswordSchema
-from common.credentials.ssh_keypair import SSHKeypairSchema
-from common.credentials.username import UsernameSchema
-
-PARAMETRIZED_PARAMETER_NAMES = (
- "credential_component_class, schema_class, credential_component_type, credential_component_data"
-)
-
-PARAMETRIZED_PARAMETER_VALUES = [
- (Username, UsernameSchema, CredentialComponentType.USERNAME, {"username": "test_user"}),
- (Password, PasswordSchema, CredentialComponentType.PASSWORD, {"password": "123456"}),
- (
- LMHash,
- LMHashSchema,
- CredentialComponentType.LM_HASH,
- {"lm_hash": "E52CAC67419A9A224A3B108F3FA6CB6D"},
- ),
- (
- NTHash,
- NTHashSchema,
- CredentialComponentType.NT_HASH,
- {"nt_hash": "E52CAC67419A9A224A3B108F3FA6CB6D"},
- ),
- (
- SSHKeypair,
- SSHKeypairSchema,
- CredentialComponentType.SSH_KEYPAIR,
- {"public_key": "TEST_PUBLIC_KEY", "private_key": "TEST_PRIVATE_KEY"},
- ),
-]
-
-
-INVALID_COMPONENT_DATA = {
- CredentialComponentType.USERNAME: ({"username": None}, {"username": 1}, {"username": 2.0}),
- CredentialComponentType.PASSWORD: ({"password": None}, {"password": 1}, {"password": 2.0}),
- CredentialComponentType.LM_HASH: (
- {"lm_hash": None},
- {"lm_hash": 1},
- {"lm_hash": 2.0},
- {"lm_hash": "0123456789012345678901234568901"},
- {"lm_hash": "E52GAC67419A9A224A3B108F3FA6CB6D"},
- ),
- CredentialComponentType.NT_HASH: (
- {"nt_hash": None},
- {"nt_hash": 1},
- {"nt_hash": 2.0},
- {"nt_hash": "0123456789012345678901234568901"},
- {"nt_hash": "E52GAC67419A9A224A3B108F3FA6CB6D"},
- ),
- CredentialComponentType.SSH_KEYPAIR: (
- {"public_key": None, "private_key": "TEST_PRIVATE_KEY"},
- {"public_key": "TEST_PUBLIC_KEY", "private_key": None},
- {"public_key": 1, "private_key": "TEST_PRIVATE_KEY"},
- {"public_key": "TEST_PUBLIC_KEY", "private_key": 999},
- ),
-}
-
-
-def build_component_dict(
- credential_component_type: CredentialComponentType, credential_component_data: Mapping[str, Any]
-):
- return {"credential_type": credential_component_type.name, **credential_component_data}
-
-
-@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES)
-def test_credential_component_serialize(
- credential_component_class, schema_class, credential_component_type, credential_component_data
-):
- schema = schema_class()
- constructed_object = credential_component_class(**credential_component_data)
-
- serialized_object = schema.dump(constructed_object)
-
- assert serialized_object == build_component_dict(
- credential_component_type, credential_component_data
- )
-
-
-@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES)
-def test_credential_component_deserialize(
- credential_component_class, schema_class, credential_component_type, credential_component_data
-):
- schema = schema_class()
- credential_dict = build_component_dict(credential_component_type, credential_component_data)
- expected_deserialized_object = credential_component_class(**credential_component_data)
-
- deserialized_object = credential_component_class(**schema.load(credential_dict))
-
- assert deserialized_object == expected_deserialized_object
-
-
-@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES)
-def test_invalid_credential_type(
- credential_component_class, schema_class, credential_component_type, credential_component_data
-):
- invalid_component_dict = build_component_dict(
- credential_component_type, credential_component_data
- )
- invalid_component_dict["credential_type"] = "INVALID"
- schema = schema_class()
-
- with pytest.raises(ValidationError):
- credential_component_class(**schema.load(invalid_component_dict))
-
-
-@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES)
-def test_encorrect_credential_type(
- credential_component_class, schema_class, credential_component_type, credential_component_data
-):
- incorrect_component_dict = build_component_dict(
- credential_component_type, credential_component_data
- )
- incorrect_component_dict["credential_type"] = (
- CredentialComponentType.USERNAME.name
- if credential_component_type != CredentialComponentType.USERNAME
- else CredentialComponentType.PASSWORD
- )
- schema = schema_class()
-
- with pytest.raises(ValidationError):
- credential_component_class(**schema.load(incorrect_component_dict))
-
-
-@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES)
-def test_invalid_values(
- credential_component_class, schema_class, credential_component_type, credential_component_data
-):
- schema = schema_class()
-
- for invalid_component_data in INVALID_COMPONENT_DATA[credential_component_type]:
- component_dict = build_component_dict(credential_component_type, invalid_component_data)
- with pytest.raises(ValidationError):
- credential_component_class(**schema.load(component_dict))
diff --git a/monkey/tests/unit_tests/common/credentials/test_credentials.py b/monkey/tests/unit_tests/common/credentials/test_credentials.py
index d4e14fbad..89238b839 100644
--- a/monkey/tests/unit_tests/common/credentials/test_credentials.py
+++ b/monkey/tests/unit_tests/common/credentials/test_credentials.py
@@ -1,122 +1,14 @@
-import json
-from itertools import product
-
import pytest
-from tests.data_for_tests.propagation_credentials import (
- LM_HASH,
- NT_HASH,
- PASSWORD_1,
- PRIVATE_KEY,
- PUBLIC_KEY,
- USERNAME,
-)
+from tests.data_for_tests.propagation_credentials import CREDENTIALS, CREDENTIALS_DICTS
-from common.credentials import (
- Credentials,
- InvalidCredentialComponentError,
- InvalidCredentialsError,
- LMHash,
- NTHash,
- Password,
- SSHKeypair,
- Username,
-)
-
-IDENTITIES = [Username(USERNAME), None]
-IDENTITY_DICTS = [{"credential_type": "USERNAME", "username": USERNAME}, None]
-
-SECRETS = (
- Password(PASSWORD_1),
- LMHash(LM_HASH),
- NTHash(NT_HASH),
- SSHKeypair(PRIVATE_KEY, PUBLIC_KEY),
- None,
-)
-SECRET_DICTS = [
- {"credential_type": "PASSWORD", "password": PASSWORD_1},
- {"credential_type": "LM_HASH", "lm_hash": LM_HASH},
- {"credential_type": "NT_HASH", "nt_hash": NT_HASH},
- {
- "credential_type": "SSH_KEYPAIR",
- "public_key": PUBLIC_KEY,
- "private_key": PRIVATE_KEY,
- },
- None,
-]
-
-CREDENTIALS = [
- Credentials(identity=identity, secret=secret)
- for identity, secret in product(IDENTITIES, SECRETS)
- if not (identity is None and secret is None)
-]
-
-CREDENTIALS_DICTS = [
- {"identity": identity, "secret": secret}
- for identity, secret in product(IDENTITY_DICTS, SECRET_DICTS)
- if not (identity is None and secret is None)
-]
+from common.credentials import Credentials
@pytest.mark.parametrize(
"credentials, expected_credentials_dict", zip(CREDENTIALS, CREDENTIALS_DICTS)
)
def test_credentials_serialization_json(credentials, expected_credentials_dict):
- serialized_credentials = Credentials.to_json(credentials)
+ serialized_credentials = credentials.json()
+ deserialized_credentials = Credentials.parse_raw(serialized_credentials)
- assert json.loads(serialized_credentials) == expected_credentials_dict
-
-
-@pytest.mark.parametrize(
- "credentials, expected_credentials_dict", zip(CREDENTIALS, CREDENTIALS_DICTS)
-)
-def test_credentials_serialization_mapping(credentials, expected_credentials_dict):
- serialized_credentials = Credentials.to_mapping(credentials)
-
- assert serialized_credentials == expected_credentials_dict
-
-
-@pytest.mark.parametrize(
- "expected_credentials, credentials_dict", zip(CREDENTIALS, CREDENTIALS_DICTS)
-)
-def test_credentials_deserialization__from_mapping(expected_credentials, credentials_dict):
- deserialized_credentials = Credentials.from_mapping(credentials_dict)
-
- assert deserialized_credentials == expected_credentials
-
-
-@pytest.mark.parametrize(
- "expected_credentials, credentials_dict", zip(CREDENTIALS, CREDENTIALS_DICTS)
-)
-def test_credentials_deserialization__from_json(expected_credentials, credentials_dict):
- deserialized_credentials = Credentials.from_json(json.dumps(credentials_dict))
-
- assert deserialized_credentials == expected_credentials
-
-
-def test_credentials_deserialization__invalid_credentials():
- invalid_data = {"secret": SECRET_DICTS[0], "unknown_key": []}
- with pytest.raises(InvalidCredentialsError):
- Credentials.from_mapping(invalid_data)
-
-
-def test_credentials_deserialization__invalid_component_type():
- invalid_data = {
- "secret": SECRET_DICTS[0],
- "identity": {"credential_type": "FAKE", "username": "user1"},
- }
- with pytest.raises(InvalidCredentialsError):
- Credentials.from_mapping(invalid_data)
-
-
-def test_credentials_deserialization__invalid_component():
- invalid_data = {
- "secret": SECRET_DICTS[0],
- "identity": {"credential_type": "USERNAME", "unknown_field": "user1"},
- }
- with pytest.raises(InvalidCredentialComponentError):
- Credentials.from_mapping(invalid_data)
-
-
-def test_create_credentials__none_none():
- with pytest.raises(InvalidCredentialsError):
- Credentials(None, None)
+ assert credentials == deserialized_credentials
diff --git a/monkey/tests/unit_tests/common/credentials/test_lm_hash.py b/monkey/tests/unit_tests/common/credentials/test_lm_hash.py
new file mode 100644
index 000000000..4bcfb3270
--- /dev/null
+++ b/monkey/tests/unit_tests/common/credentials/test_lm_hash.py
@@ -0,0 +1,14 @@
+import pytest
+
+from common.credentials import LMHash
+
+
+def test_construct_valid_nt_hash(valid_ntlm_hash):
+ # This test will fail if an exception is raised
+ LMHash(lm_hash=valid_ntlm_hash)
+
+
+def test_construct_invalid_nt_hash(invalid_ntlm_hashes):
+ for invalid_hash in invalid_ntlm_hashes:
+ with pytest.raises(ValueError):
+ LMHash(lm_hash=invalid_hash)
diff --git a/vulture_allowlist.py b/vulture_allowlist.py
index 42d46716f..127a319d2 100644
--- a/vulture_allowlist.py
+++ b/vulture_allowlist.py
@@ -7,8 +7,7 @@ from common.agent_configuration.agent_sub_configurations import (
CustomPBAConfiguration,
ScanTargetConfiguration,
)
-from common.credentials import Credentials
-from common.utils import IJSONSerializable
+from common.credentials import LMHash, NTHash
from infection_monkey.exploit.log4shell_utils.ldap_server import LDAPServerFactory
from monkey_island.cc.event_queue import IslandEventTopic, PyPubSubIslandEventQueue
from monkey_island.cc.models import Report
@@ -166,6 +165,8 @@ LDAPServerFactory.buildProtocol
get_file_sha256_hash
strict_slashes # unused attribute (monkey/monkey_island/cc/app.py:96)
post_breach_actions # unused variable (monkey\infection_monkey\config.py:95)
+LMHash.validate_hash_format
+NTHash.validate_hash_format
# Deployments
DEVELOP # unused variable (monkey/monkey/monkey_island/cc/deployment.py:5)
@@ -314,9 +315,6 @@ EXPLOITED
CC
CC_TUNNEL
-Credentials.from_json
-IJSONSerializable.from_json
-
IslandEventTopic.AGENT_CONNECTED
IslandEventTopic.CLEAR_SIMULATION_DATA
IslandEventTopic.RESET_AGENT_CONFIGURATION
From 12bc514a92c7ae0db25642f44d592ff3647a791e Mon Sep 17 00:00:00 2001
From: vakarisz
Date: Thu, 1 Sep 2022 12:56:50 +0300
Subject: [PATCH 03/29] Agent: Remove unused ransomware directory
---
monkey/infection_monkey/ransomware/__init__.py | 0
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 monkey/infection_monkey/ransomware/__init__.py
diff --git a/monkey/infection_monkey/ransomware/__init__.py b/monkey/infection_monkey/ransomware/__init__.py
deleted file mode 100644
index e69de29bb..000000000
From f018b85f561eb5f5c3c86d9d1d9c8e858f454a44 Mon Sep 17 00:00:00 2001
From: vakarisz
Date: Thu, 1 Sep 2022 13:59:08 +0300
Subject: [PATCH 04/29] UT: Add nt/lm hash unit test and data
---
.../unit_tests/common/credentials/conftest.py | 20 ++++++++++++++
.../common/credentials/test_nt_hash.py | 14 ++++++++++
.../common/credentials/test_ntlm_hash.py | 26 -------------------
3 files changed, 34 insertions(+), 26 deletions(-)
create mode 100644 monkey/tests/unit_tests/common/credentials/conftest.py
create mode 100644 monkey/tests/unit_tests/common/credentials/test_nt_hash.py
delete mode 100644 monkey/tests/unit_tests/common/credentials/test_ntlm_hash.py
diff --git a/monkey/tests/unit_tests/common/credentials/conftest.py b/monkey/tests/unit_tests/common/credentials/conftest.py
new file mode 100644
index 000000000..b6e60289c
--- /dev/null
+++ b/monkey/tests/unit_tests/common/credentials/conftest.py
@@ -0,0 +1,20 @@
+from typing import List
+
+import pytest
+
+
+@pytest.fixture(scope="session")
+def valid_ntlm_hash() -> str:
+ return "E520AC67419A9A224A3B108F3FA6CB6D"
+
+
+@pytest.fixture(scope="session")
+def invalid_ntlm_hashes() -> List[str]:
+ return [
+ 0,
+ 1,
+ 2.0,
+ "invalid",
+ "0123456789012345678901234568901",
+ "E52GAC67419A9A224A3B108F3FA6CB6D",
+ ]
diff --git a/monkey/tests/unit_tests/common/credentials/test_nt_hash.py b/monkey/tests/unit_tests/common/credentials/test_nt_hash.py
new file mode 100644
index 000000000..1c60f4fca
--- /dev/null
+++ b/monkey/tests/unit_tests/common/credentials/test_nt_hash.py
@@ -0,0 +1,14 @@
+import pytest
+
+from common.credentials import NTHash
+
+
+def test_construct_valid_nt_hash(valid_ntlm_hash):
+ # This test will fail if an exception is raised
+ NTHash(nt_hash=valid_ntlm_hash)
+
+
+def test_construct_invalid_nt_hash(invalid_ntlm_hashes):
+ for invalid_hash in invalid_ntlm_hashes:
+ with pytest.raises(ValueError):
+ NTHash(nt_hash=invalid_hash)
diff --git a/monkey/tests/unit_tests/common/credentials/test_ntlm_hash.py b/monkey/tests/unit_tests/common/credentials/test_ntlm_hash.py
deleted file mode 100644
index 5f50110e8..000000000
--- a/monkey/tests/unit_tests/common/credentials/test_ntlm_hash.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import pytest
-
-from common.credentials import InvalidCredentialComponentError, LMHash, NTHash
-
-VALID_HASH = "E520AC67419A9A224A3B108F3FA6CB6D"
-INVALID_HASHES = (
- 0,
- 1,
- 2.0,
- "invalid",
- "0123456789012345678901234568901",
- "E52GAC67419A9A224A3B108F3FA6CB6D",
-)
-
-
-@pytest.mark.parametrize("ntlm_hash_class", (LMHash, NTHash))
-def test_construct_valid_ntlm_hash(ntlm_hash_class):
- # This test will fail if an exception is raised
- ntlm_hash_class(VALID_HASH)
-
-
-@pytest.mark.parametrize("ntlm_hash_class", (LMHash, NTHash))
-def test_construct_invalid_ntlm_hash(ntlm_hash_class):
- for invalid_hash in INVALID_HASHES:
- with pytest.raises(InvalidCredentialComponentError):
- ntlm_hash_class(invalid_hash)
From d73cbee5910a41476e16ad8a5412d3147f046196 Mon Sep 17 00:00:00 2001
From: vakarisz
Date: Thu, 1 Sep 2022 14:10:54 +0300
Subject: [PATCH 05/29] Agent, Island: Use pydantic credentials and methods
Since the interface of credential serialization changed, code was modified to use the new interface
---
.../island_client/monkey_island_client.py | 4 +-
.../mimikatz_credential_collector.py | 12 +++---
.../ssh_collector/ssh_handler.py | 7 ++--
.../telemetry/credentials_telem.py | 3 +-
.../mongo_credentials_repository.py | 4 +-
.../cc/resources/propagation_credentials.py | 2 +-
.../test_mimikatz_collector.py | 28 ++++++++-----
.../test_ssh_credentials_collector.py | 15 +++----
.../test_add_credentials_from_event.py | 6 ++-
...ting_propagation_credentials_repository.py | 29 +++++++------
.../telemetry/test_credentials_telem.py | 5 +--
.../test_mongo_credentials_repository.py | 18 ++++----
.../resources/test_propagation_credentials.py | 41 ++++++++-----------
13 files changed, 92 insertions(+), 82 deletions(-)
diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
index 88219cef2..a7009e459 100644
--- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
+++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
@@ -30,7 +30,7 @@ class MonkeyIslandClient(object):
def get_propagation_credentials(self) -> Sequence[Credentials]:
response = self.requests.get("api/propagation-credentials")
- return [Credentials.from_mapping(credentials) for credentials in response.json()]
+ return [Credentials(**credentials) for credentials in response.json()]
@avoid_race_condition
def import_config(self, test_configuration: TestConfiguration):
@@ -61,7 +61,7 @@ class MonkeyIslandClient(object):
@avoid_race_condition
def _import_credentials(self, propagation_credentials: Credentials):
serialized_propagation_credentials = [
- Credentials.to_mapping(credentials) for credentials in propagation_credentials
+ Credentials.dict(credentials) for credentials in propagation_credentials
]
response = self.requests.put_json(
"/api/propagation-credentials/configured-credentials",
diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py
index 882ced8ca..10bccade3 100644
--- a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py
+++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py
@@ -55,22 +55,22 @@ class MimikatzCredentialCollector(ICredentialCollector):
identity = None
if wc.username:
- identity = Username(wc.username)
+ identity = Username(username=wc.username)
if wc.password:
- password = Password(wc.password)
- credentials.append(Credentials(identity, password))
+ password = Password(password=wc.password)
+ credentials.append(Credentials(identity=identity, secret=password))
if wc.lm_hash:
lm_hash = LMHash(lm_hash=wc.lm_hash)
- credentials.append(Credentials(identity, lm_hash))
+ credentials.append(Credentials(identity=identity, secret=lm_hash))
if wc.ntlm_hash:
ntlm_hash = NTHash(nt_hash=wc.ntlm_hash)
- credentials.append(Credentials(identity, ntlm_hash))
+ credentials.append(Credentials(identity=identity, secret=ntlm_hash))
if len(credentials) == 0 and identity is not None:
- credentials.append(Credentials(identity, None))
+ credentials.append(Credentials(identity=identity, secret=None))
return credentials
diff --git a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py
index ca271a5d8..8c1129455 100644
--- a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py
+++ b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py
@@ -149,7 +149,7 @@ def to_credentials(ssh_info: Iterable[Dict]) -> Sequence[Credentials]:
secret = None
if info.get("name", ""):
- identity = Username(info["name"])
+ identity = Username(username=info["name"])
ssh_keypair = {}
for key in ["public_key", "private_key"]:
@@ -158,11 +158,12 @@ def to_credentials(ssh_info: Iterable[Dict]) -> Sequence[Credentials]:
if len(ssh_keypair):
secret = SSHKeypair(
- ssh_keypair.get("private_key", ""), ssh_keypair.get("public_key", "")
+ private_key=ssh_keypair.get("private_key", ""),
+ public_key=ssh_keypair.get("public_key", ""),
)
if any([identity, secret]):
- ssh_credentials.append(Credentials(identity, secret))
+ ssh_credentials.append(Credentials(identity=identity, secret=secret))
return ssh_credentials
diff --git a/monkey/infection_monkey/telemetry/credentials_telem.py b/monkey/infection_monkey/telemetry/credentials_telem.py
index 7504b9c65..439ec7f1a 100644
--- a/monkey/infection_monkey/telemetry/credentials_telem.py
+++ b/monkey/infection_monkey/telemetry/credentials_telem.py
@@ -1,4 +1,3 @@
-import json
from typing import Iterable
from common.common_consts.telem_categories import TelemCategoryEnum
@@ -24,4 +23,4 @@ class CredentialsTelem(BaseTelem):
super().send(log_data=False)
def get_data(self):
- return [json.loads(Credentials.to_json(c)) for c in self._credentials]
+ return [c.dict(simplify=True) for c in self._credentials]
diff --git a/monkey/monkey_island/cc/repository/mongo_credentials_repository.py b/monkey/monkey_island/cc/repository/mongo_credentials_repository.py
index 351cac981..3affc6cb6 100644
--- a/monkey/monkey_island/cc/repository/mongo_credentials_repository.py
+++ b/monkey/monkey_island/cc/repository/mongo_credentials_repository.py
@@ -59,7 +59,7 @@ class MongoCredentialsRepository(ICredentialsRepository):
for encrypted_credentials in list_collection_result:
del encrypted_credentials[MONGO_OBJECT_ID_KEY]
plaintext_credentials = self._decrypt_credentials_mapping(encrypted_credentials)
- collection_result.append(Credentials.from_mapping(plaintext_credentials))
+ collection_result.append(Credentials(**plaintext_credentials))
return collection_result
except Exception as err:
@@ -68,7 +68,7 @@ class MongoCredentialsRepository(ICredentialsRepository):
def _save_credentials_to_collection(self, credentials: Sequence[Credentials], collection):
try:
for c in credentials:
- encrypted_credentials = self._encrypt_credentials_mapping(Credentials.to_mapping(c))
+ encrypted_credentials = self._encrypt_credentials_mapping(c.dict())
collection.insert_one(encrypted_credentials)
except Exception as err:
raise StorageError(err)
diff --git a/monkey/monkey_island/cc/resources/propagation_credentials.py b/monkey/monkey_island/cc/resources/propagation_credentials.py
index 1130d50bd..22d717e72 100644
--- a/monkey/monkey_island/cc/resources/propagation_credentials.py
+++ b/monkey/monkey_island/cc/resources/propagation_credentials.py
@@ -29,7 +29,7 @@ class PropagationCredentials(AbstractResource):
return propagation_credentials, HTTPStatus.OK
def put(self, collection=None):
- credentials = [Credentials.from_mapping(c) for c in request.json]
+ credentials = [Credentials.parse_raw(c) for c in request.json]
if collection == _configured_collection:
self._credentials_repository.remove_configured_credentials()
self._credentials_repository.save_configured_credentials(credentials)
diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py
index 0476d8a13..11c6d695b 100644
--- a/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py
+++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py
@@ -41,9 +41,9 @@ def test_pypykatz_result_parsing(monkeypatch):
win_creds = [WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash="")]
patch_pypykatz(win_creds, monkeypatch)
- username = Username("user")
- password = Password("secret")
- expected_credentials = Credentials(username, password)
+ username = Username(username="user")
+ password = Password(password="secret")
+ expected_credentials = Credentials(identity=username, secret=password)
collected_credentials = collect_credentials()
assert len(collected_credentials) == 1
@@ -70,10 +70,13 @@ def test_pypykatz_result_parsing_defaults(monkeypatch):
patch_pypykatz(win_creds, monkeypatch)
# Expected credentials
- username = Username("user2")
- password = Password("secret2")
- lm_hash = LMHash("0182BD0BD4444BF8FC83B5D9042EED2E")
- expected_credentials = [Credentials(username, password), Credentials(username, lm_hash)]
+ username = Username(username="user2")
+ password = Password(password="secret2")
+ lm_hash = LMHash(lm_hash="0182BD0BD4444BF8FC83B5D9042EED2E")
+ expected_credentials = [
+ Credentials(identity=username, secret=password),
+ Credentials(identity=username, secret=lm_hash),
+ ]
collected_credentials = collect_credentials()
assert len(collected_credentials) == 2
@@ -91,9 +94,12 @@ def test_pypykatz_result_parsing_no_identities(monkeypatch):
]
patch_pypykatz(win_creds, monkeypatch)
- lm_hash = LMHash("0182BD0BD4444BF8FC83B5D9042EED2E")
- nt_hash = NTHash("E9F85516721DDC218359AD5280DB4450")
- expected_credentials = [Credentials(None, lm_hash), Credentials(None, nt_hash)]
+ lm_hash = LMHash(lm_hash="0182BD0BD4444BF8FC83B5D9042EED2E")
+ nt_hash = NTHash(nt_hash="E9F85516721DDC218359AD5280DB4450")
+ expected_credentials = [
+ Credentials(identity=None, secret=lm_hash),
+ Credentials(identity=None, secret=nt_hash),
+ ]
collected_credentials = collect_credentials()
assert len(collected_credentials) == 2
@@ -112,7 +118,7 @@ def test_pypykatz_result_parsing_no_secrets(monkeypatch):
]
patch_pypykatz(win_creds, monkeypatch)
- expected_credentials = [Credentials(Username(username), None)]
+ expected_credentials = [Credentials(identity=Username(username=username), secret=None)]
collected_credentials = collect_credentials()
assert len(collected_credentials) == 1
diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_ssh_credentials_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_ssh_credentials_collector.py
index 1c7fbe1b4..bf41060d8 100644
--- a/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_ssh_credentials_collector.py
+++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_ssh_credentials_collector.py
@@ -31,7 +31,6 @@ def test_ssh_credentials_empty_results(monkeypatch, ssh_creds, patch_telemetry_m
def test_ssh_info_result_parsing(monkeypatch, patch_telemetry_messenger):
-
ssh_creds = [
{
"name": "ubuntu",
@@ -56,13 +55,15 @@ def test_ssh_info_result_parsing(monkeypatch, patch_telemetry_messenger):
patch_ssh_handler(ssh_creds, monkeypatch)
# Expected credentials
- username = Username("ubuntu")
- username2 = Username("mcus")
- username3 = Username("guest")
+ username = Username(username="ubuntu")
+ username2 = Username(username="mcus")
+ username3 = Username(username="guest")
- ssh_keypair1 = SSHKeypair("ExtremelyGoodPrivateKey", "SomePublicKeyUbuntu")
- ssh_keypair2 = SSHKeypair("", "AnotherPublicKey")
- ssh_keypair3 = SSHKeypair("PrivKey", "PubKey")
+ ssh_keypair1 = SSHKeypair(
+ private_key="ExtremelyGoodPrivateKey", public_key="SomePublicKeyUbuntu"
+ )
+ ssh_keypair2 = SSHKeypair(private_key="", public_key="AnotherPublicKey")
+ ssh_keypair3 = SSHKeypair(private_key="PrivKey", public_key="PubKey")
expected = [
Credentials(identity=username, secret=ssh_keypair1),
diff --git a/monkey/tests/unit_tests/infection_monkey/credential_store/test_add_credentials_from_event.py b/monkey/tests/unit_tests/infection_monkey/credential_store/test_add_credentials_from_event.py
index e01321fce..26f4f6a50 100644
--- a/monkey/tests/unit_tests/infection_monkey/credential_store/test_add_credentials_from_event.py
+++ b/monkey/tests/unit_tests/infection_monkey/credential_store/test_add_credentials_from_event.py
@@ -8,7 +8,11 @@ from infection_monkey.credential_repository import (
add_credentials_from_event_to_propagation_credentials_repository,
)
-credentials = [Credentials(identity=Username("test_username"), secret=Password("some_password"))]
+credentials = [
+ Credentials(
+ identity=Username(username="test_username"), secret=Password(password="some_password")
+ )
+]
credentials_stolen_event = CredentialsStolenEvent(
source=UUID("f811ad00-5a68-4437-bd51-7b5cc1768ad5"),
diff --git a/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py
index 074095cb6..5ff9547a0 100644
--- a/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py
+++ b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py
@@ -2,13 +2,13 @@ from unittest.mock import MagicMock
import pytest
from tests.data_for_tests.propagation_credentials import (
+ CREDENTIALS,
LM_HASH,
NT_HASH,
PASSWORD_1,
PASSWORD_2,
PASSWORD_3,
PRIVATE_KEY,
- PROPAGATION_CREDENTIALS,
PUBLIC_KEY,
SPECIAL_USERNAME,
USERNAME,
@@ -17,7 +17,6 @@ from tests.data_for_tests.propagation_credentials import (
from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username
from infection_monkey.credential_repository import AggregatingPropagationCredentialsRepository
-CONTROL_CHANNEL_CREDENTIALS = PROPAGATION_CREDENTIALS
TRANSFORMED_CONTROL_CHANNEL_CREDENTIALS = {
"exploit_user_list": {USERNAME, SPECIAL_USERNAME},
"exploit_password_list": {PASSWORD_1, PASSWORD_2, PASSWORD_3},
@@ -41,26 +40,32 @@ STOLEN_PRIVATE_KEY_1 = "some_private_key_1"
STOLEN_PRIVATE_KEY_2 = "some_private_key_2"
STOLEN_CREDENTIALS = [
Credentials(
- identity=Username(STOLEN_USERNAME_1),
- secret=Password(PASSWORD_1),
+ identity=Username(username=STOLEN_USERNAME_1),
+ secret=Password(password=PASSWORD_1),
),
- Credentials(identity=Username(STOLEN_USERNAME_1), secret=Password(STOLEN_PASSWORD_1)),
Credentials(
- identity=Username(STOLEN_USERNAME_2),
+ identity=Username(username=STOLEN_USERNAME_1), secret=Password(password=STOLEN_PASSWORD_1)
+ ),
+ Credentials(
+ identity=Username(username=STOLEN_USERNAME_2),
secret=SSHKeypair(public_key=STOLEN_PUBLIC_KEY_1, private_key=STOLEN_PRIVATE_KEY_1),
),
Credentials(
identity=None,
- secret=Password(STOLEN_PASSWORD_2),
+ secret=Password(password=STOLEN_PASSWORD_2),
),
- Credentials(identity=Username(STOLEN_USERNAME_2), secret=LMHash(STOLEN_LM_HASH)),
- Credentials(identity=Username(STOLEN_USERNAME_2), secret=NTHash(STOLEN_NT_HASH)),
- Credentials(identity=Username(STOLEN_USERNAME_3), secret=None),
+ Credentials(
+ identity=Username(username=STOLEN_USERNAME_2), secret=LMHash(lm_hash=STOLEN_LM_HASH)
+ ),
+ Credentials(
+ identity=Username(username=STOLEN_USERNAME_2), secret=NTHash(nt_hash=STOLEN_NT_HASH)
+ ),
+ Credentials(identity=Username(username=STOLEN_USERNAME_3), secret=None),
]
STOLEN_SSH_KEYS_CREDENTIALS = [
Credentials(
- identity=Username(USERNAME),
+ identity=Username(username=USERNAME),
secret=SSHKeypair(public_key=STOLEN_PUBLIC_KEY_2, private_key=STOLEN_PRIVATE_KEY_2),
)
]
@@ -69,7 +74,7 @@ STOLEN_SSH_KEYS_CREDENTIALS = [
@pytest.fixture
def aggregating_credentials_repository() -> AggregatingPropagationCredentialsRepository:
control_channel = MagicMock()
- control_channel.get_credentials_for_propagation.return_value = CONTROL_CHANNEL_CREDENTIALS
+ control_channel.get_credentials_for_propagation.return_value = CREDENTIALS
return AggregatingPropagationCredentialsRepository(control_channel)
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
index 07a29ef95..6727e3fd5 100644
--- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py
+++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py
@@ -13,13 +13,12 @@ PRIVATE_KEY = "priv_key"
@pytest.fixture
def credentials_for_test():
-
- return Credentials(Username(USERNAME), Password(PASSWORD))
+ return Credentials(identity=Username(username=USERNAME), secret=Password(password=PASSWORD))
def test_credential_telem_send(spy_send_telemetry, credentials_for_test):
- expected_data = [Credentials.to_mapping(credentials_for_test)]
+ expected_data = [credentials_for_test.dict(simplify=True)]
telem = CredentialsTelem([credentials_for_test])
telem.send()
diff --git a/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py b/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py
index 010f26b8f..4b660e6d9 100644
--- a/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py
@@ -6,14 +6,14 @@ import pytest
from pymongo import MongoClient
from pymongo.collection import Collection
from pymongo.database import Database
-from tests.data_for_tests.propagation_credentials import PROPAGATION_CREDENTIALS
+from tests.data_for_tests.propagation_credentials import CREDENTIALS
from common.credentials import Credentials
from monkey_island.cc.repository import MongoCredentialsRepository
from monkey_island.cc.server_utils.encryption import ILockableEncryptor
-CONFIGURED_CREDENTIALS = PROPAGATION_CREDENTIALS[0:3]
-STOLEN_CREDENTIALS = PROPAGATION_CREDENTIALS[3:]
+CONFIGURED_CREDENTIALS = CREDENTIALS[0:3]
+STOLEN_CREDENTIALS = CREDENTIALS[3:]
def reverse(data: bytes) -> bytes:
@@ -59,9 +59,9 @@ def test_mongo_repository_get_all(mongo_repository):
def test_mongo_repository_configured(mongo_repository):
- mongo_repository.save_configured_credentials(PROPAGATION_CREDENTIALS)
+ mongo_repository.save_configured_credentials(CREDENTIALS)
actual_configured_credentials = mongo_repository.get_configured_credentials()
- assert actual_configured_credentials == PROPAGATION_CREDENTIALS
+ assert actual_configured_credentials == CREDENTIALS
mongo_repository.remove_configured_credentials()
actual_configured_credentials = mongo_repository.get_configured_credentials()
@@ -82,7 +82,7 @@ def test_mongo_repository_all(mongo_repository):
mongo_repository.save_configured_credentials(CONFIGURED_CREDENTIALS)
mongo_repository.save_stolen_credentials(STOLEN_CREDENTIALS)
actual_credentials = mongo_repository.get_all_credentials()
- assert actual_credentials == PROPAGATION_CREDENTIALS
+ assert actual_credentials == CREDENTIALS
mongo_repository.remove_all_credentials()
@@ -91,7 +91,7 @@ def test_mongo_repository_all(mongo_repository):
assert mongo_repository.get_configured_credentials() == []
-@pytest.mark.parametrize("credentials", PROPAGATION_CREDENTIALS)
+@pytest.mark.parametrize("credentials", CREDENTIALS)
def test_configured_secrets_encrypted(
mongo_repository: MongoCredentialsRepository,
mongo_client: MongoClient,
@@ -101,14 +101,14 @@ def test_configured_secrets_encrypted(
check_if_stored_credentials_encrypted(mongo_client, credentials)
-@pytest.mark.parametrize("credentials", PROPAGATION_CREDENTIALS)
+@pytest.mark.parametrize("credentials", CREDENTIALS)
def test_stolen_secrets_encrypted(mongo_repository, mongo_client, credentials: Credentials):
mongo_repository.save_stolen_credentials([credentials])
check_if_stored_credentials_encrypted(mongo_client, credentials)
def check_if_stored_credentials_encrypted(mongo_client: MongoClient, original_credentials):
- original_credentials_mapping = Credentials.to_mapping(original_credentials)
+ original_credentials_mapping = original_credentials.dict()
raw_credentials = get_all_credentials_in_mongo(mongo_client)
for rc in raw_credentials:
diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_propagation_credentials.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_propagation_credentials.py
index 0de5c2c91..c48e64564 100644
--- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_propagation_credentials.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_propagation_credentials.py
@@ -5,15 +5,10 @@ from urllib.parse import urljoin
import pytest
from tests.common import StubDIContainer
-from tests.data_for_tests.propagation_credentials import (
- LM_HASH_CREDENTIALS,
- NT_HASH_CREDENTIALS,
- PASSWORD_CREDENTIALS_1,
- PASSWORD_CREDENTIALS_2,
-)
+from tests.data_for_tests.propagation_credentials import LM_HASH, NT_HASH, PASSWORD_1, PASSWORD_2
from tests.monkey_island import InMemoryCredentialsRepository
-from common.credentials import Credentials
+from common.credentials import Credentials, LMHash, NTHash, Password
from monkey_island.cc.repository import ICredentialsRepository
from monkey_island.cc.resources import PropagationCredentials
from monkey_island.cc.resources.propagation_credentials import (
@@ -24,6 +19,10 @@ from monkey_island.cc.resources.propagation_credentials import (
ALL_CREDENTIALS_URL = PropagationCredentials.urls[0]
CONFIGURED_CREDENTIALS_URL = urljoin(ALL_CREDENTIALS_URL + "/", _configured_collection)
STOLEN_CREDENTIALS_URL = urljoin(ALL_CREDENTIALS_URL + "/", _stolen_collection)
+CREDENTIALS_1 = Credentials(identity=None, secret=Password(password=PASSWORD_1))
+CREDENTIALS_2 = Credentials(identity=None, secret=LMHash(lm_hash=LM_HASH))
+CREDENTIALS_3 = Credentials(identity=None, secret=NTHash(nt_hash=NT_HASH))
+CREDENTIALS_4 = Credentials(identity=None, secret=Password(password=PASSWORD_2))
@pytest.fixture
@@ -42,20 +41,18 @@ def flask_client(build_flask_client, credentials_repository):
def test_propagation_credentials_endpoint_get(flask_client, credentials_repository):
- credentials_repository.save_configured_credentials(
- [PASSWORD_CREDENTIALS_1, NT_HASH_CREDENTIALS]
- )
- credentials_repository.save_stolen_credentials([LM_HASH_CREDENTIALS, PASSWORD_CREDENTIALS_2])
+ credentials_repository.save_configured_credentials([CREDENTIALS_1, CREDENTIALS_2])
+ credentials_repository.save_stolen_credentials([CREDENTIALS_3, CREDENTIALS_4])
resp = flask_client.get(ALL_CREDENTIALS_URL)
- actual_propagation_credentials = [Credentials.from_mapping(creds) for creds in resp.json]
+ actual_propagation_credentials = [Credentials(**creds) for creds in resp.json]
assert resp.status_code == HTTPStatus.OK
assert len(actual_propagation_credentials) == 4
- assert PASSWORD_CREDENTIALS_1 in actual_propagation_credentials
- assert LM_HASH_CREDENTIALS in actual_propagation_credentials
- assert NT_HASH_CREDENTIALS in actual_propagation_credentials
- assert PASSWORD_CREDENTIALS_2 in actual_propagation_credentials
+ assert CREDENTIALS_1 in actual_propagation_credentials
+ assert CREDENTIALS_2 in actual_propagation_credentials
+ assert CREDENTIALS_3 in actual_propagation_credentials
+ assert CREDENTIALS_4 in actual_propagation_credentials
def pre_populate_repository(
@@ -69,24 +66,22 @@ def pre_populate_repository(
@pytest.mark.parametrize("url", [CONFIGURED_CREDENTIALS_URL, STOLEN_CREDENTIALS_URL])
def test_propagation_credentials_endpoint__get_stolen(flask_client, credentials_repository, url):
- pre_populate_repository(
- url, credentials_repository, [PASSWORD_CREDENTIALS_1, LM_HASH_CREDENTIALS]
- )
+ pre_populate_repository(url, credentials_repository, [CREDENTIALS_1, CREDENTIALS_2])
resp = flask_client.get(url)
- actual_propagation_credentials = [Credentials.from_mapping(creds) for creds in resp.json]
+ actual_propagation_credentials = [Credentials(**creds) for creds in resp.json]
assert resp.status_code == HTTPStatus.OK
assert len(actual_propagation_credentials) == 2
- assert actual_propagation_credentials[0] == PASSWORD_CREDENTIALS_1
- assert actual_propagation_credentials[1] == LM_HASH_CREDENTIALS
+ assert actual_propagation_credentials[0].secret.password == PASSWORD_1
+ assert actual_propagation_credentials[1].secret.lm_hash == LM_HASH
def test_configured_propagation_credentials_endpoint_put(flask_client, credentials_repository):
pre_populate_repository(
CONFIGURED_CREDENTIALS_URL,
credentials_repository,
- [PASSWORD_CREDENTIALS_1, LM_HASH_CREDENTIALS],
+ [CREDENTIALS_1, CREDENTIALS_2],
)
resp = flask_client.put(CONFIGURED_CREDENTIALS_URL, json=[])
assert resp.status_code == HTTPStatus.NO_CONTENT
From 7149c704a22cac5107511379d8e1931bd63fb4e8 Mon Sep 17 00:00:00 2001
From: vakarisz
Date: Thu, 1 Sep 2022 14:38:34 +0300
Subject: [PATCH 06/29] Island, Agent: Fix credential parsing to match pydantic
syntax
---
...ting_propagation_credentials_repository.py | 17 ++---
.../services/reporting/format_credentials.py | 15 ++---
.../configuration-components/ReformatHook.js | 50 ++++++---------
.../report-components/credentialParsing.js | 64 ++++++++++---------
.../src/components/utils/CredentialTypes.js | 14 ++++
.../reporting/test_format_credentials.py | 49 ++------------
6 files changed, 92 insertions(+), 117 deletions(-)
create mode 100644 monkey/monkey_island/cc/ui/src/components/utils/CredentialTypes.js
diff --git a/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py b/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py
index 97076fbc4..c0bb669c6 100644
--- a/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py
+++ b/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py
@@ -1,7 +1,8 @@
import logging
from typing import Any, Iterable
-from common.credentials import CredentialComponentType, Credentials, ICredentialComponent
+from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username
+from common.credentials.credentials import Identity, Secret
from infection_monkey.custom_types import PropagationCredentials
from infection_monkey.i_control_channel import IControlChannel
from infection_monkey.utils.decorators import request_cache
@@ -43,18 +44,18 @@ class AggregatingPropagationCredentialsRepository(IPropagationCredentialsReposit
if credentials.secret:
self._add_secret(credentials.secret)
- def _add_identity(self, identity: ICredentialComponent):
- if identity.credential_type is CredentialComponentType.USERNAME:
+ def _add_identity(self, identity: Identity):
+ if type(identity) == Username:
self._stored_credentials.setdefault("exploit_user_list", set()).add(identity.username)
- def _add_secret(self, secret: ICredentialComponent):
- if secret.credential_type is CredentialComponentType.PASSWORD:
+ def _add_secret(self, secret: Secret):
+ if type(secret) is Password:
self._stored_credentials.setdefault("exploit_password_list", set()).add(secret.password)
- elif secret.credential_type is CredentialComponentType.LM_HASH:
+ elif type(secret) is LMHash:
self._stored_credentials.setdefault("exploit_lm_hash_list", set()).add(secret.lm_hash)
- elif secret.credential_type is CredentialComponentType.NT_HASH:
+ elif type(secret) is NTHash:
self._stored_credentials.setdefault("exploit_ntlm_hash_list", set()).add(secret.nt_hash)
- elif secret.credential_type is CredentialComponentType.SSH_KEYPAIR:
+ elif type(secret) is SSHKeypair:
self._set_attribute(
"exploit_ssh_keys",
[{"public_key": secret.public_key, "private_key": secret.private_key}],
diff --git a/monkey/monkey_island/cc/services/reporting/format_credentials.py b/monkey/monkey_island/cc/services/reporting/format_credentials.py
index 721868cdc..fec479370 100644
--- a/monkey/monkey_island/cc/services/reporting/format_credentials.py
+++ b/monkey/monkey_island/cc/services/reporting/format_credentials.py
@@ -1,7 +1,7 @@
import logging
from typing import Mapping, Sequence
-from common.credentials import CredentialComponentType, Credentials
+from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair
logger = logging.getLogger(__name__)
@@ -11,23 +11,22 @@ def format_creds_for_reporting(credentials: Sequence[Credentials]) -> Sequence[M
formatted_creds = []
cred_type_dict = {
- CredentialComponentType.PASSWORD: "Clear Password",
- CredentialComponentType.LM_HASH: "LM hash",
- CredentialComponentType.NT_HASH: "NTLM hash",
- CredentialComponentType.SSH_KEYPAIR: "Clear SSH private key",
+ Password: "Clear Password",
+ LMHash: "LM hash",
+ NTHash: "NTLM hash",
+ SSHKeypair: "Clear SSH private key",
}
for cred in credentials:
secret = cred.secret
if secret is None:
continue
- if secret.credential_type not in cred_type_dict:
+ if type(secret) not in cred_type_dict:
continue
username = _get_username(cred)
cred_row = {
"username": username,
- "_type": secret.credential_type.name,
- "type": cred_type_dict[secret.credential_type],
+ "type": cred_type_dict[type(secret)],
}
if cred_row not in formatted_creds:
formatted_creds.append(cred_row)
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js
index a26987b37..335427b70 100644
--- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js
@@ -1,4 +1,5 @@
import {defaultCredentials} from '../../services/configuration/propagation/credentials';
+import {CredentialTypes, SecretTypes} from '../utils/CredentialTypes.js';
import _ from 'lodash';
export function reformatConfig(config, reverse = false) {
@@ -35,16 +36,16 @@ export function formatCredentialsForForm(credentials) {
let secret = credentials[i]['secret'];
if(secret !== null){
- if (secret['credential_type'] === 'PASSWORD') {
+ if (secret.hasOwnProperty(SecretTypes.Password)) {
formattedCredentials['exploit_password_list'].push(secret['password'])
}
- if (secret['credential_type'] === 'NT_HASH') {
+ if (secret.hasOwnProperty(SecretTypes.NTHash)) {
formattedCredentials['exploit_ntlm_hash_list'].push(secret['nt_hash'])
}
- if (secret['credential_type'] === 'LM_HASH') {
+ if (secret.hasOwnProperty(SecretTypes.LMHash)) {
formattedCredentials['exploit_lm_hash_list'].push(secret['lm_hash'])
}
- if (secret['credential_type'] === 'SSH_KEY') {
+ if (secret.hasOwnProperty(SecretTypes.PrivateKey)) {
let keypair = {'public_key': secret['public_key'], 'private_key': secret['private_key']}
formattedCredentials['exploit_ssh_keys'].push(keypair)
}
@@ -64,43 +65,34 @@ export function formatCredentialsForIsland(credentials) {
let usernames = credentials['exploit_user_list'];
for (let i = 0; i < usernames.length; i++) {
formattedCredentials.push({
- 'identity': {'username': usernames[i], 'credential_type': 'USERNAME'},
+ 'identity': {'username': usernames[i]},
'secret': null
})
}
- let passwords = credentials['exploit_password_list'];
- for (let i = 0; i < passwords.length; i++) {
- formattedCredentials.push({
- 'identity': null,
- 'secret': {'credential_type': 'PASSWORD', 'password': passwords[i]}
- })
- }
-
- let nt_hashes = credentials['exploit_ntlm_hash_list'];
- for (let i = 0; i < nt_hashes.length; i++) {
- formattedCredentials.push({
- 'identity': null,
- 'secret': {'credential_type': 'NT_HASH', 'nt_hash': nt_hashes[i]}
- })
- }
-
- let lm_hashes = credentials['exploit_lm_hash_list'];
- for (let i = 0; i < lm_hashes.length; i++) {
- formattedCredentials.push({
- 'identity': null,
- 'secret': {'credential_type': 'LM_HASH', 'lm_hash': lm_hashes[i]}
- })
- }
+ formattedCredentials.push(...getFormattedCredentials(credentials['exploit_password_list'], 'password'))
+ formattedCredentials.push(...getFormattedCredentials(credentials['exploit_ntlm_hash_list'], 'nt_hash'))
+ formattedCredentials.push(...getFormattedCredentials(credentials['exploit_lm_hash_list'], 'lm_hash'))
let ssh_keys = credentials['exploit_ssh_keys'];
for (let i = 0; i < ssh_keys.length; i++) {
formattedCredentials.push({
'identity': null,
- 'secret': {'credential_type': 'SSH_KEYPAIR', 'private_key': ssh_keys[i]['private_key'],
+ 'secret': {'private_key': ssh_keys[i]['private_key'],
'public_key': ssh_keys[i]['public_key']}
})
}
return formattedCredentials;
}
+
+function getFormattedCredentials(credentials, keyOfSecret) {
+ let formattedCredentials = [];
+ for (let i = 0; i < credentials.length; i++) {
+ formattedCredentials.push({
+ 'identity': null,
+ 'secret': {[keyOfSecret]: credentials[i]}
+ })
+ }
+ return formattedCredentials;
+}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js b/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js
index c1eb57198..7dfe5e9a7 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js
@@ -1,4 +1,6 @@
-export function getAllUsernames(stolen, configured){
+import {CredentialTypes, SecretTypes} from '../utils/CredentialTypes.js';
+
+export function getAllUsernames(stolen, configured) {
let usernames = [];
usernames.push(...getCredentialsUsernames(stolen));
usernames.push(...getCredentialsUsernames(configured));
@@ -7,60 +9,62 @@ export function getAllUsernames(stolen, configured){
export function getCredentialsUsernames(credentials) {
let usernames = [];
- for(let i = 0; i < credentials.length; i++){
+ for (let i = 0; i < credentials.length; i++) {
let username = credentials[i]['identity'];
- if(username !== null) {
+ if (username !== null) {
usernames.push(username['username']);
}
}
return usernames;
}
-export function getAllSecrets(stolen, configured){
+export function getAllSecrets(stolen, configured) {
let secrets = [];
- for(let i = 0; i < stolen.length; i++){
+ for (let i = 0; i < stolen.length; i++) {
let secret = stolen[i]['secret'];
- if(secret !== null){
- secrets.push(getSecretsFromCredential(secret));
+ if (secret !== null) {
+ secrets.push(reformatSecret(secret));
}
}
- for(let i = 0; i < configured.length; i++){
+ for (let i = 0; i < configured.length; i++) {
let secret = configured[i]['secret'];
- if(secret !== null){
- secrets.push(getSecretsFromCredential(secret));
+ if (secret !== null) {
+ secrets.push(reformatSecret(secret));
}
}
return secrets;
}
-function getSecretsFromCredential(credential) {
- if(credential['credential_type'] === 'SSH_KEYPAIR'){
- return {'type': 'SSH keypair', 'content': credential['private_key']}
+function reformatSecret(secret) {
+ if (secret.hasOwnProperty(SecretTypes.Password)) {
+ return {'type': CredentialTypes.Password, 'content': secret[SecretTypes.Password]}
}
- if(credential['credential_type'] === 'NT_HASH'){
- return {'type': 'NT hash', 'content': credential['nt_hash']}
+ if (secret.hasOwnProperty(SecretTypes.NTHash)) {
+ return {'type': CredentialTypes.NTHash, 'content': secret[SecretTypes.NTHash]}
}
- if(credential['credential_type'] === 'LM_HASH'){
- return {'type': 'LM hash', 'content': credential['lm_hash']}
+ if (secret.hasOwnProperty(SecretTypes.LMHash)) {
+ return {'type': CredentialTypes.LMHash, 'content': secret[SecretTypes.LMHash]}
}
- if(credential['credential_type'] === 'PASSWORD'){
- return {'type': 'Password', 'content': credential['password']}
+ if (secret.hasOwnProperty(SecretTypes.PrivateKey)) {
+ return {
+ 'type': CredentialTypes.SSHKeys,
+ 'content': secret[SecretTypes.PrivateKey]
+ }
}
}
export function getCredentialsTableData(credentials) {
+ let table_data = [];
- let table_data = [];
+ let identites = getCredentialsUsernames(credentials);
+ let secrets = getAllSecrets(credentials, [])
- let identites = getCredentialsUsernames(credentials);
- let secrets = getAllSecrets(credentials, [])
+ for (let i = 0; i < credentials.length; i++) {
+ let row_data = {};
+ row_data['username'] = identites[i];
+ row_data['type'] = secrets[i]['type'];
+ table_data.push(row_data);
+ }
- for(let i=0; i
Date: Fri, 2 Sep 2022 07:40:49 +0000
Subject: [PATCH 07/29] Island, Agent: Fix bugs in credential object creation
---
monkey/infection_monkey/master/control_channel.py | 2 +-
monkey/monkey_island/cc/resources/propagation_credentials.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/monkey/infection_monkey/master/control_channel.py b/monkey/infection_monkey/master/control_channel.py
index 28b6d7533..8c6653573 100644
--- a/monkey/infection_monkey/master/control_channel.py
+++ b/monkey/infection_monkey/master/control_channel.py
@@ -121,7 +121,7 @@ class ControlChannel(IControlChannel):
)
response.raise_for_status()
- return [Credentials.from_mapping(credentials) for credentials in response.json()]
+ return [Credentials(**credentials) for credentials in response.json()]
except (
requests.exceptions.JSONDecodeError,
requests.exceptions.ConnectionError,
diff --git a/monkey/monkey_island/cc/resources/propagation_credentials.py b/monkey/monkey_island/cc/resources/propagation_credentials.py
index 22d717e72..6700bffd3 100644
--- a/monkey/monkey_island/cc/resources/propagation_credentials.py
+++ b/monkey/monkey_island/cc/resources/propagation_credentials.py
@@ -29,7 +29,7 @@ class PropagationCredentials(AbstractResource):
return propagation_credentials, HTTPStatus.OK
def put(self, collection=None):
- credentials = [Credentials.parse_raw(c) for c in request.json]
+ credentials = [Credentials(**c) for c in request.json]
if collection == _configured_collection:
self._credentials_repository.remove_configured_credentials()
self._credentials_repository.save_configured_credentials(credentials)
From ed24108bfa867285dcc7c9a11fd10853a23b60f5 Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Fri, 2 Sep 2022 11:58:43 +0000
Subject: [PATCH 08/29] Project: Add temporary IJSONSerializable ignore
---
vulture_allowlist.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/vulture_allowlist.py b/vulture_allowlist.py
index 127a319d2..e6550b8f9 100644
--- a/vulture_allowlist.py
+++ b/vulture_allowlist.py
@@ -315,6 +315,10 @@ EXPLOITED
CC
CC_TUNNEL
+# TODO Remove with #2217
+IJSONSerializable
+from_json
+
IslandEventTopic.AGENT_CONNECTED
IslandEventTopic.CLEAR_SIMULATION_DATA
IslandEventTopic.RESET_AGENT_CONFIGURATION
From f1b8ad373b4dd8e07a2df68df36e868d450cf3be Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Fri, 2 Sep 2022 11:59:46 +0000
Subject: [PATCH 09/29] Island: Fix bug in credentials_parser.py
---
.../telemetry/processing/credentials/credentials_parser.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py
index 4d675b42c..f11d76002 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py
@@ -19,8 +19,6 @@ class CredentialsParser:
self._parse_credentials(telemetry_dict, _agent_configuration)
def _parse_credentials(self, telemetry_dict: Mapping, _agent_configuration):
- credentials = [
- Credentials.from_mapping(credential) for credential in telemetry_dict["data"]
- ]
+ credentials = [Credentials(**credential) for credential in telemetry_dict["data"]]
self._credentials_repository.save_stolen_credentials(credentials)
From 9cc11f6a0908db34d383007a04d128845296d7c3 Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Fri, 2 Sep 2022 12:00:31 +0000
Subject: [PATCH 10/29] Island: Remove misleading comment
---
.../monkey_island/cc/services/reporting/format_credentials.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/monkey/monkey_island/cc/services/reporting/format_credentials.py b/monkey/monkey_island/cc/services/reporting/format_credentials.py
index fec479370..60d9b5530 100644
--- a/monkey/monkey_island/cc/services/reporting/format_credentials.py
+++ b/monkey/monkey_island/cc/services/reporting/format_credentials.py
@@ -7,8 +7,6 @@ logger = logging.getLogger(__name__)
def format_creds_for_reporting(credentials: Sequence[Credentials]) -> Sequence[Mapping]:
- logger.info("Stolen creds generated for reporting")
-
formatted_creds = []
cred_type_dict = {
Password: "Clear Password",
From 7dba3c4fede9fdf3d02fd8a53dd5d699432a4071 Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Fri, 2 Sep 2022 12:46:11 +0000
Subject: [PATCH 11/29] UI: Remove duplicate credentials from report
---
.../report-components/credentialParsing.js | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js b/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js
index 7dfe5e9a7..fa5660d4e 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js
@@ -1,10 +1,10 @@
import {CredentialTypes, SecretTypes} from '../utils/CredentialTypes.js';
export function getAllUsernames(stolen, configured) {
- let usernames = [];
- usernames.push(...getCredentialsUsernames(stolen));
- usernames.push(...getCredentialsUsernames(configured));
- return usernames;
+ let usernames = new Set();
+ usernames.add(...getCredentialsUsernames(stolen));
+ usernames.add(...getCredentialsUsernames(configured));
+ return Array.from(usernames);
}
export function getCredentialsUsernames(credentials) {
@@ -19,20 +19,20 @@ export function getCredentialsUsernames(credentials) {
}
export function getAllSecrets(stolen, configured) {
- let secrets = [];
+ let secrets = new Set();
for (let i = 0; i < stolen.length; i++) {
let secret = stolen[i]['secret'];
if (secret !== null) {
- secrets.push(reformatSecret(secret));
+ secrets.add(reformatSecret(secret));
}
}
for (let i = 0; i < configured.length; i++) {
let secret = configured[i]['secret'];
if (secret !== null) {
- secrets.push(reformatSecret(secret));
+ secrets.add(reformatSecret(secret));
}
}
- return secrets;
+ return Array.from(secrets);
}
function reformatSecret(secret) {
From e5574240e9f3c2c93497ce18384aa64382a181ed Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Tue, 6 Sep 2022 11:33:38 +0000
Subject: [PATCH 12/29] Island, Agent: Change json encryptors to handle
pydantic models
---
monkey/infection_monkey/telemetry/telem_encoder.py | 6 ++++++
monkey/monkey_island/cc/services/representations.py | 3 +++
2 files changed, 9 insertions(+)
diff --git a/monkey/infection_monkey/telemetry/telem_encoder.py b/monkey/infection_monkey/telemetry/telem_encoder.py
index 3abe77439..224e14133 100644
--- a/monkey/infection_monkey/telemetry/telem_encoder.py
+++ b/monkey/infection_monkey/telemetry/telem_encoder.py
@@ -1,5 +1,7 @@
import json
+from pydantic import BaseModel, SecretField
+
from common import OperatingSystem
@@ -7,4 +9,8 @@ class TelemetryJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, OperatingSystem):
return obj.name
+ if issubclass(type(obj), BaseModel):
+ return obj.dict(simplify=True)
+ if issubclass(type(obj), SecretField):
+ return obj.get_secret_value()
return json.JSONEncoder.default(self, obj)
diff --git a/monkey/monkey_island/cc/services/representations.py b/monkey/monkey_island/cc/services/representations.py
index e679231c1..19218ba37 100644
--- a/monkey/monkey_island/cc/services/representations.py
+++ b/monkey/monkey_island/cc/services/representations.py
@@ -5,6 +5,7 @@ from typing import Any
import bson
from flask import make_response
+from pydantic import BaseModel
from common.utils import IJSONSerializable
@@ -23,6 +24,8 @@ class APIEncoder(JSONEncoder):
return loads(value.__class__.to_json(value))
if issubclass(type(value), set):
return list(value)
+ if issubclass(type(value), BaseModel):
+ return value.dict(simplify=True)
try:
return JSONEncoder.default(self, value)
except TypeError:
From 472ca382f13a743b1f20ceaa954c285a81c6dcc9 Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Tue, 6 Sep 2022 11:51:16 +0000
Subject: [PATCH 13/29] Island, BB: Fix credential conversion to dict bugs
---
.../blackbox/island_client/monkey_island_client.py | 6 +++---
.../cc/repository/mongo_credentials_repository.py | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
index a7009e459..d8243fcfe 100644
--- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
+++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
@@ -1,7 +1,7 @@
import json
import logging
import time
-from typing import Sequence, Union
+from typing import List, Sequence, Union
from bson import json_util
@@ -59,9 +59,9 @@ class MonkeyIslandClient(object):
assert False
@avoid_race_condition
- def _import_credentials(self, propagation_credentials: Credentials):
+ def _import_credentials(self, propagation_credentials: List[Credentials]):
serialized_propagation_credentials = [
- Credentials.dict(credentials) for credentials in propagation_credentials
+ credentials.dict() for credentials in propagation_credentials
]
response = self.requests.put_json(
"/api/propagation-credentials/configured-credentials",
diff --git a/monkey/monkey_island/cc/repository/mongo_credentials_repository.py b/monkey/monkey_island/cc/repository/mongo_credentials_repository.py
index 3affc6cb6..237f052e4 100644
--- a/monkey/monkey_island/cc/repository/mongo_credentials_repository.py
+++ b/monkey/monkey_island/cc/repository/mongo_credentials_repository.py
@@ -68,7 +68,7 @@ class MongoCredentialsRepository(ICredentialsRepository):
def _save_credentials_to_collection(self, credentials: Sequence[Credentials], collection):
try:
for c in credentials:
- encrypted_credentials = self._encrypt_credentials_mapping(c.dict())
+ encrypted_credentials = self._encrypt_credentials_mapping(c.dict(simplify=True))
collection.insert_one(encrypted_credentials)
except Exception as err:
raise StorageError(err)
From 9fb2804202f50ff2fd23e6412dceead4238385fa Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Tue, 6 Sep 2022 12:54:24 +0000
Subject: [PATCH 14/29] Agent: Use SecretStr and SecretBytes instead of
SecretField
SecretField is a base type for secrets, but it doesn't seem to be exported. When the agent is ran, this import fails
---
monkey/infection_monkey/telemetry/telem_encoder.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/monkey/infection_monkey/telemetry/telem_encoder.py b/monkey/infection_monkey/telemetry/telem_encoder.py
index 224e14133..ec060b250 100644
--- a/monkey/infection_monkey/telemetry/telem_encoder.py
+++ b/monkey/infection_monkey/telemetry/telem_encoder.py
@@ -1,6 +1,6 @@
import json
-from pydantic import BaseModel, SecretField
+from pydantic import BaseModel, SecretBytes, SecretStr
from common import OperatingSystem
@@ -11,6 +11,6 @@ class TelemetryJSONEncoder(json.JSONEncoder):
return obj.name
if issubclass(type(obj), BaseModel):
return obj.dict(simplify=True)
- if issubclass(type(obj), SecretField):
+ if issubclass(type(obj), (SecretStr, SecretBytes)):
return obj.get_secret_value()
return json.JSONEncoder.default(self, obj)
From 792895a25cf09e06f1e642b42f4a9beffac63a8d Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Tue, 6 Sep 2022 12:56:06 +0000
Subject: [PATCH 15/29] UI: Improve the naming/handling of credential types in
the UI
---
.../configuration-components/ReformatHook.js | 10 +++++-----
.../report-components/credentialParsing.js | 12 ++++++------
.../report-components/security/StolenPasswords.js | 2 +-
.../report-components/security/UsedCredentials.js | 2 +-
.../{CredentialTypes.js => CredentialTitles.js} | 6 +++++-
5 files changed, 18 insertions(+), 14 deletions(-)
rename monkey/monkey_island/cc/ui/src/components/utils/{CredentialTypes.js => CredentialTitles.js} (73%)
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js
index 335427b70..239d0c5a0 100644
--- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js
@@ -1,5 +1,5 @@
import {defaultCredentials} from '../../services/configuration/propagation/credentials';
-import {CredentialTypes, SecretTypes} from '../utils/CredentialTypes.js';
+import {PlaintextTypes, SecretTypes} from '../utils/CredentialTitles.js';
import _ from 'lodash';
export function reformatConfig(config, reverse = false) {
@@ -37,16 +37,16 @@ export function formatCredentialsForForm(credentials) {
let secret = credentials[i]['secret'];
if(secret !== null){
if (secret.hasOwnProperty(SecretTypes.Password)) {
- formattedCredentials['exploit_password_list'].push(secret['password'])
+ formattedCredentials['exploit_password_list'].push(secret[SecretTypes.Password])
}
if (secret.hasOwnProperty(SecretTypes.NTHash)) {
- formattedCredentials['exploit_ntlm_hash_list'].push(secret['nt_hash'])
+ formattedCredentials['exploit_ntlm_hash_list'].push(secret[SecretTypes.NTHash])
}
if (secret.hasOwnProperty(SecretTypes.LMHash)) {
- formattedCredentials['exploit_lm_hash_list'].push(secret['lm_hash'])
+ formattedCredentials['exploit_lm_hash_list'].push(secret[SecretTypes.LMHash])
}
if (secret.hasOwnProperty(SecretTypes.PrivateKey)) {
- let keypair = {'public_key': secret['public_key'], 'private_key': secret['private_key']}
+ let keypair = {'public_key': secret[PlaintextTypes.PublicKey], 'private_key': secret[SecretTypes.PrivateKey]}
formattedCredentials['exploit_ssh_keys'].push(keypair)
}
}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js b/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js
index fa5660d4e..affc9d967 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js
@@ -1,4 +1,4 @@
-import {CredentialTypes, SecretTypes} from '../utils/CredentialTypes.js';
+import {CredentialTitles, SecretTypes} from '../utils/CredentialTitles.js';
export function getAllUsernames(stolen, configured) {
let usernames = new Set();
@@ -37,17 +37,17 @@ export function getAllSecrets(stolen, configured) {
function reformatSecret(secret) {
if (secret.hasOwnProperty(SecretTypes.Password)) {
- return {'type': CredentialTypes.Password, 'content': secret[SecretTypes.Password]}
+ return {'title': CredentialTitles.Password, 'content': secret[SecretTypes.Password]}
}
if (secret.hasOwnProperty(SecretTypes.NTHash)) {
- return {'type': CredentialTypes.NTHash, 'content': secret[SecretTypes.NTHash]}
+ return {'title': CredentialTitles.NTHash, 'content': secret[SecretTypes.NTHash]}
}
if (secret.hasOwnProperty(SecretTypes.LMHash)) {
- return {'type': CredentialTypes.LMHash, 'content': secret[SecretTypes.LMHash]}
+ return {'title': CredentialTitles.LMHash, 'content': secret[SecretTypes.LMHash]}
}
if (secret.hasOwnProperty(SecretTypes.PrivateKey)) {
return {
- 'type': CredentialTypes.SSHKeys,
+ 'title': CredentialTitles.SSHKeys,
'content': secret[SecretTypes.PrivateKey]
}
}
@@ -62,7 +62,7 @@ export function getCredentialsTableData(credentials) {
for (let i = 0; i < credentials.length; i++) {
let row_data = {};
row_data['username'] = identites[i];
- row_data['type'] = secrets[i]['type'];
+ row_data['title'] = secrets[i]['title'];
table_data.push(row_data);
}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/StolenPasswords.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/StolenPasswords.js
index 441bac8b1..b1b6ac5c2 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/security/StolenPasswords.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/StolenPasswords.js
@@ -7,7 +7,7 @@ const columns = [
Header: 'Stolen Credentials',
columns: [
{Header: 'Username', accessor: 'username'},
- {Header: 'Type', accessor: 'type'}
+ {Header: 'Type', accessor: 'title'}
]
}
];
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/UsedCredentials.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/UsedCredentials.js
index 7a5becde7..536ec5ec7 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/security/UsedCredentials.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/UsedCredentials.js
@@ -24,7 +24,7 @@ class UsedCredentials extends React.Component {
{allSecrets.map((x, index) => - {x['type']}: {x['content'].substr(0, 3) + '******'}
)}
+ key={index}>{x['title']}: {x['content'].substr(0, 3) + '******'})}
>
:
diff --git a/monkey/monkey_island/cc/ui/src/components/utils/CredentialTypes.js b/monkey/monkey_island/cc/ui/src/components/utils/CredentialTitles.js
similarity index 73%
rename from monkey/monkey_island/cc/ui/src/components/utils/CredentialTypes.js
rename to monkey/monkey_island/cc/ui/src/components/utils/CredentialTitles.js
index 494e5b36f..d24c1e3e1 100644
--- a/monkey/monkey_island/cc/ui/src/components/utils/CredentialTypes.js
+++ b/monkey/monkey_island/cc/ui/src/components/utils/CredentialTitles.js
@@ -1,4 +1,4 @@
-export const CredentialTypes = {
+export const CredentialTitles = {
Password: 'Clear Password',
SSHKeys: 'Clear SSH private key',
LMHash: 'LM hash',
@@ -12,3 +12,7 @@ export const SecretTypes = {
LMHash: 'lm_hash',
NTHash: 'nt_hash'
}
+
+export const PlaintextTypes = {
+ PublicKey: 'public_key'
+}
From 233090942a92356d05a786fdfb15ca1e7001d34b Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Tue, 6 Sep 2022 13:06:03 +0000
Subject: [PATCH 16/29] Agent: Use issintance instead of type comparison in
credential repo
---
.../aggregating_propagation_credentials_repository.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py b/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py
index c0bb669c6..cffa2b277 100644
--- a/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py
+++ b/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py
@@ -49,13 +49,13 @@ class AggregatingPropagationCredentialsRepository(IPropagationCredentialsReposit
self._stored_credentials.setdefault("exploit_user_list", set()).add(identity.username)
def _add_secret(self, secret: Secret):
- if type(secret) is Password:
+ if isinstance(secret, Password):
self._stored_credentials.setdefault("exploit_password_list", set()).add(secret.password)
- elif type(secret) is LMHash:
+ elif isinstance(secret, LMHash):
self._stored_credentials.setdefault("exploit_lm_hash_list", set()).add(secret.lm_hash)
- elif type(secret) is NTHash:
+ elif isinstance(secret, NTHash):
self._stored_credentials.setdefault("exploit_ntlm_hash_list", set()).add(secret.nt_hash)
- elif type(secret) is SSHKeypair:
+ elif isinstance(secret, SSHKeypair):
self._set_attribute(
"exploit_ssh_keys",
[{"public_key": secret.public_key, "private_key": secret.private_key}],
From 16a59a3f5a5c1751dd01bdcbe948d52d58f2bb59 Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Tue, 6 Sep 2022 13:09:19 +0000
Subject: [PATCH 17/29] UT: Fix typehint in credentials conftest.py
---
monkey/tests/unit_tests/common/credentials/conftest.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/monkey/tests/unit_tests/common/credentials/conftest.py b/monkey/tests/unit_tests/common/credentials/conftest.py
index b6e60289c..ad293354e 100644
--- a/monkey/tests/unit_tests/common/credentials/conftest.py
+++ b/monkey/tests/unit_tests/common/credentials/conftest.py
@@ -1,4 +1,4 @@
-from typing import List
+from typing import List, Union
import pytest
@@ -9,7 +9,7 @@ def valid_ntlm_hash() -> str:
@pytest.fixture(scope="session")
-def invalid_ntlm_hashes() -> List[str]:
+def invalid_ntlm_hashes() -> List[Union[str, int, float]]:
return [
0,
1,
From b08097b131e8bff5a3e91e862c3315a7b62e5954 Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Tue, 6 Sep 2022 13:24:03 +0000
Subject: [PATCH 18/29] Island, Common: Small style improvements
---
monkey/common/credentials/lm_hash.py | 2 +-
monkey/common/credentials/nt_hash.py | 2 +-
.../configuration-components/ReformatHook.js | 48 +++++++++++--------
.../report-components/credentialParsing.js | 8 ++--
4 files changed, 35 insertions(+), 25 deletions(-)
diff --git a/monkey/common/credentials/lm_hash.py b/monkey/common/credentials/lm_hash.py
index 02525d386..9dd201536 100644
--- a/monkey/common/credentials/lm_hash.py
+++ b/monkey/common/credentials/lm_hash.py
@@ -12,5 +12,5 @@ class LMHash(BaseModel):
@validator("lm_hash")
def validate_hash_format(cls, nt_hash):
if not re.match(ntlm_hash_regex, nt_hash):
- raise ValueError(f"Invalid lm hash provided: {nt_hash}")
+ raise ValueError(f"Invalid LM hash provided: {nt_hash}")
return nt_hash
diff --git a/monkey/common/credentials/nt_hash.py b/monkey/common/credentials/nt_hash.py
index dd288afba..5a5dacd97 100644
--- a/monkey/common/credentials/nt_hash.py
+++ b/monkey/common/credentials/nt_hash.py
@@ -12,5 +12,5 @@ class NTHash(InfectionMonkeyBaseModel):
@validator("nt_hash")
def validate_hash_format(cls, nt_hash):
if not re.match(ntlm_hash_regex, nt_hash):
- raise ValueError(f"Invalid nt hash provided: {nt_hash}")
+ raise ValueError(f"Invalid NT hash provided: {nt_hash}")
return nt_hash
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js
index 239d0c5a0..5792a82ba 100644
--- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js
@@ -6,16 +6,21 @@ export function reformatConfig(config, reverse = false) {
let formattedConfig = _.clone(config);
if (reverse) {
- if(formattedConfig['payloads'].length === 1){
+ if (formattedConfig['payloads'].length === 1) {
// Second click on Export
- formattedConfig['payloads'] = [{'name': 'ransomware', 'options': formattedConfig['payloads'][0]['options']}];
+ formattedConfig['payloads'] = [{
+ 'name': 'ransomware',
+ 'options': formattedConfig['payloads'][0]['options']
+ }];
} else {
- formattedConfig['payloads'] = [{'name': 'ransomware', 'options': formattedConfig['payloads']}];
+ formattedConfig['payloads'] = [{
+ 'name': 'ransomware',
+ 'options': formattedConfig['payloads']
+ }];
}
formattedConfig['keep_tunnel_open_time'] = formattedConfig['advanced']['keep_tunnel_open_time'];
} else {
- if(formattedConfig['payloads'].length !== 0)
- {
+ if (formattedConfig['payloads'].length !== 0) {
formattedConfig['payloads'] = formattedConfig['payloads'][0]['options'];
} else {
formattedConfig['payloads'] = {'encryption': {}, 'other_behaviors': {}}
@@ -30,23 +35,26 @@ export function formatCredentialsForForm(credentials) {
let formattedCredentials = _.clone(defaultCredentials);
for (let i = 0; i < credentials.length; i++) {
let identity = credentials[i]['identity'];
- if(identity !== null) {
+ if (identity !== null) {
formattedCredentials['exploit_user_list'].push(identity.username)
}
let secret = credentials[i]['secret'];
- if(secret !== null){
- if (secret.hasOwnProperty(SecretTypes.Password)) {
+ if (secret !== null) {
+ if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.Password)) {
formattedCredentials['exploit_password_list'].push(secret[SecretTypes.Password])
}
- if (secret.hasOwnProperty(SecretTypes.NTHash)) {
+ if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.NTHash)) {
formattedCredentials['exploit_ntlm_hash_list'].push(secret[SecretTypes.NTHash])
}
- if (secret.hasOwnProperty(SecretTypes.LMHash)) {
+ if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.LMHash)) {
formattedCredentials['exploit_lm_hash_list'].push(secret[SecretTypes.LMHash])
}
- if (secret.hasOwnProperty(SecretTypes.PrivateKey)) {
- let keypair = {'public_key': secret[PlaintextTypes.PublicKey], 'private_key': secret[SecretTypes.PrivateKey]}
+ if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.PrivateKey)) {
+ let keypair = {
+ 'public_key': secret[PlaintextTypes.PublicKey],
+ 'private_key': secret[SecretTypes.PrivateKey]
+ }
formattedCredentials['exploit_ssh_keys'].push(keypair)
}
}
@@ -78,8 +86,10 @@ export function formatCredentialsForIsland(credentials) {
for (let i = 0; i < ssh_keys.length; i++) {
formattedCredentials.push({
'identity': null,
- 'secret': {'private_key': ssh_keys[i]['private_key'],
- 'public_key': ssh_keys[i]['public_key']}
+ 'secret': {
+ 'private_key': ssh_keys[i]['private_key'],
+ 'public_key': ssh_keys[i]['public_key']
+ }
})
}
@@ -89,10 +99,10 @@ export function formatCredentialsForIsland(credentials) {
function getFormattedCredentials(credentials, keyOfSecret) {
let formattedCredentials = [];
for (let i = 0; i < credentials.length; i++) {
- formattedCredentials.push({
- 'identity': null,
- 'secret': {[keyOfSecret]: credentials[i]}
- })
- }
+ formattedCredentials.push({
+ 'identity': null,
+ 'secret': {[keyOfSecret]: credentials[i]}
+ })
+ }
return formattedCredentials;
}
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js b/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js
index affc9d967..fc77f6d4f 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js
@@ -36,16 +36,16 @@ export function getAllSecrets(stolen, configured) {
}
function reformatSecret(secret) {
- if (secret.hasOwnProperty(SecretTypes.Password)) {
+ if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.Password)) {
return {'title': CredentialTitles.Password, 'content': secret[SecretTypes.Password]}
}
- if (secret.hasOwnProperty(SecretTypes.NTHash)) {
+ if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.NTHash)) {
return {'title': CredentialTitles.NTHash, 'content': secret[SecretTypes.NTHash]}
}
- if (secret.hasOwnProperty(SecretTypes.LMHash)) {
+ if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.LMHash)) {
return {'title': CredentialTitles.LMHash, 'content': secret[SecretTypes.LMHash]}
}
- if (secret.hasOwnProperty(SecretTypes.PrivateKey)) {
+ if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.PrivateKey)) {
return {
'title': CredentialTitles.SSHKeys,
'content': secret[SecretTypes.PrivateKey]
From 8fd226cd232f034f609ee5fb40b2353fcfd24985 Mon Sep 17 00:00:00 2001
From: Mike Salvatore
Date: Tue, 6 Sep 2022 09:46:52 -0400
Subject: [PATCH 19/29] UT: Add safety test since we will switch to
pydantic.SecretType
---
.../cc/repository/test_mongo_credentials_repository.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py b/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py
index 4b660e6d9..07b7807d6 100644
--- a/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py
@@ -119,6 +119,10 @@ def check_if_stored_credentials_encrypted(mongo_client: MongoClient, original_cr
for key, value in credentials_component.items():
assert original_credentials_mapping[identity_or_secret][key] != value.decode()
+ # Since secrets use the pydantic.SecretType, make sure we're not just storing
+ # all '*' characters.
+ assert "***" not in value.decode()
+
def get_all_credentials_in_mongo(
mongo_client: MongoClient,
From 646b3a1b46ebc760d85fbb0b3044b54ae66d31b5 Mon Sep 17 00:00:00 2001
From: Mike Salvatore
Date: Tue, 6 Sep 2022 15:22:03 -0400
Subject: [PATCH 20/29] BB: Add `simplify=True` to Credentials serialization
---
envs/monkey_zoo/blackbox/island_client/monkey_island_client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
index d8243fcfe..351de3c32 100644
--- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
+++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py
@@ -61,7 +61,7 @@ class MonkeyIslandClient(object):
@avoid_race_condition
def _import_credentials(self, propagation_credentials: List[Credentials]):
serialized_propagation_credentials = [
- credentials.dict() for credentials in propagation_credentials
+ credentials.dict(simplify=True) for credentials in propagation_credentials
]
response = self.requests.put_json(
"/api/propagation-credentials/configured-credentials",
From 17e3b3d205ae921ab842b3f38a5ac218eb9c3e2c Mon Sep 17 00:00:00 2001
From: Mike Salvatore
Date: Tue, 6 Sep 2022 15:23:55 -0400
Subject: [PATCH 21/29] UT: Add `simplify=True` to Credentials serialization
---
.../cc/repository/test_mongo_credentials_repository.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py b/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py
index 07b7807d6..ffd3c3bfa 100644
--- a/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py
+++ b/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py
@@ -108,7 +108,7 @@ def test_stolen_secrets_encrypted(mongo_repository, mongo_client, credentials: C
def check_if_stored_credentials_encrypted(mongo_client: MongoClient, original_credentials):
- original_credentials_mapping = original_credentials.dict()
+ original_credentials_mapping = original_credentials.dict(simplify=True)
raw_credentials = get_all_credentials_in_mongo(mongo_client)
for rc in raw_credentials:
From ece4d9383e382c106095456cf7a6516a0c77b36b Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Tue, 6 Sep 2022 14:29:42 +0000
Subject: [PATCH 22/29] Agent, Common: Refactor pydantic credentials to use
SecretStr
---
monkey/common/credentials/credentials.py | 21 +++++++++++++
monkey/common/credentials/lm_hash.py | 16 +++++-----
monkey/common/credentials/nt_hash.py | 8 ++---
monkey/common/credentials/password.py | 4 ++-
monkey/common/credentials/ssh_keypair.py | 4 ++-
monkey/infection_monkey/exploit/mssqlexec.py | 3 +-
.../powershell_utils/powershell_client.py | 9 ++++--
monkey/infection_monkey/exploit/smbexec.py | 13 ++++++--
monkey/infection_monkey/exploit/sshexec.py | 3 +-
.../exploit/tools/smb_tools.py | 18 ++++++++---
monkey/infection_monkey/exploit/wmiexec.py | 10 ++++++-
monkey/infection_monkey/exploit/zerologon.py | 4 +--
.../data_for_tests/propagation_credentials.py | 14 +++++----
.../common/credentials/test_credentials.py | 30 +++++++++++++++++++
...ting_propagation_credentials_repository.py | 13 ++++----
vulture_allowlist.py | 3 +-
16 files changed, 132 insertions(+), 41 deletions(-)
diff --git a/monkey/common/credentials/credentials.py b/monkey/common/credentials/credentials.py
index 053d7f33d..ae8de63d1 100644
--- a/monkey/common/credentials/credentials.py
+++ b/monkey/common/credentials/credentials.py
@@ -2,6 +2,8 @@ from __future__ import annotations
from typing import Optional, Union
+from pydantic import SecretBytes, SecretStr
+
from ..base_models import InfectionMonkeyBaseModel
from . import LMHash, NTHash, Password, SSHKeypair, Username
@@ -9,6 +11,25 @@ Secret = Union[Password, LMHash, NTHash, SSHKeypair]
Identity = Username
+def get_plain_text(secret: Union[SecretStr, SecretBytes, None, str]) -> Optional[str]:
+ if secret:
+ return secret.get_secret_value()
+ else:
+ return secret
+
+
class Credentials(InfectionMonkeyBaseModel):
+ """Represents a credential pair (some form of identity and a secret)"""
+
identity: Optional[Identity]
+ """Identity part of credentials, like a username or an email"""
+
secret: Optional[Secret]
+ """Secret part of credentials, like a password or a hash"""
+
+ class Config:
+ json_encoders = {
+ # This makes secrets dumpable to json, but not loggable
+ SecretStr: lambda v: v.get_secret_value() if v else None,
+ SecretBytes: lambda v: v.get_secret_value() if v else None,
+ }
diff --git a/monkey/common/credentials/lm_hash.py b/monkey/common/credentials/lm_hash.py
index 9dd201536..2bace0c84 100644
--- a/monkey/common/credentials/lm_hash.py
+++ b/monkey/common/credentials/lm_hash.py
@@ -1,16 +1,16 @@
import re
-from pydantic import validator
-from pydantic.main import BaseModel
+from pydantic import SecretStr, validator
+from ..base_models import InfectionMonkeyBaseModel
from .validators import ntlm_hash_regex
-class LMHash(BaseModel):
- lm_hash: str
+class LMHash(InfectionMonkeyBaseModel):
+ lm_hash: SecretStr
@validator("lm_hash")
- def validate_hash_format(cls, nt_hash):
- if not re.match(ntlm_hash_regex, nt_hash):
- raise ValueError(f"Invalid LM hash provided: {nt_hash}")
- return nt_hash
+ def validate_hash_format(cls, lm_hash):
+ if not re.match(ntlm_hash_regex, lm_hash.get_secret_value()):
+ raise ValueError("Invalid LM hash provided")
+ return lm_hash
diff --git a/monkey/common/credentials/nt_hash.py b/monkey/common/credentials/nt_hash.py
index 5a5dacd97..5c901fa9b 100644
--- a/monkey/common/credentials/nt_hash.py
+++ b/monkey/common/credentials/nt_hash.py
@@ -1,16 +1,16 @@
import re
-from pydantic import validator
+from pydantic import SecretStr, validator
from ..base_models import InfectionMonkeyBaseModel
from .validators import ntlm_hash_regex
class NTHash(InfectionMonkeyBaseModel):
- nt_hash: str
+ nt_hash: SecretStr
@validator("nt_hash")
def validate_hash_format(cls, nt_hash):
- if not re.match(ntlm_hash_regex, nt_hash):
- raise ValueError(f"Invalid NT hash provided: {nt_hash}")
+ if not re.match(ntlm_hash_regex, nt_hash.get_secret_value()):
+ raise ValueError("Invalid NT hash provided")
return nt_hash
diff --git a/monkey/common/credentials/password.py b/monkey/common/credentials/password.py
index a9c1f2016..1745de170 100644
--- a/monkey/common/credentials/password.py
+++ b/monkey/common/credentials/password.py
@@ -1,5 +1,7 @@
+from pydantic import SecretStr
+
from ..base_models import InfectionMonkeyBaseModel
class Password(InfectionMonkeyBaseModel):
- password: str
+ password: SecretStr
diff --git a/monkey/common/credentials/ssh_keypair.py b/monkey/common/credentials/ssh_keypair.py
index 49d886b8b..651716965 100644
--- a/monkey/common/credentials/ssh_keypair.py
+++ b/monkey/common/credentials/ssh_keypair.py
@@ -1,6 +1,8 @@
+from pydantic import SecretStr
+
from ..base_models import InfectionMonkeyBaseModel
class SSHKeypair(InfectionMonkeyBaseModel):
- private_key: str
+ private_key: SecretStr
public_key: str
diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py
index 6c155f468..b1d700945 100644
--- a/monkey/infection_monkey/exploit/mssqlexec.py
+++ b/monkey/infection_monkey/exploit/mssqlexec.py
@@ -6,6 +6,7 @@ from typing import Sequence, Tuple
import pymssql
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
+from common.credentials.credentials import get_plain_text
from common.utils.exceptions import FailedExploitationError
from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
@@ -111,7 +112,7 @@ class MSSQLExploiter(HostExploiter):
conn = pymssql.connect(
host,
user,
- password,
+ get_plain_text(password),
port=port,
login_timeout=self.LOGIN_TIMEOUT,
timeout=self.QUERY_TIMEOUT,
diff --git a/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py b/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py
index df2cf65b1..8113026e7 100644
--- a/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py
+++ b/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py
@@ -11,6 +11,7 @@ from pypsrp.powershell import PowerShell, RunspacePool
from typing_extensions import Protocol
from urllib3 import connectionpool
+from common.credentials.credentials import get_plain_text
from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions
from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType
@@ -42,14 +43,16 @@ def format_password(credentials: Credentials) -> Optional[str]:
if credentials.secret_type == SecretType.CACHED:
return None
+ plaintext_secret = get_plain_text(credentials.secret)
+
if credentials.secret_type == SecretType.PASSWORD:
- return credentials.secret
+ return plaintext_secret
if credentials.secret_type == SecretType.LM_HASH:
- return f"{credentials.secret}:00000000000000000000000000000000"
+ return f"{plaintext_secret}:00000000000000000000000000000000"
if credentials.secret_type == SecretType.NT_HASH:
- return f"00000000000000000000000000000000:{credentials.secret}"
+ return f"00000000000000000000000000000000:{plaintext_secret}"
raise ValueError(f"Unknown secret type {credentials.secret_type}")
diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py
index 80c09547f..778a4fb36 100644
--- a/monkey/infection_monkey/exploit/smbexec.py
+++ b/monkey/infection_monkey/exploit/smbexec.py
@@ -4,6 +4,7 @@ from impacket.dcerpc.v5 import scmr, transport
from impacket.dcerpc.v5.scmr import DCERPCSessionError
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
+from common.credentials.credentials import get_plain_text
from common.utils.attack_utils import ScanStatus, UsageEnum
from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
@@ -107,7 +108,14 @@ class SMBExploiter(HostExploiter):
rpctransport.setRemoteHost(self.host.ip_addr)
if hasattr(rpctransport, "set_credentials"):
# This method exists only for selected protocol sequences.
- rpctransport.set_credentials(user, password, "", lm_hash, ntlm_hash, None)
+ rpctransport.set_credentials(
+ user,
+ get_plain_text(password),
+ "",
+ get_plain_text(lm_hash),
+ get_plain_text(ntlm_hash),
+ None,
+ )
rpctransport.set_kerberos(SMBExploiter.USE_KERBEROS)
scmr_rpc = rpctransport.get_dce_rpc()
@@ -116,7 +124,8 @@ class SMBExploiter(HostExploiter):
scmr_rpc.connect()
except Exception as exc:
logger.debug(
- f"Can't connect to SCM on exploited machine {self.host}, port {port} : {exc}"
+ f"Can't connect to SCM on exploited machine {self.host}, port {port} : "
+ f"{exc}"
)
continue
diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py
index 8b10c8c44..ab96794a2 100644
--- a/monkey/infection_monkey/exploit/sshexec.py
+++ b/monkey/infection_monkey/exploit/sshexec.py
@@ -5,6 +5,7 @@ from pathlib import PurePath
import paramiko
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
+from common.credentials.credentials import get_plain_text
from common.utils import Timer
from common.utils.attack_utils import ScanStatus
from common.utils.exceptions import FailedExploitationError
@@ -59,7 +60,7 @@ class SSHExploiter(HostExploiter):
for user, ssh_key_pair in ssh_key_pairs_iterator:
# Creating file-like private key for paramiko
- pkey = io.StringIO(ssh_key_pair["private_key"])
+ pkey = io.StringIO(get_plain_text(ssh_key_pair["private_key"]))
ssh_string = "%s@%s" % (user, self.host.ip_addr)
ssh = paramiko.SSHClient()
diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py
index d90997b24..d143da84e 100644
--- a/monkey/infection_monkey/exploit/tools/smb_tools.py
+++ b/monkey/infection_monkey/exploit/tools/smb_tools.py
@@ -8,7 +8,9 @@ from typing import Optional
from impacket.dcerpc.v5 import srvs, transport
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.smbconnection import SMB_DIALECT, SMBConnection
+from pydantic import SecretStr
+from common.credentials.credentials import get_plain_text
from common.utils.attack_utils import ScanStatus
from infection_monkey.network.tools import get_interface_to_target
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
@@ -26,8 +28,8 @@ class SmbTools(object):
host,
agent_file: BytesIO,
dst_path: PurePath,
- username,
- password,
+ username: str,
+ password: SecretStr,
lm_hash="",
ntlm_hash="",
timeout=30,
@@ -190,7 +192,9 @@ class SmbTools(object):
return remote_full_path
@staticmethod
- def new_smb_connection(host, username, password, lm_hash="", ntlm_hash="", timeout=30):
+ def new_smb_connection(
+ host, username: str, password: SecretStr, lm_hash="", ntlm_hash="", timeout=30
+ ):
try:
smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
except Exception as exc:
@@ -212,7 +216,13 @@ class SmbTools(object):
# we know this should work because the WMI connection worked
try:
- smb.login(username, password, "", lm_hash, ntlm_hash)
+ smb.login(
+ username,
+ get_plain_text(password),
+ "",
+ get_plain_text(lm_hash),
+ get_plain_text(ntlm_hash),
+ )
except Exception as exc:
logger.error(f'Error while logging into {host} using user "{username}": {exc}')
return None, dialect
diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py
index cdbb3b63f..e79d18d25 100644
--- a/monkey/infection_monkey/exploit/wmiexec.py
+++ b/monkey/infection_monkey/exploit/wmiexec.py
@@ -5,6 +5,7 @@ import traceback
from impacket.dcerpc.v5.rpcrt import DCERPCException
+from common.credentials.credentials import get_plain_text
from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
from infection_monkey.exploit.tools.smb_tools import SmbTools
@@ -44,7 +45,14 @@ class WmiExploiter(HostExploiter):
wmi_connection = WmiTools.WmiConnection()
try:
- wmi_connection.connect(self.host, user, password, None, lm_hash, ntlm_hash)
+ wmi_connection.connect(
+ self.host,
+ user,
+ get_plain_text(password),
+ None,
+ get_plain_text(lm_hash),
+ get_plain_text(ntlm_hash),
+ )
except AccessDeniedException:
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
logger.debug(f"Failed connecting to {self.host} using WMI")
diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py
index f43a61069..de19956c8 100644
--- a/monkey/infection_monkey/exploit/zerologon.py
+++ b/monkey/infection_monkey/exploit/zerologon.py
@@ -299,8 +299,8 @@ class ZerologonExploiter(HostExploiter):
self, user: str, lmhash: str, nthash: str
) -> None:
extracted_credentials = [
- Credentials(Username(user), LMHash(lmhash)),
- Credentials(Username(user), NTHash(nthash)),
+ Credentials(identity=Username(username=user), secret=LMHash(lm_hash=lmhash)),
+ Credentials(identity=Username(username=user), secret=NTHash(nt_hash=nthash)),
]
self.telemetry_messenger.send_telemetry(CredentialsTelem(extracted_credentials))
diff --git a/monkey/tests/data_for_tests/propagation_credentials.py b/monkey/tests/data_for_tests/propagation_credentials.py
index a1d783f78..a23334e14 100644
--- a/monkey/tests/data_for_tests/propagation_credentials.py
+++ b/monkey/tests/data_for_tests/propagation_credentials.py
@@ -1,16 +1,18 @@
from itertools import product
+from pydantic import SecretStr
+
from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username
USERNAME = "m0nk3y_user"
SPECIAL_USERNAME = "m0nk3y.user"
-NT_HASH = "C1C58F96CDF212B50837BC11A00BE47C"
-LM_HASH = "299BD128C1101FD6299BD128C1101FD6"
-PASSWORD_1 = "trytostealthis"
-PASSWORD_2 = "password!"
-PASSWORD_3 = "rubberbabybuggybumpers"
+NT_HASH = SecretStr("C1C58F96CDF212B50837BC11A00BE47C")
+LM_HASH = SecretStr("299BD128C1101FD6299BD128C1101FD6")
+PASSWORD_1 = SecretStr("trytostealthis")
+PASSWORD_2 = SecretStr("password!")
+PASSWORD_3 = SecretStr("rubberbabybuggybumpers")
PUBLIC_KEY = "MY_PUBLIC_KEY"
-PRIVATE_KEY = "MY_PRIVATE_KEY"
+PRIVATE_KEY = SecretStr("MY_PRIVATE_KEY")
IDENTITIES = [Username(username=USERNAME), None, Username(username=SPECIAL_USERNAME)]
IDENTITY_DICTS = [{"username": USERNAME}, None]
diff --git a/monkey/tests/unit_tests/common/credentials/test_credentials.py b/monkey/tests/unit_tests/common/credentials/test_credentials.py
index 89238b839..30f27bbdb 100644
--- a/monkey/tests/unit_tests/common/credentials/test_credentials.py
+++ b/monkey/tests/unit_tests/common/credentials/test_credentials.py
@@ -1,6 +1,11 @@
+import logging
+
import pytest
+from pydantic import SecretBytes
+from pydantic.types import SecretStr
from tests.data_for_tests.propagation_credentials import CREDENTIALS, CREDENTIALS_DICTS
+from common.base_models import InfectionMonkeyBaseModel
from common.credentials import Credentials
@@ -12,3 +17,28 @@ def test_credentials_serialization_json(credentials, expected_credentials_dict):
deserialized_credentials = Credentials.parse_raw(serialized_credentials)
assert credentials == deserialized_credentials
+
+
+logger = logging.getLogger()
+logger.level = logging.DEBUG
+
+
+def test_credentials_secrets_not_logged(caplog):
+ class TestSecret(InfectionMonkeyBaseModel):
+ some_secret: SecretStr
+ some_secret_in_bytes: SecretBytes
+
+ class TestCredentials(Credentials):
+ secret: TestSecret
+
+ sensitive = "super_secret"
+ creds = TestCredentials(
+ identity=None,
+ secret=TestSecret(some_secret=sensitive, some_secret_in_bytes=sensitive.encode()),
+ )
+
+ logging.getLogger().info(
+ f"{creds.secret.some_secret} and" f" {creds.secret.some_secret_in_bytes}"
+ )
+
+ assert sensitive not in caplog.text
diff --git a/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py
index 5ff9547a0..c6ae98f11 100644
--- a/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py
+++ b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py
@@ -1,6 +1,7 @@
from unittest.mock import MagicMock
import pytest
+from pydantic import SecretStr
from tests.data_for_tests.propagation_credentials import (
CREDENTIALS,
LM_HASH,
@@ -30,14 +31,14 @@ EMPTY_CHANNEL_CREDENTIALS = []
STOLEN_USERNAME_1 = "user1"
STOLEN_USERNAME_2 = "user2"
STOLEN_USERNAME_3 = "user3"
-STOLEN_PASSWORD_1 = "abcdefg"
-STOLEN_PASSWORD_2 = "super_secret"
+STOLEN_PASSWORD_1 = SecretStr("abcdefg")
+STOLEN_PASSWORD_2 = SecretStr("super_secret")
STOLEN_PUBLIC_KEY_1 = "some_public_key_1"
STOLEN_PUBLIC_KEY_2 = "some_public_key_2"
-STOLEN_LM_HASH = "AAD3B435B51404EEAAD3B435B51404EE"
-STOLEN_NT_HASH = "C0172DFF622FE29B5327CB79DC12D24C"
-STOLEN_PRIVATE_KEY_1 = "some_private_key_1"
-STOLEN_PRIVATE_KEY_2 = "some_private_key_2"
+STOLEN_LM_HASH = SecretStr("AAD3B435B51404EEAAD3B435B51404EE")
+STOLEN_NT_HASH = SecretStr("C0172DFF622FE29B5327CB79DC12D24C")
+STOLEN_PRIVATE_KEY_1 = SecretStr("some_private_key_1")
+STOLEN_PRIVATE_KEY_2 = SecretStr("some_private_key_2")
STOLEN_CREDENTIALS = [
Credentials(
identity=Username(username=STOLEN_USERNAME_1),
diff --git a/vulture_allowlist.py b/vulture_allowlist.py
index e6550b8f9..caf7c3e24 100644
--- a/vulture_allowlist.py
+++ b/vulture_allowlist.py
@@ -7,7 +7,7 @@ from common.agent_configuration.agent_sub_configurations import (
CustomPBAConfiguration,
ScanTargetConfiguration,
)
-from common.credentials import LMHash, NTHash
+from common.credentials import Credentials, LMHash, NTHash
from infection_monkey.exploit.log4shell_utils.ldap_server import LDAPServerFactory
from monkey_island.cc.event_queue import IslandEventTopic, PyPubSubIslandEventQueue
from monkey_island.cc.models import Report
@@ -167,6 +167,7 @@ strict_slashes # unused attribute (monkey/monkey_island/cc/app.py:96)
post_breach_actions # unused variable (monkey\infection_monkey\config.py:95)
LMHash.validate_hash_format
NTHash.validate_hash_format
+Credentials.Config.json_encoders
# Deployments
DEVELOP # unused variable (monkey/monkey/monkey_island/cc/deployment.py:5)
From dc8e644bc5140720645b0130bbba600691f3925a Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Tue, 6 Sep 2022 14:30:41 +0000
Subject: [PATCH 23/29] UT: Fix some of powershell UT's
---
.../powershell_utils/test_powershell_client.py | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_powershell_client.py b/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_powershell_client.py
index 73f7ea64c..2d6d1788f 100644
--- a/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_powershell_client.py
+++ b/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_powershell_client.py
@@ -1,4 +1,5 @@
import pytest
+from pydantic import SecretStr
from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType
from infection_monkey.exploit.powershell_utils.powershell_client import format_password
@@ -14,17 +15,17 @@ def test_format_cached_credentials():
def test_format_password():
- expected = "test_password"
+ expected = SecretStr("test_password")
creds = Credentials("test_user", expected, SecretType.PASSWORD)
actual = format_password(creds)
- assert expected == actual
+ assert expected.get_secret_value() == actual
def test_format_lm_hash():
- lm_hash = "c080132b6f2a0c4e5d1029cc06f48a92"
- expected = f"{lm_hash}:00000000000000000000000000000000"
+ lm_hash = SecretStr("c080132b6f2a0c4e5d1029cc06f48a92")
+ expected = f"{lm_hash.get_secret_value()}:00000000000000000000000000000000"
creds = Credentials("test_user", lm_hash, SecretType.LM_HASH)
@@ -34,8 +35,8 @@ def test_format_lm_hash():
def test_format_nt_hash():
- nt_hash = "c080132b6f2a0c4e5d1029cc06f48a92"
- expected = f"00000000000000000000000000000000:{nt_hash}"
+ nt_hash = SecretStr("c080132b6f2a0c4e5d1029cc06f48a92")
+ expected = f"00000000000000000000000000000000:{nt_hash.get_secret_value()}"
creds = Credentials("test_user", nt_hash, SecretType.NT_HASH)
From 6e254c71d7ef594a0e66c67be8cdc4a14d72143c Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Wed, 7 Sep 2022 08:15:12 +0000
Subject: [PATCH 24/29] Common: Fix failing UT and add more for get_plain_text
---
monkey/common/credentials/credentials.py | 2 +-
.../data_for_tests/propagation_credentials.py | 12 +++++---
.../common/credentials/test_credentials.py | 30 ++++++++++++++++++-
3 files changed, 38 insertions(+), 6 deletions(-)
diff --git a/monkey/common/credentials/credentials.py b/monkey/common/credentials/credentials.py
index ae8de63d1..d924a2562 100644
--- a/monkey/common/credentials/credentials.py
+++ b/monkey/common/credentials/credentials.py
@@ -12,7 +12,7 @@ Identity = Username
def get_plain_text(secret: Union[SecretStr, SecretBytes, None, str]) -> Optional[str]:
- if secret:
+ if isinstance(secret, (SecretStr, SecretBytes)):
return secret.get_secret_value()
else:
return secret
diff --git a/monkey/tests/data_for_tests/propagation_credentials.py b/monkey/tests/data_for_tests/propagation_credentials.py
index a23334e14..35521e7b6 100644
--- a/monkey/tests/data_for_tests/propagation_credentials.py
+++ b/monkey/tests/data_for_tests/propagation_credentials.py
@@ -6,13 +6,17 @@ from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair
USERNAME = "m0nk3y_user"
SPECIAL_USERNAME = "m0nk3y.user"
-NT_HASH = SecretStr("C1C58F96CDF212B50837BC11A00BE47C")
-LM_HASH = SecretStr("299BD128C1101FD6299BD128C1101FD6")
-PASSWORD_1 = SecretStr("trytostealthis")
+PLAINTEXT_NT_HASH = "C1C58F96CDF212B50837BC11A00BE47C"
+PLAINTEXT_LM_HASH = "299BD128C1101FD6299BD128C1101FD6"
+PLAINTEXT_PASSWORD = "trytostealthis"
+PLAINTEXT_PRIVATE_KEY = "MY_PRIVATE_KEY"
+NT_HASH = SecretStr(PLAINTEXT_NT_HASH)
+LM_HASH = SecretStr(PLAINTEXT_LM_HASH)
+PASSWORD_1 = SecretStr(PLAINTEXT_PASSWORD)
PASSWORD_2 = SecretStr("password!")
PASSWORD_3 = SecretStr("rubberbabybuggybumpers")
PUBLIC_KEY = "MY_PUBLIC_KEY"
-PRIVATE_KEY = SecretStr("MY_PRIVATE_KEY")
+PRIVATE_KEY = SecretStr(PLAINTEXT_PRIVATE_KEY)
IDENTITIES = [Username(username=USERNAME), None, Username(username=SPECIAL_USERNAME)]
IDENTITY_DICTS = [{"username": USERNAME}, None]
diff --git a/monkey/tests/unit_tests/common/credentials/test_credentials.py b/monkey/tests/unit_tests/common/credentials/test_credentials.py
index 30f27bbdb..79b727a15 100644
--- a/monkey/tests/unit_tests/common/credentials/test_credentials.py
+++ b/monkey/tests/unit_tests/common/credentials/test_credentials.py
@@ -1,12 +1,23 @@
import logging
+from pathlib import Path
import pytest
from pydantic import SecretBytes
from pydantic.types import SecretStr
-from tests.data_for_tests.propagation_credentials import CREDENTIALS, CREDENTIALS_DICTS
+from tests.data_for_tests.propagation_credentials import (
+ CREDENTIALS,
+ CREDENTIALS_DICTS,
+ LM_HASH,
+ PASSWORD_1,
+ PLAINTEXT_LM_HASH,
+ PLAINTEXT_PASSWORD,
+ PLAINTEXT_PRIVATE_KEY,
+ PRIVATE_KEY,
+)
from common.base_models import InfectionMonkeyBaseModel
from common.credentials import Credentials
+from common.credentials.credentials import get_plain_text
@pytest.mark.parametrize(
@@ -42,3 +53,20 @@ def test_credentials_secrets_not_logged(caplog):
)
assert sensitive not in caplog.text
+
+
+_plaintext = [
+ PLAINTEXT_PASSWORD,
+ PLAINTEXT_PRIVATE_KEY,
+ PLAINTEXT_LM_HASH,
+ "",
+ "already_plaintext",
+ Path("C:\\jolly_fella"),
+ None,
+]
+_hidden = [PASSWORD_1, PRIVATE_KEY, LM_HASH, "", "already_plaintext", Path("C:\\jolly_fella"), None]
+
+
+@pytest.mark.parametrize("expected, hidden", list(zip(_plaintext, _hidden)))
+def test_get_plain_text(expected, hidden):
+ assert expected == get_plain_text(hidden)
From 5608bb3f11031997f50724bdae0facdefb80f31b Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Wed, 7 Sep 2022 08:27:27 +0000
Subject: [PATCH 25/29] Common, Agent: Rename get_plain_text to get_plaintext
---
monkey/common/credentials/credentials.py | 2 +-
monkey/infection_monkey/exploit/mssqlexec.py | 4 ++--
.../exploit/powershell_utils/powershell_client.py | 4 ++--
monkey/infection_monkey/exploit/smbexec.py | 8 ++++----
monkey/infection_monkey/exploit/sshexec.py | 4 ++--
monkey/infection_monkey/exploit/tools/smb_tools.py | 8 ++++----
monkey/infection_monkey/exploit/wmiexec.py | 8 ++++----
.../unit_tests/common/credentials/test_credentials.py | 4 ++--
8 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/monkey/common/credentials/credentials.py b/monkey/common/credentials/credentials.py
index d924a2562..09d869876 100644
--- a/monkey/common/credentials/credentials.py
+++ b/monkey/common/credentials/credentials.py
@@ -11,7 +11,7 @@ Secret = Union[Password, LMHash, NTHash, SSHKeypair]
Identity = Username
-def get_plain_text(secret: Union[SecretStr, SecretBytes, None, str]) -> Optional[str]:
+def get_plaintext(secret: Union[SecretStr, SecretBytes, None, str]) -> Optional[str]:
if isinstance(secret, (SecretStr, SecretBytes)):
return secret.get_secret_value()
else:
diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py
index b1d700945..b037c782a 100644
--- a/monkey/infection_monkey/exploit/mssqlexec.py
+++ b/monkey/infection_monkey/exploit/mssqlexec.py
@@ -6,7 +6,7 @@ from typing import Sequence, Tuple
import pymssql
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
-from common.credentials.credentials import get_plain_text
+from common.credentials.credentials import get_plaintext
from common.utils.exceptions import FailedExploitationError
from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
@@ -112,7 +112,7 @@ class MSSQLExploiter(HostExploiter):
conn = pymssql.connect(
host,
user,
- get_plain_text(password),
+ get_plaintext(password),
port=port,
login_timeout=self.LOGIN_TIMEOUT,
timeout=self.QUERY_TIMEOUT,
diff --git a/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py b/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py
index 8113026e7..0ea71c6f1 100644
--- a/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py
+++ b/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py
@@ -11,7 +11,7 @@ from pypsrp.powershell import PowerShell, RunspacePool
from typing_extensions import Protocol
from urllib3 import connectionpool
-from common.credentials.credentials import get_plain_text
+from common.credentials.credentials import get_plaintext
from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions
from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType
@@ -43,7 +43,7 @@ def format_password(credentials: Credentials) -> Optional[str]:
if credentials.secret_type == SecretType.CACHED:
return None
- plaintext_secret = get_plain_text(credentials.secret)
+ plaintext_secret = get_plaintext(credentials.secret)
if credentials.secret_type == SecretType.PASSWORD:
return plaintext_secret
diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py
index 778a4fb36..272f150eb 100644
--- a/monkey/infection_monkey/exploit/smbexec.py
+++ b/monkey/infection_monkey/exploit/smbexec.py
@@ -4,7 +4,7 @@ from impacket.dcerpc.v5 import scmr, transport
from impacket.dcerpc.v5.scmr import DCERPCSessionError
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
-from common.credentials.credentials import get_plain_text
+from common.credentials.credentials import get_plaintext
from common.utils.attack_utils import ScanStatus, UsageEnum
from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
@@ -110,10 +110,10 @@ class SMBExploiter(HostExploiter):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(
user,
- get_plain_text(password),
+ get_plaintext(password),
"",
- get_plain_text(lm_hash),
- get_plain_text(ntlm_hash),
+ get_plaintext(lm_hash),
+ get_plaintext(ntlm_hash),
None,
)
rpctransport.set_kerberos(SMBExploiter.USE_KERBEROS)
diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py
index ab96794a2..69b29c813 100644
--- a/monkey/infection_monkey/exploit/sshexec.py
+++ b/monkey/infection_monkey/exploit/sshexec.py
@@ -5,7 +5,7 @@ from pathlib import PurePath
import paramiko
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
-from common.credentials.credentials import get_plain_text
+from common.credentials.credentials import get_plaintext
from common.utils import Timer
from common.utils.attack_utils import ScanStatus
from common.utils.exceptions import FailedExploitationError
@@ -60,7 +60,7 @@ class SSHExploiter(HostExploiter):
for user, ssh_key_pair in ssh_key_pairs_iterator:
# Creating file-like private key for paramiko
- pkey = io.StringIO(get_plain_text(ssh_key_pair["private_key"]))
+ pkey = io.StringIO(get_plaintext(ssh_key_pair["private_key"]))
ssh_string = "%s@%s" % (user, self.host.ip_addr)
ssh = paramiko.SSHClient()
diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py
index d143da84e..c9ac254e1 100644
--- a/monkey/infection_monkey/exploit/tools/smb_tools.py
+++ b/monkey/infection_monkey/exploit/tools/smb_tools.py
@@ -10,7 +10,7 @@ from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.smbconnection import SMB_DIALECT, SMBConnection
from pydantic import SecretStr
-from common.credentials.credentials import get_plain_text
+from common.credentials.credentials import get_plaintext
from common.utils.attack_utils import ScanStatus
from infection_monkey.network.tools import get_interface_to_target
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
@@ -218,10 +218,10 @@ class SmbTools(object):
try:
smb.login(
username,
- get_plain_text(password),
+ get_plaintext(password),
"",
- get_plain_text(lm_hash),
- get_plain_text(ntlm_hash),
+ get_plaintext(lm_hash),
+ get_plaintext(ntlm_hash),
)
except Exception as exc:
logger.error(f'Error while logging into {host} using user "{username}": {exc}')
diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py
index e79d18d25..6c5b189f7 100644
--- a/monkey/infection_monkey/exploit/wmiexec.py
+++ b/monkey/infection_monkey/exploit/wmiexec.py
@@ -5,7 +5,7 @@ import traceback
from impacket.dcerpc.v5.rpcrt import DCERPCException
-from common.credentials.credentials import get_plain_text
+from common.credentials.credentials import get_plaintext
from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
from infection_monkey.exploit.tools.smb_tools import SmbTools
@@ -48,10 +48,10 @@ class WmiExploiter(HostExploiter):
wmi_connection.connect(
self.host,
user,
- get_plain_text(password),
+ get_plaintext(password),
None,
- get_plain_text(lm_hash),
- get_plain_text(ntlm_hash),
+ get_plaintext(lm_hash),
+ get_plaintext(ntlm_hash),
)
except AccessDeniedException:
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
diff --git a/monkey/tests/unit_tests/common/credentials/test_credentials.py b/monkey/tests/unit_tests/common/credentials/test_credentials.py
index 79b727a15..09d68a4c3 100644
--- a/monkey/tests/unit_tests/common/credentials/test_credentials.py
+++ b/monkey/tests/unit_tests/common/credentials/test_credentials.py
@@ -17,7 +17,7 @@ from tests.data_for_tests.propagation_credentials import (
from common.base_models import InfectionMonkeyBaseModel
from common.credentials import Credentials
-from common.credentials.credentials import get_plain_text
+from common.credentials.credentials import get_plaintext
@pytest.mark.parametrize(
@@ -69,4 +69,4 @@ _hidden = [PASSWORD_1, PRIVATE_KEY, LM_HASH, "", "already_plaintext", Path("C:\\
@pytest.mark.parametrize("expected, hidden", list(zip(_plaintext, _hidden)))
def test_get_plain_text(expected, hidden):
- assert expected == get_plain_text(hidden)
+ assert expected == get_plaintext(hidden)
From c8f131e6b0f8b1b46baac2d2494bdebc11832b4b Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Wed, 7 Sep 2022 08:45:26 +0000
Subject: [PATCH 26/29] Common: Small style improvements in credentials.py
---
monkey/common/credentials/credentials.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/monkey/common/credentials/credentials.py b/monkey/common/credentials/credentials.py
index 09d869876..dcd011d01 100644
--- a/monkey/common/credentials/credentials.py
+++ b/monkey/common/credentials/credentials.py
@@ -19,7 +19,7 @@ def get_plaintext(secret: Union[SecretStr, SecretBytes, None, str]) -> Optional[
class Credentials(InfectionMonkeyBaseModel):
- """Represents a credential pair (some form of identity and a secret)"""
+ """Represents a credential pair (an identity and a secret)"""
identity: Optional[Identity]
"""Identity part of credentials, like a username or an email"""
@@ -30,6 +30,6 @@ class Credentials(InfectionMonkeyBaseModel):
class Config:
json_encoders = {
# This makes secrets dumpable to json, but not loggable
- SecretStr: lambda v: v.get_secret_value() if v else None,
- SecretBytes: lambda v: v.get_secret_value() if v else None,
+ SecretStr: get_plaintext,
+ SecretBytes: get_plaintext,
}
From 1fc07185546d6ec301e3252b5c875445a7210a3d Mon Sep 17 00:00:00 2001
From: Mike Salvatore
Date: Wed, 7 Sep 2022 07:50:39 -0400
Subject: [PATCH 27/29] Agent: Use isinstance() to check if identity is
Username
---
.../aggregating_propagation_credentials_repository.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py b/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py
index cffa2b277..f7c86c30d 100644
--- a/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py
+++ b/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py
@@ -45,7 +45,7 @@ class AggregatingPropagationCredentialsRepository(IPropagationCredentialsReposit
self._add_secret(credentials.secret)
def _add_identity(self, identity: Identity):
- if type(identity) == Username:
+ if isinstance(identity, Username):
self._stored_credentials.setdefault("exploit_user_list", set()).add(identity.username)
def _add_secret(self, secret: Secret):
From adca1683c8e9ea52d033098f14adc528c036b63f Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Wed, 7 Sep 2022 12:27:54 +0000
Subject: [PATCH 28/29] UI: Improve style in ReformatHook.js
---
.../src/components/configuration-components/ReformatHook.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js
index 5792a82ba..3b8546614 100644
--- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js
+++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js
@@ -78,9 +78,9 @@ export function formatCredentialsForIsland(credentials) {
})
}
- formattedCredentials.push(...getFormattedCredentials(credentials['exploit_password_list'], 'password'))
- formattedCredentials.push(...getFormattedCredentials(credentials['exploit_ntlm_hash_list'], 'nt_hash'))
- formattedCredentials.push(...getFormattedCredentials(credentials['exploit_lm_hash_list'], 'lm_hash'))
+ formattedCredentials.push(...getFormattedCredentials(credentials['exploit_password_list'], SecretTypes.Password))
+ formattedCredentials.push(...getFormattedCredentials(credentials['exploit_ntlm_hash_list'], SecretTypes.NTHash))
+ formattedCredentials.push(...getFormattedCredentials(credentials['exploit_lm_hash_list'], SecretTypes.LMHash))
let ssh_keys = credentials['exploit_ssh_keys'];
for (let i = 0; i < ssh_keys.length; i++) {
From d7dde14cae3fbfa885185f58d582ca01ce0bf135 Mon Sep 17 00:00:00 2001
From: vakaris_zilius
Date: Wed, 7 Sep 2022 13:00:45 +0000
Subject: [PATCH 29/29] Agent, Island: Update pydantic version to 1.10.2 from
1.9.2
---
monkey/infection_monkey/Pipfile.lock | 337 ++++++++++----------
monkey/monkey_island/Pipfile.lock | 461 ++++++++++++++-------------
2 files changed, 405 insertions(+), 393 deletions(-)
diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock
index 4bc8564ce..36879a9ca 100644
--- a/monkey/infection_monkey/Pipfile.lock
+++ b/monkey/infection_monkey/Pipfile.lock
@@ -18,11 +18,11 @@
"default": {
"aiosmb": {
"hashes": [
- "sha256:0afa901093f0ad91d0b8421dec66c80bd2e9cb237a8da405984413a5d7475398",
- "sha256:0e98390ba00fdc4190e698f184dfcf72b02b592cdfe9274e03cc7316ac4ee368"
+ "sha256:2668d63ae6e6ca30a999696c444d4afc349f85a872a204994394aa6abbf5673d",
+ "sha256:b0b0e7068e757b8d3a8e4be2ebf2b499933d9fa7853bc3a8e198d6a57dc77131"
],
"markers": "python_version >= '3.7'",
- "version": "==0.3.8"
+ "version": "==0.4.0"
},
"aiowinreg": {
"hashes": [
@@ -46,13 +46,21 @@
],
"version": "==1.5.1"
},
+ "asyauth": {
+ "hashes": [
+ "sha256:a7a7998dcfa672ebae9837ba4996df237412a7f335c0f8291bb14091d0e6d19a",
+ "sha256:f20c497b02d17f25298d3701fb65c9a337893b68a9c7dddecc09ab956b09f828"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==0.0.2"
+ },
"asysocks": {
"hashes": [
- "sha256:23d5fcfae71a75826c3ed787bd9b1bc3b189ec37658961bce83c9e99455e354c",
- "sha256:731eda25d41783c5243153d3cb4f9357fef337c7317135488afab9ecd6b7f1a1"
+ "sha256:8f4516088ebec7f08d8c549e5e75e7a86b41e6043af920ba4895cf3f6c654150",
+ "sha256:d4c619c9d2e8be0cbdd21fa635a7eaf5886809edc948e2f76625b33b3cccfc47"
],
"markers": "python_version >= '3.6'",
- "version": "==0.1.7"
+ "version": "==0.2.1"
},
"attrs": {
"hashes": [
@@ -71,20 +79,21 @@
},
"bcrypt": {
"hashes": [
- "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521",
- "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb",
- "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e",
- "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26",
- "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a",
- "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e",
- "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa",
- "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129",
- "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb",
- "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40",
- "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"
+ "sha256:0b0f0c7141622a31e9734b7f649451147c04ebb5122327ac0bd23744df84be90",
+ "sha256:1c3334446fac200499e8bc04a530ce3cf0b3d7151e0e4ac5c0dddd3d95e97843",
+ "sha256:2d0dd19aad87e4ab882ef1d12df505f4c52b28b69666ce83c528f42c07379227",
+ "sha256:594780b364fb45f2634c46ec8d3e61c1c0f1811c4f2da60e8eb15594ecbf93ed",
+ "sha256:7c7dd6c1f05bf89e65261d97ac3a6520f34c2acb369afb57e3ea4449be6ff8fd",
+ "sha256:845b1daf4df2dd94d2fdbc9454953ca9dd0e12970a0bfc9f3dcc6faea3fa96e4",
+ "sha256:8780e69f9deec9d60f947b169507d2c9816e4f11548f1f7ebee2af38b9b22ae4",
+ "sha256:bf413f2a9b0a2950fc750998899013f2e718d20fa4a58b85ca50b6df5ed1bbf9",
+ "sha256:bfb67f6a6c72dfb0a02f3df51550aa1862708e55128b22543e2b42c74f3620d7",
+ "sha256:c59c170fc9225faad04dde1ba61d85b413946e8ce2e5f5f5ff30dfd67283f319",
+ "sha256:dc6ec3dc19b1c193b2f7cf279d3e32e7caf447532fbcb7af0906fe4398900c33",
+ "sha256:ede0f506554571c8eda80db22b83c139303ec6b595b8f60c4c8157bdd0bdee36"
],
"markers": "python_version >= '3.6'",
- "version": "==3.2.2"
+ "version": "==4.0.0"
},
"certifi": {
"hashes": [
@@ -173,11 +182,11 @@
},
"charset-normalizer": {
"hashes": [
- "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5",
- "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"
+ "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
+ "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
],
"markers": "python_version >= '3.6'",
- "version": "==2.1.0"
+ "version": "==2.1.1"
},
"click": {
"hashes": [
@@ -204,47 +213,51 @@
},
"cryptography": {
"hashes": [
- "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59",
- "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596",
- "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3",
- "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5",
- "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab",
- "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884",
- "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82",
- "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b",
- "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441",
- "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa",
- "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d",
- "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b",
- "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a",
- "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6",
- "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157",
- "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280",
- "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282",
- "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67",
- "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8",
- "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046",
- "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327",
- "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"
+ "sha256:03363b92b6d76e4292ace708dcc8fd31b28ce325714f5af9f72afca6e7ac4070",
+ "sha256:144c9b09135caf6580fb8bf03594760a351dcb1bb6df3a4ed871527a90597e31",
+ "sha256:18253e7aeb7a6a12d1af7548a70349ab41da099addc0bfdc8f980c0a55624206",
+ "sha256:24d4137f3118900db02a2ec9a585d6dec2e79697fc90e84f19e5462dd1eeca44",
+ "sha256:2cbd0989e8b2bc7182f4eaa85145e1264a139da94b78adf9440be0d9ccce8898",
+ "sha256:3f1b0207dd0ec3167d5faec10d72368b7e1ba9eb2e3d54718963f6319254102b",
+ "sha256:634f38529aac216844df4df8632cdc1d7fff11c1ba2440c7299c38c1aa7a3e19",
+ "sha256:684134063c108b6a8af59c1869e09400a1c078336e79fa8cc1cbfb82e67d992a",
+ "sha256:7208cf3fa37e4c2d3ec9b49ac7721c457b793c64d548c4f7f2989ded0405f700",
+ "sha256:723581cbcfe89b83f86ed710b591520b6b148186b555dad2f84f717ad136f9e5",
+ "sha256:7ac685fe7c1103f5802e52a49f90f010dd768fd1cffd797fe01df2c674f3862a",
+ "sha256:7ed89815ea3483f92d6ca934ca8b2a8c35579453c1e2472a960a5086996981dd",
+ "sha256:88fbfc4703987ed17990a0a5b6f465447e8996cc4c4117db08afce9d0f75ff82",
+ "sha256:8fe3f1df20286d50dc5ab201cce4bf5c326e813d7fa39a62ed41bc01d46cd3ab",
+ "sha256:959cce516bacb775fb52ff122f26e23178c76ca708d68dc353e177cb7ee6f167",
+ "sha256:aaba2a47f0ce5c795d7caf46ff9c9ecc5d42b8ca5cecd11c0bf03d3df4626f9c",
+ "sha256:b224d7e73676a3ea27cd1a726591c7984db32ceb63a801e11d80669eb9964f37",
+ "sha256:b69f3f316711567f03eab907713a2c076f43fc84f92a6d7ce5ffc8c26e6709bc",
+ "sha256:c27e541b532d13a15fe969977c55753d0f187696280ba073273efb0c733e522b",
+ "sha256:cec962ac6db460a22ac78c1e316c7238b6979a9e369b9b1e3810af10c7445b1e",
+ "sha256:d2700d1593e5a03bc32d1a9e9fda9b136bf7e67e7ae4aeefcfeac5a5090c200e",
+ "sha256:d89a685ce8dd2c6869e9b9b884c9e0944cc74d67ca53a5bf4cd69f262fca0c3f",
+ "sha256:db58dcd824d67bc7f9735f3b0f402add8b33687994f0e822cbe16868d68aa2a1",
+ "sha256:ebe9e75f9a759e1019bdd2a823b51c8d4002bed006d60c025e3d6b9dff33ebaa",
+ "sha256:efa101918a6b332e1db0d3c69085217eb754b85124ab8ca94bba9b1e8d079f84",
+ "sha256:ff8709fcd3e49c41bc3da5720ab39882b76af8d15e268807e6d906f8c3a252fc"
],
"markers": "python_version >= '3.6'",
- "version": "==37.0.4"
+ "version": "==38.0.0"
},
"dnspython": {
"hashes": [
"sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e",
"sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"
],
- "markers": "python_version >= '3.6' and python_version < '4'",
+ "markers": "python_version >= '3.6' and python_version < '4.0'",
"version": "==2.2.1"
},
"flask": {
"hashes": [
- "sha256:3c604c48c3d5b4c63e72134044c0b4fe90ff01ef65280b9fe2d38c8860d99fe5",
- "sha256:9c2b81b9b1edcc835af72d600f1955e713a065e7cb41d7e51ee762b449d9c65d"
+ "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b",
+ "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"
],
"markers": "python_version >= '3.7'",
- "version": "==2.2.1"
+ "version": "==2.2.2"
},
"future": {
"hashes": [
@@ -388,11 +401,11 @@
},
"marshmallow": {
"hashes": [
- "sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb",
- "sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7"
+ "sha256:1172ce82765bf26c24a3f9299ed6dbeeca4d213f638eaa39a37772656d7ce408",
+ "sha256:48e2d88d4ab431ad5a17c25556d9da529ea6e966876f2a38d274082e270287f0"
],
"index": "pypi",
- "version": "==3.17.0"
+ "version": "==3.17.1"
},
"marshmallow-enum": {
"hashes": [
@@ -412,19 +425,19 @@
},
"minikerberos": {
"hashes": [
- "sha256:3c383f67ebcf6f28105ed54f623a6a5c677a24e3f0c9ad69ed453f77e569d714",
- "sha256:789f802263fa1882f701b123f6eec048b45cd731bf1b528870005daf07402047"
+ "sha256:c05cfcd846b1973b2b0d501e9e9fa2a263543d7762b052fc803fc1de849286a3",
+ "sha256:e912eb4bea899e1875707e7998001ed1047e1b32d5d7bf74d8b6137acf9614d3"
],
"markers": "python_version >= '3.6'",
- "version": "==0.2.20"
+ "version": "==0.3.1"
},
"msldap": {
"hashes": [
- "sha256:ac8174ed7e0162eb64b3e9dfeff13b2e1021612d0a4b2cfc6b8e5bed7c00ffe0",
- "sha256:e2c22a6e396b4d7d65d73863ed44612120e8e2570ff895b5421ddf6a350085bb"
+ "sha256:236eacd04b0d2886e71b2890ec6c67fc626e1b9812c93a0fe21e998697415927",
+ "sha256:ccb5c1f40de165141931659eb71d4bbad326665aaff7bf23dd0dccb410dfa78c"
],
"markers": "python_version >= '3.7'",
- "version": "==0.3.40"
+ "version": "==0.4.0"
},
"netifaces": {
"hashes": [
@@ -506,49 +519,49 @@
},
"prompt-toolkit": {
"hashes": [
- "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0",
- "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289"
+ "sha256:9696f386133df0fc8ca5af4895afe5d78f5fcfe5258111c2a79a1c3e41ffa96d",
+ "sha256:9ada952c9d1787f52ff6d5f3484d0b4df8952787c087edf6a1f7c2cb1ea88148"
],
"markers": "python_full_version >= '3.6.2'",
- "version": "==3.0.30"
+ "version": "==3.0.31"
},
"psutil": {
"hashes": [
- "sha256:068935df39055bf27a29824b95c801c7a5130f118b806eee663cad28dca97685",
- "sha256:0904727e0b0a038830b019551cf3204dd48ef5c6868adc776e06e93d615fc5fc",
- "sha256:0f15a19a05f39a09327345bc279c1ba4a8cfb0172cc0d3c7f7d16c813b2e7d36",
- "sha256:19f36c16012ba9cfc742604df189f2f28d2720e23ff7d1e81602dbe066be9fd1",
- "sha256:20b27771b077dcaa0de1de3ad52d22538fe101f9946d6dc7869e6f694f079329",
- "sha256:28976df6c64ddd6320d281128817f32c29b539a52bdae5e192537bc338a9ec81",
- "sha256:29a442e25fab1f4d05e2655bb1b8ab6887981838d22effa2396d584b740194de",
- "sha256:3054e923204b8e9c23a55b23b6df73a8089ae1d075cb0bf711d3e9da1724ded4",
- "sha256:32c52611756096ae91f5d1499fe6c53b86f4a9ada147ee42db4991ba1520e574",
- "sha256:3a76ad658641172d9c6e593de6fe248ddde825b5866464c3b2ee26c35da9d237",
- "sha256:44d1826150d49ffd62035785a9e2c56afcea66e55b43b8b630d7706276e87f22",
- "sha256:4b6750a73a9c4a4e689490ccb862d53c7b976a2a35c4e1846d049dcc3f17d83b",
- "sha256:56960b9e8edcca1456f8c86a196f0c3d8e3e361320071c93378d41445ffd28b0",
- "sha256:57f1819b5d9e95cdfb0c881a8a5b7d542ed0b7c522d575706a80bedc848c8954",
- "sha256:58678bbadae12e0db55186dc58f2888839228ac9f41cc7848853539b70490021",
- "sha256:645bd4f7bb5b8633803e0b6746ff1628724668681a434482546887d22c7a9537",
- "sha256:799759d809c31aab5fe4579e50addf84565e71c1dc9f1c31258f159ff70d3f87",
- "sha256:79c9108d9aa7fa6fba6e668b61b82facc067a6b81517cab34d07a84aa89f3df0",
- "sha256:91c7ff2a40c373d0cc9121d54bc5f31c4fa09c346528e6a08d1845bce5771ffc",
- "sha256:9272167b5f5fbfe16945be3db475b3ce8d792386907e673a209da686176552af",
- "sha256:944c4b4b82dc4a1b805329c980f270f170fdc9945464223f2ec8e57563139cf4",
- "sha256:a6a11e48cb93a5fa606306493f439b4aa7c56cb03fc9ace7f6bfa21aaf07c453",
- "sha256:a8746bfe4e8f659528c5c7e9af5090c5a7d252f32b2e859c584ef7d8efb1e689",
- "sha256:abd9246e4cdd5b554a2ddd97c157e292ac11ef3e7af25ac56b08b455c829dca8",
- "sha256:b14ee12da9338f5e5b3a3ef7ca58b3cba30f5b66f7662159762932e6d0b8f680",
- "sha256:b88f75005586131276634027f4219d06e0561292be8bd6bc7f2f00bdabd63c4e",
- "sha256:c7be9d7f5b0d206f0bbc3794b8e16fb7dbc53ec9e40bbe8787c6f2d38efcf6c9",
- "sha256:d2d006286fbcb60f0b391741f520862e9b69f4019b4d738a2a45728c7e952f1b",
- "sha256:db417f0865f90bdc07fa30e1aadc69b6f4cad7f86324b02aa842034efe8d8c4d",
- "sha256:e7e10454cb1ab62cc6ce776e1c135a64045a11ec4c6d254d3f7689c16eb3efd2",
- "sha256:f65f9a46d984b8cd9b3750c2bdb419b2996895b005aefa6cbaba9a143b1ce2c5",
- "sha256:fea896b54f3a4ae6f790ac1d017101252c93f6fe075d0e7571543510f11d2676"
+ "sha256:14b29f581b5edab1f133563272a6011925401804d52d603c5c606936b49c8b97",
+ "sha256:256098b4f6ffea6441eb54ab3eb64db9ecef18f6a80d7ba91549195d55420f84",
+ "sha256:39ec06dc6c934fb53df10c1672e299145ce609ff0611b569e75a88f313634969",
+ "sha256:404f4816c16a2fcc4eaa36d7eb49a66df2d083e829d3e39ee8759a411dbc9ecf",
+ "sha256:42638876b7f5ef43cef8dcf640d3401b27a51ee3fa137cb2aa2e72e188414c32",
+ "sha256:4642fd93785a29353d6917a23e2ac6177308ef5e8be5cc17008d885cb9f70f12",
+ "sha256:4fb54941aac044a61db9d8eb56fc5bee207db3bc58645d657249030e15ba3727",
+ "sha256:561dec454853846d1dd0247b44c2e66a0a0c490f937086930ec4b8f83bf44f06",
+ "sha256:5d39e3a2d5c40efa977c9a8dd4f679763c43c6c255b1340a56489955dbca767c",
+ "sha256:614337922702e9be37a39954d67fdb9e855981624d8011a9927b8f2d3c9625d9",
+ "sha256:67b33f27fc0427483b61563a16c90d9f3b547eeb7af0ef1b9fe024cdc9b3a6ea",
+ "sha256:68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8",
+ "sha256:7cbb795dcd8ed8fd238bc9e9f64ab188f3f4096d2e811b5a82da53d164b84c3f",
+ "sha256:8f024fbb26c8daf5d70287bb3edfafa22283c255287cf523c5d81721e8e5d82c",
+ "sha256:91aa0dac0c64688667b4285fa29354acfb3e834e1fd98b535b9986c883c2ce1d",
+ "sha256:94e621c6a4ddb2573d4d30cba074f6d1aa0186645917df42c811c473dd22b339",
+ "sha256:9770c1d25aee91417eba7869139d629d6328a9422ce1cdd112bd56377ca98444",
+ "sha256:b1928b9bf478d31fdffdb57101d18f9b70ed4e9b0e41af751851813547b2a9ab",
+ "sha256:b2f248ffc346f4f4f0d747ee1947963613216b06688be0be2e393986fe20dbbb",
+ "sha256:b315febaebae813326296872fdb4be92ad3ce10d1d742a6b0c49fb619481ed0b",
+ "sha256:b3591616fa07b15050b2f87e1cdefd06a554382e72866fcc0ab2be9d116486c8",
+ "sha256:b4018d5f9b6651f9896c7a7c2c9f4652e4eea53f10751c4e7d08a9093ab587ec",
+ "sha256:d75291912b945a7351d45df682f9644540d564d62115d4a20d45fa17dc2d48f8",
+ "sha256:dc9bda7d5ced744622f157cc8d8bdd51735dafcecff807e928ff26bdb0ff097d",
+ "sha256:e3ac2c0375ef498e74b9b4ec56df3c88be43fe56cac465627572dbfb21c4be34",
+ "sha256:e4c4a7636ffc47b7141864f1c5e7d649f42c54e49da2dd3cceb1c5f5d29bfc85",
+ "sha256:ed29ea0b9a372c5188cdb2ad39f937900a10fb5478dc077283bf86eeac678ef1",
+ "sha256:f40ba362fefc11d6bea4403f070078d60053ed422255bd838cd86a40674364c9",
+ "sha256:f4cb67215c10d4657e320037109939b1c1d2fd70ca3d76301992f89fe2edb1f1",
+ "sha256:f7929a516125f62399d6e8e026129c8835f6c5a3aab88c3fff1a05ee8feb840d",
+ "sha256:fd331866628d18223a4265371fd255774affd86244fc307ef66eaf00de0633d5",
+ "sha256:feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c"
],
"index": "pypi",
- "version": "==5.9.1"
+ "version": "==5.9.2"
},
"pyasn1": {
"hashes": [
@@ -632,44 +645,45 @@
},
"pydantic": {
"hashes": [
- "sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44",
- "sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d",
- "sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84",
- "sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555",
- "sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7",
- "sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131",
- "sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8",
- "sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3",
- "sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56",
- "sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0",
- "sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4",
- "sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453",
- "sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044",
- "sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e",
- "sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15",
- "sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb",
- "sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001",
- "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d",
- "sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3",
- "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e",
- "sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f",
- "sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c",
- "sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b",
- "sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8",
- "sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567",
- "sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979",
- "sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326",
- "sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb",
- "sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f",
- "sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa",
- "sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747",
- "sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801",
- "sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55",
- "sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08",
- "sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76"
+ "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42",
+ "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624",
+ "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e",
+ "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559",
+ "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709",
+ "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9",
+ "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d",
+ "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52",
+ "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda",
+ "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912",
+ "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c",
+ "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525",
+ "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe",
+ "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41",
+ "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b",
+ "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283",
+ "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965",
+ "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c",
+ "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410",
+ "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5",
+ "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116",
+ "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98",
+ "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f",
+ "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644",
+ "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13",
+ "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd",
+ "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254",
+ "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6",
+ "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488",
+ "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5",
+ "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c",
+ "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1",
+ "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a",
+ "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2",
+ "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d",
+ "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"
],
"index": "pypi",
- "version": "==1.9.2"
+ "version": "==1.10.2"
},
"pyinstaller": {
"hashes": [
@@ -690,11 +704,11 @@
},
"pyinstaller-hooks-contrib": {
"hashes": [
- "sha256:c4210fc50282c9c6a918e485e0bfae9405592390508e3be9fde19acc2213da56",
- "sha256:e46f099934dd4577fb1ddcf37a99fa04027c92f8f5291c8802f326345988d001"
+ "sha256:d1dd6ea059dc30e77813cc12a5efa8b1d228e7da8f5b884fe11775f946db1784",
+ "sha256:e5edd4094175e78c178ef987b61be19efff6caa23d266ade456fc753e847f62e"
],
"markers": "python_version >= '3.7'",
- "version": "==2022.8"
+ "version": "==2022.10"
},
"pymssql": {
"hashes": [
@@ -795,21 +809,19 @@
},
"pyspnego": {
"hashes": [
- "sha256:1ee612f20c843365fbc6cf7f95c526b4ee8795281641a9bb87083622a2f87939",
- "sha256:284ca7a6218344bb90aeae02fb1d2ed73e5c991d6e4c16c0df404aeab5eb58a3",
- "sha256:416fd2d67e82b44ba3d2d9062485056e4dde3c141630170e9190379d6b19972a",
- "sha256:4c1be83e0aca12d64f5eec638259c77eaa8bf552c89ac69f0af2322a3be9afeb",
- "sha256:4d1ea987b9c2539457235793014e0d9c5e4766da9e4e028d4b6b596cfbe53828",
- "sha256:725df2030e5d1b1155bb696eca3d684f403164da8e6a6b0bee3eb02f8748f72b",
- "sha256:7320539f45da463029e12f3742102085d2a0343bfe77ac550c11d2fdac1d34f5",
- "sha256:77b86002082f278c3f5935d8b428a0d0659ea709e305537294ba95fc49907339",
- "sha256:aa93d94775d01bf70d16840426d1ddd58c11a6a71c4d0d1d7e529ad541fa0a60",
- "sha256:c2abca03b6d3c71d7ec9678c7b2220d99d9a29ef204b4c52549080169e586310",
- "sha256:e6645107f200fb7bf6698512d04ea0790b292028861ce169eb97e5ad8eba14ed",
- "sha256:f4784d9f8e9c50a36109d715a140150add1990fce16805a56217e8ccaf69d234"
+ "sha256:15cd6d3fc4d18b4b7f80259bfab1580c87dc9677d47e7cf801dad71dc23d1afc",
+ "sha256:210a2248060a2d789a333f7553a1a478d21812f675c507393169143cbf038d9b",
+ "sha256:4e967f29c099c196cbf4622587cd27e8c61f20adf78b1d3007b72596e60c9f23",
+ "sha256:4fab51afb757be21d543ddf78aaeb83db600a7e7daec773568db90d4b7499a2c",
+ "sha256:53d30afbef1255cb1a8930c14604184b07f989b6ac295a1397eac8c27fd59d8b",
+ "sha256:838f875ee55004a274f6470460e62b7713237ae8b66a02680a2f31e43b3b5387",
+ "sha256:b78a3370ace76209a52dc7816636a8c8437e323637eefe86a2193cc4ec352b3b",
+ "sha256:e08709c4e0838bf37d4ef8ceff2163a51abe2b071e285bb5774de5b73eab214f",
+ "sha256:ea8570c5363e5dd359aaf599eac6f70116e0ada734ebe557e17cc608c8bb93fc",
+ "sha256:fa2946ba5059f79d13cb8c47e83474de55569c16ed8f953cc47a24dda6f38f57"
],
- "markers": "python_version >= '3.6'",
- "version": "==0.5.3"
+ "markers": "python_version >= '3.7'",
+ "version": "==0.6.0"
},
"pywin32": {
"hashes": [
@@ -858,11 +870,11 @@
},
"setuptools": {
"hashes": [
- "sha256:7c7854ee1429a240090297628dc9f75b35318d193537968e2dc14010ee2f5bca",
- "sha256:dc2662692f47d99cb8ae15a784529adeed535bcd7c277fee0beccf961522baf6"
+ "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82",
+ "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"
],
"markers": "python_version >= '3.7'",
- "version": "==63.4.1"
+ "version": "==65.3.0"
},
"six": {
"hashes": [
@@ -874,11 +886,11 @@
},
"tqdm": {
"hashes": [
- "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d",
- "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6"
+ "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4",
+ "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==4.64.0"
+ "version": "==4.64.1"
},
"twisted": {
"extras": [
@@ -919,11 +931,11 @@
},
"unicrypto": {
"hashes": [
- "sha256:0487f9dd9009c326ee9531a79412ae18ad673425a1c800d64947b96fdeb04cdf",
- "sha256:fab49ee41926bb31be49552aa135f7aedc04436b49c8fe326d7b6a823810575e"
+ "sha256:4d1de0f0a379bb4c213302ae61d927eb8f98179bde9a0ffb8e120998a0c882a6",
+ "sha256:9d5dd858ad5ad608fa524987b17e8855d64d6d2450ca0ca11638f4d92fc6c80b"
],
"markers": "python_version >= '3.6'",
- "version": "==0.0.8"
+ "version": "==0.0.9"
},
"urllib3": {
"hashes": [
@@ -942,11 +954,11 @@
},
"werkzeug": {
"hashes": [
- "sha256:4d7013ef96fd197d1cdeb03e066c6c5a491ccb44758a5b2b91137319383e5a5a",
- "sha256:7e1db6a5ba6b9a8be061e47e900456355b8714c0f238b0313f53afce1a55a79a"
+ "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f",
+ "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"
],
"markers": "python_version >= '3.7'",
- "version": "==2.2.1"
+ "version": "==2.2.2"
},
"winacl": {
"hashes": [
@@ -956,14 +968,6 @@
"markers": "python_version >= '3.6'",
"version": "==0.1.3"
},
- "winsspi": {
- "hashes": [
- "sha256:2f5a8d2c4b9f459144426909e26a74e550512e23b6cf9af52c2a00003c7c3fdb",
- "sha256:59b7c7595f91414528cfd80c6cfc77ec6f5e4e28185ebd6418f8368ddc7aca82"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==0.0.10"
- },
"winsys-3.x": {
"hashes": [
"sha256:cef3df1dce2a5a71efa46446e6007ad9f7dbae31e83ffcc2ea3485c00c914cc3"
@@ -976,6 +980,7 @@
"sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942",
"sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6"
],
+ "index": "pypi",
"markers": "sys_platform == 'win32'",
"version": "==1.5.1"
},
diff --git a/monkey/monkey_island/Pipfile.lock b/monkey/monkey_island/Pipfile.lock
index c5c7d2c47..237a1f133 100644
--- a/monkey/monkey_island/Pipfile.lock
+++ b/monkey/monkey_island/Pipfile.lock
@@ -150,11 +150,11 @@
},
"charset-normalizer": {
"hashes": [
- "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5",
- "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"
+ "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
+ "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
],
"markers": "python_version >= '3.6'",
- "version": "==2.1.0"
+ "version": "==2.1.1"
},
"click": {
"hashes": [
@@ -174,31 +174,35 @@
},
"cryptography": {
"hashes": [
- "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59",
- "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596",
- "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3",
- "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5",
- "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab",
- "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884",
- "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82",
- "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b",
- "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441",
- "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa",
- "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d",
- "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b",
- "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a",
- "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6",
- "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157",
- "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280",
- "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282",
- "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67",
- "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8",
- "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046",
- "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327",
- "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"
+ "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a",
+ "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f",
+ "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0",
+ "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407",
+ "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7",
+ "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6",
+ "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153",
+ "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750",
+ "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad",
+ "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6",
+ "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b",
+ "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5",
+ "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a",
+ "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d",
+ "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d",
+ "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294",
+ "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0",
+ "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a",
+ "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac",
+ "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61",
+ "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013",
+ "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e",
+ "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb",
+ "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9",
+ "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd",
+ "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"
],
"index": "pypi",
- "version": "==37.0.4"
+ "version": "==38.0.1"
},
"dpath": {
"hashes": [
@@ -210,19 +214,19 @@
},
"flask": {
"hashes": [
- "sha256:3c604c48c3d5b4c63e72134044c0b4fe90ff01ef65280b9fe2d38c8860d99fe5",
- "sha256:9c2b81b9b1edcc835af72d600f1955e713a065e7cb41d7e51ee762b449d9c65d"
+ "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b",
+ "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"
],
"index": "pypi",
- "version": "==2.2.1"
+ "version": "==2.2.2"
},
"flask-jwt-extended": {
"hashes": [
- "sha256:188907ea9332bdd123a95a457e7487556770480264ce3b78c8835b4347e324cc",
- "sha256:a2571df484c5635ad996d364242ec28fc69f386915cd69b1842639712b84c36d"
+ "sha256:62b521d75494c290a646ae8acc77123721e4364790f1e64af0038d823961fbf0",
+ "sha256:a85eebfa17c339a7260c4643475af444784ba6de5588adda67406f0a75599553"
],
"index": "pypi",
- "version": "==4.4.3"
+ "version": "==4.4.4"
},
"flask-pymongo": {
"hashes": [
@@ -288,64 +292,63 @@
},
"greenlet": {
"hashes": [
- "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3",
- "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711",
- "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd",
- "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073",
- "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708",
- "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67",
- "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23",
- "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1",
- "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08",
- "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd",
- "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2",
- "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa",
- "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8",
- "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40",
- "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab",
- "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6",
- "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc",
- "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b",
- "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e",
- "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963",
- "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3",
- "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d",
- "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d",
- "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe",
- "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28",
- "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3",
- "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e",
- "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c",
- "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d",
- "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0",
- "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497",
- "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee",
- "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713",
- "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58",
- "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a",
- "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06",
- "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88",
- "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965",
- "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f",
- "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4",
- "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5",
- "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c",
- "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a",
- "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1",
- "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43",
- "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627",
- "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b",
- "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168",
- "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d",
- "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5",
- "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478",
- "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf",
- "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce",
- "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c",
- "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"
+ "sha256:0118817c9341ef2b0f75f5af79ac377e4da6ff637e5ee4ac91802c0e379dadb4",
+ "sha256:048d2bed76c2aa6de7af500ae0ea51dd2267aec0e0f2a436981159053d0bc7cc",
+ "sha256:07c58e169bbe1e87b8bbf15a5c1b779a7616df9fd3e61cadc9d691740015b4f8",
+ "sha256:095a980288fe05adf3d002fbb180c99bdcf0f930e220aa66fcd56e7914a38202",
+ "sha256:0b181e9aa6cb2f5ec0cacc8cee6e5a3093416c841ba32c185c30c160487f0380",
+ "sha256:1626185d938d7381631e48e6f7713e8d4b964be246073e1a1d15c2f061ac9f08",
+ "sha256:184416e481295832350a4bf731ba619a92f5689bf5d0fa4341e98b98b1265bd7",
+ "sha256:1dd51d2650e70c6c4af37f454737bf4a11e568945b27f74b471e8e2a9fd21268",
+ "sha256:1ec2779774d8e42ed0440cf8bc55540175187e8e934f2be25199bf4ed948cd9e",
+ "sha256:2cf45e339cabea16c07586306a31cfcc5a3b5e1626d365714d283732afed6809",
+ "sha256:2fb0aa7f6996879551fd67461d5d3ab0c3c0245da98be90c89fcb7a18d437403",
+ "sha256:44b4817c34c9272c65550b788913620f1fdc80362b209bc9d7dd2f40d8793080",
+ "sha256:466ce0928e33421ee84ae04c4ac6f253a3a3e6b8d600a79bd43fd4403e0a7a76",
+ "sha256:4f166b4aca8d7d489e82d74627a7069ab34211ef5ebb57c300ec4b9337b60fc0",
+ "sha256:510c3b15587afce9800198b4b142202b323bf4b4b5f9d6c79cb9a35e5e3c30d2",
+ "sha256:5b756e6730ea59b2745072e28ad27f4c837084688e6a6b3633c8b1e509e6ae0e",
+ "sha256:5fbe1ab72b998ca77ceabbae63a9b2e2dc2d963f4299b9b278252ddba142d3f1",
+ "sha256:6200a11f003ec26815f7e3d2ded01b43a3810be3528dd760d2f1fa777490c3cd",
+ "sha256:65ad1a7a463a2a6f863661329a944a5802c7129f7ad33583dcc11069c17e622c",
+ "sha256:694ffa7144fa5cc526c8f4512665003a39fa09ef00d19bbca5c8d3406db72fbe",
+ "sha256:6f5d4b2280ceea76c55c893827961ed0a6eadd5a584a7c4e6e6dd7bc10dfdd96",
+ "sha256:7532a46505470be30cbf1dbadb20379fb481244f1ca54207d7df3bf0bbab6a20",
+ "sha256:76a53bfa10b367ee734b95988bd82a9a5f0038a25030f9f23bbbc005010ca600",
+ "sha256:77e41db75f9958f2083e03e9dd39da12247b3430c92267df3af77c83d8ff9eed",
+ "sha256:7a43bbfa9b6cfdfaeefbd91038dde65ea2c421dc387ed171613df340650874f2",
+ "sha256:7b41d19c0cfe5c259fe6c539fd75051cd39a5d33d05482f885faf43f7f5e7d26",
+ "sha256:7c5227963409551ae4a6938beb70d56bf1918c554a287d3da6853526212fbe0a",
+ "sha256:870a48007872d12e95a996fca3c03a64290d3ea2e61076aa35d3b253cf34cd32",
+ "sha256:88b04e12c9b041a1e0bcb886fec709c488192638a9a7a3677513ac6ba81d8e79",
+ "sha256:8c287ae7ac921dfde88b1c125bd9590b7ec3c900c2d3db5197f1286e144e712b",
+ "sha256:903fa5716b8fbb21019268b44f73f3748c41d1a30d71b4a49c84b642c2fed5fa",
+ "sha256:9537e4baf0db67f382eb29255a03154fcd4984638303ff9baaa738b10371fa57",
+ "sha256:9951dcbd37850da32b2cb6e391f621c1ee456191c6ae5528af4a34afe357c30e",
+ "sha256:9b2f7d0408ddeb8ea1fd43d3db79a8cefaccadd2a812f021333b338ed6b10aba",
+ "sha256:9c88e134d51d5e82315a7c32b914a58751b7353eb5268dbd02eabf020b4c4700",
+ "sha256:9fae214f6c43cd47f7bef98c56919b9222481e833be2915f6857a1e9e8a15318",
+ "sha256:a3a669f11289a8995d24fbfc0e63f8289dd03c9aaa0cc8f1eab31d18ca61a382",
+ "sha256:aa741c1a8a8cc25eb3a3a01a62bdb5095a773d8c6a86470bde7f607a447e7905",
+ "sha256:b0877a9a2129a2c56a2eae2da016743db7d9d6a05d5e1c198f1b7808c602a30e",
+ "sha256:bcb6c6dd1d6be6d38d6db283747d07fda089ff8c559a835236560a4410340455",
+ "sha256:caff52cb5cd7626872d9696aee5b794abe172804beb7db52eed1fd5824b63910",
+ "sha256:cbc1eb55342cbac8f7ec159088d54e2cfdd5ddf61c87b8bbe682d113789331b2",
+ "sha256:cd16a89efe3a003029c87ff19e9fba635864e064da646bc749fc1908a4af18f3",
+ "sha256:ce5b64dfe8d0cca407d88b0ee619d80d4215a2612c1af8c98a92180e7109f4b5",
+ "sha256:d58a5a71c4c37354f9e0c24c9c8321f0185f6945ef027460b809f4bb474bfe41",
+ "sha256:db41f3845eb579b544c962864cce2c2a0257fe30f0f1e18e51b1e8cbb4e0ac6d",
+ "sha256:db5b25265010a1b3dca6a174a443a0ed4c4ab12d5e2883a11c97d6e6d59b12f9",
+ "sha256:dd0404d154084a371e6d2bafc787201612a1359c2dee688ae334f9118aa0bf47",
+ "sha256:de431765bd5fe62119e0bc6bc6e7b17ac53017ae1782acf88fcf6b7eae475a49",
+ "sha256:df02fdec0c533301497acb0bc0f27f479a3a63dcdc3a099ae33a902857f07477",
+ "sha256:e8533f5111704d75de3139bf0b8136d3a6c1642c55c067866fa0a51c2155ee33",
+ "sha256:f2f908239b7098799b8845e5936c2ccb91d8c2323be02e82f8dcb4a80dcf4a25",
+ "sha256:f8bfd36f368efe0ab2a6aa3db7f14598aac454b06849fb633b762ddbede1db90",
+ "sha256:ffe73f9e7aea404722058405ff24041e59d31ca23d1da0895af48050a07b6932"
],
"markers": "platform_python_implementation == 'CPython'",
- "version": "==1.1.2"
+ "version": "==1.1.3"
},
"idna": {
"hashes": [
@@ -451,11 +454,11 @@
},
"marshmallow": {
"hashes": [
- "sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb",
- "sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7"
+ "sha256:1172ce82765bf26c24a3f9299ed6dbeeca4d213f638eaa39a37772656d7ce408",
+ "sha256:48e2d88d4ab431ad5a17c25556d9da529ea6e966876f2a38d274082e270287f0"
],
"index": "pypi",
- "version": "==3.17.0"
+ "version": "==3.17.1"
},
"marshmallow-enum": {
"hashes": [
@@ -542,44 +545,45 @@
},
"pydantic": {
"hashes": [
- "sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44",
- "sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d",
- "sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84",
- "sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555",
- "sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7",
- "sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131",
- "sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8",
- "sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3",
- "sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56",
- "sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0",
- "sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4",
- "sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453",
- "sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044",
- "sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e",
- "sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15",
- "sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb",
- "sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001",
- "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d",
- "sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3",
- "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e",
- "sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f",
- "sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c",
- "sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b",
- "sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8",
- "sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567",
- "sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979",
- "sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326",
- "sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb",
- "sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f",
- "sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa",
- "sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747",
- "sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801",
- "sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55",
- "sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08",
- "sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76"
+ "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42",
+ "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624",
+ "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e",
+ "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559",
+ "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709",
+ "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9",
+ "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d",
+ "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52",
+ "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda",
+ "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912",
+ "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c",
+ "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525",
+ "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe",
+ "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41",
+ "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b",
+ "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283",
+ "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965",
+ "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c",
+ "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410",
+ "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5",
+ "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116",
+ "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98",
+ "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f",
+ "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644",
+ "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13",
+ "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd",
+ "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254",
+ "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6",
+ "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488",
+ "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5",
+ "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c",
+ "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1",
+ "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a",
+ "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2",
+ "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d",
+ "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"
],
"index": "pypi",
- "version": "==1.9.2"
+ "version": "==1.10.2"
},
"pyinstaller": {
"hashes": [
@@ -600,11 +604,11 @@
},
"pyinstaller-hooks-contrib": {
"hashes": [
- "sha256:c4210fc50282c9c6a918e485e0bfae9405592390508e3be9fde19acc2213da56",
- "sha256:e46f099934dd4577fb1ddcf37a99fa04027c92f8f5291c8802f326345988d001"
+ "sha256:d1dd6ea059dc30e77813cc12a5efa8b1d228e7da8f5b884fe11775f946db1784",
+ "sha256:e5edd4094175e78c178ef987b61be19efff6caa23d266ade456fc753e847f62e"
],
"markers": "python_version >= '3.7'",
- "version": "==2022.8"
+ "version": "==2022.10"
},
"pyjwt": {
"hashes": [
@@ -779,10 +783,10 @@
},
"pytz": {
"hashes": [
- "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7",
- "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"
+ "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197",
+ "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"
],
- "version": "==2022.1"
+ "version": "==2022.2.1"
},
"pywin32": {
"hashes": [
@@ -855,11 +859,11 @@
},
"setuptools": {
"hashes": [
- "sha256:7c7854ee1429a240090297628dc9f75b35318d193537968e2dc14010ee2f5bca",
- "sha256:dc2662692f47d99cb8ae15a784529adeed535bcd7c277fee0beccf961522baf6"
+ "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82",
+ "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"
],
"markers": "python_version >= '3.7'",
- "version": "==63.4.1"
+ "version": "==65.3.0"
},
"six": {
"hashes": [
@@ -874,24 +878,24 @@
"sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02",
"sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"
],
- "markers": "python_version >= '3.7'",
+ "markers": "python_version < '3.8'",
"version": "==4.3.0"
},
"urllib3": {
"hashes": [
- "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc",
- "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"
+ "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e",
+ "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'",
- "version": "==1.26.11"
+ "version": "==1.26.12"
},
"werkzeug": {
"hashes": [
- "sha256:4d7013ef96fd197d1cdeb03e066c6c5a491ccb44758a5b2b91137319383e5a5a",
- "sha256:7e1db6a5ba6b9a8be061e47e900456355b8714c0f238b0313f53afce1a55a79a"
+ "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f",
+ "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"
],
"index": "pypi",
- "version": "==2.2.1"
+ "version": "==2.2.2"
},
"wirerope": {
"hashes": [
@@ -980,13 +984,6 @@
],
"version": "==0.7.12"
},
- "atomicwrites": {
- "hashes": [
- "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"
- ],
- "markers": "sys_platform == 'win32'",
- "version": "==1.4.1"
- },
"attrs": {
"hashes": [
"sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6",
@@ -1000,7 +997,7 @@
"sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51",
"sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"
],
- "markers": "python_full_version >= '3.6.0'",
+ "markers": "python_version >= '3.6'",
"version": "==2.10.3"
},
"black": {
@@ -1042,11 +1039,11 @@
},
"charset-normalizer": {
"hashes": [
- "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5",
- "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"
+ "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
+ "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
],
"markers": "python_version >= '3.6'",
- "version": "==2.1.0"
+ "version": "==2.1.1"
},
"click": {
"hashes": [
@@ -1066,57 +1063,66 @@
},
"coverage": {
"hashes": [
- "sha256:0895ea6e6f7f9939166cc835df8fa4599e2d9b759b02d1521b574e13b859ac32",
- "sha256:0f211df2cba951ffcae210ee00e54921ab42e2b64e0bf2c0befc977377fb09b7",
- "sha256:147605e1702d996279bb3cc3b164f408698850011210d133a2cb96a73a2f7996",
- "sha256:24b04d305ea172ccb21bee5bacd559383cba2c6fcdef85b7701cf2de4188aa55",
- "sha256:25b7ec944f114f70803d6529394b64f8749e93cbfac0fe6c5ea1b7e6c14e8a46",
- "sha256:2b20286c2b726f94e766e86a3fddb7b7e37af5d0c635bdfa7e4399bc523563de",
- "sha256:2dff52b3e7f76ada36f82124703f4953186d9029d00d6287f17c68a75e2e6039",
- "sha256:2f8553878a24b00d5ab04b7a92a2af50409247ca5c4b7a2bf4eabe94ed20d3ee",
- "sha256:3def6791adf580d66f025223078dc84c64696a26f174131059ce8e91452584e1",
- "sha256:422fa44070b42fef9fb8dabd5af03861708cdd6deb69463adc2130b7bf81332f",
- "sha256:4f89d8e03c8a3757aae65570d14033e8edf192ee9298303db15955cadcff0c63",
- "sha256:5336e0352c0b12c7e72727d50ff02557005f79a0b8dcad9219c7c4940a930083",
- "sha256:54d8d0e073a7f238f0666d3c7c0d37469b2aa43311e4024c925ee14f5d5a1cbe",
- "sha256:5ef42e1db047ca42827a85e34abe973971c635f83aed49611b7f3ab49d0130f0",
- "sha256:5f65e5d3ff2d895dab76b1faca4586b970a99b5d4b24e9aafffc0ce94a6022d6",
- "sha256:6c3ccfe89c36f3e5b9837b9ee507472310164f352c9fe332120b764c9d60adbe",
- "sha256:6d0b48aff8e9720bdec315d67723f0babd936a7211dc5df453ddf76f89c59933",
- "sha256:6fe75dcfcb889b6800f072f2af5a331342d63d0c1b3d2bf0f7b4f6c353e8c9c0",
- "sha256:79419370d6a637cb18553ecb25228893966bd7935a9120fa454e7076f13b627c",
- "sha256:7bb00521ab4f99fdce2d5c05a91bddc0280f0afaee0e0a00425e28e209d4af07",
- "sha256:80db4a47a199c4563d4a25919ff29c97c87569130375beca3483b41ad5f698e8",
- "sha256:866ebf42b4c5dbafd64455b0a1cd5aa7b4837a894809413b930026c91e18090b",
- "sha256:8af6c26ba8df6338e57bedbf916d76bdae6308e57fc8f14397f03b5da8622b4e",
- "sha256:a13772c19619118903d65a91f1d5fea84be494d12fd406d06c849b00d31bf120",
- "sha256:a697977157adc052284a7160569b36a8bbec09db3c3220642e6323b47cec090f",
- "sha256:a9032f9b7d38bdf882ac9f66ebde3afb8145f0d4c24b2e600bc4c6304aafb87e",
- "sha256:b5e28db9199dd3833cc8a07fa6cf429a01227b5d429facb56eccd765050c26cd",
- "sha256:c77943ef768276b61c96a3eb854eba55633c7a3fddf0a79f82805f232326d33f",
- "sha256:d230d333b0be8042ac34808ad722eabba30036232e7a6fb3e317c49f61c93386",
- "sha256:d4548be38a1c810d79e097a38107b6bf2ff42151900e47d49635be69943763d8",
- "sha256:d4e7ced84a11c10160c0697a6cc0b214a5d7ab21dfec1cd46e89fbf77cc66fae",
- "sha256:d56f105592188ce7a797b2bd94b4a8cb2e36d5d9b0d8a1d2060ff2a71e6b9bbc",
- "sha256:d714af0bdba67739598849c9f18efdcc5a0412f4993914a0ec5ce0f1e864d783",
- "sha256:d774d9e97007b018a651eadc1b3970ed20237395527e22cbeb743d8e73e0563d",
- "sha256:e0524adb49c716ca763dbc1d27bedce36b14f33e6b8af6dba56886476b42957c",
- "sha256:e2618cb2cf5a7cc8d698306e42ebcacd02fb7ef8cfc18485c59394152c70be97",
- "sha256:e36750fbbc422c1c46c9d13b937ab437138b998fe74a635ec88989afb57a3978",
- "sha256:edfdabe7aa4f97ed2b9dd5dde52d2bb29cb466993bb9d612ddd10d0085a683cf",
- "sha256:f22325010d8824594820d6ce84fa830838f581a7fd86a9235f0d2ed6deb61e29",
- "sha256:f23876b018dfa5d3e98e96f5644b109090f16a4acb22064e0f06933663005d39",
- "sha256:f7bd0ffbcd03dc39490a1f40b2669cc414fae0c4e16b77bb26806a4d0b7d1452"
+ "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2",
+ "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820",
+ "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827",
+ "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3",
+ "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d",
+ "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145",
+ "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875",
+ "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2",
+ "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74",
+ "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f",
+ "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c",
+ "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973",
+ "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1",
+ "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782",
+ "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0",
+ "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760",
+ "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a",
+ "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3",
+ "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7",
+ "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a",
+ "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f",
+ "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e",
+ "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86",
+ "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa",
+ "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa",
+ "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796",
+ "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a",
+ "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928",
+ "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0",
+ "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac",
+ "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c",
+ "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685",
+ "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d",
+ "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e",
+ "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f",
+ "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558",
+ "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58",
+ "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781",
+ "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a",
+ "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa",
+ "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc",
+ "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892",
+ "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d",
+ "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817",
+ "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1",
+ "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c",
+ "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908",
+ "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19",
+ "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60",
+ "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"
],
"index": "pypi",
- "version": "==6.4.2"
+ "version": "==6.4.4"
},
"distlib": {
"hashes": [
- "sha256:a7f75737c70be3b25e2bee06288cec4e4c221de18455b2dd037fe2a795cab2fe",
- "sha256:b710088c59f06338ca514800ad795a132da19fda270e3ce4affc74abf955a26c"
+ "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46",
+ "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"
],
- "version": "==0.3.5"
+ "version": "==0.3.6"
},
"dlint": {
"hashes": [
@@ -1135,11 +1141,11 @@
},
"filelock": {
"hashes": [
- "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404",
- "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"
+ "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc",
+ "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"
],
"markers": "python_version >= '3.7'",
- "version": "==3.7.1"
+ "version": "==3.8.0"
},
"flake8": {
"hashes": [
@@ -1274,10 +1280,11 @@
},
"pathspec": {
"hashes": [
- "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
- "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"
+ "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93",
+ "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"
],
- "version": "==0.9.0"
+ "markers": "python_version >= '3.7'",
+ "version": "==0.10.1"
},
"platformdirs": {
"hashes": [
@@ -1292,7 +1299,7 @@
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
],
- "markers": "python_full_version >= '3.6.0'",
+ "markers": "python_version >= '3.6'",
"version": "==1.0.0"
},
"py": {
@@ -1321,11 +1328,11 @@
},
"pygments": {
"hashes": [
- "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb",
- "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"
+ "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1",
+ "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"
],
- "markers": "python_full_version >= '3.6.0'",
- "version": "==2.12.0"
+ "markers": "python_version >= '3.6'",
+ "version": "==2.13.0"
},
"pyparsing": {
"hashes": [
@@ -1337,11 +1344,11 @@
},
"pytest": {
"hashes": [
- "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c",
- "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"
+ "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7",
+ "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"
],
"index": "pypi",
- "version": "==7.1.2"
+ "version": "==7.1.3"
},
"pytest-cov": {
"hashes": [
@@ -1353,10 +1360,10 @@
},
"pytz": {
"hashes": [
- "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7",
- "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"
+ "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197",
+ "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"
],
- "version": "==2022.1"
+ "version": "==2022.2.1"
},
"requests": {
"hashes": [
@@ -1382,11 +1389,11 @@
},
"setuptools": {
"hashes": [
- "sha256:7c7854ee1429a240090297628dc9f75b35318d193537968e2dc14010ee2f5bca",
- "sha256:dc2662692f47d99cb8ae15a784529adeed535bcd7c277fee0beccf961522baf6"
+ "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82",
+ "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"
],
"markers": "python_version >= '3.7'",
- "version": "==63.4.1"
+ "version": "==65.3.0"
},
"six": {
"hashes": [
@@ -1448,7 +1455,7 @@
"sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07",
"sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"
],
- "markers": "python_full_version >= '3.6.0'",
+ "markers": "python_version >= '3.6'",
"version": "==2.0.0"
},
"sphinxcontrib-jsmath": {
@@ -1493,11 +1500,11 @@
},
"tqdm": {
"hashes": [
- "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d",
- "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6"
+ "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4",
+ "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1"
],
"index": "pypi",
- "version": "==4.64.0"
+ "version": "==4.64.1"
},
"typed-ast": {
"hashes": [
@@ -1534,16 +1541,16 @@
"sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02",
"sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"
],
- "markers": "python_version >= '3.7'",
+ "markers": "python_version < '3.8'",
"version": "==4.3.0"
},
"urllib3": {
"hashes": [
- "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc",
- "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"
+ "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e",
+ "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'",
- "version": "==1.26.11"
+ "version": "==1.26.12"
},
"virtualenv": {
"hashes": [