Skip to content

Commit

Permalink
Adds user management module (#198)
Browse files Browse the repository at this point in the history
  • Loading branch information
natalie363 authored Aug 13, 2024
1 parent 14e9ec4 commit 73b853d
Show file tree
Hide file tree
Showing 17 changed files with 590 additions and 424 deletions.
39 changes: 31 additions & 8 deletions caracara/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@
from caracara_filters import FQLGenerator
from caracara.common.interpolation import VariableInterpolator
from caracara.common.meta import user_agent_string
from caracara.common.module import ModuleMapper
from caracara.modules import (
CustomIoaApiModule,
FlightControlApiModule,
HostsApiModule,
PreventionPoliciesApiModule,
ResponsePoliciesApiModule,
RTRApiModule,
SensorDownloadApiModule,
SensorUpdatePoliciesApiModule,
UsersApiModule,
)
Expand Down Expand Up @@ -146,23 +148,44 @@ def __init__( # pylint: disable=R0913,R0914,R0915
self.api_authentication.token() # Need to force the authentication to resolve the base_url
self.logger.info("Resolved Base URL: %s", self.api_authentication.base_url)

mapper = ModuleMapper()

# Configure modules here so that IDEs can pick them up
self.logger.debug("Setting up Custom IOA module")
self.custom_ioas = CustomIoaApiModule(self.api_authentication)
self.custom_ioas = CustomIoaApiModule(self.api_authentication, mapper)
mapper.custom_ioas = self.custom_ioas

self.logger.debug("Setting up the Flight Control module")
self.flight_control = FlightControlApiModule(self.api_authentication)
self.flight_control = FlightControlApiModule(self.api_authentication, mapper)
mapper.flight_control = self.flight_control

self.logger.debug("Setting up the Hosts module")
self.hosts = HostsApiModule(self.api_authentication)
self.hosts = HostsApiModule(self.api_authentication, mapper)
mapper.hosts = self.hosts

self.logger.debug("Setting up the Prevention Policies module")
self.prevention_policies = PreventionPoliciesApiModule(self.api_authentication)
self.prevention_policies = PreventionPoliciesApiModule(self.api_authentication, mapper)
mapper.prevention_policies = self.prevention_policies

self.logger.debug("Setting up the Response Policies module")
self.response_policies = ResponsePoliciesApiModule(self.api_authentication)
self.response_policies = ResponsePoliciesApiModule(self.api_authentication, mapper)
mapper.response_policies = self.response_policies

self.logger.debug("Setting up the RTR module")
self.rtr = RTRApiModule(self.api_authentication)
self.rtr = RTRApiModule(self.api_authentication, mapper)
mapper.rtr = self.rtr

self.logger.debug("Setting up the Sensor Download module")
self.sensor_download = SensorDownloadApiModule(self.api_authentication, mapper)
mapper.sensor_download = self.sensor_download

self.logger.debug("Setting up the Sensor Update Policies module")
self.sensor_update_policies = SensorUpdatePoliciesApiModule(self.api_authentication)
self.sensor_update_policies = SensorUpdatePoliciesApiModule(self.api_authentication, mapper)
mapper.sensor_update_policies = self.sensor_update_policies

self.logger.debug("Setting up the Users module")
self.users = UsersApiModule(self.api_authentication)
self.users = UsersApiModule(self.api_authentication, mapper)
mapper.users = self.users

self.logger.info("Caracara client configured")

Expand Down
13 changes: 12 additions & 1 deletion caracara/common/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@
from falconpy import OAuth2


# pylint: disable=R0903
class ModuleMapper:
"""Empty container class to allow modules to map into other modules.
This is deliberately empty to start with as the modules are loaded into
the class dynamically when the Client is set up. This allows modules to
call into one another post-initialisation.
"""


class FalconApiModule(ABC):
"""
Meta class for a generic Caracara API Module.
Expand All @@ -28,10 +38,11 @@ def name(self) -> str:
def help(self) -> str:
"""Store the help string to be made available for each API module."""

def __init__(self, api_authentication: OAuth2):
def __init__(self, api_authentication: OAuth2, mapper: ModuleMapper):
"""Configure a Caracara API module with a FalconPy OAuth2 module."""
class_name = self.__class__.__name__
self.logger = logging.getLogger(f"caracara.modules.{class_name}")
self.logger.debug("Initialising API module: %s", class_name)

self.api_authentication = api_authentication
self.mapper = mapper
2 changes: 2 additions & 0 deletions caracara/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
'PreventionPoliciesApiModule',
'ResponsePoliciesApiModule',
'RTRApiModule',
'SensorDownloadApiModule',
'SensorUpdatePoliciesApiModule',
'UsersApiModule',
]
Expand All @@ -21,5 +22,6 @@
from caracara.modules.prevention_policies import PreventionPoliciesApiModule
from caracara.modules.response_policies import ResponsePoliciesApiModule
from caracara.modules.rtr import RTRApiModule
from caracara.modules.sensor_download import SensorDownloadApiModule
from caracara.modules.sensor_update_policies import SensorUpdatePoliciesApiModule
from caracara.modules.users import UsersApiModule
6 changes: 3 additions & 3 deletions caracara/modules/custom_ioa/custom_ioa.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

from caracara.common.batching import batch_get_data
from caracara.common.constants import DEFAULT_COMMENT
from caracara.common.module import FalconApiModule, ModuleMapper
from caracara.common.pagination import all_pages_numbered_offset_parallel
from caracara.filters import FalconFilter
from caracara.filters.decorators import filter_string
from caracara.common.module import FalconApiModule
from caracara.modules.custom_ioa.rules import CustomIoaRule, IoaRuleGroup, RuleType


Expand Down Expand Up @@ -41,9 +41,9 @@ class CustomIoaApiModule(FalconApiModule):
_rule_type_cache_time: int = None
_rule_type_cache_ttl: int = 10 * 60 # 10 minute default cache TTL

def __init__(self, api_authentication: OAuth2):
def __init__(self, api_authentication: OAuth2, mapper: ModuleMapper):
"""Create an Custom IOA API object and configure it with a FalconPy OAuth2 object."""
super().__init__(api_authentication)
super().__init__(api_authentication, mapper)
self.custom_ioa_api = CustomIOA(auth_object=api_authentication)

def _get_rule_types_cached(self, force_update: bool = False) -> Dict[str, RuleType]:
Expand Down
6 changes: 3 additions & 3 deletions caracara/modules/flight_control/flight_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
)

from caracara.common.batching import batch_get_data
from caracara.common.module import FalconApiModule
from caracara.common.module import FalconApiModule, ModuleMapper
from caracara.common.pagination import all_pages_numbered_offset_parallel


Expand All @@ -28,9 +28,9 @@ class FlightControlApiModule(FalconApiModule):
name = "CrowdStrike Falcon Flight Control API Module"
help = "Interact with the management API calls in a Falcon Flight Control (MSSP) Parent CID"

def __init__(self, api_authentication: OAuth2):
def __init__(self, api_authentication: OAuth2, mapper: ModuleMapper):
"""Construct an instance of the FlightControlApiModule class."""
super().__init__(api_authentication)
super().__init__(api_authentication, mapper)

self.logger.debug("Configuring the FalconPy Flight Control API")
self.flight_control_api = FlightControl(auth_object=self.api_authentication)
Expand Down
24 changes: 11 additions & 13 deletions caracara/modules/hosts/hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from caracara.common.exceptions import (
GenericAPIError,
)
from caracara.common.module import FalconApiModule
from caracara.common.module import FalconApiModule, ModuleMapper
from caracara.common.pagination import (
all_pages_token_offset,
)
Expand All @@ -46,9 +46,9 @@ class HostsApiModule(FalconApiModule):
name = "CrowdStrike Hosts API Module"
help = "Interact with hosts and host groups within your Falcon tenant."

def __init__(self, api_authentication: OAuth2):
def __init__(self, api_authentication: OAuth2, mapper: ModuleMapper):
"""Construct an instance of HostApiModule class."""
super().__init__(api_authentication)
super().__init__(api_authentication, mapper)

self.logger.debug("Configuring the FalconPy Hosts API")
self.hosts_api = Hosts(auth_object=self.api_authentication)
Expand Down Expand Up @@ -150,19 +150,17 @@ def describe_devices(
self.logger.info("Describing devices according to the filter string %s", filters)
device_ids = self.get_device_ids(filters)

if enrich_with_online_state:
if enrich_with_online_state or online_state is not None:
# Collect state data
device_state_data = self.get_online_state(device_ids)

# Filter by online state, if applicable.
if online_state is not None:
if not enrich_with_online_state:
device_state_data = self.get_online_state(device_ids)
self.validate_online_state(online_state)
device_ids = list(filter(
lambda key: device_state_data[key]["state"] == online_state,
device_state_data,
))
# Filter by online state, if applicable.
if online_state is not None:
self.validate_online_state(online_state)
device_ids = list(filter(
lambda key: device_state_data[key]["state"] == online_state,
device_state_data,
))

device_data = self.get_device_data(device_ids)

Expand Down
6 changes: 3 additions & 3 deletions caracara/modules/prevention_policies/prevention_policies.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
)

from caracara.common.decorators import platform_name_check
from caracara.common.module import FalconApiModule
from caracara.common.module import FalconApiModule, ModuleMapper
from caracara.common.pagination import all_pages_numbered_offset_parallel
from caracara.common.policy_wrapper import Policy
from caracara.common.sorting import SORT_ASC, SORTING_OPTIONS
Expand All @@ -27,9 +27,9 @@ class PreventionPoliciesApiModule(FalconApiModule):
name = "CrowdStrike Prevention Policies API Module"
help = "Describe, create, delete and edit Falcon Prevention policies"

def __init__(self, api_authentication: OAuth2):
def __init__(self, api_authentication: OAuth2, mapper: ModuleMapper):
"""Construct an instance of the PreventionPoliciesApiModule class."""
super().__init__(api_authentication)
super().__init__(api_authentication, mapper)
self.logger.debug("Configuring the FalconPy Prevention Policies API")
self.prevention_policies_api = PreventionPolicies(auth_object=self.api_authentication)

Expand Down
6 changes: 3 additions & 3 deletions caracara/modules/response_policies/response_policies.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
)

from caracara.common.decorators import platform_name_check
from caracara.common.module import FalconApiModule
from caracara.common.module import FalconApiModule, ModuleMapper
from caracara.common.pagination import all_pages_numbered_offset_parallel
from caracara.common.policy_wrapper import Policy
from caracara.common.sorting import SORT_ASC, SORTING_OPTIONS
Expand All @@ -27,9 +27,9 @@ class ResponsePoliciesApiModule(FalconApiModule):
name = "CrowdStrike Response Policies API Module"
help = "Describe, create, delete and edit Falcon Response policies"

def __init__(self, api_authentication: OAuth2):
def __init__(self, api_authentication: OAuth2, mapper: ModuleMapper):
"""Construct an instance of the ResponsePoliciesApiModule class."""
super().__init__(api_authentication)
super().__init__(api_authentication, mapper)
self.logger.debug("Configuring the FalconPy Response Policies API")
self.response_policies_api = ResponsePolicies(auth_object=self.api_authentication)

Expand Down
4 changes: 3 additions & 1 deletion caracara/modules/rtr/get_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ def download(
full_output_path_7z = full_output_path + ".7z"
else:
# We should ensure that the user's path ends in .7z if they are not extracting
if not full_output_path.endswith(".7z"):
if full_output_path.endswith(".7z"):
full_output_path_7z = full_output_path
else:
full_output_path_7z = full_output_path + ".7z"

file_contents = self.batch_session.api.get_extracted_file_contents(
Expand Down
6 changes: 3 additions & 3 deletions caracara/modules/rtr/rtr.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
)

from caracara.common.batching import batch_get_data
from caracara.common.module import FalconApiModule
from caracara.common.module import FalconApiModule, ModuleMapper
from caracara.common.pagination import all_pages_numbered_offset_parallel
from caracara.filters import FalconFilter
from caracara.filters.decorators import filter_string
Expand All @@ -36,9 +36,9 @@ class RTRApiModule(FalconApiModule):

default_timeout = 30

def __init__(self, api_authentication: OAuth2):
def __init__(self, api_authentication: OAuth2, mapper: ModuleMapper):
"""Create an RTR module object and configure it with a FalconPy OAuth2 object."""
super().__init__(api_authentication)
super().__init__(api_authentication, mapper)
self.rtr_api = RealTimeResponse(auth_object=self.api_authentication)
self.rtr_admin_api = RealTimeResponseAdmin(auth_object=self.api_authentication)

Expand Down
6 changes: 6 additions & 0 deletions caracara/modules/sensor_download/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Sensor Download API module."""

__all__ = [
"SensorDownloadApiModule",
]
from caracara.modules.sensor_download.sensor_download import SensorDownloadApiModule
59 changes: 59 additions & 0 deletions caracara/modules/sensor_download/sensor_download.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Falcon Sensor Download API."""

from falconpy import (
OAuth2,
SensorDownload,
)

from caracara.common.module import FalconApiModule, ModuleMapper
from caracara.common.exceptions import GenericAPIError


class SensorDownloadApiModule(FalconApiModule):
"""
Sensor Download API Module.
Whilst this module will eventually contain the logic required to inspect the available
versions of the Falcon sensor available for download, its primary purpose is to allow
other modules to retrieve the (C)CID.
"""

name = "CrowdStrike Sensor Download API Module"
help = "Download the Falcon sensor installer and fetch the installation CCID"

def __init__(self, api_authentication: OAuth2, mapper: ModuleMapper):
"""Construct an instance of the SensorDownloadApiModule class."""
super().__init__(api_authentication, mapper)
self.logger.debug("Configuring the FalconPy Sensor Download API")
self.sensor_download_api = SensorDownload(auth_object=self.api_authentication)

def get_cid(self, include_checksum: bool = False) -> str:
"""Obtain the Customer ID (CID) associated with the currently authenticated API token.
If include_checksum=True, Checksummed CID (CCID) will be returned, which includes a hyphen
and a two character checksum appended to the CID. The CCID is required for installing the
sensor.
If include_checksum=False, the resultant CID will be lower-cased for broad compatibility
outside of sensor installers.
"""
self.logger.info("Obtaining the CCID from the cloud using the Sensor Download API")

response = self.sensor_download_api.get_sensor_installer_ccid()
self.logger.debug(response)

try:
ccid = response["body"]["resources"][0]
except (KeyError, IndexError) as exc:
self.logger.info(
"Failed to retrieve the CCID from the cloud. "
"Check your API credentials."
)
raise GenericAPIError(response["body"]["errors"]) from exc

if include_checksum:
return ccid

# Strip the hyphen and two character checksum if we just need the CID, then send to
# lower case.
cid = ccid[:-3].lower()
return cid
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
)

from caracara.common.exceptions import BaseCaracaraError
from caracara.common.module import FalconApiModule
from caracara.common.module import FalconApiModule, ModuleMapper


class SensorUpdatePoliciesApiModule(FalconApiModule):
Expand All @@ -22,9 +22,9 @@ class SensorUpdatePoliciesApiModule(FalconApiModule):
name = "CrowdStrike Sensor Update Policies API Module"
help = "Interact with the Sensor Update Policies API, and get maintenance tokens"

def __init__(self, api_authentication: OAuth2):
def __init__(self, api_authentication: OAuth2, mapper: ModuleMapper):
"""Construct an instance of the SensorUpdatePoliciesApiModule."""
super().__init__(api_authentication)
super().__init__(api_authentication, mapper)

self.logger.debug("Configuring the FalconPy Sensor Update Policies module")
self.sensor_update_policies_api = SensorUpdatePolicies(auth_object=self.api_authentication)
Expand Down
3 changes: 2 additions & 1 deletion caracara/modules/users/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Caracara User Management module."""

__all__ = [
'UsersApiModule',
"UsersApiModule",
]
from caracara.modules.users.users import UsersApiModule
Loading

0 comments on commit 73b853d

Please sign in to comment.