Merge
This commit is contained in:
commit
d6075ebc11
|
@ -155,17 +155,12 @@ Specifying the `SQLALCHEMY_MAX_OVERFLOW` to 0 will enforce limit to not create c
|
||||||
|
|
||||||
LEMUR_ENCRYPTION_KEYS = ['1YeftooSbxCiX2zo8m1lXtpvQjy27smZcUUaGmffhMY=', 'LAfQt6yrkLqOK5lwpvQcT4jf2zdeTQJV1uYeh9coT5s=']
|
LEMUR_ENCRYPTION_KEYS = ['1YeftooSbxCiX2zo8m1lXtpvQjy27smZcUUaGmffhMY=', 'LAfQt6yrkLqOK5lwpvQcT4jf2zdeTQJV1uYeh9coT5s=']
|
||||||
|
|
||||||
.. data:: PUBLIC_CA_AUTHORITY_NAMES
|
|
||||||
:noindex:
|
|
||||||
A list of public issuers which would be checked against to determine whether limit of max validity of 397 days
|
|
||||||
should be applied to the certificate. Configure public CA authority names in this list to enforce validity check.
|
|
||||||
This is an optional setting. Using this will allow the sanity check as mentioned. The name check is a case-insensitive
|
|
||||||
string comparision.
|
|
||||||
|
|
||||||
.. data:: PUBLIC_CA_MAX_VALIDITY_DAYS
|
.. data:: PUBLIC_CA_MAX_VALIDITY_DAYS
|
||||||
:noindex:
|
:noindex:
|
||||||
Use this config to override the limit of 397 days of validity for certificates issued by public issuers configured
|
Use this config to override the limit of 397 days of validity for certificates issued by CA/Browser compliant authorities.
|
||||||
using PUBLIC_CA_AUTHORITY_NAMES. Below example overrides the default validity of 397 days and sets it to 365 days.
|
The authorities with cab_compliant option set to true will use this config. The example below overrides the default validity
|
||||||
|
of 397 days and sets it to 365 days.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
@ -175,8 +170,8 @@ Specifying the `SQLALCHEMY_MAX_OVERFLOW` to 0 will enforce limit to not create c
|
||||||
.. data:: DEFAULT_VALIDITY_DAYS
|
.. data:: DEFAULT_VALIDITY_DAYS
|
||||||
:noindex:
|
:noindex:
|
||||||
Use this config to override the default validity of 365 days for certificates offered through Lemur UI. Any CA which
|
Use this config to override the default validity of 365 days for certificates offered through Lemur UI. Any CA which
|
||||||
is not listed in PUBLIC_CA_AUTHORITY_NAMES will be using this value as default validity to be displayed on UI. Please
|
is not CA/Browser Forum compliant will be using this value as default validity to be displayed on UI. Please
|
||||||
note that this config is used for cert issuance only through Lemur UI. Below example overrides the default validity
|
note that this config is used for cert issuance only through Lemur UI. The example below overrides the default validity
|
||||||
of 365 days and sets it to 1095 days (3 years).
|
of 365 days and sets it to 1095 days (3 years).
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
Column,
|
Column,
|
||||||
|
@ -98,5 +99,17 @@ class Authority(db.Model):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_issuance_days(self):
|
||||||
|
if self.is_cab_compliant:
|
||||||
|
return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_validity_days(self):
|
||||||
|
if self.is_cab_compliant:
|
||||||
|
return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397)
|
||||||
|
|
||||||
|
return current_app.config.get("DEFAULT_VALIDITY_DAYS", 365) # 1 year default
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "Authority(name={name})".format(name=self.name)
|
return "Authority(name={name})".format(name=self.name)
|
||||||
|
|
|
@ -111,8 +111,6 @@ class RootAuthorityCertificateOutputSchema(LemurOutputSchema):
|
||||||
cn = fields.String()
|
cn = fields.String()
|
||||||
not_after = fields.DateTime()
|
not_after = fields.DateTime()
|
||||||
not_before = fields.DateTime()
|
not_before = fields.DateTime()
|
||||||
max_issuance_days = fields.Integer()
|
|
||||||
default_validity_days = fields.Integer()
|
|
||||||
owner = fields.Email()
|
owner = fields.Email()
|
||||||
status = fields.Boolean()
|
status = fields.Boolean()
|
||||||
user = fields.Nested(UserNestedOutputSchema)
|
user = fields.Nested(UserNestedOutputSchema)
|
||||||
|
@ -127,6 +125,8 @@ class AuthorityOutputSchema(LemurOutputSchema):
|
||||||
active = fields.Boolean()
|
active = fields.Boolean()
|
||||||
options = fields.Dict()
|
options = fields.Dict()
|
||||||
roles = fields.List(fields.Nested(AssociatedRoleSchema))
|
roles = fields.List(fields.Nested(AssociatedRoleSchema))
|
||||||
|
max_issuance_days = fields.Integer()
|
||||||
|
default_validity_days = fields.Integer()
|
||||||
authority_certificate = fields.Nested(RootAuthorityCertificateOutputSchema)
|
authority_certificate = fields.Nested(RootAuthorityCertificateOutputSchema)
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,8 +138,10 @@ class AuthorityNestedOutputSchema(LemurOutputSchema):
|
||||||
owner = fields.Email()
|
owner = fields.Email()
|
||||||
plugin = fields.Nested(PluginOutputSchema)
|
plugin = fields.Nested(PluginOutputSchema)
|
||||||
active = fields.Boolean()
|
active = fields.Boolean()
|
||||||
authority_certificate = fields.Nested(RootAuthorityCertificateOutputSchema, only=["max_issuance_days", "default_validity_days"])
|
authority_certificate = fields.Nested(RootAuthorityCertificateOutputSchema, only=["not_after", "not_before"])
|
||||||
is_cab_compliant = fields.Boolean()
|
is_cab_compliant = fields.Boolean()
|
||||||
|
max_issuance_days = fields.Integer()
|
||||||
|
default_validity_days = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
authority_update_schema = AuthorityUpdateSchema()
|
authority_update_schema = AuthorityUpdateSchema()
|
||||||
|
|
|
@ -317,20 +317,6 @@ class Certificate(db.Model):
|
||||||
def validity_range(self):
|
def validity_range(self):
|
||||||
return self.not_after - self.not_before
|
return self.not_after - self.not_before
|
||||||
|
|
||||||
@property
|
|
||||||
def max_issuance_days(self):
|
|
||||||
public_CA = current_app.config.get("PUBLIC_CA_AUTHORITY_NAMES", [])
|
|
||||||
if self.name.lower() in [ca.lower() for ca in public_CA]:
|
|
||||||
return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def default_validity_days(self):
|
|
||||||
public_CA = current_app.config.get("PUBLIC_CA_AUTHORITY_NAMES", [])
|
|
||||||
if self.name.lower() in [ca.lower() for ca in public_CA]:
|
|
||||||
return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397)
|
|
||||||
|
|
||||||
return current_app.config.get("DEFAULT_VALIDITY_DAYS", 365) # 1 year default
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def subject(self):
|
def subject(self):
|
||||||
return self.parsed_cert.subject
|
return self.parsed_cert.subject
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
@ -36,8 +37,6 @@ def get_certificates(exclude=None):
|
||||||
now = arrow.utcnow()
|
now = arrow.utcnow()
|
||||||
max = now + timedelta(days=90)
|
max = now + timedelta(days=90)
|
||||||
|
|
||||||
print("ALPACA: Checking for certs not after {0} with notify enabled and not expired".format(max))
|
|
||||||
|
|
||||||
q = (
|
q = (
|
||||||
database.db.session.query(Certificate)
|
database.db.session.query(Certificate)
|
||||||
.filter(Certificate.not_after <= max)
|
.filter(Certificate.not_after <= max)
|
||||||
|
@ -45,8 +44,6 @@ def get_certificates(exclude=None):
|
||||||
.filter(Certificate.expired == False)
|
.filter(Certificate.expired == False)
|
||||||
) # noqa
|
) # noqa
|
||||||
|
|
||||||
print("ALPACA: Excluding {0}".format(exclude))
|
|
||||||
|
|
||||||
exclude_conditions = []
|
exclude_conditions = []
|
||||||
if exclude:
|
if exclude:
|
||||||
for e in exclude:
|
for e in exclude:
|
||||||
|
@ -60,8 +57,6 @@ def get_certificates(exclude=None):
|
||||||
if needs_notification(c):
|
if needs_notification(c):
|
||||||
certs.append(c)
|
certs.append(c)
|
||||||
|
|
||||||
print("ALPACA: Found {0} eligible certs".format(len(certs)))
|
|
||||||
|
|
||||||
return certs
|
return certs
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,27 +70,21 @@ def get_eligible_certificates(exclude=None):
|
||||||
certificates = defaultdict(dict)
|
certificates = defaultdict(dict)
|
||||||
certs = get_certificates(exclude=exclude)
|
certs = get_certificates(exclude=exclude)
|
||||||
|
|
||||||
print("ALPACA: Found {0} certificates to check for notifications".format(len(certs)))
|
|
||||||
|
|
||||||
# group by owner
|
# group by owner
|
||||||
for owner, items in groupby(certs, lambda x: x.owner):
|
for owner, items in groupby(certs, lambda x: x.owner):
|
||||||
notification_groups = []
|
notification_groups = []
|
||||||
|
|
||||||
for certificate in items:
|
for certificate in items:
|
||||||
notifications = needs_notification(certificate)
|
notifications = needs_notification(certificate)
|
||||||
print("ALPACA: Considering sending {0} notifications for cert {1}".format(len(notifications), certificate))
|
|
||||||
|
|
||||||
if notifications:
|
if notifications:
|
||||||
for notification in notifications:
|
for notification in notifications:
|
||||||
print("ALPACA: Will send notification {0} for certificate {1}".format(notification, certificate))
|
|
||||||
notification_groups.append((notification, certificate))
|
notification_groups.append((notification, certificate))
|
||||||
|
|
||||||
# group by notification
|
# group by notification
|
||||||
for notification, items in groupby(notification_groups, lambda x: x[0].label):
|
for notification, items in groupby(notification_groups, lambda x: x[0].label):
|
||||||
certificates[owner][notification] = list(items)
|
certificates[owner][notification] = list(items)
|
||||||
|
|
||||||
print("ALPACA: Certificates that need notifications: {0}".format(certificates))
|
|
||||||
|
|
||||||
return certificates
|
return certificates
|
||||||
|
|
||||||
|
|
||||||
|
@ -109,15 +98,20 @@ def send_plugin_notification(event_type, data, recipients, notification):
|
||||||
:param notification:
|
:param notification:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
function = f"{__name__}.{sys._getframe().f_code.co_name}"
|
||||||
|
log_data = {
|
||||||
|
"function": function,
|
||||||
|
"message": f"Sending expiration notification for to recipients {recipients}",
|
||||||
|
"notification_type": "expiration",
|
||||||
|
"certificate_targets": recipients,
|
||||||
|
}
|
||||||
status = FAILURE_METRIC_STATUS
|
status = FAILURE_METRIC_STATUS
|
||||||
try:
|
try:
|
||||||
print("ALPACA: Trying to send notification {0} (plugin: {1})".format(notification, notification.plugin))
|
|
||||||
notification.plugin.send(event_type, data, recipients, notification.options)
|
notification.plugin.send(event_type, data, recipients, notification.options)
|
||||||
status = SUCCESS_METRIC_STATUS
|
status = SUCCESS_METRIC_STATUS
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
log_data["message"] = f"Unable to send expiration notification to recipients {recipients}"
|
||||||
"Unable to send notification {}.".format(notification), exc_info=True
|
current_app.logger.error(log_data, exc_info=True)
|
||||||
)
|
|
||||||
sentry.captureException()
|
sentry.captureException()
|
||||||
|
|
||||||
metrics.send(
|
metrics.send(
|
||||||
|
@ -157,8 +151,6 @@ def send_expiration_notifications(exclude):
|
||||||
notification_data.append(cert_data)
|
notification_data.append(cert_data)
|
||||||
security_data.append(cert_data)
|
security_data.append(cert_data)
|
||||||
|
|
||||||
print("ALPACA: Sending owner notification to {0} for certificate {1}. Data: {2}".format(owner, certificates, notification_data))
|
|
||||||
|
|
||||||
if send_default_notification(
|
if send_default_notification(
|
||||||
"expiration", notification_data, [owner], notification.options
|
"expiration", notification_data, [owner], notification.options
|
||||||
):
|
):
|
||||||
|
@ -166,9 +158,8 @@ def send_expiration_notifications(exclude):
|
||||||
else:
|
else:
|
||||||
failure += 1
|
failure += 1
|
||||||
|
|
||||||
recipients = notification.plugin.filter_recipients(security_email + [owner], notification.options)
|
recipients = notification.plugin.filter_recipients(notification.options, security_email + [owner])
|
||||||
|
|
||||||
print("ALPACA: Sending plugin notification {0} for certificate {1} to recipients {2}".format(notification, certificates, recipients))
|
|
||||||
if send_plugin_notification(
|
if send_plugin_notification(
|
||||||
"expiration",
|
"expiration",
|
||||||
notification_data,
|
notification_data,
|
||||||
|
@ -179,7 +170,6 @@ def send_expiration_notifications(exclude):
|
||||||
else:
|
else:
|
||||||
failure += 1
|
failure += 1
|
||||||
|
|
||||||
print("ALPACA: Sending security notification to {0}".format(security_email))
|
|
||||||
if send_default_notification(
|
if send_default_notification(
|
||||||
"expiration", security_data, security_email, notification.options
|
"expiration", security_data, security_email, notification.options
|
||||||
):
|
):
|
||||||
|
@ -201,6 +191,12 @@ def send_default_notification(notification_type, data, targets, notification_opt
|
||||||
:param notification_options:
|
:param notification_options:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
function = f"{__name__}.{sys._getframe().f_code.co_name}"
|
||||||
|
log_data = {
|
||||||
|
"function": function,
|
||||||
|
"message": f"Sending notification for certificate data {data}",
|
||||||
|
"notification_type": notification_type,
|
||||||
|
}
|
||||||
status = FAILURE_METRIC_STATUS
|
status = FAILURE_METRIC_STATUS
|
||||||
notification_plugin = plugins.get(
|
notification_plugin = plugins.get(
|
||||||
current_app.config.get("LEMUR_DEFAULT_NOTIFICATION_PLUGIN", "email-notification")
|
current_app.config.get("LEMUR_DEFAULT_NOTIFICATION_PLUGIN", "email-notification")
|
||||||
|
@ -211,9 +207,9 @@ def send_default_notification(notification_type, data, targets, notification_opt
|
||||||
notification_plugin.send(notification_type, data, targets, notification_options)
|
notification_plugin.send(notification_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(
|
log_data["message"] = f"Unable to send {notification_type} notification for certificate data {data} " \
|
||||||
"Unable to send notification to {}.".format(targets), exc_info=True
|
f"to target {targets}"
|
||||||
)
|
current_app.logger.error(log_data, exc_info=True)
|
||||||
sentry.captureException()
|
sentry.captureException()
|
||||||
|
|
||||||
metrics.send(
|
metrics.send(
|
||||||
|
@ -272,11 +268,7 @@ def needs_notification(certificate):
|
||||||
|
|
||||||
notifications = []
|
notifications = []
|
||||||
|
|
||||||
print("ALPACA: Considering if cert {0} needs notifications".format(certificate))
|
|
||||||
print("ALPACA: Notifications for {0}: {1}".format(certificate, certificate.notifications))
|
|
||||||
|
|
||||||
for notification in certificate.notifications:
|
for notification in certificate.notifications:
|
||||||
print("ALPACA: Considering if cert {0} needs notification {1}".format(certificate, notification))
|
|
||||||
if not notification.active or not notification.options:
|
if not notification.active or not notification.options:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -294,11 +286,10 @@ def needs_notification(certificate):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Invalid base unit for expiration interval: {0}".format(unit)
|
f"Invalid base unit for expiration interval: {unit}"
|
||||||
)
|
)
|
||||||
|
print(f"Does cert {certificate.name} need a notification {notification.label}? Actual: {days}, "
|
||||||
print("ALPACA: Considering if cert {0} is applicable for notification {1}: {2} days remaining, configured as "
|
f"configured: {interval}") # TODO REMOVE
|
||||||
"{3} days".format(certificate, notification, days, interval))
|
|
||||||
if days == interval:
|
if days == interval:
|
||||||
notifications.append(notification)
|
notifications.append(notification)
|
||||||
return notifications
|
return notifications
|
||||||
|
|
|
@ -434,7 +434,7 @@ class SNSNotificationPlugin(ExpirationNotificationPlugin):
|
||||||
"helpMessage": "Region in which the SNS topic is located, e.g. \"us-east-1\"",
|
"helpMessage": "Region in which the SNS topic is located, e.g. \"us-east-1\"",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Topic Name",
|
"name": "topicName",
|
||||||
"type": "str",
|
"type": "str",
|
||||||
"required": True,
|
"required": True,
|
||||||
# base topic name is 1-256 characters (alphanumeric plus underscore and hyphen)
|
# base topic name is 1-256 characters (alphanumeric plus underscore and hyphen)
|
||||||
|
@ -449,13 +449,12 @@ class SNSNotificationPlugin(ExpirationNotificationPlugin):
|
||||||
plugin configuration, and can't reasonably be changed dynamically.
|
plugin configuration, and can't reasonably be changed dynamically.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
topic_arn = "arn:aws:sns:{0}:{1}:{2}".format(self.get_option("region", options),
|
topic_arn = f"arn:aws:sns:{self.get_option('region', options)}:" \
|
||||||
self.get_option("accountNumber", options),
|
f"{self.get_option('accountNumber', options)}:" \
|
||||||
self.get_option("Topic Name", options))
|
f"{self.get_option('topicName', options)}"
|
||||||
|
|
||||||
current_app.logger.debug("Publishing {0} notification to topic {1}".format(notification_type, topic_arn))
|
current_app.logger.debug(f"Publishing {notification_type} notification to topic {topic_arn}")
|
||||||
print("ALPACA: Trying to send {0} SNS notification to topic {1}".format(notification_type, topic_arn))
|
|
||||||
try:
|
try:
|
||||||
sns.publish(topic_arn, message, notification_type, region_name=self.get_option("region", options))
|
sns.publish(topic_arn, message, notification_type, region_name=self.get_option("region", options))
|
||||||
except Exception:
|
except Exception:
|
||||||
current_app.logger.exception("Error publishing {0} notification to topic {1}".format(notification_type, topic_arn))
|
current_app.logger.exception(f"Error publishing {notification_type} notification to topic {topic_arn}")
|
||||||
|
|
|
@ -14,7 +14,6 @@ from flask import current_app
|
||||||
|
|
||||||
def publish(topic_arn, certificates, notification_type, **kwargs):
|
def publish(topic_arn, certificates, notification_type, **kwargs):
|
||||||
sns_client = boto3.client("sns", **kwargs)
|
sns_client = boto3.client("sns", **kwargs)
|
||||||
print("ALPACA: SNS client: {0}, certificates: {1}".format(sns_client, certificates))
|
|
||||||
message_ids = {}
|
message_ids = {}
|
||||||
for certificate in certificates:
|
for certificate in certificates:
|
||||||
message_ids[certificate["name"]] = publish_single(sns_client, topic_arn, certificate, notification_type)
|
message_ids[certificate["name"]] = publish_single(sns_client, topic_arn, certificate, notification_type)
|
||||||
|
@ -30,11 +29,9 @@ def publish_single(sns_client, topic_arn, certificate, notification_type):
|
||||||
|
|
||||||
response_code = response["ResponseMetadata"]["HTTPStatusCode"]
|
response_code = response["ResponseMetadata"]["HTTPStatusCode"]
|
||||||
if response_code != 200:
|
if response_code != 200:
|
||||||
raise Exception("Failed to publish notification to SNS, response code was {}".format(response_code))
|
raise Exception(f"Failed to publish notification to SNS, response code was {response_code}")
|
||||||
|
|
||||||
current_app.logger.debug(
|
current_app.logger.debug(f"AWS SNS message published to topic [{topic_arn}]: [{response}]")
|
||||||
"AWS SNS message published to topic [{0}]: [{1}]".format(topic_arn, response)
|
|
||||||
)
|
|
||||||
|
|
||||||
return response["MessageId"]
|
return response["MessageId"]
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
from moto import mock_sts, mock_sns, mock_sqs
|
|
||||||
import boto3
|
|
||||||
import json
|
import json
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
|
import boto3
|
||||||
|
from moto import mock_sns, mock_sqs, mock_ses
|
||||||
|
|
||||||
|
from lemur.certificates.schemas import certificate_notification_output_schema
|
||||||
from lemur.plugins.lemur_aws.sns import format_message
|
from lemur.plugins.lemur_aws.sns import format_message
|
||||||
from lemur.plugins.lemur_aws.sns import publish
|
from lemur.plugins.lemur_aws.sns import publish
|
||||||
from lemur.certificates.schemas import certificate_notification_output_schema
|
from lemur.tests.factories import NotificationFactory, CertificateFactory
|
||||||
|
from lemur.tests.test_messaging import verify_sender_email
|
||||||
|
|
||||||
|
|
||||||
@mock_sns()
|
@mock_sns()
|
||||||
def test_format(certificate, endpoint):
|
def test_format(certificate, endpoint):
|
||||||
|
|
||||||
data = [certificate_notification_output_schema.dump(certificate).data]
|
data = [certificate_notification_output_schema.dump(certificate).data]
|
||||||
|
|
||||||
for certificate in data:
|
for certificate in data:
|
||||||
|
@ -25,10 +29,7 @@ def test_format(certificate, endpoint):
|
||||||
|
|
||||||
@mock_sns()
|
@mock_sns()
|
||||||
@mock_sqs()
|
@mock_sqs()
|
||||||
def test_publish(certificate, endpoint):
|
def create_and_subscribe_to_topic():
|
||||||
|
|
||||||
data = [certificate_notification_output_schema.dump(certificate).data]
|
|
||||||
|
|
||||||
sns_client = boto3.client("sns", region_name="us-east-1")
|
sns_client = boto3.client("sns", region_name="us-east-1")
|
||||||
topic_arn = sns_client.create_topic(Name='lemursnstest')["TopicArn"]
|
topic_arn = sns_client.create_topic(Name='lemursnstest')["TopicArn"]
|
||||||
|
|
||||||
|
@ -38,13 +39,82 @@ def test_publish(certificate, endpoint):
|
||||||
queue_arn = sqs_client.get_queue_attributes(QueueUrl=queue_url)["Attributes"]["QueueArn"]
|
queue_arn = sqs_client.get_queue_attributes(QueueUrl=queue_url)["Attributes"]["QueueArn"]
|
||||||
sns_client.subscribe(TopicArn=topic_arn, Protocol="sqs", Endpoint=queue_arn)
|
sns_client.subscribe(TopicArn=topic_arn, Protocol="sqs", Endpoint=queue_arn)
|
||||||
|
|
||||||
|
return [topic_arn, sqs_client, queue_url]
|
||||||
|
|
||||||
|
|
||||||
|
@mock_sns()
|
||||||
|
@mock_sqs()
|
||||||
|
def test_publish(certificate, endpoint):
|
||||||
|
data = [certificate_notification_output_schema.dump(certificate).data]
|
||||||
|
|
||||||
|
topic_arn, sqs_client, queue_url = create_and_subscribe_to_topic()
|
||||||
|
|
||||||
message_ids = publish(topic_arn, data, "expiration", region_name="us-east-1")
|
message_ids = publish(topic_arn, data, "expiration", region_name="us-east-1")
|
||||||
assert len(message_ids) == len(data)
|
assert len(message_ids) == len(data)
|
||||||
received_messages = sqs_client.receive_message(QueueUrl=queue_url)["Messages"]
|
received_messages = sqs_client.receive_message(QueueUrl=queue_url)["Messages"]
|
||||||
|
|
||||||
print("ALPACA: Received messages = {}".format(received_messages))
|
|
||||||
|
|
||||||
for certificate in data:
|
for certificate in data:
|
||||||
expected_message_id = message_ids[certificate["name"]]
|
expected_message_id = message_ids[certificate["name"]]
|
||||||
actual_message = next((m for m in received_messages if json.loads(m["Body"])["MessageId"] == expected_message_id), None)
|
actual_message = next(
|
||||||
|
(m for m in received_messages if json.loads(m["Body"])["MessageId"] == expected_message_id), None)
|
||||||
assert json.loads(actual_message["Body"])["Message"] == format_message(certificate, "expiration")
|
assert json.loads(actual_message["Body"])["Message"] == format_message(certificate, "expiration")
|
||||||
|
|
||||||
|
|
||||||
|
def get_options():
|
||||||
|
return [
|
||||||
|
{"name": "interval", "value": 10},
|
||||||
|
{"name": "unit", "value": "days"},
|
||||||
|
{"name": "region", "value": "us-east-1"},
|
||||||
|
{"name": "accountNumber", "value": "123456789012"},
|
||||||
|
{"name": "topicName", "value": "lemursnstest"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@mock_sns()
|
||||||
|
@mock_sqs()
|
||||||
|
@mock_ses() # because email notifications are also sent
|
||||||
|
def test_send_expiration_notification():
|
||||||
|
from lemur.notifications.messaging import send_expiration_notifications
|
||||||
|
|
||||||
|
verify_sender_email() # emails are sent to owner and security; SNS only used for configured notification
|
||||||
|
topic_arn, sqs_client, queue_url = create_and_subscribe_to_topic()
|
||||||
|
|
||||||
|
notification = NotificationFactory(plugin_name="aws-sns")
|
||||||
|
notification.options = get_options()
|
||||||
|
|
||||||
|
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()
|
||||||
|
certificate.not_after = in_ten_days
|
||||||
|
certificate.notifications.append(notification)
|
||||||
|
|
||||||
|
assert send_expiration_notifications([]) == (3, 0) # owner, SNS, and security
|
||||||
|
|
||||||
|
received_messages = sqs_client.receive_message(QueueUrl=queue_url)["Messages"]
|
||||||
|
assert len(received_messages) == 1
|
||||||
|
expected_message = format_message(certificate_notification_output_schema.dump(certificate).data, "expiration")
|
||||||
|
actual_message = json.loads(received_messages[0]["Body"])["Message"]
|
||||||
|
assert actual_message == expected_message
|
||||||
|
|
||||||
|
|
||||||
|
# Currently disabled as the SNS plugin doesn't support this type of notification
|
||||||
|
# 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="aws-sns")
|
||||||
|
# notification.options = get_options()
|
||||||
|
#
|
||||||
|
# new_certificate = CertificateFactory()
|
||||||
|
# rotate_certificate(endpoint, new_certificate)
|
||||||
|
# assert endpoint.certificate == new_certificate
|
||||||
|
#
|
||||||
|
# assert send_rotation_notification(new_certificate)
|
||||||
|
|
||||||
|
|
||||||
|
# Currently disabled as the SNS 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)
|
||||||
|
|
|
@ -20,14 +20,16 @@ from lemur.plugins.lemur_email.templates.config import env
|
||||||
from lemur.plugins.utils import get_plugin_option
|
from lemur.plugins.utils import get_plugin_option
|
||||||
|
|
||||||
|
|
||||||
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"))
|
||||||
|
@ -101,25 +103,21 @@ 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()
|
||||||
|
|
||||||
current_app.logger.info("ALPACA: Would send an email to {0} with subject \"{1}\" here".format(targets, subject))
|
print(f"Would send {s_type} email to {targets}: {subject}")
|
||||||
|
|
||||||
# if s_type == "ses":
|
# if s_type == "ses":
|
||||||
# send_via_ses(subject, body, targets)
|
# send_via_ses(subject, body, targets)
|
||||||
#
|
|
||||||
# elif s_type == "smtp":
|
# elif s_type == "smtp":
|
||||||
# send_via_smtp(subject, body, targets)
|
# send_via_smtp(subject, body, targets)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def filter_recipients(options, excluded_recipients):
|
def filter_recipients(options, excluded_recipients, **kwargs):
|
||||||
print("ALPACA: Getting recipients for notification {0}".format(options))
|
|
||||||
notification_recipients = get_plugin_option("recipients", options)
|
notification_recipients = get_plugin_option("recipients", options)
|
||||||
print(
|
|
||||||
"ALPACA: Sending certificate notifications to recipients {0}".format(notification_recipients.split(",")))
|
|
||||||
if notification_recipients:
|
if notification_recipients:
|
||||||
notification_recipients = notification_recipients.split(",")
|
notification_recipients = notification_recipients.split(",")
|
||||||
# removing owner and security_email from notification_recipient
|
# removing owner and security_email from notification_recipient
|
||||||
|
|
|
@ -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,39 +1,81 @@
|
||||||
import os
|
import os
|
||||||
from lemur.plugins.lemur_email.templates.config import env
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import arrow
|
||||||
|
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
|
||||||
|
from lemur.tests.test_messaging import verify_sender_email
|
||||||
|
|
||||||
dir_path = os.path.dirname(os.path.realpath(__file__))
|
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
|
||||||
def test_render(certificate, endpoint):
|
def get_options():
|
||||||
from lemur.certificates.schemas import certificate_notification_output_schema
|
return [
|
||||||
|
{"name": "interval", "value": 10},
|
||||||
|
{"name": "unit", "value": "days"},
|
||||||
|
{"name": "recipients", "value": "person1@example.com,person2@example.com"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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([]) == (3, 0) # owner, recipients (only counted as 1), and security
|
||||||
|
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|
||||||
|
|
||||||
def test_filter_recipients(certificate, endpoint):
|
def test_filter_recipients(certificate, endpoint):
|
||||||
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import arrow
|
||||||
|
from moto import mock_ses
|
||||||
|
|
||||||
|
from lemur.tests.factories import NotificationFactory, CertificateFactory
|
||||||
|
from lemur.tests.test_messaging import verify_sender_email
|
||||||
|
|
||||||
|
|
||||||
def test_formatting(certificate):
|
def test_formatting(certificate):
|
||||||
from lemur.plugins.lemur_slack.plugin import create_expiration_attachments
|
from lemur.plugins.lemur_slack.plugin import create_expiration_attachments
|
||||||
from lemur.certificates.schemas import certificate_notification_output_schema
|
from lemur.certificates.schemas import certificate_notification_output_schema
|
||||||
|
@ -21,3 +30,52 @@ 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"},
|
||||||
|
{"name": "webhook", "value": "https://slack.com/api/api.test"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ses() # because email notifications are also sent
|
||||||
|
def test_send_expiration_notification():
|
||||||
|
from lemur.notifications.messaging import send_expiration_notifications
|
||||||
|
|
||||||
|
verify_sender_email() # emails are sent to owner and security; Slack only used for configured notification
|
||||||
|
|
||||||
|
notification = NotificationFactory(plugin_name="slack-notification")
|
||||||
|
notification.options = get_options()
|
||||||
|
|
||||||
|
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()
|
||||||
|
certificate.not_after = in_ten_days
|
||||||
|
certificate.notifications.append(notification)
|
||||||
|
|
||||||
|
assert send_expiration_notifications([]) == (3, 0) # owner, Slack, and security
|
||||||
|
|
||||||
|
|
||||||
|
# Currently disabled as the Slack plugin doesn't support this type of notification
|
||||||
|
# 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")
|
||||||
|
# notification.options = get_options()
|
||||||
|
#
|
||||||
|
# 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"))
|
||||||
|
|
|
@ -190,7 +190,7 @@ angular.module('lemur')
|
||||||
function populateValidityDateAsPerDefault(certificate) {
|
function populateValidityDateAsPerDefault(certificate) {
|
||||||
// calculate start and end date as per default validity
|
// calculate start and end date as per default validity
|
||||||
let startDate = new Date(), endDate = new Date();
|
let startDate = new Date(), endDate = new Date();
|
||||||
endDate.setDate(startDate.getDate() + certificate.authority.authorityCertificate.defaultValidityDays);
|
endDate.setDate(startDate.getDate() + certificate.authority.defaultValidityDays);
|
||||||
certificate.validityStart = startDate;
|
certificate.validityStart = startDate;
|
||||||
certificate.validityEnd = endDate;
|
certificate.validityEnd = endDate;
|
||||||
}
|
}
|
||||||
|
@ -359,7 +359,7 @@ angular.module('lemur')
|
||||||
function populateValidityDateAsPerDefault(certificate) {
|
function populateValidityDateAsPerDefault(certificate) {
|
||||||
// calculate start and end date as per default validity
|
// calculate start and end date as per default validity
|
||||||
let startDate = new Date(), endDate = new Date();
|
let startDate = new Date(), endDate = new Date();
|
||||||
endDate.setDate(startDate.getDate() + certificate.authority.authorityCertificate.defaultValidityDays);
|
endDate.setDate(startDate.getDate() + certificate.authority.defaultValidityDays);
|
||||||
certificate.validityStart = startDate;
|
certificate.validityStart = startDate;
|
||||||
certificate.validityEnd = endDate;
|
certificate.validityEnd = endDate;
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,7 +139,7 @@
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="btn-group btn-group-toggle" data-toggle="buttons">
|
<div class="btn-group btn-group-toggle" data-toggle="buttons">
|
||||||
<label class="btn btn-info" ng-model="certificate.validityType" uib-btn-radio="'defaultDays'" ng-click="clearDates()">
|
<label class="btn btn-info" ng-model="certificate.validityType" uib-btn-radio="'defaultDays'" ng-click="clearDates()">
|
||||||
Default ({{certificate.authority.authorityCertificate.defaultValidityDays}} days)</label>
|
Default ({{certificate.authority.defaultValidityDays}} days)</label>
|
||||||
<label class="btn btn-info" ng-model="certificate.validityType" uib-btn-radio="'customDates'" ng-change="clearDates()">Custom</label>
|
<label class="btn btn-info" ng-model="certificate.validityType" uib-btn-radio="'customDates'" ng-change="clearDates()">Custom</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -172,12 +172,12 @@ angular.module('lemur')
|
||||||
// Minimum end date will be same as selected start date
|
// Minimum end date will be same as selected start date
|
||||||
this.authority.authorityCertificate.minValidityEnd = value;
|
this.authority.authorityCertificate.minValidityEnd = value;
|
||||||
|
|
||||||
if(!this.authority.authorityCertificate || !this.authority.authorityCertificate.maxIssuanceDays) {
|
if(!this.authority.maxIssuanceDays) {
|
||||||
this.authority.authorityCertificate.maxValidityEnd = this.authority.authorityCertificate.notAfter;
|
this.authority.authorityCertificate.maxValidityEnd = this.authority.authorityCertificate.notAfter;
|
||||||
} else {
|
} else {
|
||||||
// Move max end date by maxIssuanceDays
|
// Move max end date by maxIssuanceDays
|
||||||
let endDate = new Date(value);
|
let endDate = new Date(value);
|
||||||
endDate.setDate(endDate.getDate() + this.authority.authorityCertificate.maxIssuanceDays);
|
endDate.setDate(endDate.getDate() + this.authority.maxIssuanceDays);
|
||||||
this.authority.authorityCertificate.maxValidityEnd = endDate;
|
this.authority.authorityCertificate.maxValidityEnd = endDate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,12 +152,12 @@ angular.module('lemur')
|
||||||
// Minimum end date will be same as selected start date
|
// Minimum end date will be same as selected start date
|
||||||
this.authority.authorityCertificate.minValidityEnd = value;
|
this.authority.authorityCertificate.minValidityEnd = value;
|
||||||
|
|
||||||
if(!this.authority.authorityCertificate || !this.authority.authorityCertificate.maxIssuanceDays) {
|
if(!this.authority.maxIssuanceDays) {
|
||||||
this.authority.authorityCertificate.maxValidityEnd = this.authority.authorityCertificate.notAfter;
|
this.authority.authorityCertificate.maxValidityEnd = this.authority.authorityCertificate.notAfter;
|
||||||
} else {
|
} else {
|
||||||
// Move max end date by maxIssuanceDays
|
// Move max end date by maxIssuanceDays
|
||||||
let endDate = new Date(value);
|
let endDate = new Date(value);
|
||||||
endDate.setDate(endDate.getDate() + this.authority.authorityCertificate.maxIssuanceDays);
|
endDate.setDate(endDate.getDate() + this.authority.maxIssuanceDays);
|
||||||
this.authority.authorityCertificate.maxValidityEnd = endDate;
|
this.authority.authorityCertificate.maxValidityEnd = endDate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -14,5 +14,4 @@ class TestNotificationPlugin(NotificationPlugin):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def send(notification_type, message, targets, options, **kwargs):
|
def send(notification_type, message, targets, options, **kwargs):
|
||||||
print("TODO REMOVE: sending email to {}".format(targets))
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import arrow
|
||||||
|
import boto3
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ses
|
||||||
|
def verify_sender_email():
|
||||||
|
ses_client = boto3.client("ses", region_name="us-east-1")
|
||||||
|
ses_client.verify_email_identity(EmailAddress="lemur@example.com")
|
||||||
|
|
||||||
|
|
||||||
def test_needs_notification(app, certificate, notification):
|
def test_needs_notification(app, certificate, notification):
|
||||||
from lemur.notifications.messaging import needs_notification
|
from lemur.notifications.messaging import needs_notification
|
||||||
|
|
||||||
|
@ -78,6 +85,7 @@ def test_get_eligible_certificates(app, certificate, notification):
|
||||||
@mock_ses
|
@mock_ses
|
||||||
def test_send_expiration_notification(certificate, notification, notification_plugin):
|
def test_send_expiration_notification(certificate, notification, notification_plugin):
|
||||||
from lemur.notifications.messaging import send_expiration_notifications
|
from lemur.notifications.messaging import send_expiration_notifications
|
||||||
|
verify_sender_email()
|
||||||
|
|
||||||
certificate.notifications.append(notification)
|
certificate.notifications.append(notification)
|
||||||
certificate.notifications[0].options = [
|
certificate.notifications[0].options = [
|
||||||
|
@ -105,23 +113,15 @@ def test_send_expiration_notification_with_no_notifications(
|
||||||
|
|
||||||
@mock_ses
|
@mock_ses
|
||||||
def test_send_rotation_notification(notification_plugin, certificate):
|
def test_send_rotation_notification(notification_plugin, certificate):
|
||||||
from lemur.tests.factories import UserFactory
|
|
||||||
from lemur.tests.factories import CertificateFactory
|
|
||||||
from lemur.notifications.messaging import send_rotation_notification
|
from lemur.notifications.messaging import send_rotation_notification
|
||||||
|
verify_sender_email()
|
||||||
|
|
||||||
user = UserFactory(email="jschladen@netflix.com")
|
assert send_rotation_notification(certificate)
|
||||||
|
|
||||||
new_cert = CertificateFactory(user=user)
|
|
||||||
assert send_rotation_notification(new_cert)
|
|
||||||
|
|
||||||
|
|
||||||
@mock_ses
|
@mock_ses
|
||||||
def test_send_pending_failure_notification(certificate, endpoint):
|
def test_send_pending_failure_notification(notification_plugin, async_issuer_plugin, pending_certificate):
|
||||||
from lemur.tests.factories import UserFactory
|
|
||||||
from lemur.tests.factories import PendingCertificateFactory
|
|
||||||
from lemur.notifications.messaging import send_pending_failure_notification
|
from lemur.notifications.messaging import send_pending_failure_notification
|
||||||
|
verify_sender_email()
|
||||||
|
|
||||||
user = UserFactory(email="jschladen@netflix.com")
|
assert send_pending_failure_notification(pending_certificate)
|
||||||
|
|
||||||
pending_cert = PendingCertificateFactory(user=user)
|
|
||||||
assert send_pending_failure_notification(pending_cert)
|
|
||||||
|
|
Loading…
Reference in New Issue