def load(self, conf, args=None): """ Discover and load all the rules as defined in the conf and args. :param dict conf: Configuration dict :param dict args: Arguments dict :return: List of rules :rtype: list """ names = [] use_rule = None if args is None else args.rule # Load each rule configuration file rules = [] rule_files = self.get_names(conf, use_rule) for rule_file in rule_files: try: rule = self.load_configuration(rule_file, conf, args) # A rule failed to load, don't try to process it if not rule: elastalert_logger.error('Invalid rule file skipped: %s' % rule_file) continue if rule['name'] in names: raise EAException('Duplicate rule named %s' % (rule['name'])) except EAException as e: raise EAException('Error loading file %s: %s' % (rule_file, e)) rules.append(rule) names.append(rule['name']) return rules
def alert(self, matches): # Format the command and arguments command = [ resolve_string(command_arg, matches[0]) for command_arg in self.rule['command'] ] self.last_command = command # Run command and pipe data try: subp = subprocess.Popen(command, stdin=subprocess.PIPE, shell=self.shell) if self.rule.get('pipe_match_json'): match_json = json.dumps(matches, cls=DateTimeEncoder) + '\n' stdout, stderr = subp.communicate(input=match_json.encode()) elif self.rule.get('pipe_alert_text'): alert_text = self.create_alert_body(matches) stdout, stderr = subp.communicate(input=alert_text.encode()) if self.rule.get("fail_on_non_zero_exit", False) and subp.wait(): raise EAException( "Non-zero exit code while running command %s" % (' '.join(command))) except OSError as e: raise EAException("Error while running command %s: %s" % (' '.join(command), e)) elastalert_logger.info("Alert sent to Command")
def alert(self, matches): client = TwilioClient(self.twilio_account_sid, self.twilio_auth_token) try: if self.twilio_use_copilot: if self.twilio_message_service_sid is None: raise EAException( "Twilio Copilot requires the 'twilio_message_service_sid' option" ) client.messages.create( body=self.rule['name'], to=self.twilio_to_number, messaging_service_sid=self.twilio_message_service_sid) else: if self.twilio_from_number is None: raise EAException( "Twilio SMS requires the 'twilio_from_number' option") client.messages.create(body=self.rule['name'], to=self.twilio_to_number, from_=self.twilio_from_number) except TwilioRestException as e: raise EAException("Error posting to twilio: %s" % e) elastalert_logger.info("Trigger sent to Twilio")
def get_token(self): #获取token是有次数限制的,本想本地缓存过期时间和token,但是elastalert每次调用都是一次性的,不能全局缓存 if self.expires_in >= datetime.datetime.now() and self.access_token: return self.access_token #构建获取token的url get_token_url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s' %(self.corp_id,self.secret) try: response = requests.get(get_token_url,verify=False) response.raise_for_status() except RequestException as e: raise EAException("get access_token failed , stacktrace:%s" % e) #sys.exit("get access_token failed, system exit") token_json = response.json() if 'access_token' not in token_json : raise EAException("get access_token failed , , the response is :%s" % response.text()) #sys.exit("get access_token failed, system exit") #获取access_token和expires_in self.access_token = token_json['access_token'] self.expires_in = datetime.datetime.now() + datetime.timedelta(seconds=token_json['expires_in']) return self.access_token
def __init__(self, *args): super(UniqueLongTermRule, self).__init__(*args) self.values = [] self.garbage_time = 0 self.exec_num = 0 self.field = self.rules['compare_key'] self.timeperiods_index = 0 self.no_of_timeperiods = int(self.rules['no_of_timeperiods']) for i in range(0, self.no_of_timeperiods): self.values.append(set()) timeperiod_sec = int(self.rules['timeframe'].total_seconds()) run_every_sec = int(self.rules['run_every'].total_seconds()) if run_every_sec > timeperiod_sec: raise EAException( "Run Every option cannot be greater than Timeperiod option") if timeperiod_sec % run_every_sec != 0: raise EAException( "Run Every must fit integer number of times in Timeperiod") self.runs_per_timeperiod = int(timeperiod_sec / run_every_sec) elastalert_logger.info( "Timeperiod sec: %d, Number of executions per timeperiod: %d, Number of timeperiods: %d" % (timeperiod_sec, self.runs_per_timeperiod, self.no_of_timeperiods))
def alert(self, matches): # Format the command and arguments try: command = [ resolve_string(command_arg, matches[0]) for command_arg in self.rule['command'] ] self.last_command = command except KeyError as e: raise EAException("Error formatting command: %s" % (e)) # Run command and pipe data try: subp = subprocess.Popen(command, stdin=subprocess.PIPE, shell=self.shell) match_json = json.dumps(matches, cls=DateTimeEncoder) + '\n' input_string = self.rule['name'] + ":||:" + match_json stdout, stderr = subp.communicate(input=input_string.encode()) if self.rule.get("fail_on_non_zero_exit", False) and subp.wait(): raise EAException( "Non-zero exit code while running command %s" % (' '.join(command))) except OSError as e: raise EAException("Error while running command %s: %s" % (' '.join(command), e))
def alert(self, matches): message = self.create_title(matches) detailed_message = self.create_alert_body(matches) command = [ self.msend_command, '-l', self.cell_home, '-n', self.cell_name, '-a', self.event_class, '-r', self.event_severity, '-m', '\'%s\'' % message ] slotsetvalues = '' # # Transform slotsetvalues dict into a string if (isinstance(self.slotsetvalues, dict)): for k, v in self.slotsetvalues.items(): slotsetvalues += "%s=%s;" % (k, v) # If slotsetvalues is a string, copy it straight across elif (isinstance(self.slotsetvalues, basestring)): slotsetvalues += self.slotsetvalues if slotsetvalues.endswith(';'): slotsetvalues = slotsetvalues[:-1] try: if self.new_style_string_format: slotsetvalues = slotsetvalues.format(match=matches[0]) else: slotsetvalues = slotsetvalues % matches[0] except KeyError as e: raise EAException("Error formatting command: %s" % (e)) if (len(slotsetvalues) > 0): command.extend(['-b', '\'%s\'' % slotsetvalues]) self.last_command = command logging.warning("MSEND Command: %s" % ' '.join(command)) # Run command and pipe data try: subp = subprocess.Popen(' '.join(command), stdin=subprocess.PIPE, shell=self.shell) if self.rule.get('pipe_match_json'): match_json = json.dumps(matches, cls=DateTimeEncoder) + '\n' stdout, stderr = subp.communicate(input=match_json) if self.rule.get("fail_on_non_zero_exit", False) and subp.wait(): raise EAException( "Non-zero exit code while running msend command %s" % (' '.join(command))) except OSError as e: raise EAException("Error while running msend command %s: %s" % (' '.join(command), e))
def alert(self, matches): client = Exotel(self.exotel_account_sid, self.exotel_auth_token) try: message_body = self.rule['name'] + self.sms_body response = client.sms(self.rule['exotel_from_number'], self.rule['exotel_to_number'], message_body) if response != 200: raise EAException("Error posting to Exotel, response code is %s" % response) except RequestException: raise EAException("Error posting to Exotel").with_traceback(sys.exc_info()[2]) elastalert_logger.info("Trigger sent to Exotel")
def create_alert(alert, alert_config): alert_class = self.alerts_mapping.get(alert) or get_module(alert) if not issubclass(alert_class, alerts.Alerter): raise EAException( 'Alert module %s is not a subclass of Alerter' % alert) missing_options = (rule['type'].required_options | alert_class.required_options) - frozenset( alert_config or []) if missing_options: raise EAException('Missing required option(s): %s' % (', '.join(missing_options))) return alert_class(alert_config)
def alert(self, matches): body = '' for match in matches: body += str(BasicMatchString(self.rule, match)) if len(matches) > 1: body += '\n----------------------------------------\n' if len(body) > 2047: body = body[ 0: 1950] + '\n *message was cropped according to chatwork embed description limits!*' headers = {'X-ChatWorkToken': self.chatwork_apikey} # set https proxy, if it was provided proxies = { 'https': self.chatwork_proxy } if self.chatwork_proxy else None auth = HTTPProxyAuth( self.chatwork_proxy_login, self.chatwork_proxy_pass) if self.chatwork_proxy_login else None params = {'body': body} try: response = requests.post(self.url, params=params, headers=headers, proxies=proxies, auth=auth) response.raise_for_status() except RequestException as e: raise EAException( "Error posting to Chattwork: %s. Details: %s" % (e, "" if e.response is None else e.response.text)) elastalert_logger.info("Alert sent to Chatwork room %s" % self.chatwork_room_id)
def alert(self, matches): body = '⚠ *%s* ⚠ ```\n' % (self.create_title(matches)) for match in matches: body += str(BasicMatchString(self.rule, match)) # Separate text of aggregated alerts with dashes if len(matches) > 1: body += '\n----------------------------------------\n' if len(body) > 4095: body = body[0:4000] + "\n⚠ *message was cropped according to telegram limits!* ⚠" body += ' ```' headers = {'content-type': 'application/json'} # set https proxy, if it was provided proxies = {'https': self.telegram_proxy} if self.telegram_proxy else None auth = HTTPProxyAuth(self.telegram_proxy_login, self.telegram_proxy_password) if self.telegram_proxy_login else None payload = { 'chat_id': self.telegram_room_id, 'text': body, 'parse_mode': 'markdown', 'disable_web_page_preview': True } try: response = requests.post(self.url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers, proxies=proxies, auth=auth) warnings.resetwarnings() response.raise_for_status() except RequestException as e: raise EAException("Error posting to Telegram: %s. Details: %s" % (e, "" if e.response is None else e.response.text)) elastalert_logger.info( "Alert sent to Telegram room %s" % self.telegram_room_id)
def __init__(self, *args): super(DifferenceRule, self).__init__(*args) # self.diff_key = self.rules['compare_key'].split('.') self.diff_key = self.rules['compare_key'] self.threshold_pct = self.rules['threshold_pct'] self.delta_sec = self.rules['delta_min'] * 60 self.agg_sec = self.rules['agg_min'] * 60 self.qkey = self.rules['query_key'] # keys are query_key values and values are objects of inner class # self.qobj = {} self.include = self.rules['include'] # do not include @timestamp self.include = [i for i in self.include if i != '@timestamp'] # self.include_all = [] # if 'include_all' in self.rules: # self.include_all = self.rules['include_all'] # self.include = list(set(self.include) - set(self.include_all)) # set realert to 0 to get alert for each query_key in one minute # since this query_key is not part of core elastalert self.rules['realert'] = datetime.timedelta(minutes=0) if not self.delta_sec >= self.agg_sec: raise EAException("delta_min must be greater or equal to agg_min") self.es = elasticsearch_client(self.rules) self.filter_query = {"query_string": {"query": "*"}} if self.rules['filter']: self.filter_query = self.rules['filter'][0]
def __init__(self, rule): super(MSendAlerter, self).__init__(rule) self.cell_name = self.rule['msend_cell_name'] self.event_class = self.rule['msend_event_class'] self.event_severity = self.rule['msend_event_severity'] self.cell_home = self.rule.get('msend_cell_home', '/opt/msend') self.msend_command = self.rule.get('msend_command', '/opt/msend/bin/msend') self.slotsetvalues = self.rule.get('msend_slotsetvalues') self.new_style_string_format = False if 'new_style_string_format' in self.rule and self.rule[ 'new_style_string_format']: self.new_style_string_format = True self.last_command = [] # Slot Set Values validation if (isinstance(self.slotsetvalues, basestring)): regex = re.compile("^\w+=[^\n;'\"]+(;(\w+=[^\n;'\"]+))*$") if (regex.match(self.slotsetvalues) is None): raise EAException('Invalid slotsetvalues format: %s' % self.slotsetvalues) # MSEND Command Validation self.shell = False if isinstance(self.msend_command, basestring): self.shell = True if '%' in self.msend_command: logging.warning( 'Warning! You could be vulnerable to shell injection!')
def alert(self, matches): headers = { "Content-Type": "application/json", "Accept": "application/json;charset=utf-8" } # pdb.set_trace() body = self.my_create_alert_body(matches) #body="这是用于创建的第二个规则" title = self.rule['name'] + '- alert' payload = { "msgtype": self.dingtalk_msgtype, "markdown": { "title": title, "text": body }, "at": { "isAtAll": False } } try: response = requests.post(self.dingtalk_webhook_url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers) response.raise_for_status() except RequestException as e: raise EAException("Error request to Dingtalk: {0}".format(str(e)))
def senddata(self, content): config_file = os.environ.get('ALERTA_CONF_FILE') or OPTIONS['config_file'] config = configparser.RawConfigParser(defaults=OPTIONS) try: config.read(os.path.expanduser(config_file)) except Exception: sys.exit("Problem reading configuration file %s - is this an ini file?" % config_file) want_profile = os.environ.get('ALERTA_DEFAULT_PROFILE') or config.defaults().get('profile') if want_profile and config.has_section('profile %s' % want_profile): for opt in OPTIONS: try: OPTIONS[opt] = config.getboolean('profile %s' % want_profile, opt) except (ValueError, AttributeError): OPTIONS[opt] = config.get('profile %s' % want_profile, opt) else: for opt in OPTIONS: try: OPTIONS[opt] = config.getboolean('DEFAULT', opt) except (ValueError, AttributeError): OPTIONS[opt] = config.get('DEFAULT', opt) try: LOG.debug("[alerta] sendto=%s ", OPTIONS.get("endpoint")) api = Client(endpoint=OPTIONS.get("endpoint"), key=OPTIONS.get("key"), ssl_verify=OPTIONS.get("sslverify")) api.send_alert(**content) except RequestException as e: raise EAException("send message has error: %s" % e) elastalert_logger.info("send msg success" )
def alert(self, matches): """ Each match will trigger a POST to the specified endpoint(s). """ for match in matches: payload = match if self.post_all_values else {} payload.update(self.post_static_payload) for post_key, es_key in list(self.post_payload.items()): payload[post_key] = lookup_es_key(match, es_key) headers = { "Content-Type": "application/json", "Accept": "application/json;charset=utf-8" } if self.post_ca_certs: verify = self.post_ca_certs else: verify = not self.post_ignore_ssl_errors if self.post_ignore_ssl_errors: requests.packages.urllib3.disable_warnings() headers.update(self.post_http_headers) proxies = {'https': self.post_proxy} if self.post_proxy else None for url in self.post_url: try: response = requests.post(url, data=json.dumps( payload, cls=DateTimeEncoder), headers=headers, proxies=proxies, timeout=self.timeout, verify=verify) response.raise_for_status() except RequestException as e: raise EAException("Error posting HTTP Post alert: %s" % e) elastalert_logger.info("HTTP Post alert sent.")
def __init__(self, *args): super(SpikeAggregationRule, self).__init__(*args) # shared setup self.ts_field = self.rules.get('timestamp_field', '@timestamp') # aggregation setup # if 'max_threshold' not in self.rules and 'min_threshold' not in self.rules: # raise EAException("MetricAggregationRule must have at least one of either max_threshold or min_threshold") self.metric_key = self.rules['metric_agg_key'] + '_' + self.rules[ 'metric_agg_type'] if not self.rules['metric_agg_type'] in self.allowed_aggregations: raise EAException("metric_agg_type must be one of %s" % (str(self.allowed_aggregations))) self.rules[ 'aggregation_query_element'] = self.generate_aggregation_query() self.ref_window_filled_once = False # spike setup self.timeframe = self.rules['timeframe'] self.ref_windows = {} self.cur_windows = {} self.get_ts = new_get_event_ts(self.ts_field) self.first_event = {} self.skip_checks = {}
def __init__(self, rule): super(JiraAlerter, self).__init__(rule) self.server = self.rule['jira_server'] self.get_account(self.rule['jira_account_file']) self.project = self.rule['jira_project'] self.issue_type = self.rule['jira_issuetype'] # Deferred settings refer to values that can only be resolved when a match # is found and as such loading them will be delayed until we find a match self.deferred_settings = [] # We used to support only a single component. This allows us to maintain backwards compatibility # while also giving the user-facing API a more representative name self.components = self.rule.get('jira_components', self.rule.get('jira_component')) # We used to support only a single label. This allows us to maintain backwards compatibility # while also giving the user-facing API a more representative name self.labels = self.rule.get('jira_labels', self.rule.get('jira_label')) self.description = self.rule.get('jira_description', '') self.assignee = self.rule.get('jira_assignee') self.max_age = self.rule.get('jira_max_age', 30) self.priority = self.rule.get('jira_priority') self.bump_tickets = self.rule.get('jira_bump_tickets', False) self.bump_not_in_statuses = self.rule.get('jira_bump_not_in_statuses') self.bump_in_statuses = self.rule.get('jira_bump_in_statuses') self.bump_after_inactivity = self.rule.get( 'jira_bump_after_inactivity', 0) self.bump_only = self.rule.get('jira_bump_only', False) self.transition = self.rule.get('jira_transition_to', False) self.watchers = self.rule.get('jira_watchers') self.client = None if self.bump_in_statuses and self.bump_not_in_statuses: msg = 'Both jira_bump_in_statuses (%s) and jira_bump_not_in_statuses (%s) are set.' % \ (','.join(self.bump_in_statuses), ','.join(self.bump_not_in_statuses)) intersection = list( set(self.bump_in_statuses) & set(self.bump_in_statuses)) if intersection: msg = '%s Both have common statuses of (%s). As such, no tickets will ever be found.' % ( msg, ','.join(intersection)) msg += ' This should be simplified to use only one or the other.' elastalert_logger.warning(msg) self.reset_jira_args() try: self.client = JIRA(self.server, basic_auth=(self.user, self.password)) self.get_priorities() self.jira_fields = self.client.fields() self.get_arbitrary_fields() except JIRAError as e: # JIRAError may contain HTML, pass along only first 1024 chars raise EAException("Error connecting to JIRA: %s" % (str(e)[:1024])).with_traceback( sys.exc_info()[2]) self.set_priority()
def alert(self, matches): body = self.create_alert_body(matches) # post to victorops headers = {'content-type': 'application/json'} # set https proxy, if it was provided proxies = { 'https': self.victorops_proxy } if self.victorops_proxy else None payload = { "message_type": self.victorops_message_type, "entity_display_name": self.victorops_entity_display_name, "monitoring_tool": "ElastAlert", "state_message": body } if self.victorops_entity_id: payload["entity_id"] = self.victorops_entity_id try: response = requests.post(self.url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers, proxies=proxies) response.raise_for_status() except RequestException as e: raise EAException("Error posting to VictorOps: %s" % e) elastalert_logger.info("Trigger sent to VictorOps")
def alert(self, matches): body = self.create_alert_body(matches) body = self.format_body(body) # post to Teams headers = {'content-type': 'application/json'} # set https proxy, if it was provided proxies = { 'https': self.ms_teams_proxy } if self.ms_teams_proxy else None payload = { '@type': 'MessageCard', '@context': 'http://schema.org/extensions', 'summary': self.ms_teams_alert_summary, 'title': self.create_title(matches), 'text': body } if self.ms_teams_theme_color != '': payload['themeColor'] = self.ms_teams_theme_color for url in self.ms_teams_webhook_url: try: response = requests.post(url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers, proxies=proxies) response.raise_for_status() except RequestException as e: raise EAException("Error posting to ms teams: %s" % e) elastalert_logger.info("Alert sent to MS Teams")
def alert(self, matches): headers = { 'content-type': 'application/json', 'Accept': 'application/json;charset=utf-8', } body = self.create_alert_body(matches) data = { "at": { "atMobiles": self.mobiles, "isAtAll": self.at_all, }, "msgtype": self.msgtype, } if self.msgtype == 'markdown': content = {'title': self.create_title(matches), 'text': body} else: content = {'content': body} data[self.msgtype] = content webhook_url = 'https://oapi.dingtalk.com/robot/send?access_token=%s' % ( self.access_token) if self.security_type == "sign": webhook_url = '%s%s' % (webhook_url, self.sign()) try: response = requests.post(webhook_url, data=json.dumps(data), headers=headers) response.raise_for_status() except RequestException as e: raise EAException("send message has error: %s" % e)
def get_names(self, conf, use_rule=None): # Passing a filename directly can bypass rules_folder and .yaml checks if use_rule and os.path.isfile(use_rule): return [use_rule] # In case of a bad type, convert string to list: rule_folders = conf['rules_folder'] if isinstance( conf['rules_folder'], list) else [conf['rules_folder']] rule_files = [] if 'scan_subdirectories' in conf and conf['scan_subdirectories']: for ruledir in rule_folders: if not os.path.exists(ruledir): raise EAException( 'Specified rule_folder does not exist: %s ' % ruledir) for root, folders, files in os.walk(ruledir, followlinks=True): # Openshift/k8s configmap fix for ..data and ..2021_05..date directories that loop with os.walk() folders[:] = [d for d in folders if not d.startswith('..')] for filename in files: if use_rule and use_rule != filename: continue if self.is_yaml(filename): rule_files.append(os.path.join(root, filename)) else: for ruledir in rule_folders: if not os.path.isdir(ruledir): continue for file in os.scandir(ruledir): fullpath = os.path.join(ruledir, file.name) if os.path.isfile(fullpath) and self.is_yaml(file.name): rule_files.append(fullpath) return rule_files
def alert(self, matches): body = self.create_alert_body(matches) body = self.format_body(body) headers = {'content-type': 'application/json'} proxies = { 'https': self.rocket_chat_proxy } if self.rocket_chat_proxy else None payload = { 'username': self.rocket_chat_username_override, 'text': self.rocket_chat_text_string, 'attachments': [{ 'color': self.rocket_chat_msg_color, 'title': self.create_title(matches), 'text': body, 'fields': [] }] } # if we have defined fields, populate noteable fields for the alert if self.rocket_chat_alert_fields != '': payload['attachments'][0]['fields'] = self.populate_fields(matches) if self.rocket_chat_emoji_override != '': payload['emoji'] = self.rocket_chat_emoji_override if self.rocket_chat_attach_kibana_discover_url: kibana_discover_url = lookup_es_key(matches[0], 'kibana_discover_url') if kibana_discover_url: payload['attachments'].append({ 'color': self.rocket_chat_kibana_discover_color, 'title': self.rocket_chat_kibana_discover_title, 'title_link': kibana_discover_url }) for url in self.rocket_chat_webhook_url: for channel_override in self.rocket_chat_channel_override: try: if self.rocket_chat_ca_certs: verify = self.rocket_chat_ca_certs else: verify = not self.rocket_chat_ignore_ssl_errors if self.rocket_chat_ignore_ssl_errors: requests.packages.urllib3.disable_warnings() payload['channel'] = channel_override response = requests.post(url, data=json.dumps( payload, cls=DateTimeEncoder), headers=headers, verify=verify, proxies=proxies, timeout=self.rocket_chat_timeout) warnings.resetwarnings() response.raise_for_status() except RequestException as e: raise EAException("Error posting to Rocket.Chat: %s" % e) elastalert_logger.info("Alert sent to Rocket.Chat")
def alert(self, matches): # Build TheHive alert object, starting with some defaults, updating with any # user-specified config alert_config = { 'artifacts': [], 'customFields': {}, 'date': int(time.time()) * 1000, 'description': self.create_alert_body(matches), 'sourceRef': str(uuid.uuid4()), 'tags': [], 'title': self.create_title(matches), } alert_config.update(self.rule.get('hive_alert_config', {})) # Iterate through each match found, populating the alert tags and observables as required tags = set() artifacts = [] for match in matches: artifacts = artifacts + self.load_observable_artifacts(match) tags.update(self.load_tags(alert_config['tags'], match)) alert_config['artifacts'] = artifacts alert_config['tags'] = list(tags) # Populate the customFields alert_config['customFields'] = self.load_custom_fields( alert_config['customFields'], matches[0]) # POST the alert to TheHive connection_details = self.rule['hive_connection'] api_key = connection_details.get('hive_apikey', '') hive_host = connection_details.get('hive_host', 'http://localhost') hive_port = connection_details.get('hive_port', 9000) proxies = connection_details.get('hive_proxies', { 'http': '', 'https': '' }) verify = connection_details.get('hive_verify', False) alert_body = json.dumps(alert_config, indent=4, sort_keys=True) req = f'{hive_host}:{hive_port}/api/alert' headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {api_key}' } try: response = requests.post(req, headers=headers, data=alert_body, proxies=proxies, verify=verify) response.raise_for_status() except RequestException as e: raise EAException(f"Error posting to TheHive: {e}") elastalert_logger.info("Alert sent to TheHive")
def load_modules(self, rule, args=None): """ Loads things that could be modules. Enhancements, alerts and rule type. """ # Set match enhancements match_enhancements = [] for enhancement_name in rule.get('match_enhancements', []): if enhancement_name in dir(enhancements): enhancement = getattr(enhancements, enhancement_name) else: enhancement = get_module(enhancement_name) if not issubclass(enhancement, enhancements.BaseEnhancement): raise EAException( "Enhancement module %s not a subclass of BaseEnhancement" % enhancement_name) match_enhancements.append(enhancement(rule)) rule['match_enhancements'] = match_enhancements # Convert rule type into RuleType object if rule['type'] in self.rules_mapping: rule['type'] = self.rules_mapping[rule['type']] else: rule['type'] = get_module(rule['type']) if not issubclass(rule['type'], ruletypes.RuleType): raise EAException( 'Rule module %s is not a subclass of RuleType' % (rule['type'])) # Make sure we have required alert and type options reqs = rule['type'].required_options if reqs - frozenset(list(rule.keys())): raise EAException('Missing required option(s): %s' % (', '.join(reqs - frozenset(list(rule.keys()))))) # Instantiate rule try: rule['type'] = rule['type'](rule, args) except (KeyError, EAException) as e: raise EAException('Error initializing rule %s: %s' % (rule['name'], e)).with_traceback( sys.exc_info()[2]) # Instantiate alerts only if we're not in debug mode # In debug mode alerts are not actually sent so don't bother instantiating them if not args or not args.debug: rule['alert'] = self.load_alerts(rule, alert_field=rule['alert'])
def alert(self, matches): body = self.create_alert_body(matches) to_addr = self.email if 'ses_email_from_field' in self.rule: recipient = lookup_es_key(matches[0], self.rule['ses_email_from_field']) if isinstance(recipient, str): if '@' in recipient: to_addr = [recipient] elif 'ses_email_add_domain' in self.rule: to_addr = [recipient + self.rule['ses_email_add_domain']] elif isinstance(recipient, list): to_addr = recipient if 'ses_email_add_domain' in self.rule: to_addr = [ name + self.rule['ses_email_add_domain'] for name in to_addr ] try: if self.aws_profile != '': session = boto3.Session(profile_name=self.aws_profile) else: session = boto3.Session( aws_access_key_id=self.aws_access_key_id, aws_secret_access_key=self.aws_secret_access_key, region_name=self.aws_region) client = session.client('ses') client.send_email(Source=self.from_addr, Destination={ 'ToAddresses': to_addr, 'CcAddresses': self.rule.get('ses_cc', []), 'BccAddresses': self.rule.get('ses_bcc', []) }, Message={ 'Subject': { 'Charset': 'UTF-8', 'Data': self.create_title(matches), }, 'Body': { 'Text': { 'Charset': 'UTF-8', 'Data': body, } } }, ReplyToAddresses=self.rule.get( 'ses_email_reply_to', [])) except Exception as e: raise EAException("Error sending Amazon SES: %s" % e) elastalert_logger.info("Sent Amazon SES to %s" % (to_addr, ))
def __init__(self, rule): super(HttpPostAlerter, self).__init__(rule) self.url = self.rule['http_post_url'] self.post_data = self.rule.get('http_post_data') self.send_as_json = self.rule.get('http_post_data_as_json', True) self.headers = self.rule.get('http_post_headers') self.include_alert_subject = self.rule.get( 'http_post_include_alert_subject', False) self.include_alert_body = self.rule.get('http_post_include_alert_body', False) self.new_style_string_format = self.rule.get('new_style_string_format', False) if self.post_data and not isinstance(self.post_data, (basestring, dict)): raise EAException( 'http_post_data must be either a string or a dict.') if self.headers and not isinstance(self.headers, dict): raise EAException('http_post_headers must be a dict.')
def alert(self, matches): body = self.create_alert_body(matches) body = self.format_body(body) # post to Teams headers = {'content-type': 'application/json'} if self.ms_teams_ca_certs: verify = self.ms_teams_ca_certs else: verify = not self.ms_teams_ignore_ssl_errors if self.ms_teams_ignore_ssl_errors: requests.packages.urllib3.disable_warnings() # set https proxy, if it was provided proxies = {'https': self.ms_teams_proxy} if self.ms_teams_proxy else None payload = { '@type': 'MessageCard', '@context': 'http://schema.org/extensions', 'summary': self.ms_teams_alert_summary, 'title': self.create_title(matches), 'sections': [{'text': body}], } if self.ms_teams_alert_facts != '': payload['sections'][0]['facts'] = self.populate_facts(matches) if self.ms_teams_theme_color != '': payload['themeColor'] = self.ms_teams_theme_color if self.ms_teams_attach_kibana_discover_url: kibana_discover_url = lookup_es_key(matches[0], 'kibana_discover_url') if kibana_discover_url: payload['potentialAction'] = [ { '@type': 'OpenUri', 'name': self.ms_teams_kibana_discover_title, 'targets': [ { 'os': 'default', 'uri': kibana_discover_url, } ], } ] for url in self.ms_teams_webhook_url: try: response = requests.post(url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers, proxies=proxies, verify=verify) response.raise_for_status() except RequestException as e: raise EAException("Error posting to MS Teams: %s" % e) elastalert_logger.info("Alert sent to MS Teams")
def get_tenant_access_token(self): body = {"app_id": self.app_id, "app_secret": self.app_secret} try: r = get( "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/", json=body).json() if r['code'] == 0: # elastalert_logger.debug("token: " + r['tenant_access_token']) return r['tenant_access_token'] except Exception as e: raise EAException("Get tenant_access_token failed:".format(e))
def alert(self, matches): body = '' title = u'%s' % (self.create_title(matches)) for match in matches: body += str(BasicMatchString(self.rule, match)) if len(matches) > 1: body += '\n----------------------------------------\n' if len(body) > 2047: body = body[ 0: 1950] + '\n *message was cropped according to discord embed description limits!*' proxies = {'https': self.discord_proxy} if self.discord_proxy else None auth = HTTPProxyAuth( self.discord_proxy_login, self.discord_proxy_password) if self.discord_proxy_login else None headers = {"Content-Type": "application/json"} data = {} data["content"] = "%s %s %s" % (self.discord_emoji_title, title, self.discord_emoji_title) data["embeds"] = [] embed = {} embed["description"] = "%s" % (body) embed["color"] = (self.discord_embed_color) if self.discord_embed_footer: embed["footer"] = {} embed["footer"]["text"] = (self.discord_embed_footer ) if self.discord_embed_footer else None embed["footer"]["icon_url"] = ( self.discord_embed_icon_url ) if self.discord_embed_icon_url else None else: None data["embeds"].append(embed) try: response = requests.post(self.discord_webhook_url, data=json.dumps(data), headers=headers, proxies=proxies, auth=auth) warnings.resetwarnings() response.raise_for_status() except RequestException as e: raise EAException( "Error posting to Discord: %s. Details: %s" % (e, "" if e.response is None else e.response.text)) elastalert_logger.info("Alert sent to the webhook %s" % self.discord_webhook_url)