def test_error_type(self) -> None: result = RuleResult(rule_id='failed.rule', rule_severity='INFO') self.assertIsNone(result.error_type) result = RuleResult(rule_id='failed.rule', rule_severity='INFO', rule_exception=TypeError('something went wrong')) self.assertEqual(result.error_type, 'TypeError')
def test_short_error_message(self) -> None: result = RuleResult(rule_id='failed.rule', rule_severity='INFO') self.assertIsNone(result.short_error_message) result = RuleResult( rule_severity='INFO', rule_id='failed.rule', rule_exception=TypeError('something went wrong'), ) self.assertEqual(result.short_error_message, "TypeError('something went wrong')")
def test_errored(self) -> None: result = RuleResult(rule_id='failed.rule', rule_severity='INFO', rule_exception=TypeError()) self.assertTrue(result.errored) result = RuleResult(rule_id='failed.rule', rule_severity='INFO', title_exception=TypeError()) self.assertTrue(result.errored) result = RuleResult(rule_id='failed.rule', rule_severity='INFO') self.assertFalse(result.errored)
def test_fatal_error(self) -> None: result = RuleResult(rule_id='failed.rule', rule_severity='INFO') self.assertIsNone(result.fatal_error) fields = ('rule_exception', 'setup_exception', 'input_exception') exc = TypeError('something went wrong') for field in fields: # https://github.com/python/mypy/issues/1969 params = { field: exc, 'rule_id': 'failed.rule', 'rule_severity': 'INFO', } result = RuleResult(**params) # type: ignore self.assertIs(result.fatal_error, exc)
def test_rule_with_invalid_destinations_type(self) -> None: rule_body = 'def rule(event):\n\treturn True\n' \ 'def alert_context(event):\n\treturn {}\n' \ 'def title(event):\n\treturn "test_rule_with_valid_severity_case_insensitive"\n' \ 'def severity(event):\n\treturn "cRiTiCaL"\n' \ 'def destinations(event):\n\treturn "bad input"\n' rule = Rule({ 'id': 'test_rule_with_valid_severity_case_insensitive', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_result = RuleResult( rule_id='test_rule_with_valid_severity_case_insensitive', matched=True, alert_context='{}', title_output='test_rule_with_valid_severity_case_insensitive', dedup_output='test_rule_with_valid_severity_case_insensitive', severity_output="CRITICAL", destinations_output=None, destinations_exception=Exception( 'rule [{}] function [{}] returned [{}], expected a list'. format(rule.rule_id, 'destinations', 'str')), rule_severity='INFO') result = rule.run(PantherEvent({}, None), {}, {}, batch_mode=False) self.assertEqual(str(expected_result), str(result)) self.assertTrue(result.errored) self.assertIsNotNone(result.destinations_exception)
def test_rule_with_all_generated_fields(self) -> None: rule_body = 'def rule(event):\n\treturn True\n' \ 'def alert_context(event):\n\treturn {}\n' \ 'def title(event):\n\treturn "test_rule_with_all_generated_fields"\n' \ 'def description(event):\n\treturn "test description"\n' \ 'def severity(event):\n\treturn "HIGH"\n' \ 'def reference(event):\n\treturn "test reference"\n' \ 'def runbook(event):\n\treturn "test runbook"\n' \ 'def destinations(event):\n\treturn []' rule = Rule({ 'id': 'test_rule_with_all_generated_fields', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_result = RuleResult( rule_id='test_rule_with_all_generated_fields', matched=True, alert_context='{}', title_output='test_rule_with_all_generated_fields', dedup_output='test_rule_with_all_generated_fields', description_output='test description', severity_output='HIGH', reference_output='test reference', runbook_output='test runbook', destinations_output=["SKIP"], rule_severity='INFO', ) self.assertEqual( expected_result, rule.run(PantherEvent({}, None), {}, {}, batch_mode=False))
def test_rule_with_invalid_severity(self) -> None: rule_body = 'def rule(event):\n\treturn True\n' \ 'def alert_context(event):\n\treturn {}\n' \ 'def title(event):\n\treturn "test_rule_with_invalid_severity"\n' \ 'def severity(event):\n\treturn "CRITICAL-ISH"\n' rule = Rule({ 'id': 'test_rule_with_invalid_severity', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_result = RuleResult( rule_id='test_rule_with_invalid_severity', matched=True, alert_context='{}', title_output='test_rule_with_invalid_severity', dedup_output='test_rule_with_invalid_severity', severity_exception=AssertionError( "Expected severity to be any of the following: [['INFO', 'LOW', 'MEDIUM', 'HIGH', " "'CRITICAL']], got [CRITICAL-ISH] instead."), rule_severity='INFO', ) result = rule.run(PantherEvent({}, None), {}, {}, batch_mode=False) self.assertEqual(str(expected_result), str(result)) self.assertTrue(result.errored) self.assertIsNotNone(result.severity_exception)
def test_rule_with_severity_raising_exception_unit_test(self) -> None: rule_body = 'def rule(event):\n\treturn True\n' \ 'def title(event):\n\treturn"test_rule_with_severity_raising_exception_unit_test"\n' \ 'def severity(event):\n\traise AssertionError("something bad happened")\n' rule = Rule({ 'id': 'test_rule_with_severity_raising_exception_unit_test', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_result = RuleResult( rule_id='test_rule_with_severity_raising_exception_unit_test', matched=True, title_output='test_rule_with_severity_raising_exception_unit_test', dedup_output='test_rule_with_severity_raising_exception_unit_test', severity_output=None, severity_exception=AssertionError("something bad happened"), rule_severity='INFO', ) result = rule.run(PantherEvent({}, None), {}, {}, batch_mode=False) self.assertTrue(result.errored) self.assertIsNotNone(result.severity_exception) # Exception instances cannot be compared self.assertDataclassEqual(expected_result, result, fields_as_string=('severity_exception', ))
def test_alert_context_too_big(self) -> None: # Function should generate alert_context exceeding limit alert_context_function = 'def alert_context(event):\n' \ '\ttest_dict = {}\n' \ '\tfor i in range(300000):\n' \ '\t\ttest_dict[str(i)] = "value"\n' \ '\treturn test_dict' rule_body = 'def rule(event):\n\treturn True\n{}'.format( alert_context_function) rule = Rule({ 'id': 'test_alert_context_too_big', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_alert_context = json.dumps({ '_error': 'alert_context size is [5588890] characters, bigger than maximum of [204800] characters' }) expected_result = RuleResult( rule_id='test_alert_context_too_big', matched=True, dedup_output='defaultDedupString:test_alert_context_too_big', alert_context=expected_alert_context, rule_severity='INFO') self.assertEqual(expected_result, rule.run(PantherEvent({}, None), {}, {}))
def test_error_message(self) -> None: # Generate traceback try: raise TypeError('rule failed') except TypeError as exception: exc = exception result = RuleResult(rule_exception=exc, rule_id='failed.rule', rule_severity='INFO') self.assertRegex( # type: ignore # error_message return value is Optional[str] # but here we know that it is a string result.error_message, r"rule failed: test_rule.py, line [0-9]+, " r"in test_error_message\s+raise TypeError\('rule failed'\)") result = RuleResult(rule_id='failed.rule', rule_severity='INFO') self.assertIsNone(result.error_message)
def test_rule_doesnt_match(self) -> None: rule_body = 'def rule(event):\n\treturn False' rule = Rule({ 'id': 'test_rule_doesnt_match', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_rule = RuleResult(matched=False, rule_id='test_rule_doesnt_match', rule_severity='INFO') self.assertEqual(expected_rule, rule.run(PantherEvent({}, None), {}, {}))
def test_rule_with_dedup(self) -> None: rule_body = 'def rule(event):\n\treturn True\ndef dedup(event):\n\treturn "testdedup"' rule = Rule({ 'id': 'test_rule_with_dedup', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_rule = RuleResult(rule_id='test_rule_with_dedup', matched=True, dedup_output='testdedup', rule_severity='INFO') self.assertEqual(expected_rule, rule.run(PantherEvent({}, None), {}, {}))
def test_dedup_throws_exception(self) -> None: rule_body = 'def rule(event):\n\treturn True\ndef dedup(event):\n\traise Exception("test")' rule = Rule({ 'id': 'test_dedup_throws_exception', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_rule = RuleResult( rule_id='test_dedup_throws_exception', matched=True, dedup_output='defaultDedupString:test_dedup_throws_exception', rule_severity='INFO') self.assertEqual(expected_rule, rule.run(PantherEvent({}, None), {}, {}))
def test_rule_invalid_title_return(self) -> None: rule_body = 'def rule(event):\n\treturn True\ndef title(event):\n\treturn {}' rule = Rule({ 'id': 'test_rule_invalid_title_return', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_result = RuleResult( rule_id='test_rule_invalid_title_return', matched=True, dedup_output='test_rule_invalid_title_return', title_output='test_rule_invalid_title_return', rule_severity='INFO') self.assertEqual(rule.run(PantherEvent({}, None), {}, {}), expected_result)
def test_rule_dedup_returns_empty_string(self) -> None: rule_body = 'def rule(event):\n\treturn True\ndef dedup(event):\n\treturn ""' rule = Rule({ 'id': 'test_rule_dedup_returns_empty_string', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_result = RuleResult( rule_id='test_rule_dedup_returns_empty_string', matched=True, dedup_output= 'defaultDedupString:test_rule_dedup_returns_empty_string', rule_severity='INFO') self.assertEqual(rule.run(PantherEvent({}, None), {}, {}), expected_result)
def test_alert_context(self) -> None: rule_body = 'def rule(event):\n\treturn True\ndef alert_context(event):\n\treturn {"string": "string", "int": 1, "nested": {}}' rule = Rule({ 'id': 'test_alert_context', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_result = RuleResult( rule_id='test_alert_context', matched=True, dedup_output='defaultDedupString:test_alert_context', alert_context='{"string": "string", "int": 1, "nested": {}}', rule_severity='INFO', ) self.assertEqual(expected_result, rule.run(PantherEvent({}, None), {}, {}))
def test_restrict_dedup_size(self) -> None: rule_body = 'def rule(event):\n\treturn True\ndef dedup(event):\n\treturn "".join("a" for i in range({}))'. \ format(MAX_DEDUP_STRING_SIZE + 1) rule = Rule({ 'id': 'test_restrict_dedup_size', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_dedup_string_prefix = ''.join( 'a' for _ in range(MAX_DEDUP_STRING_SIZE - len(TRUNCATED_STRING_SUFFIX))) expected_rule = RuleResult(rule_id='test_restrict_dedup_size', matched=True, dedup_output=expected_dedup_string_prefix + TRUNCATED_STRING_SUFFIX, rule_severity='INFO') self.assertEqual(expected_rule, rule.run(PantherEvent({}, None), {}, {}))
def test_rule_with_severity_raising_exception_batch_mode(self) -> None: rule_body = 'def rule(event):\n\treturn True\n' \ 'def title(event):\n\treturn"test_rule_with_severity_raising_exception_batch_mode"\n' \ 'def severity(event):\n\traise AssertionError("something bad happened")\n' rule = Rule({ 'id': 'test_rule_with_severity_raising_exception_batch_mode', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_result = RuleResult( rule_id='test_rule_with_severity_raising_exception_batch_mode', matched=True, title_output='test_rule_with_severity_raising_exception_batch_mode', dedup_output='test_rule_with_severity_raising_exception_batch_mode', severity_output='INFO', rule_severity='INFO', ) result = rule.run(PantherEvent({}, None), {}, {}, batch_mode=True) self.assertEqual(str(expected_result), str(result))
def test_alert_context_immutable_event(self) -> None: alert_context_function = 'def alert_context(event):\n' \ '\treturn {"headers": event["headers"],\n' \ '\t\t"get_params": event["query_string_args"]}' rule_body = 'def rule(event):\n\treturn True\n{}'.format( alert_context_function) rule = Rule({ 'id': 'test_alert_context_immutable_event', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) event = { 'headers': { 'User-Agent': 'Chrome' }, 'query_string_args': [{ 'a': '1' }, { 'b': '2' }] } expected_alert_context = json.dumps({ 'headers': event['headers'], 'get_params': event['query_string_args'] }) expected_result = RuleResult( rule_id='test_alert_context_immutable_event', matched=True, dedup_output= 'defaultDedupString:test_alert_context_immutable_event', alert_context=expected_alert_context, rule_severity='INFO', ) self.assertEqual(expected_result, rule.run(PantherEvent(event, None), {}, {}))
def test_rule_with_valid_severity_case_insensitive(self) -> None: rule_body = 'def rule(event):\n\treturn True\n' \ 'def alert_context(event):\n\treturn {}\n' \ 'def title(event):\n\treturn "test_rule_with_valid_severity_case_insensitive"\n' \ 'def severity(event):\n\treturn "cRiTiCaL"\n' rule = Rule({ 'id': 'test_rule_with_valid_severity_case_insensitive', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_result = RuleResult( matched=True, rule_id='test_rule_with_valid_severity_case_insensitive', alert_context='{}', title_output='test_rule_with_valid_severity_case_insensitive', dedup_output='test_rule_with_valid_severity_case_insensitive', severity_output="CRITICAL", rule_severity='INFO') result = rule.run(PantherEvent({}, None), {}, {}) self.assertEqual(expected_result, result)
def test_alert_context_returns_full_event(self) -> None: alert_context_function = 'def alert_context(event):\n\treturn event' rule_body = 'def rule(event):\n\treturn True\n{}'.format( alert_context_function) rule = Rule({ 'id': 'test_alert_context_returns_full_event', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) event = {'test': 'event'} expected_alert_context = json.dumps(event) expected_result = RuleResult( rule_id='test_alert_context_returns_full_event', matched=True, dedup_output= 'defaultDedupString:test_alert_context_returns_full_event', alert_context=expected_alert_context, rule_severity='INFO') self.assertEqual(expected_result, rule.run(PantherEvent(event, None), {}, {}))
def test_rule_matches(self) -> None: rule_body = 'def rule(event):\n\treturn True' rule = Rule({ 'id': 'test_rule_matches', 'body': rule_body, 'dedupPeriodMinutes': 100, 'versionId': 'test', 'severity': 'INFO' }) self.assertEqual('test_rule_matches', rule.rule_id) self.assertEqual(rule_body, rule.rule_body) self.assertEqual('test', rule.rule_version) self.assertEqual(100, rule.rule_dedup_period_mins) expected_rule = RuleResult( rule_id='test_rule_matches', matched=True, dedup_output='defaultDedupString:test_rule_matches', rule_severity='INFO', ) self.assertEqual(expected_rule, rule.run(PantherEvent({}, None), {}, {}))
def test_alert_context_invalid_return_value(self) -> None: rule_body = 'def rule(event):\n\treturn True\ndef alert_context(event):\n\treturn ""' rule = Rule({ 'id': 'test_alert_context_invalid_return_value', 'body': rule_body, 'versionId': 'versionId', 'severity': 'INFO' }) expected_alert_context = json.dumps({ '_error': 'Exception(\'rule [test_alert_context_invalid_return_value] function [alert_context] returned [str], expected [Mapping]\')' # pylint: disable=C0301 }) expected_result = RuleResult( rule_id='test_alert_context_invalid_return_value', matched=True, dedup_output= 'defaultDedupString:test_alert_context_invalid_return_value', alert_context=expected_alert_context, rule_severity='INFO', ) self.assertEqual(expected_result, rule.run(PantherEvent({}, None), {}, {}))