CrowdStrikeApiModule
Common CrowdStrike code that will be appended to each CrowdStrike integration when it is deployed to enable oauth2 authentication automatically.
- Type
- python
- Pack
- 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.