Skip to content

Commit

Permalink
Merge branch 'main' into ianhelle/lazy-import-init-2023-09-26
Browse files Browse the repository at this point in the history
  • Loading branch information
ianhelle authored Oct 6, 2023
2 parents 55c932a + df22c8b commit fb4987a
Show file tree
Hide file tree
Showing 20 changed files with 605 additions and 53 deletions.
3 changes: 2 additions & 1 deletion docs/source/getting_started/Installing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -331,11 +331,12 @@ Windows Source to Isolated Linux Environment
<https://github.com/microsoft/msticpy/blob/main/tools/download_python_package.py>`__ script.

Example:

.. code-block:: powershell
python \path\to\python\file --python-version "3.8.5" --module-name "msticpy[sentinel]" --module-version "2.7.0" --directory \path\to\destination
3. Zip and copy the directory folder to the isolated environment.
3. Copy the directory folder to the isolated environment.

4. From the isolated environment, unzip if needed and then you will need to run the following for each .whl file:

Expand Down
2 changes: 1 addition & 1 deletion msticpy/_version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
"""Version file."""
VERSION = "2.8.0.pre1"
VERSION = "2.8.0"
96 changes: 96 additions & 0 deletions msticpy/common/data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,99 @@ def _get_dot_attrib(obj, elem_path: str) -> Any:
if cur_node is None:
raise KeyError(f"{elem} value of {elem_path} is not a valid path")
return cur_node


# Descriptors


class SharedProperty:
"""Descriptor to share property between instances of a class."""

def __init__(self, instance_name: str):
"""
Initialize the descriptor.
Parameters
----------
instance_name : str
Name of the instance attribute to use to store the value.
"""
self.instance_name = instance_name

def __get__(self, instance, owner):
"""Return the value of the instance attribute."""
return getattr(instance, self.instance_name, None)

def __set__(self, instance, value):
"""Set the value of the instance attribute."""
setattr(instance, self.instance_name, value)


class FallbackProperty:
"""Descriptor for aliased property with fallback property."""

def __init__(self, instance_name: str, default_name: str):
"""
Initialize the descriptor.
Parameters
----------
instance_name : str
Name of the instance attribute to use to store the value.
default_name : str
Name of the instance attribute to use as a fallback value.
"""
self.instance_name = instance_name
self.default_name = default_name

def __get__(self, instance, owner):
"""Return the value of the instance (or default) attribute."""
return getattr(
instance, self.instance_name, getattr(instance, self.default_name, None)
)

def __set__(self, instance, value):
"""Set the value of the instance attribute."""
setattr(instance, self.instance_name, value)


class SplitProperty:
"""Descriptor for property that is stored as two delimited attributes."""

def __init__(self, inst_left_name: str, inst_right_name: str, split_char: str):
"""
Initialize the descriptor.
Parameters
----------
inst_left_name : str
Name of the instance attribute to use to store the left value.
inst_right_name : str
Name of the instance attribute to use to store the right value.
split_char : str
Character to use to split the value.
"""
self.inst_left_name = inst_left_name
self.inst_right_name = inst_right_name
self.split_char = split_char

def __get__(self, instance, owner):
"""Return the value of the instance attribute."""
left_part = getattr(instance, self.inst_left_name, None)
right_part = getattr(instance, self.inst_right_name, None)
if left_part and right_part:
return f"{left_part}{self.split_char}{right_part}"
return None

def __set__(self, instance, value):
"""Set the value of the instance attribute."""
if value is None:
return
if self.split_char not in value:
setattr(instance, self.inst_left_name, value)
return
left, right = value.split(self.split_char, maxsplit=1)
setattr(instance, self.inst_left_name, left)
setattr(instance, self.inst_right_name, right)
27 changes: 22 additions & 5 deletions msticpy/common/wsconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,17 +248,34 @@ def from_settings(cls, settings: Dict[str, Any]) -> "WorkspaceConfig":
@classmethod
def from_connection_string(cls, connection_str: str) -> "WorkspaceConfig":
"""Create a WorkstationConfig from a connection string."""
tenant_regex = r".*tenant\(\s?['\"](?P<tenant_id>[\w]+)['\"].*"
workspace_regex = r".*workspace\(\s?['\"](?P<workspace_id>[\w]+)['\"].*"
tenant_id = workspace_id = None
if match := re.match(tenant_regex, connection_str):
tenant_regex = r"""
.*tenant\s?=\s?['\"]\{?
(?P<tenant_id>[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})
\}?['\"].*"""
workspace_regex = r"""
.*workspace\s?=\s?['\"]\{?
(?P<workspace_id>[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})
\}?['\"].*"""
ws_name_regex = r".*alias\s?=\s?['\"]\{?(?P<workspace_name>\w+)['\"].*"

tenant_id = workspace_id = workspace_name = None
if match := re.match(tenant_regex, connection_str, re.IGNORECASE | re.VERBOSE):
tenant_id = match.groupdict()["tenant_id"]
if match := re.match(workspace_regex, connection_str):
else:
raise ValueError("Could not find tenant ID in connection string.")
if match := re.match(
workspace_regex, connection_str, re.IGNORECASE | re.VERBOSE
):
workspace_id = match.groupdict()["workspace_id"]
else:
raise ValueError("Could not find workspace ID in connection string.")
if match := re.match(ws_name_regex, connection_str, re.IGNORECASE | re.VERBOSE):
workspace_name = match.groupdict()["workspace_name"]
return cls(
config={
cls.CONF_WS_ID_KEY: workspace_id, # type: ignore[dict-item]
cls.CONF_TENANT_ID_KEY: tenant_id, # type: ignore[dict-item]
cls.CONF_WS_NAME_KEY: workspace_name, # type: ignore[dict-item]
}
)

Expand Down
10 changes: 6 additions & 4 deletions msticpy/data/drivers/azure_monitor_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
azure/monitor-query-readme?view=azure-python
"""
import contextlib
import logging
from datetime import datetime
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union, cast
Expand Down Expand Up @@ -409,10 +410,11 @@ def _get_workspaces(self, connection_str: Optional[str] = None, **kwargs):
)
elif isinstance(connection_str, str):
self._def_connection_str = connection_str
ws_config = WorkspaceConfig.from_connection_string(connection_str)
logger.info(
"WorkspaceConfig created from connection_str %s", connection_str
)
with contextlib.suppress(ValueError):
ws_config = WorkspaceConfig.from_connection_string(connection_str)
logger.info(
"WorkspaceConfig created from connection_str %s", connection_str
)
elif isinstance(connection_str, WorkspaceConfig):
logger.info("WorkspaceConfig as parameter %s", connection_str.workspace_id)
ws_config = connection_str
Expand Down
2 changes: 1 addition & 1 deletion msticpy/data/drivers/mordor_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ def _to_datetime(date_val) -> datetime:
DS_PREFIX = "https://raw.githubusercontent.com/OTRF/Security-Datasets/master/datasets/"


# pylint: disable=not-an-iterable, no-member
# pylint: disable=not-an-iterable, no-member, too-many-instance-attributes


@attr.s(auto_attribs=True)
Expand Down
94 changes: 64 additions & 30 deletions msticpy/datamodel/entities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# --------------------------------------------------------------------------
"""Entity sub-package."""
import difflib
from typing import List

from .account import Account
from .alert import Alert
Expand All @@ -29,92 +30,125 @@
from .mail_cluster import MailCluster
from .mail_message import MailMessage
from .mailbox import Mailbox
from .mailbox_configuration import MailboxConfiguration
from .malware import Malware
from .network_connection import NetworkConnection
from .oauth_application import OAuthApplication
from .process import Process
from .registry_key import RegistryKey
from .registry_value import RegistryValue
from .security_group import SecurityGroup
from .service_principal import ServicePrincipal
from .submission_mail import SubmissionMail
from .threat_intelligence import Threatintelligence
from .unknown_entity import UnknownEntity
from .url import Url

from ..soc.incident import Incident # isort: skip


# Defender class equivalents
class User(Account):
"""Alias for Account."""


class Ip(IpAddress):
"""Alias for IpAddress."""


class Machine(Host):
"""Alias for Host."""


# Dictionary to map text names of types to the class.
Entity.ENTITY_NAME_MAP.update(
{
"account": Account,
"azureresource": AzureResource,
"alert": Alert,
"alerts": Alert,
"azure-resource": AzureResource,
"host": Host,
"process": Process,
"file": File,
"cloudapplication": CloudApplication,
"azureresource": AzureResource,
"cloud-application": CloudApplication,
"cloud-logon-session": CloudLogonSession,
"cloudapplication": CloudApplication,
"cloudlogonsession": CloudLogonSession,
"dns": Dns,
"dnsresolve": Dns,
"ipaddress": IpAddress,
"file": File,
"filehash": FileHash,
"geolocation": GeoLocation,
"host-logon-session": HostLogonSession,
"host": Host,
"hostlogonsession": HostLogonSession,
"incident": Incident,
"iotdevice": IoTDevice,
"ip": IpAddress,
"networkconnection": NetworkConnection,
"network-connection": NetworkConnection,
"mailbox": Mailbox,
"mail-message": MailMessage,
"mailmessage": MailMessage,
"ipaddress": IpAddress,
"location": GeoLocation,
"machine": Machine,
"mail-cluster": MailCluster,
"mail-message": MailMessage,
"mailbox": Mailbox,
"mailcluster": MailCluster,
"mailboxconfiguration": MailboxConfiguration,
"mailmessage": MailMessage,
"malware": Malware,
"network-connection": NetworkConnection,
"networkconnection": NetworkConnection,
"oauthapplication": OAuthApplication,
"process": Process,
"registry-key": RegistryKey,
"registrykey": RegistryKey,
"registry-value": RegistryValue,
"registrykey": RegistryKey,
"registryvalue": RegistryValue,
"host-logon-session": HostLogonSession,
"hostlogonsession": HostLogonSession,
"filehash": FileHash,
"security-group": SecurityGroup,
"securitygroup": SecurityGroup,
"ServicePrincipal": ServicePrincipal,
"SubmissionMail": SubmissionMail,
"alerts": Alert,
"alert": Alert,
"threatintelligence": Threatintelligence,
"url": Url,
"unknown": UnknownEntity,
"geolocation": GeoLocation,
"location": GeoLocation,
"incident": Incident,
"cloud-logon-session": CloudLogonSession,
"url": Url,
"user": User,
}
)


def find_entity(entity):
"""Find entity name."""
entity_cf = entity.casefold()
entity_classes = {
entity_cls_dict = {
cls.__name__.casefold(): cls for cls in Entity.ENTITY_NAME_MAP.values()
}
if entity_cf in Entity.ENTITY_NAME_MAP:
print(f"Match found '{Entity.ENTITY_NAME_MAP[entity].__name__}'")
return Entity.ENTITY_NAME_MAP[entity]
if entity_cf in entity_classes:
print(f"Match found '{entity_classes[entity_cf].__name__}'")
return entity_classes[entity_cf]
if entity_cf in entity_cls_dict:
print(f"Match found '{entity_cls_dict[entity_cf].__name__}'")
return entity_cls_dict[entity_cf]
# Try to find the closest matches
closest = difflib.get_close_matches(entity, entity_classes.keys(), cutoff=0.4)
closest = difflib.get_close_matches(entity, entity_cls_dict.keys(), cutoff=0.4)
mssg = [f"No exact match found for '{entity}'. "]
if len(closest) == 1:
mssg.append(f"Closest match is '{entity_classes[closest[0]].__name__}'")
mssg.append(f"Closest match is '{entity_cls_dict[closest[0]].__name__}'")
elif closest:
match_list = [f"'{entity_classes[mtch].__name__}'" for mtch in closest]
match_list = [f"'{entity_cls_dict[match].__name__}'" for match in closest]
mssg.append(f"Closest matches are {', '.join(match_list)}")
else:
mssg.extend(
[
"No close match found. Entities available:",
*(cls.__name__ for cls in entity_classes.values()),
*(cls.__name__ for cls in entity_cls_dict.values()),
]
)
print("\n".join(mssg))
return None


def list_entities() -> List[str]:
"""List entities."""
return sorted([cls.__name__ for cls in set(Entity.ENTITY_NAME_MAP.values())])


def entity_classes() -> List[type]:
"""Return a list of all entity classes."""
return list(set(Entity.ENTITY_NAME_MAP.values()))
Loading

0 comments on commit fb4987a

Please sign in to comment.