-
Notifications
You must be signed in to change notification settings - Fork 451
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
[Mandiant] fix: infinitely crashes if the state is empty #3018
[Mandiant] fix: infinitely crashes if the state is empty #3018
Conversation
else: | ||
# Fix problem when end state is in the future | ||
if ( | ||
Timestamp.from_iso( | ||
self.get_state_value( | ||
collection_name=collection, state_key=STATE_END | ||
) | ||
).value | ||
> Timestamp.now().value | ||
): | ||
self.set_state_value( | ||
collection_name=collection, state_key=STATE_END, value=None | ||
) | ||
end_short_format = Timestamp.from_iso( | ||
self.get_state_value( | ||
collection_name=collection, state_key=STATE_END | ||
) | ||
).short_format | ||
|
||
if ( | ||
first_run is False | ||
and date_now_value - collection_interval < last_run_value | ||
): | ||
diff_time = round( | ||
((date_now_value - last_run_value).total_seconds()) / 60 | ||
# Additional information for the "work" depending on the collection (offset, epoch) | ||
start_work = ( | ||
start_short_format | ||
if collection in collection_with_start_epoch | ||
else start_offset | ||
) | ||
remaining_time = round( | ||
( | ||
end_work = ( | ||
end_short_format | ||
if collection in collection_with_start_epoch | ||
else end_offset | ||
) | ||
|
||
import_start_date = ( | ||
self.mandiant_indicator_import_start_date | ||
if collection == "indicators" | ||
else self.mandiant_import_start_date | ||
) | ||
|
||
if collection in collection_with_start_epoch: | ||
first_run = ( | ||
self.get_state_value( | ||
collection_name=collection, state_key=STATE_START | ||
) | ||
== Timestamp.from_iso(import_start_date).iso_format | ||
) | ||
else: | ||
first_run = start_offset == 0 | ||
|
||
""" | ||
We check that after each API call the collection respects the interval, | ||
either the default or the one specified in the config. | ||
If it does not, we terminate the job and move on to the next collection. | ||
""" | ||
|
||
if ( | ||
first_run is False | ||
and date_now_value - collection_interval < last_run_value | ||
): | ||
diff_time = round( | ||
((date_now_value - last_run_value).total_seconds()) / 60 | ||
) | ||
remaining_time = round( | ||
( | ||
( | ||
collection_interval - timedelta(minutes=diff_time) | ||
).total_seconds() | ||
( | ||
collection_interval - timedelta(minutes=diff_time) | ||
).total_seconds() | ||
) | ||
/ 60 | ||
) | ||
/ 60 | ||
) | ||
self.helper.connector_logger.info( | ||
f"Ignore the '{collection}' collection because the collection interval in the config is '{collection_interval}', the remaining time until the next collection pull: {remaining_time} min" | ||
) | ||
continue | ||
|
||
work_id = self.helper.api.work.initiate_work( | ||
self.helper.connect_id, | ||
f"{collection.title()} {start_work} - {end_work}", | ||
) | ||
self.helper.connector_logger.info( | ||
f"Ignore the '{collection}' collection because the collection interval in the config is '{collection_interval}', the remaining time until the next collection pull: {remaining_time} min" | ||
) | ||
continue | ||
|
||
work_id = self.helper.api.work.initiate_work( | ||
self.helper.connect_id, | ||
f"{collection.title()} {start_work} - {end_work}", | ||
) | ||
try: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note for the reviewer : Re indented to be in the try: except clause.
collection_interval = getattr(self, f"mandiant_{collection}_interval") | ||
|
||
last_run_value = Timestamp.from_iso( | ||
self.get_state_value( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use of safe get_state_value responsible for handling the Error rather than direct key access
# API types related to simple offset | ||
collection_with_offset = ["malwares", "actors", "campaigns"] | ||
# Start and End, Offset | ||
start_offset = self.get_state_value( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use of safe get_state_value responsible for handling the Error rather than direct key access
] | ||
# Start and End, Timestamp short format | ||
start_date = Timestamp.from_iso( | ||
self.get_state_value( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use of safe get_state_value responsible for handling the Error rather than direct key access
""" | ||
# If no end date, put the proper period using delta | ||
if ( | ||
self.get_state_value( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use of safe get_state_value responsible for handling the Error rather than direct key access
collection_name=collection, state_key=STATE_END, value=None | ||
) | ||
end_short_format = Timestamp.from_iso( | ||
self.get_state_value( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use of safe get_state_value responsible for handling the Error rather than direct key access
).value | ||
> Timestamp.now().value | ||
): | ||
self.set_state_value( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use of safe set_state_value method,responsible for handling the Error, rather than direct key access
|
||
if collection in collection_with_start_epoch: | ||
first_run = ( | ||
self.get_state_value( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use of safe get_state_value responsible for handling the Error rather than direct key access
@@ -646,13 +737,21 @@ def process_message(self): | |||
self.helper.connector_logger.info("Connector stop") | |||
sys.exit(0) | |||
|
|||
except StateError as err: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Core of the PR
@@ -6,7 +6,7 @@ class Timestamp: | |||
format = "%Y-%m-%dT%H:%M:%S.%fZ" | |||
|
|||
def __init__(self, value): | |||
if type(value) == datetime: | |||
if isinstance(value, datetime): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aside: fix E721
@@ -646,13 +737,21 @@ def process_message(self): | |||
self.helper.connector_logger.info("Connector stop") | |||
sys.exit(0) | |||
|
|||
except StateError as err: | |||
self.helper.connector_logger.error(str(err)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be interesting to clarify the message ?
self.helper.connector_logger.error(str(err)) | |
self.helper.connector_logger.error( | |
"Failed du to connector state error", {"error": str(err)} | |
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your suggestion !
if work_id is not None: | ||
self.helper.api.work.to_processed( | ||
work_id, "Failed du to connector state error", in_error=True | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if work_id is not None:
self.helper.api.work.to_processed(
work_id, "Failed du to connector state error", in_error=True
)
work_id = None
break
-
Why suggest "work_id = None" :
When we intercept the error (stateError) and the work_id exists, we perform a to_processed (and indeed, we see a return of this information on the front end!). However, because of the finally block, a second to_processed is executed, as at this point the work_id still exists. This is why we're considering setting the work_id to None before entering the finally block, to avoid this behavior. -
Why suggest "break" :
If we don't add a break when a StateError is triggered, then the same error message will be generated for each of the “active” collections. Since a problem at state level affects all collections, it would be appropriate to exit the loop (for collection in self.mandiant_collections:) and wait for the next connector schedule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your suggestion !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything seems ok ;)
Proposed changes
Related issues
Checklist
Further comments
Investigation page
Simplified behavior changes