예제 #1
0
    def create_custom_title(self, matches):
        # Assume rule['alert_subject'] to be a jinja templated string. See Alerter.create_title()
        subject = self.rule['alert_subject']

        es_conn_conf = util.build_es_conn_config(self.rule)
        env = {
            'rule':
            self.rule,
            'matches':
            matches,
            'pipeline':
            self.pipeline,
            'jira_server':
            self.pipeline['jira_server'] if
            (self.pipeline and 'jira_server' in self.pipeline) else None,
            'jira_ticket':
            self.pipeline['jira_ticket'] if
            (self.pipeline and 'jira_ticket' in self.pipeline) else None,
            'es':
            util.elasticsearch_client(es_conn_conf),
            'util':
            util,
            'datetime':
            datetime,
        }

        return Environment().from_string(subject).render(**env)
예제 #2
0
 def _get_es_client(conf):
     """
     Created for testing reasons. Making it easier for us to mock this method.
     :param conf:
     :return:
     """
     return elasticsearch_client(conf)
예제 #3
0
    def get_all_terms(self, args):
        """ Performs a terms aggregation for each field to get every existing term. """
        self.es = elasticsearch_client(self.rules)
        window_size = datetime.timedelta(**self.rules.get('terms_window_size', {'days': 30}))
        field_name = {"field": "", "size": 2147483647}  # Integer.MAX_VALUE
        query_template = {"aggs": {"values": {"terms": field_name}}}
        if args and args.start:
            end = ts_to_dt(args.start)
        else:
            end = ts_now()
        start = end - window_size
        step = datetime.timedelta(**self.rules.get('window_step_size', {'days': 1}))

        for field in self.fields:
            tmp_start = start
            tmp_end = min(start + step, end)

            time_filter = {self.rules['timestamp_field']: {'lt': dt_to_ts(tmp_end), 'gte': dt_to_ts(tmp_start)}}
            query_template['filter'] = {'bool': {'must': [{'range': time_filter}]}}
            query = {'aggs': {'filtered': query_template}}
            # For composite keys, we will need to perform sub-aggregations
            if type(field) == list:
                self.seen_values.setdefault(tuple(field), [])
                level = query_template['aggs']
                # Iterate on each part of the composite key and add a sub aggs clause to the elastic search query
                for i, sub_field in enumerate(field):
                    level['values']['terms']['field'] = add_raw_postfix(sub_field)
                    if i < len(field) - 1:
                        # If we have more fields after the current one, then set up the next nested structure
                        level['values']['aggs'] = {'values': {'terms': copy.deepcopy(field_name)}}
                        level = level['values']['aggs']
            else:
                self.seen_values.setdefault(field, [])
                # For non-composite keys, only a single agg is needed
                field_name['field'] = add_raw_postfix(field)

            # Query the entire time range in small chunks
            while tmp_start < end:
                if self.rules.get('use_strftime_index'):
                    index = format_index(self.rules['index'], tmp_start, tmp_end)
                else:
                    index = self.rules['index']
                res = self.es.search(body=query, index=index, ignore_unavailable=True, timeout='50s')
                if 'aggregations' in res:
                    buckets = res['aggregations']['filtered']['values']['buckets']
                    if type(field) == list:
                        # For composite keys, make the lookup based on all fields
                        # Make it a tuple since it can be hashed and used in dictionary lookups
                        for bucket in buckets:
                            # We need to walk down the hierarchy and obtain the value at each level
                            self.seen_values[tuple(field)] += self.flatten_aggregation_hierarchy(bucket)
                    else:
                        keys = [bucket['key'] for bucket in buckets]
                        self.seen_values[field] += keys
                else:
                    self.seen_values.setdefault(field, [])
                if tmp_start == tmp_end:
                    break
                tmp_start = tmp_end
                tmp_end = min(tmp_start + step, end)
                time_filter[self.rules['timestamp_field']] = {'lt': dt_to_ts(tmp_end), 'gte': dt_to_ts(tmp_start)}

            for key, values in self.seen_values.iteritems():
                if not values:
                    if type(key) == tuple:
                        # If we don't have any results, it could either be because of the absence of any baseline data
                        # OR it may be because the composite key contained a non-primitive type.  Either way, give the
                        # end-users a heads up to help them debug what might be going on.
                        elastalert_logger.warning((
                            'No results were found from all sub-aggregations.  This can either indicate that there is '
                            'no baseline data OR that a non-primitive field was used in a composite key.'
                        ))
                    else:
                        elastalert_logger.info('Found no values for %s' % (field))
                    continue
                self.seen_values[key] = list(set(values))
                elastalert_logger.info('Found %s unique values for %s' % (len(values), key))
예제 #4
0
    def alert(self, matches):

        if False:
            print "*********** STACK ***********"
            import traceback
            traceback.print_stack()
            print "*********** MATCHES ***********"
            print matches
            print "*********** RULES ************"
            print self.rule

        if 'template_text_content' in self.rule:
            text = self.rule['template_text_content']
        elif 'template_text_file' in self.rule:
            with open(self.rule['template_text_file'], 'r') as f:
                text = f.read()
        else:
            text = '{{ matches|length }} items found'
        if 'template_html_content' in self.rule:
            html = self.rule['template_html_content']
        elif 'template_html_file' in self.rule:
            with open(self.rule['template_html_file'], 'r') as f:
                html = f.read()
        else:
            html = '{{ matches|length }} items found'

        if 'email_from_field' in self.rule:
            recipient = lookup_es_key(matches[0],
                                      self.rule['email_from_field'])
            if isinstance(recipient, basestring):
                if '@' in recipient:
                    to_addr = [recipient]
                elif 'email_add_domain' in self.rule:
                    to_addr = [recipient + self.rule['email_add_domain']]
            elif isinstance(recipient, list):
                to_addr = recipient
                if 'email_add_domain' in self.rule:
                    to_addr = [
                        name + self.rule['email_add_domain']
                        for name in to_addr
                    ]

        es_conn_conf = util.build_es_conn_config(self.rule)

        env = {
            'rule':
            self.rule,
            'matches':
            matches,
            'pipeline':
            self.pipeline,
            'jira_server':
            self.pipeline['jira_server'] if
            (self.pipeline and 'jira_server' in self.pipeline) else None,
            'jira_ticket':
            self.pipeline['jira_ticket'] if
            (self.pipeline and 'jira_ticket' in self.pipeline) else None,
            'es':
            util.elasticsearch_client(es_conn_conf),
            'json':
            json,
            'util':
            util,
            'datetime':
            datetime,
            'kibana_context_args':
            kibana4_context_args,
        }

        text = Environment().from_string(text).render(**env)
        html = Environment().from_string(html).render(**env)

        messageRoot = MIMEMultipart('related')
        messageRoot['Subject'] = _encode_str(self.create_title(matches))
        messageRoot['From'] = _encode_str(self.from_addr)
        if 'email_from_field' in self.rule:
            messageRoot['To'] = _convert_to_strings(to_addr)
        else:
            messageRoot['To'] = _convert_to_strings(self.rule['email'])

        if self.rule.get('email_reply_to'):
            messageRoot['Reply-To'] = _convert_to_strings(
                self.rule.get('email_reply_to'))

        messageRoot.preamble = 'This is a multi-part message in MIME format.'

        # Encapsulate the plain and HTML versions of the message body in an
        # 'alternative' part, so message agents can decide which they want to display.
        msgAlternative = MIMEMultipart('alternative')
        msgAlternative.attach(MIMEText(_encode_str(text), 'plain'))
        msgAlternative.attach(MIMEText(_encode_str(html), 'html'))
        messageRoot.attach(msgAlternative)

        try:
            if self.smtp_ssl:
                if self.smtp_port:
                    self.smtp = SMTP_SSL(self.smtp_host, self.smtp_port)
                else:
                    self.smtp = SMTP_SSL(self.smtp_host)
            else:
                if self.smtp_port:
                    self.smtp = SMTP(self.smtp_host, self.smtp_port)
                else:
                    self.smtp = SMTP(self.smtp_host)
                self.smtp.ehlo()
                if self.smtp.has_extn('STARTTLS'):
                    self.smtp.starttls()
            if 'smtp_auth_file' in self.rule:
                self.smtp.login(self.user, self.password)
        except (SMTPException, error) as e:
            raise EAException("Error connecting to SMTP host: %s" % (e))
        except SMTPAuthenticationError as e:
            raise EAException("SMTP username/password rejected: %s" % (e))

        self.smtp.sendmail(messageRoot['From'], self.rule['email'],
                           messageRoot.as_string())
        self.smtp.close()

        elastalert_logger.info("Sent email to %s for rule: %s" %
                               (self.rule['email'], self.rule['name']))