Miscellaneous notification fixes and tests
This commit is contained in:
parent
67c184a97c
commit
60bb0037f0
|
@ -101,6 +101,9 @@ def send_notification(event_type, data, targets, notification):
|
||||||
notification.plugin.send(event_type, data, targets, notification.options)
|
notification.plugin.send(event_type, data, targets, notification.options)
|
||||||
status = SUCCESS_METRIC_STATUS
|
status = SUCCESS_METRIC_STATUS
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
current_app.logger.error(
|
||||||
|
"Unable to send notification to {}.".format(targets), exc_info=True
|
||||||
|
)
|
||||||
sentry.captureException()
|
sentry.captureException()
|
||||||
|
|
||||||
metrics.send(
|
metrics.send(
|
||||||
|
@ -190,13 +193,13 @@ def send_rotation_notification(certificate, notification_plugin=None):
|
||||||
status = FAILURE_METRIC_STATUS
|
status = FAILURE_METRIC_STATUS
|
||||||
if not notification_plugin:
|
if not notification_plugin:
|
||||||
notification_plugin = plugins.get(
|
notification_plugin = plugins.get(
|
||||||
current_app.config.get("LEMUR_DEFAULT_NOTIFICATION_PLUGIN")
|
current_app.config.get("LEMUR_DEFAULT_NOTIFICATION_PLUGIN", "email-notification")
|
||||||
)
|
)
|
||||||
|
|
||||||
data = certificate_notification_output_schema.dump(certificate).data
|
data = certificate_notification_output_schema.dump(certificate).data
|
||||||
|
|
||||||
try:
|
try:
|
||||||
notification_plugin.send("rotation", data, [data["owner"]])
|
notification_plugin.send("rotation", data, [data["owner"]], [])
|
||||||
status = SUCCESS_METRIC_STATUS
|
status = SUCCESS_METRIC_STATUS
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
|
@ -290,7 +293,7 @@ def needs_notification(certificate):
|
||||||
|
|
||||||
for notification in certificate.notifications:
|
for notification in certificate.notifications:
|
||||||
if not notification.active or not notification.options:
|
if not notification.active or not notification.options:
|
||||||
return
|
continue
|
||||||
|
|
||||||
interval = get_plugin_option("interval", notification.options)
|
interval = get_plugin_option("interval", notification.options)
|
||||||
unit = get_plugin_option("unit", notification.options)
|
unit = get_plugin_option("unit", notification.options)
|
||||||
|
|
|
@ -19,14 +19,16 @@ from lemur.plugins import lemur_email as email
|
||||||
from lemur.plugins.lemur_email.templates.config import env
|
from lemur.plugins.lemur_email.templates.config import env
|
||||||
|
|
||||||
|
|
||||||
def render_html(template_name, message):
|
def render_html(template_name, options, certificates):
|
||||||
"""
|
"""
|
||||||
Renders the html for our email notification.
|
Renders the html for our email notification.
|
||||||
|
|
||||||
:param template_name:
|
:param template_name:
|
||||||
:param message:
|
:param options:
|
||||||
|
:param certificates:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
message = {"options": options, "certificates": certificates}
|
||||||
template = env.get_template("{}.html".format(template_name))
|
template = env.get_template("{}.html".format(template_name))
|
||||||
return template.render(
|
return template.render(
|
||||||
dict(message=message, hostname=current_app.config.get("LEMUR_HOSTNAME"))
|
dict(message=message, hostname=current_app.config.get("LEMUR_HOSTNAME"))
|
||||||
|
@ -100,8 +102,7 @@ class EmailNotificationPlugin(ExpirationNotificationPlugin):
|
||||||
|
|
||||||
subject = "Lemur: {0} Notification".format(notification_type.capitalize())
|
subject = "Lemur: {0} Notification".format(notification_type.capitalize())
|
||||||
|
|
||||||
data = {"options": options, "certificates": message}
|
body = render_html(notification_type, options, message)
|
||||||
body = render_html(notification_type, data)
|
|
||||||
|
|
||||||
s_type = current_app.config.get("LEMUR_EMAIL_SENDER", "ses").lower()
|
s_type = current_app.config.get("LEMUR_EMAIL_SENDER", "ses").lower()
|
||||||
|
|
||||||
|
|
|
@ -83,12 +83,12 @@
|
||||||
<td width="32px"></td>
|
<td width="32px"></td>
|
||||||
<td width="16px"></td>
|
<td width="16px"></td>
|
||||||
<td style="line-height:1.2">
|
<td style="line-height:1.2">
|
||||||
<span style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:20px;color:#202020">{{ certificate.name }}</span>
|
<span style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:20px;color:#202020">{{ message.certificates.name }}</span>
|
||||||
<br>
|
<br>
|
||||||
<span style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:13px;color:#727272">
|
<span style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:13px;color:#727272">
|
||||||
<br>{{ certificate.owner }}
|
<br>{{ message.certificates.owner }}
|
||||||
<br>{{ certificate.validityEnd | time }}
|
<br>{{ message.certificates.validityEnd | time }}
|
||||||
<a href="https://{{ hostname }}/#/certificates/{{ certificate.name }}" target="_blank">Details</a>
|
<a href="https://{{ hostname }}/#/certificates/{{ message.certificates.name }}" target="_blank">Details</a>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -110,12 +110,12 @@
|
||||||
<td width="32px"></td>
|
<td width="32px"></td>
|
||||||
<td width="16px"></td>
|
<td width="16px"></td>
|
||||||
<td style="line-height:1.2">
|
<td style="line-height:1.2">
|
||||||
<span style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:20px;color:#202020">{{ certificate.replacedBy[0].name }}</span>
|
<span style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:20px;color:#202020">{{ message.certificates.name }}</span>
|
||||||
<br>
|
<br>
|
||||||
<span style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:13px;color:#727272">
|
<span style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:13px;color:#727272">
|
||||||
<br>{{ certificate.replacedBy[0].owner }}
|
<br>{{ message.certificates.owner }}
|
||||||
<br>{{ certificate.replacedBy[0].validityEnd | time }}
|
<br>{{ message.certificates.validityEnd | time }}
|
||||||
<a href="https://{{ hostname }}/#/certificates/{{ certificate.replacedBy[0].name }}" target="_blank">Details</a>
|
<a href="https://{{ hostname }}/#/certificates/{{ message.certificates.name }}" target="_blank">Details</a>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -133,7 +133,7 @@
|
||||||
<table border="0" cellspacing="0" cellpadding="0"
|
<table border="0" cellspacing="0" cellpadding="0"
|
||||||
style="margin-top:48px;margin-bottom:48px">
|
style="margin-top:48px;margin-bottom:48px">
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for endpoint in certificate.endpoints %}
|
{% for endpoint in message.certificates.endpoints %}
|
||||||
<tr valign="middle">
|
<tr valign="middle">
|
||||||
<td width="32px"></td>
|
<td width="32px"></td>
|
||||||
<td width="16px"></td>
|
<td width="16px"></td>
|
||||||
|
|
|
@ -1,36 +1,83 @@
|
||||||
import os
|
import os
|
||||||
from lemur.plugins.lemur_email.templates.config import env
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import arrow
|
||||||
|
import boto3
|
||||||
|
from moto import mock_ses
|
||||||
|
|
||||||
|
from lemur.certificates.schemas import certificate_notification_output_schema
|
||||||
|
from lemur.plugins.lemur_email.plugin import render_html
|
||||||
from lemur.tests.factories import CertificateFactory
|
from lemur.tests.factories import CertificateFactory
|
||||||
|
|
||||||
dir_path = os.path.dirname(os.path.realpath(__file__))
|
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
|
||||||
def test_render(certificate, endpoint):
|
@mock_ses
|
||||||
from lemur.certificates.schemas import certificate_notification_output_schema
|
def verify_sender_email():
|
||||||
|
ses_client = boto3.client("ses", region_name="us-east-1")
|
||||||
|
ses_client.verify_email_identity(EmailAddress="lemur@example.com")
|
||||||
|
|
||||||
|
|
||||||
|
def get_options():
|
||||||
|
return [
|
||||||
|
{"name": "interval", "value": 10},
|
||||||
|
{"name": "unit", "value": "days"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_expiration(certificate, endpoint):
|
||||||
|
|
||||||
new_cert = CertificateFactory()
|
new_cert = CertificateFactory()
|
||||||
new_cert.replaces.append(certificate)
|
new_cert.replaces.append(certificate)
|
||||||
|
|
||||||
data = {
|
assert render_html("expiration", get_options(), [certificate_notification_output_schema.dump(certificate).data])
|
||||||
"certificates": [certificate_notification_output_schema.dump(certificate).data],
|
|
||||||
"options": [
|
|
||||||
{"name": "interval", "value": 10},
|
|
||||||
{"name": "unit", "value": "days"},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
template = env.get_template("{}.html".format("expiration"))
|
|
||||||
|
|
||||||
body = template.render(dict(message=data, hostname="lemur.test.example.com"))
|
|
||||||
|
|
||||||
template = env.get_template("{}.html".format("rotation"))
|
|
||||||
|
|
||||||
|
def test_render_rotation(certificate, endpoint):
|
||||||
certificate.endpoints.append(endpoint)
|
certificate.endpoints.append(endpoint)
|
||||||
|
|
||||||
body = template.render(
|
assert render_html("rotation", get_options(), certificate_notification_output_schema.dump(certificate).data)
|
||||||
dict(
|
|
||||||
certificate=certificate_notification_output_schema.dump(certificate).data,
|
|
||||||
hostname="lemur.test.example.com",
|
def test_render_rotation_failure(pending_certificate):
|
||||||
)
|
assert render_html("failed", get_options(), certificate_notification_output_schema.dump(pending_certificate).data)
|
||||||
)
|
|
||||||
|
|
||||||
|
@mock_ses
|
||||||
|
def test_send_expiration_notification():
|
||||||
|
from lemur.notifications.messaging import send_expiration_notifications
|
||||||
|
from lemur.tests.factories import CertificateFactory
|
||||||
|
from lemur.tests.factories import NotificationFactory
|
||||||
|
|
||||||
|
now = arrow.utcnow()
|
||||||
|
in_ten_days = now + timedelta(days=10, hours=1) # a bit more than 10 days since we'll check in the future
|
||||||
|
certificate = CertificateFactory()
|
||||||
|
notification = NotificationFactory(plugin_name="email-notification")
|
||||||
|
|
||||||
|
certificate.not_after = in_ten_days
|
||||||
|
certificate.notifications.append(notification)
|
||||||
|
certificate.notifications[0].options = get_options()
|
||||||
|
|
||||||
|
verify_sender_email()
|
||||||
|
assert send_expiration_notifications([]) == (2, 0)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ses
|
||||||
|
def test_send_rotation_notification(endpoint, source_plugin):
|
||||||
|
from lemur.notifications.messaging import send_rotation_notification
|
||||||
|
from lemur.deployment.service import rotate_certificate
|
||||||
|
|
||||||
|
new_certificate = CertificateFactory()
|
||||||
|
rotate_certificate(endpoint, new_certificate)
|
||||||
|
assert endpoint.certificate == new_certificate
|
||||||
|
|
||||||
|
verify_sender_email()
|
||||||
|
assert send_rotation_notification(new_certificate)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ses
|
||||||
|
def test_send_pending_failure_notification(user, pending_certificate, async_issuer_plugin):
|
||||||
|
from lemur.notifications.messaging import send_pending_failure_notification
|
||||||
|
|
||||||
|
verify_sender_email()
|
||||||
|
assert send_pending_failure_notification(pending_certificate)
|
||||||
|
|
|
@ -58,26 +58,19 @@ def create_rotation_attachments(certificate):
|
||||||
"title": certificate["name"],
|
"title": certificate["name"],
|
||||||
"title_link": create_certificate_url(certificate["name"]),
|
"title_link": create_certificate_url(certificate["name"]),
|
||||||
"fields": [
|
"fields": [
|
||||||
|
{"title": "Owner", "value": certificate["owner"], "short": True},
|
||||||
{
|
{
|
||||||
{"title": "Owner", "value": certificate["owner"], "short": True},
|
"title": "Expires",
|
||||||
{
|
"value": arrow.get(certificate["validityEnd"]).format(
|
||||||
"title": "Expires",
|
"dddd, MMMM D, YYYY"
|
||||||
"value": arrow.get(certificate["validityEnd"]).format(
|
),
|
||||||
"dddd, MMMM D, YYYY"
|
"short": True,
|
||||||
),
|
},
|
||||||
"short": True,
|
{
|
||||||
},
|
"title": "Endpoints Rotated",
|
||||||
{
|
"value": len(certificate["endpoints"]),
|
||||||
"title": "Replaced By",
|
"short": True,
|
||||||
"value": len(certificate["replaced"][0]["name"]),
|
},
|
||||||
"short": True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Endpoints Rotated",
|
|
||||||
"value": len(certificate["endpoints"]),
|
|
||||||
"short": True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,3 +21,48 @@ def test_formatting(certificate):
|
||||||
}
|
}
|
||||||
|
|
||||||
assert attachment == create_expiration_attachments(data)[0]
|
assert attachment == create_expiration_attachments(data)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_options():
|
||||||
|
return [
|
||||||
|
{"name": "interval", "value": 10},
|
||||||
|
{"name": "unit", "value": "days"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Currently disabled as we have no good way to mock Slack webhooks
|
||||||
|
# def test_send_expiration_notification():
|
||||||
|
# from lemur.notifications.messaging import send_expiration_notifications
|
||||||
|
# from lemur.tests.factories import CertificateFactory
|
||||||
|
#
|
||||||
|
# now = arrow.utcnow()
|
||||||
|
# in_ten_days = now + timedelta(days=10, hours=1) # a bit more than 10 days since we'll check in the future
|
||||||
|
# certificate = CertificateFactory()
|
||||||
|
# notification = NotificationFactory(plugin_name="slack-notification")
|
||||||
|
#
|
||||||
|
# certificate.not_after = in_ten_days
|
||||||
|
# certificate.notifications.append(notification)
|
||||||
|
# certificate.notifications[0].options = get_options()
|
||||||
|
#
|
||||||
|
# assert send_expiration_notifications([]) == (2, 0)
|
||||||
|
|
||||||
|
|
||||||
|
# Currently disabled as we have no good way to mock Slack webhooks
|
||||||
|
# def test_send_rotation_notification(endpoint, source_plugin):
|
||||||
|
# from lemur.notifications.messaging import send_rotation_notification
|
||||||
|
# from lemur.deployment.service import rotate_certificate
|
||||||
|
#
|
||||||
|
# notification = NotificationFactory(plugin_name="slack-notification")
|
||||||
|
#
|
||||||
|
# new_certificate = CertificateFactory()
|
||||||
|
# rotate_certificate(endpoint, new_certificate)
|
||||||
|
# assert endpoint.certificate == new_certificate
|
||||||
|
#
|
||||||
|
# assert send_rotation_notification(new_certificate, notification_plugin=notification.plugin)
|
||||||
|
|
||||||
|
|
||||||
|
# Currently disabled as the Slack plugin doesn't support this type of notification
|
||||||
|
# def test_send_pending_failure_notification(user, pending_certificate, async_issuer_plugin):
|
||||||
|
# from lemur.notifications.messaging import send_pending_failure_notification
|
||||||
|
#
|
||||||
|
# assert send_pending_failure_notification(pending_certificate, notification_plugin=plugins.get("slack-notification"))
|
||||||
|
|
|
@ -46,7 +46,7 @@ LEMUR_ALLOWED_DOMAINS = [
|
||||||
|
|
||||||
# Lemur currently only supports SES for sending email, this address
|
# Lemur currently only supports SES for sending email, this address
|
||||||
# needs to be verified
|
# needs to be verified
|
||||||
LEMUR_EMAIL = ""
|
LEMUR_EMAIL = "lemur@example.com"
|
||||||
LEMUR_SECURITY_TEAM_EMAIL = ["security@example.com"]
|
LEMUR_SECURITY_TEAM_EMAIL = ["security@example.com"]
|
||||||
|
|
||||||
LEMUR_HOSTNAME = "lemur.example.com"
|
LEMUR_HOSTNAME = "lemur.example.com"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import arrow
|
||||||
import pytest
|
import pytest
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
import arrow
|
|
||||||
from moto import mock_ses
|
from moto import mock_ses
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,4 +105,11 @@ def test_send_expiration_notification_with_no_notifications(
|
||||||
def test_send_rotation_notification(notification_plugin, certificate):
|
def test_send_rotation_notification(notification_plugin, certificate):
|
||||||
from lemur.notifications.messaging import send_rotation_notification
|
from lemur.notifications.messaging import send_rotation_notification
|
||||||
|
|
||||||
send_rotation_notification(certificate, notification_plugin=notification_plugin)
|
assert send_rotation_notification(certificate, notification_plugin=notification_plugin)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ses
|
||||||
|
def test_send_pending_failure_notification(notification_plugin, async_issuer_plugin, pending_certificate):
|
||||||
|
from lemur.notifications.messaging import send_pending_failure_notification
|
||||||
|
|
||||||
|
assert send_pending_failure_notification(pending_certificate, notification_plugin=notification_plugin)
|
||||||
|
|
Loading…
Reference in New Issue