зеркало из https://github.com/mozilla/MozDef.git
Merge pull request #1534 from mozilla/revert-1533-revert-1528-alert-action-invoke
Reapplyn Alert action invoke
This commit is contained in:
Коммит
089d415c7c
|
@ -50,7 +50,11 @@ class message(object):
|
||||||
def write_db_entry(self, alert_record):
|
def write_db_entry(self, alert_record):
|
||||||
self.dynamodb.put_item(Item=alert_record)
|
self.dynamodb.put_item(Item=alert_record)
|
||||||
|
|
||||||
def onMessage(self, message):
|
def onMessage(self, alert):
|
||||||
|
# As of Dec. 3, 2019, alert actions are given entire alerts rather
|
||||||
|
# than just their source
|
||||||
|
message = alert['_source']
|
||||||
|
|
||||||
if 'details' not in message:
|
if 'details' not in message:
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,11 @@ class message(object):
|
||||||
except:
|
except:
|
||||||
self.options.docs = {}
|
self.options.docs = {}
|
||||||
|
|
||||||
def onMessage(self, message):
|
def onMessage(self, alert):
|
||||||
|
# As of Dec. 3, 2019, alert actions are given entire alerts rather
|
||||||
|
# than just their source
|
||||||
|
message = alert['_source']
|
||||||
|
|
||||||
# here is where you do something with the incoming alert message
|
# here is where you do something with the incoming alert message
|
||||||
doclink = 'unknown'
|
doclink = 'unknown'
|
||||||
if message['category'] in self.options.docs:
|
if message['category'] in self.options.docs:
|
||||||
|
|
|
@ -216,18 +216,7 @@ class AlertTask(Task):
|
||||||
Send alert to the kombu based message queue. The default is rabbitmq.
|
Send alert to the kombu based message queue. The default is rabbitmq.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# cherry pick items from the alertDict to send to the alerts messageQueue
|
self.log.debug(alertDict)
|
||||||
mqAlert = dict(severity="INFO", category="")
|
|
||||||
if "severity" in alertDict:
|
|
||||||
mqAlert["severity"] = alertDict["severity"]
|
|
||||||
if "category" in alertDict:
|
|
||||||
mqAlert["category"] = alertDict["category"]
|
|
||||||
if "utctimestamp" in alertDict:
|
|
||||||
mqAlert["utctimestamp"] = alertDict["utctimestamp"]
|
|
||||||
if "eventtimestamp" in alertDict:
|
|
||||||
mqAlert["eventtimestamp"] = alertDict["eventtimestamp"]
|
|
||||||
mqAlert["summary"] = alertDict["summary"]
|
|
||||||
self.log.debug(mqAlert)
|
|
||||||
ensurePublish = self.mqConn.ensure(
|
ensurePublish = self.mqConn.ensure(
|
||||||
self.mqproducer, self.mqproducer.publish, max_retries=10
|
self.mqproducer, self.mqproducer.publish, max_retries=10
|
||||||
)
|
)
|
||||||
|
@ -377,7 +366,8 @@ class AlertTask(Task):
|
||||||
alert = self.alertPlugins(alert)
|
alert = self.alertPlugins(alert)
|
||||||
alertResultES = self.alertToES(alert)
|
alertResultES = self.alertToES(alert)
|
||||||
self.tagEventsAlert([i], alertResultES)
|
self.tagEventsAlert([i], alertResultES)
|
||||||
self.alertToMessageQueue(alert)
|
full_alert_doc = self.generate_full_doc(alert, alertResultES)
|
||||||
|
self.alertToMessageQueue(full_alert_doc)
|
||||||
self.hookAfterInsertion(alert)
|
self.hookAfterInsertion(alert)
|
||||||
self.saveAlertID(alertResultES)
|
self.saveAlertID(alertResultES)
|
||||||
# did we not match anything?
|
# did we not match anything?
|
||||||
|
@ -388,7 +378,8 @@ class AlertTask(Task):
|
||||||
alert = self.tagBotNotify(alert)
|
alert = self.tagBotNotify(alert)
|
||||||
self.log.debug(alert)
|
self.log.debug(alert)
|
||||||
alertResultES = self.alertToES(alert)
|
alertResultES = self.alertToES(alert)
|
||||||
self.alertToMessageQueue(alert)
|
full_alert_doc = self.generate_full_doc(alert, alertResultES)
|
||||||
|
self.alertToMessageQueue(full_alert_doc)
|
||||||
self.hookAfterInsertion(alert)
|
self.hookAfterInsertion(alert)
|
||||||
self.saveAlertID(alertResultES)
|
self.saveAlertID(alertResultES)
|
||||||
|
|
||||||
|
@ -406,11 +397,12 @@ class AlertTask(Task):
|
||||||
self.log.debug(alert)
|
self.log.debug(alert)
|
||||||
alert = self.alertPlugins(alert)
|
alert = self.alertPlugins(alert)
|
||||||
alertResultES = self.alertToES(alert)
|
alertResultES = self.alertToES(alert)
|
||||||
|
full_alert_doc = self.generate_full_doc(alert, alertResultES)
|
||||||
# even though we only sample events in the alert
|
# even though we only sample events in the alert
|
||||||
# tag all events as alerted to avoid re-alerting
|
# tag all events as alerted to avoid re-alerting
|
||||||
# on events we've already processed.
|
# on events we've already processed.
|
||||||
self.tagEventsAlert(aggregation["allevents"], alertResultES)
|
self.tagEventsAlert(aggregation["allevents"], alertResultES)
|
||||||
self.alertToMessageQueue(alert)
|
self.alertToMessageQueue(full_alert_doc)
|
||||||
self.saveAlertID(alertResultES)
|
self.saveAlertID(alertResultES)
|
||||||
|
|
||||||
def alertPlugins(self, alert):
|
def alertPlugins(self, alert):
|
||||||
|
@ -546,3 +538,10 @@ class AlertTask(Task):
|
||||||
logger.error("FAILED to open the configuration file\n")
|
logger.error("FAILED to open the configuration file\n")
|
||||||
|
|
||||||
return json_obj
|
return json_obj
|
||||||
|
|
||||||
|
def generate_full_doc(self, alert_body, alert_es):
|
||||||
|
return {
|
||||||
|
'_id': alert_es['_id'],
|
||||||
|
'_index': alert_es['_index'],
|
||||||
|
'_source': alert_body
|
||||||
|
}
|
||||||
|
|
|
@ -289,11 +289,11 @@ class alertConsumer(ConsumerMixin):
|
||||||
try:
|
try:
|
||||||
# just to be safe..check what we were sent.
|
# just to be safe..check what we were sent.
|
||||||
if isinstance(body, dict):
|
if isinstance(body, dict):
|
||||||
bodyDict = body
|
full_body = body
|
||||||
elif isinstance(body, str):
|
elif isinstance(body, str):
|
||||||
try:
|
try:
|
||||||
bodyDict = json.loads(body) # lets assume it's json
|
full_body = json.loads(body) # lets assume it's json
|
||||||
except ValueError as e:
|
except ValueError:
|
||||||
# not json..ack but log the message
|
# not json..ack but log the message
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"alertworker exception: unknown body type received %r" % body)
|
"alertworker exception: unknown body type received %r" % body)
|
||||||
|
@ -303,7 +303,9 @@ class alertConsumer(ConsumerMixin):
|
||||||
"alertworker exception: unknown body type received %r" % body)
|
"alertworker exception: unknown body type received %r" % body)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'notify_mozdefbot' in bodyDict and bodyDict['notify_mozdefbot'] is False:
|
body_dict = full_body['_source']
|
||||||
|
|
||||||
|
if 'notify_mozdefbot' in body_dict and body_dict['notify_mozdefbot'] is False:
|
||||||
# If the alert tells us to not notify, then don't post to IRC
|
# If the alert tells us to not notify, then don't post to IRC
|
||||||
message.ack()
|
message.ack()
|
||||||
return
|
return
|
||||||
|
@ -311,9 +313,9 @@ class alertConsumer(ConsumerMixin):
|
||||||
# process valid message
|
# process valid message
|
||||||
# see where we send this alert
|
# see where we send this alert
|
||||||
ircchannel = options.alertircchannel
|
ircchannel = options.alertircchannel
|
||||||
if 'ircchannel' in bodyDict:
|
if 'ircchannel' in body_dict:
|
||||||
if bodyDict['ircchannel'] in options.join.split(","):
|
if body_dict['ircchannel'] in options.join.split(","):
|
||||||
ircchannel = bodyDict['ircchannel']
|
ircchannel = body_dict['ircchannel']
|
||||||
|
|
||||||
# see if we need to delay a bit before sending the alert, to avoid
|
# see if we need to delay a bit before sending the alert, to avoid
|
||||||
# flooding the channel
|
# flooding the channel
|
||||||
|
@ -324,11 +326,11 @@ class alertConsumer(ConsumerMixin):
|
||||||
sys.stdout.write('throttling before writing next alert\n')
|
sys.stdout.write('throttling before writing next alert\n')
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
self.lastalert = toUTC(datetime.now())
|
self.lastalert = toUTC(datetime.now())
|
||||||
if len(bodyDict['summary']) > 450:
|
if len(body_dict['summary']) > 450:
|
||||||
sys.stdout.write('alert is more than 450 bytes, truncating\n')
|
sys.stdout.write('alert is more than 450 bytes, truncating\n')
|
||||||
bodyDict['summary'] = bodyDict['summary'][:450] + ' truncated...'
|
body_dict['summary'] = body_dict['summary'][:450] + ' truncated...'
|
||||||
|
|
||||||
self.ircBot.client.msg(ircchannel, formatAlert(bodyDict))
|
self.ircBot.client.msg(ircchannel, formatAlert(body_dict))
|
||||||
|
|
||||||
message.ack()
|
message.ack()
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
|
|
@ -48,17 +48,18 @@ class AlertConsumer(ConsumerMixin):
|
||||||
try:
|
try:
|
||||||
# just to be safe..check what we were sent.
|
# just to be safe..check what we were sent.
|
||||||
if isinstance(body, dict):
|
if isinstance(body, dict):
|
||||||
body_dict = body
|
full_body = body
|
||||||
elif isinstance(body, str):
|
elif isinstance(body, str):
|
||||||
try:
|
try:
|
||||||
body_dict = json.loads(body) # lets assume it's json
|
full_body = json.loads(body)
|
||||||
except ValueError as e:
|
except ValueError:
|
||||||
# not json..ack but log the message
|
# not json..ack but log the message
|
||||||
logger.exception("mozdefbot_slack exception: unknown body type received %r" % body)
|
logger.exception("mozdefbot_slack exception: unknown body type received %r" % body)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
logger.exception("mozdefbot_slack exception: unknown body type received %r" % body)
|
logger.exception("mozdefbot_slack exception: unknown body type received %r" % body)
|
||||||
return
|
return
|
||||||
|
body_dict = full_body['_source']
|
||||||
|
|
||||||
if 'notify_mozdefbot' in body_dict and body_dict['notify_mozdefbot'] is False:
|
if 'notify_mozdefbot' in body_dict and body_dict['notify_mozdefbot'] is False:
|
||||||
# If the alert tells us to not notify, then don't post message
|
# If the alert tells us to not notify, then don't post message
|
||||||
|
|
|
@ -20,37 +20,39 @@ class TestDashboardGeomodel(object):
|
||||||
|
|
||||||
self.plugin = message()
|
self.plugin = message()
|
||||||
self.good_message_dict = {
|
self.good_message_dict = {
|
||||||
"category": "geomodel",
|
"_source": {
|
||||||
"tags": ['geomodel'],
|
"category": "geomodel",
|
||||||
"summary": "ttesterson@mozilla.com NEWCOUNTRY Diamond Bar, United States access from 1.2.3.4 (duo) [deviation:12.07010770457331] last activity was from Ottawa, Canada (3763 km away) approx 23.43 hours before",
|
"tags": ['geomodel'],
|
||||||
"events": [
|
"summary": "ttesterson@mozilla.com NEWCOUNTRY Diamond Bar, United States access from 1.2.3.4 (duo) [deviation:12.07010770457331] last activity was from Ottawa, Canada (3763 km away) approx 23.43 hours before",
|
||||||
{
|
"events": [
|
||||||
'documentsource': {
|
{
|
||||||
'details': {
|
'documentsource': {
|
||||||
'event_time': '2018-08-08T02:11:41.85Z',
|
'details': {
|
||||||
|
'event_time': '2018-08-08T02:11:41.85Z',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"details": {
|
||||||
|
"category": "NEWCOUNTRY",
|
||||||
|
'previous_locality_details': {
|
||||||
|
'city': 'Oakland',
|
||||||
|
'country': 'United States'
|
||||||
|
},
|
||||||
|
"locality_details": {
|
||||||
|
"city": "Diamond Bar",
|
||||||
|
"country": "United States"
|
||||||
|
},
|
||||||
|
'source_ip': '1.2.3.4',
|
||||||
|
"principal": "ttesterson@mozilla.com",
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"details": {
|
|
||||||
"category": "NEWCOUNTRY",
|
|
||||||
'previous_locality_details': {
|
|
||||||
'city': 'Oakland',
|
|
||||||
'country': 'United States'
|
|
||||||
},
|
|
||||||
"locality_details": {
|
|
||||||
"city": "Diamond Bar",
|
|
||||||
"country": "United States"
|
|
||||||
},
|
|
||||||
'source_ip': '1.2.3.4',
|
|
||||||
"principal": "ttesterson@mozilla.com",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_message_good(self):
|
def test_message_good(self):
|
||||||
assert self.test_result_record is None
|
assert self.test_result_record is None
|
||||||
result_message = self.plugin.onMessage(self.good_message_dict)
|
result_message = self.plugin.onMessage(self.good_message_dict)
|
||||||
assert result_message == self.good_message_dict
|
assert result_message == self.good_message_dict['_source']
|
||||||
assert self.test_connect_called is True
|
assert self.test_connect_called is True
|
||||||
result_db_entry = self.test_result_record
|
result_db_entry = self.test_result_record
|
||||||
assert type(result_db_entry['alert_code']) is str
|
assert type(result_db_entry['alert_code']) is str
|
||||||
|
@ -66,14 +68,14 @@ class TestDashboardGeomodel(object):
|
||||||
assert result_db_entry['url'] == 'https://www.mozilla.org'
|
assert result_db_entry['url'] == 'https://www.mozilla.org'
|
||||||
assert result_db_entry['url_title'] == 'Get Help'
|
assert result_db_entry['url_title'] == 'Get Help'
|
||||||
assert result_db_entry['user_id'] == 'ttesterson'
|
assert result_db_entry['user_id'] == 'ttesterson'
|
||||||
assert result_db_entry['alert_str_json'] == json.dumps(self.good_message_dict)
|
assert result_db_entry['alert_str_json'] == json.dumps(self.good_message_dict['_source'])
|
||||||
|
|
||||||
def test_unknown_new_city_message(self):
|
def test_unknown_new_city_message(self):
|
||||||
message_dict = self.good_message_dict
|
message_dict = self.good_message_dict
|
||||||
message_dict['details']['locality_details']['city'] = 'UNKNOWN'
|
message_dict['_source']['details']['locality_details']['city'] = 'UNKNOWN'
|
||||||
assert self.test_result_record is None
|
assert self.test_result_record is None
|
||||||
result_message = self.plugin.onMessage(message_dict)
|
result_message = self.plugin.onMessage(message_dict)
|
||||||
assert result_message == self.good_message_dict
|
assert result_message == self.good_message_dict['_source']
|
||||||
assert self.test_connect_called is True
|
assert self.test_connect_called is True
|
||||||
result_db_entry = self.test_result_record
|
result_db_entry = self.test_result_record
|
||||||
assert type(result_db_entry['alert_code']) is str
|
assert type(result_db_entry['alert_code']) is str
|
||||||
|
@ -81,32 +83,34 @@ class TestDashboardGeomodel(object):
|
||||||
|
|
||||||
def test_malformed_message_bad(self):
|
def test_malformed_message_bad(self):
|
||||||
message_dict = {
|
message_dict = {
|
||||||
"category": "geomodel",
|
"_source": {
|
||||||
"tags": ['geomodel'],
|
"category": "geomodel",
|
||||||
"summary": "ttesterson@mozilla.com MOVEMENT Diamond Bar, United States access from 1.2.3.4 (duo) [deviation:12.07010770457331] last activity was from Ottawa, Canada (3763 km away) approx 23.43 hours before",
|
"tags": ['geomodel'],
|
||||||
"details": {
|
"summary": "ttesterson@mozilla.com MOVEMENT Diamond Bar, United States access from 1.2.3.4 (duo) [deviation:12.07010770457331] last activity was from Ottawa, Canada (3763 km away) approx 23.43 hours before",
|
||||||
"category": "MOVEMENT",
|
"details": {
|
||||||
"locality_details": {
|
"category": "MOVEMENT",
|
||||||
"city": "Diamond Bar",
|
"locality_details": {
|
||||||
"country": "United States"
|
"city": "Diamond Bar",
|
||||||
},
|
"country": "United States"
|
||||||
"principal": "ttesterson@mozilla.com",
|
},
|
||||||
|
"principal": "ttesterson@mozilla.com",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert self.test_result_record is None
|
assert self.test_result_record is None
|
||||||
result_message = self.plugin.onMessage(message_dict)
|
result_message = self.plugin.onMessage(message_dict)
|
||||||
assert result_message == message_dict
|
assert result_message == message_dict['_source']
|
||||||
assert self.test_connect_called is True
|
assert self.test_connect_called is True
|
||||||
assert self.test_result_record is None
|
assert self.test_result_record is None
|
||||||
|
|
||||||
def test_str_location(self):
|
def test_str_location(self):
|
||||||
self.good_message_dict['summary'] = "ttesterson@mozilla.com NEWCOUNTRY \u0107abcd, \xe4Spain access from 1.2.3.4 (duo) [deviation:12.07010770457331] last activity was from Ottawa, Canada (3763 km away) approx 23.43 hours before"
|
self.good_message_dict['_source']['summary'] = "ttesterson@mozilla.com NEWCOUNTRY \u0107abcd, \xe4Spain access from 1.2.3.4 (duo) [deviation:12.07010770457331] last activity was from Ottawa, Canada (3763 km away) approx 23.43 hours before"
|
||||||
self.good_message_dict['details']['locality_details']['city'] = '\u0107abcd'
|
self.good_message_dict['_source']['details']['locality_details']['city'] = '\u0107abcd'
|
||||||
self.good_message_dict['details']['locality_details']['country'] = '\xe4Spain'
|
self.good_message_dict['_source']['details']['locality_details']['country'] = '\xe4Spain'
|
||||||
assert self.test_result_record is None
|
assert self.test_result_record is None
|
||||||
result_message = self.plugin.onMessage(self.good_message_dict)
|
result_message = self.plugin.onMessage(self.good_message_dict)
|
||||||
assert result_message == self.good_message_dict
|
assert result_message == self.good_message_dict['_source']
|
||||||
assert self.test_connect_called is True
|
assert self.test_connect_called is True
|
||||||
assert self.test_result_record is not None
|
assert self.test_result_record is not None
|
||||||
assert type(result_message['summary']) is str
|
assert type(result_message['summary']) is str
|
||||||
|
@ -114,10 +118,10 @@ class TestDashboardGeomodel(object):
|
||||||
assert type(result_message['details']['locality_details']['country']) is str
|
assert type(result_message['details']['locality_details']['country']) is str
|
||||||
|
|
||||||
def test_str_username(self):
|
def test_str_username(self):
|
||||||
self.good_message_dict['details']['principal'] = '\xfcttesterson@mozilla.com'
|
self.good_message_dict['_source']['details']['principal'] = '\xfcttesterson@mozilla.com'
|
||||||
assert self.test_result_record is None
|
assert self.test_result_record is None
|
||||||
result_message = self.plugin.onMessage(self.good_message_dict)
|
result_message = self.plugin.onMessage(self.good_message_dict)
|
||||||
assert result_message == self.good_message_dict
|
assert result_message == self.good_message_dict['_source']
|
||||||
assert self.test_connect_called is True
|
assert self.test_connect_called is True
|
||||||
assert self.test_result_record is not None
|
assert self.test_result_record is not None
|
||||||
assert type(result_message['summary']) is str
|
assert type(result_message['summary']) is str
|
||||||
|
@ -126,7 +130,7 @@ class TestDashboardGeomodel(object):
|
||||||
def test_written_details(self):
|
def test_written_details(self):
|
||||||
assert self.test_result_record is None
|
assert self.test_result_record is None
|
||||||
result_message = self.plugin.onMessage(self.good_message_dict)
|
result_message = self.plugin.onMessage(self.good_message_dict)
|
||||||
assert result_message == self.good_message_dict
|
assert result_message == self.good_message_dict['_source']
|
||||||
assert self.test_connect_called is True
|
assert self.test_connect_called is True
|
||||||
assert self.test_result_record is not None
|
assert self.test_result_record is not None
|
||||||
result_db_entry = self.test_result_record
|
result_db_entry = self.test_result_record
|
||||||
|
|
|
@ -161,12 +161,16 @@ class AlertTestSuite(UnitTestSuite):
|
||||||
rabbitmq_message = self.rabbitmq_alerts_consumer.channel.basic_get()
|
rabbitmq_message = self.rabbitmq_alerts_consumer.channel.basic_get()
|
||||||
rabbitmq_message.channel.basic_ack(rabbitmq_message.delivery_tag)
|
rabbitmq_message.channel.basic_ack(rabbitmq_message.delivery_tag)
|
||||||
document = json.loads(rabbitmq_message.body)
|
document = json.loads(rabbitmq_message.body)
|
||||||
assert document['notify_mozdefbot'] is test_case.expected_alert['notify_mozdefbot'], 'Alert from rabbitmq has bad notify_mozdefbot field'
|
assert '_id' in document
|
||||||
assert document['ircchannel'] == test_case.expected_alert['ircchannel'], 'Alert from rabbitmq has bad ircchannel field'
|
assert '_source' in document
|
||||||
assert document['summary'] == found_alert['_source']['summary'], 'Alert from rabbitmq has bad summary field'
|
assert '_index' in document
|
||||||
assert document['utctimestamp'] == found_alert['_source']['utctimestamp'], 'Alert from rabbitmq has bad utctimestamp field'
|
alert_body = document['_source']
|
||||||
assert document['category'] == found_alert['_source']['category'], 'Alert from rabbitmq has bad category field'
|
assert alert_body['notify_mozdefbot'] is test_case.expected_alert['notify_mozdefbot'], 'Alert from rabbitmq has bad notify_mozdefbot field'
|
||||||
assert len(document['events']) == len(found_alert['_source']['events']), 'Alert from rabbitmq has bad events field'
|
assert alert_body['ircchannel'] == test_case.expected_alert['ircchannel'], 'Alert from rabbitmq has bad ircchannel field'
|
||||||
|
assert alert_body['summary'] == found_alert['_source']['summary'], 'Alert from rabbitmq has bad summary field'
|
||||||
|
assert alert_body['utctimestamp'] == found_alert['_source']['utctimestamp'], 'Alert from rabbitmq has bad utctimestamp field'
|
||||||
|
assert alert_body['category'] == found_alert['_source']['category'], 'Alert from rabbitmq has bad category field'
|
||||||
|
assert len(alert_body['events']) == len(found_alert['_source']['events']), 'Alert from rabbitmq has bad events field'
|
||||||
|
|
||||||
def verify_saved_events(self, found_alert, test_case):
|
def verify_saved_events(self, found_alert, test_case):
|
||||||
"""
|
"""
|
||||||
|
|
Загрузка…
Ссылка в новой задаче