Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for AWS SES #105

Merged
merged 1 commit into from
May 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions docs/source/ruletypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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: "[email protected]"
ses_email: "[email protected]"

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: "[email protected]"
ses_email: "[email protected]"

AWS SNS
~~~~~~~

Expand Down
105 changes: 105 additions & 0 deletions elastalert/alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']}
3 changes: 2 additions & 1 deletion elastalert/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down