ibmresilient / resilient-python-api

Python Library for the IBM SOAR REST API, a Python SDK for developing Apps for IBM SOAR and more...
https://ibm.biz/soar-python-docs
MIT License
39 stars 28 forks source link

resilient-lib `OAuth2ClientCredentialsSession.update_token` method causes infinite loop #17

Open lmahoney1 opened 3 years ago

lmahoney1 commented 3 years ago

Description

I was getting the following stack trace after the fn_microsoft_security_graph integration ran for ~ 1 hour (this integration uses an instance of the resilient-lib OAuth2ClientCredentialsSession class to handle authentication / make HTTP requests):

Traceback (most recent call last):
File "/usr/lib64/python3.6/threading.py", line 916, in _bootstrap_inner
self.run()
File "/usr/lib64/python3.6/threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "/opt/app-root/lib/python3.6/site-packages/fn_microsoft_security_graph/components/microsoft_security_graph_alerts_integrations.py", line 198, in msg_polling_thread
alert_list = get_alerts(self.options, self.Microsoft_security_graph_helper)
File "/opt/app-root/lib/python3.6/site-packages/fn_microsoft_security_graph/components/microsoft_security_graph_alerts_integrations.py", line 290, in get_alerts
r = ms_graph_helper.ms_graph_session.get(url)
File "/opt/app-root/lib/python3.6/site-packages/requests/sessions.py", line 555, in get
return self.request('GET', url, **kwargs)
File "/opt/app-root/lib/python3.6/site-packages/resilient_lib/components/oauth2_client_credentials_session.py", line 140, in request
self.update_token()
File "/opt/app-root/lib/python3.6/site-packages/resilient_lib/components/oauth2_client_credentials_session.py", line 126, in update_token
self.client_secret, self.scope, self.proxies)
File "/opt/app-root/lib/python3.6/site-packages/resilient_lib/components/oauth2_client_credentials_session.py", line 86, in authenticate
r = self.get_token(token_url, client_id, client_secret, scope, proxies)
File "/opt/app-root/lib/python3.6/site-packages/resilient_lib/components/oauth2_client_credentials_session.py", line 118, in get_token
return self.post(token_url, data=post_data, proxies=proxies)
File "/opt/app-root/lib/python3.6/site-packages/requests/sessions.py", line 590, in post
return self.request('POST', url, data=data, json=json, **kwargs)
File "/opt/app-root/lib/python3.6/site-packages/resilient_lib/components/oauth2_client_credentials_session.py", line 140, in request
self.update_token()

... (the same request -> update_token -> authenticate -> get_token -> post lines were repeated many times)

File "/opt/app-root/lib/python3.6/site-packages/resilient_lib/components/oauth2_client_credentials_session.py", line 140, in request
self.update_token()
File "/opt/app-root/lib/python3.6/site-packages/resilient_lib/components/oauth2_client_credentials_session.py", line 126, in update_token
self.client_secret, self.scope, self.proxies)
File "/opt/app-root/lib/python3.6/site-packages/resilient_lib/components/oauth2_client_credentials_session.py", line 86, in authenticate
r = self.get_token(token_url, client_id, client_secret, scope, proxies)
File "/opt/app-root/lib/python3.6/site-packages/resilient_lib/components/oauth2_client_credentials_session.py", line 118, in get_token
return self.post(token_url, data=post_data, proxies=proxies)
File "/opt/app-root/lib/python3.6/site-packages/requests/sessions.py", line 590, in post
return self.request('POST', url, data=data, json=json, **kwargs)
File "/opt/app-root/lib/python3.6/site-packages/resilient_lib/components/oauth2_client_credentials_session.py", line 139, in request
if self.expiration_time < time.time():
RecursionError: maximum recursion depth exceeded in comparison

After some digging into / debugging the integration code, as well as the resilient-lib OAuth2ClientCredentialsSession code (and the requests.Session code, since OAuth2ClientCredentialsSession inherits from requests.Session) it appears to me that there's an issue with resilient_lib'sOAuth2ClientCredentialsSession`.

The error happens an hour after running as that's when the initial bearer token we get expires. The next time we try to get graph alerts after the bearer token has expired, the request method in OAuth2ClientCredentialsSession identifies that the bearer token is expired and that a new one needs to be retrieved, however this functionality appears to be broken.

Below is the process for retrieving a new bearer token:

  1. OAuth2ClientCredentialsSession.update_token is called
  2. OAuth2ClientCredentialsSession.authenticate is called
  3. OAuth2ClientCredentialsSession.get_token is called
  4. requests.Session.post is called (through inheritance) - this method calls self.request which OAuth2ClientCredentialsSession has overwritten
  5. OAuth2ClientCredentialsSession.request is called - Here's the issue, this method identifies that the bearer token is expired, and starts the process to retrieve a new bearer token again
  6. OAuth2ClientCredentialsSession.update_token is called and the process is repeated until the maximum recursion depth is reached

So the request to renew the bearer token is never sent, and there ends up being an infinite recursion loop.

Describe How to Reproduce

Try to make a request with an instance of resilient-lib.OAuth2ClientCredentialsSession after the initial bearer token retrieved expires.