diff --git a/docs/source/ruletypes.rst b/docs/source/ruletypes.rst index 19d0d547..bd145aea 100644 --- a/docs/source/ruletypes.rst +++ b/docs/source/ruletypes.rst @@ -1735,6 +1735,65 @@ Example usage:: Environment: '$VAR' # environment variable Message: { field: message } # field in the first match +AWS SES +~~~~~~~ + +The AWS SES alerter is similar to Email alerter but uses AWS SES to send emails. The AWS SES alerter can use AWS credentials +from the rule yaml, standard AWS config files or environment variables. + +AWS SES requires one option: + +``ses_email``: An address or list of addresses to sent the alert to. + +``ses_from_addr``: This sets the From header in the email. + +Optional: + +``ses_aws_access_key``: An access key to connect to AWS SES with. + +``ses_aws_secret_key``: The secret key associated with the access key. + +``ses_aws_region``: The AWS region in which the AWS SES resource is located. Default is us-east-1 + +``ses_aws_profile``: The AWS profile to use. If none specified, the default will be used. + +``ses_email_reply_to``: This sets the Reply-To header in the email. + +``ses_cc``: This adds the CC emails to the list of recipients. By default, this is left empty. + +``ses_bcc``: This adds the BCC emails to the list of recipients but does not show up in the email message. By default, this is left empty. + +Example When not using aws_profile usage:: + + alert: + - "ses" + ses_aws_access_key_id: "XXXXXXXXXXXXXXXXXX'" + ses_aws_secret_access_key: "YYYYYYYYYYYYYYYYYYYY" + ses_aws_region: "us-east-1" + ses_from_addr: "xxxx1@xxx.com" + ses_email: "xxxx1@xxx.com" + +Example When to use aws_profile usage:: + + # Create ~/.aws/credentials + + [default] + aws_access_key_id = xxxxxxxxxxxxxxxxxxxx + aws_secret_access_key = yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + + # Create ~/.aws/config + + [default] + region = us-east-1 + + # alert rule setting + + alert: + - "ses" + ses_aws_profile: "default" + ses_from_addr: "xxxx1@xxx.com" + ses_email: "xxxx1@xxx.com" + AWS SNS ~~~~~~~ diff --git a/elastalert/alerts.py b/elastalert/alerts.py index 7d09272b..baa16e90 100644 --- a/elastalert/alerts.py +++ b/elastalert/alerts.py @@ -2274,3 +2274,108 @@ def alert(self, matches): def get_info(self): return {'type': 'datadog'} + + +class SesAlerter(Alerter): + """ Sends an email alert using AWS SES """ + required_options = frozenset(['ses_email', 'ses_from_addr']) + + def __init__(self, *args): + super(SesAlerter, self).__init__(*args) + + self.aws_access_key_id = self.rule.get('ses_aws_access_key_id') + self.aws_secret_access_key = self.rule.get('ses_aws_secret_access_key') + self.aws_region = self.rule.get('ses_aws_region', 'us-east-1') + self.aws_profile = self.rule.get('ses_aws_profile', '') + + self.from_addr = self.rule.get('ses_from_addr') + + # Convert email to a list if it isn't already + if isinstance(self.rule['ses_email'], str): + self.rule['ses_email'] = [self.rule['ses_email']] + + # If there is a cc then also convert it a list if it isn't + cc = self.rule.get('ses_cc') + if cc and isinstance(cc, str): + self.rule['ses_cc'] = [self.rule['ses_cc']] + + # If there is a bcc then also convert it to a list if it isn't + bcc = self.rule.get('ses_bcc') + if bcc and isinstance(bcc, str): + self.rule['ses_bcc'] = [self.rule['ses_bcc']] + + # If there is a email_reply_to then also convert it to a list if it isn't + reply_to = self.rule.get('ses_email_reply_to') + if reply_to and isinstance(reply_to, str): + self.rule['ses_email_reply_to'] = [self.rule['ses_email_reply_to']] + + add_suffix = self.rule.get('ses_email_add_domain') + if add_suffix and not add_suffix.startswith('@'): + self.rule['ses_email_add_domain'] = '@' + add_suffix + + def alert(self, matches): + body = self.create_alert_body(matches) + + to_addr = self.rule['ses_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] + + 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') + try: + 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 ses: %s" % (e,)) + + elastalert_logger.info("Sent ses to %s" % (to_addr,)) + + def create_default_title(self, matches): + subject = 'Elastalert2: %s' % (self.rule['name']) + + # If the rule has a query_key, add that value plus timestamp to subject + if 'query_key' in self.rule: + qk = matches[0].get(self.rule['query_key']) + if qk: + subject += ' - %s' % (qk) + + return subject + + def get_info(self): + return {'type': 'ses', + 'recipients': self.rule['ses_email']} diff --git a/elastalert/loaders.py b/elastalert/loaders.py index 0cbd0d26..67e2d9a8 100644 --- a/elastalert/loaders.py +++ b/elastalert/loaders.py @@ -86,7 +86,8 @@ class RulesLoader(object): 'discord': alerts.DiscordAlerter, 'dingtalk': alerts.DingTalkAlerter, 'chatwork': alerts.ChatworkAlerter, - 'datadog': alerts.DatadogAlerter + 'datadog': alerts.DatadogAlerter, + 'ses': alerts.SesAlerter } # A partial ordering of alert types. Relative order will be preserved in the resulting alerts list