Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds user management module #198

Merged
merged 1 commit into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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