CrowdStrikeApiModule
Common CrowdStrike code that will be appended to each CrowdStrike integration when it is deployed to enable oauth2 authentication automatically.
python · ApiModules
Source
from datetime import timedelta import dateparser from CommonServerPython import * from CommonServerUserPython import * TOKEN_LIFE_TIME = timedelta(minutes=28) class CrowdStrikeClient(BaseClient): def __init__(self, params): """ CrowdStrike Client class that implements OAuth2 authentication. Args: params: Demisto params """ credentials = params.get("credentials", {}) self._client_id = credentials.get("identifier") self._client_secret = credentials.get("password") super().__init__( base_url=params.get("server_url", "https://api.crowdstrike.com/"), verify=not params.get("insecure", False), ok_codes=(), proxy=params.get("proxy", False), ) # type: ignore[misc] self.timeout = float(params.get("timeout", "10")) self._token = self._get_token() self._headers = {"Authorization": "bearer " + self._token} @staticmethod def _error_handler(res: requests.Response): """ Converting the errors of the API to a string, in case there are no error, return an empty string :param res: the request's response :return: None """ err_msg = f"Error in API call [{res.status_code}] - {res.reason}\n" try: # Try to parse json error response error_entry = res.json() errors = error_entry.get("errors", []) err_msg += "\n".join( f"{error.get('code')}: {error.get('message')}" for error in errors # pylint: disable=no-member ) if "Failed to issue access token - Not Authorized" in err_msg: err_msg = err_msg.replace("Failed to issue access token - Not Authorized", "Client Secret is invalid.") elif "Failed to generate access token for clientID" in err_msg: err_msg = err_msg.replace("Failed to generate access token for clientID=", "Client ID (") if err_msg.endswith("."): err_msg = err_msg[:-1] err_msg += ") is invalid." raise DemistoException(err_msg) except ValueError: err_msg += f"\n{res.text}" raise DemistoException(err_msg) def http_request( self, method, url_suffix, full_url=None, headers=None, json_data=None, params=None, data=None, files=None, timeout=10, ok_codes=None, return_empty_response=False, auth=None, resp_type="json", ): """A wrapper for requests lib to send our requests and handle requests and responses better. :type method: ``str`` :param method: The HTTP method, for example: GET, POST, and so on. :type url_suffix: ``str`` :param url_suffix: The API endpoint. :type full_url: ``str`` :param full_url: Bypasses the use of self._base_url + url_suffix. This is useful if you need to make a request to an address outside of the scope of the integration API. :type headers: ``dict`` :param headers: Headers to send in the request. If None, will use self._headers. :type params: ``dict`` :param params: URL parameters to specify the query. :type data: ``dict`` :param data: The data to send in a 'POST' request. :type json_data: ``dict`` :param json_data: The dictionary to send in a 'POST' request. :type files: ``dict`` :param files: The file data to send in a 'POST' request. :type timeout: ``float`` or ``tuple`` :param timeout: The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. can be only float (Connection Timeout) or a tuple (Connection Timeout, Read Timeout). :type ok_codes: ``tuple`` :param ok_codes: The request codes to accept as OK, for example: (200, 201, 204). If you specify "None", will use self._ok_codes. :type return_empty_response: ``bool`` :param return_empty_response: Indicates whether we are expecting empty response (like 204) or not. :return: Depends on the resp_type parameter :rtype: ``dict`` or ``str`` or ``requests.Response`` """ req_timeout = timeout if self.timeout: req_timeout = self.timeout return super()._http_request( method=method, url_suffix=url_suffix, full_url=full_url, headers=headers, json_data=json_data, params=params, data=data, files=files, timeout=req_timeout, ok_codes=ok_codes, return_empty_response=return_empty_response, auth=auth, error_handler=self._error_handler, resp_type=resp_type, ) def _get_token(self, force_gen_new_token=False): """ Retrieves the token from the server if it's expired and updates the global HEADERS to include it :param force_gen_new_token: If set to True will generate a new token regardless of time passed :rtype: ``str`` :return: Token """ now = datetime.now() ctx = get_integration_context() if not ctx or not ctx.get("generation_time", force_gen_new_token): # new token is needed auth_token = self._generate_token() else: generation_time = dateparser.parse(ctx.get("generation_time")) if generation_time and now: time_passed = now - generation_time else: time_passed = TOKEN_LIFE_TIME if time_passed < TOKEN_LIFE_TIME: # token hasn't expired return ctx.get("auth_token") else: # token expired auth_token = self._generate_token() ctx.update({"auth_token": auth_token, "generation_time": now.strftime("%Y-%m-%dT%H:%M:%S")}) set_integration_context(ctx) return auth_token def _generate_token(self) -> str: """Generate an Access token using the user name and password :return: valid token """ body = {"client_id": self._client_id, "client_secret": self._client_secret} token_res = self.http_request("POST", "/oauth2/token", data=body, auth=(self._client_id, self._client_secret)) return token_res.get("access_token") def check_quota_status(self) -> dict: """Checking the status of the quota :return: http response """ url_suffix = "/falconx/entities/submissions/v1?ids=" return self.http_request("GET", url_suffix)
README
The CrowdStrike API handles the oauth2 authentication process and API requests. When developing a CrowdStrike integration, import the API module to the integration and the authentication process will occur automatically.
To use the common CrowdStrike API logic, attach the from CrowdStrikeApiModule import * # noqa: E402 line of code in the following location to import it. After you import the module, the CrowdStrikeClient will be available for use.
def main():
...
from CrowdStrikeApiModule import * # noqa: E402
if __name__ in ["builtins", "__main__"]:
main()
For examples, see the CrowdStrike Falcon Intel v2 integration.