From 9374adaa4632ed4e477f875cf036f8bb364d7707 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Wed, 21 Oct 2020 08:45:12 +0200 Subject: [PATCH 01/20] do not create db_upgrade.log during migrations --- lemur/migrations/versions/c301c59688d2_.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/lemur/migrations/versions/c301c59688d2_.py b/lemur/migrations/versions/c301c59688d2_.py index 669c934f..8712d60c 100644 --- a/lemur/migrations/versions/c301c59688d2_.py +++ b/lemur/migrations/versions/c301c59688d2_.py @@ -35,11 +35,11 @@ from lemur.common import utils import time import datetime -log_file = open('db_upgrade.log', 'a') - +import logging +log = logging.getLogger(__name__) def upgrade(): - log_file.write("\n*** Starting new run(%s) ***\n" % datetime.datetime.now()) + log.info("\n*** Starting new run(%s) ***\n" % datetime.datetime.now()) start_time = time.time() # Update RSA keys using the key length information @@ -50,8 +50,7 @@ def upgrade(): # Process remaining certificates. Though below method does not make any assumptions, most of the remaining ones should be ECC certs. update_key_type() - log_file.write("--- Total %s seconds ---\n" % (time.time() - start_time)) - log_file.close() + log.info("--- Total %s seconds ---\n" % (time.time() - start_time)) def downgrade(): @@ -69,18 +68,18 @@ def downgrade(): def update_key_type_rsa(bits): - log_file.write("Processing certificate with key type RSA %s\n" % bits) + log.info("Processing certificate with key type RSA %s\n" % bits) stmt = text( f"update certificates set key_type='RSA{bits}' where bits={bits} and not_after > CURRENT_DATE - 31 and key_type is null" ) - log_file.write("Query: %s\n" % stmt) + log.info("Query: %s\n" % stmt) start_time = time.time() op.execute(stmt) commit() - log_file.write("--- %s seconds ---\n" % (time.time() - start_time)) + log.info("--- %s seconds ---\n" % (time.time() - start_time)) def update_key_type(): @@ -95,9 +94,9 @@ def update_key_type(): try: cert_key_type = utils.get_key_type_from_certificate(body) except ValueError as e: - log_file.write("Error in processing certificate - ID: %s Error: %s \n" % (cert_id, str(e))) + log.info("Error in processing certificate - ID: %s Error: %s \n" % (cert_id, str(e))) else: - log_file.write("Processing certificate - ID: %s key_type: %s\n" % (cert_id, cert_key_type)) + log.info("Processing certificate - ID: %s key_type: %s\n" % (cert_id, cert_key_type)) stmt = text( "update certificates set key_type=:key_type where id=:id" ) @@ -106,7 +105,7 @@ def update_key_type(): commit() - log_file.write("--- %s seconds ---\n" % (time.time() - start_time)) + log.info("--- %s seconds ---\n" % (time.time() - start_time)) def commit(): From 2c22d42a57d7ac219fbacd6480d63cbf67af0b63 Mon Sep 17 00:00:00 2001 From: sayali Date: Fri, 23 Oct 2020 17:06:42 -0700 Subject: [PATCH 02/20] Modify description during reissue Include the certificate ID being reissued and mention that this is created by Lemur as part of reissue --- lemur/certificates/service.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 1716ccb2..b90d7e47 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -6,6 +6,7 @@ .. moduleauthor:: Kevin Glisson """ import arrow +import re from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization @@ -778,6 +779,19 @@ def reissue_certificate(certificate, replace=None, user=None): if replace: primitives["replaces"] = [certificate] + # Modify description to include the certificate ID being reissued and mention that this is created by Lemur + # as part of reissue + reissue_message_prefix = "Reissued by Lemur for cert ID " + reissue_message = re.compile(f"{reissue_message_prefix}([0-9]+)") + if primitives["description"]: + match = reissue_message.search(primitives["description"]) + if match: + primitives["description"] = primitives["description"].replace(match.group(1), str(certificate.id)) + else: + primitives["description"] = f"{reissue_message_prefix}{certificate.id}, {primitives['description']}" + else: + primitives["description"] = f"{reissue_message_prefix}{certificate.id}" + new_cert = create(**primitives) return new_cert From 2c1e7b19a225ad71e4a6802fd293469c5601d3d7 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Fri, 23 Oct 2020 17:59:58 -0700 Subject: [PATCH 03/20] 10x 10s delay might be too long for the load balancer request --- lemur/plugins/lemur_digicert/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lemur/plugins/lemur_digicert/plugin.py b/lemur/plugins/lemur_digicert/plugin.py index ec3a0792..d8e88fa3 100644 --- a/lemur/plugins/lemur_digicert/plugin.py +++ b/lemur/plugins/lemur_digicert/plugin.py @@ -234,7 +234,7 @@ def handle_cis_response(response): return response.json() -@retry(stop_max_attempt_number=10, wait_fixed=10000) +@retry(stop_max_attempt_number=10, wait_fixed=1000) def get_certificate_id(session, base_url, order_id): """Retrieve certificate order id from Digicert API.""" order_url = "{0}/services/v2/order/certificate/{1}".format(base_url, order_id) @@ -245,7 +245,7 @@ def get_certificate_id(session, base_url, order_id): return response_data["certificate"]["id"] -@retry(stop_max_attempt_number=10, wait_fixed=10000) +@retry(stop_max_attempt_number=10, wait_fixed=1000) def get_cis_certificate(session, base_url, order_id): """Retrieve certificate order id from Digicert API, including the chain""" certificate_url = "{0}/platform/cis/certificate/{1}/download".format(base_url, order_id) From d233490c8aeb6996bb8fc936c9c86f3614fce142 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Fri, 23 Oct 2020 18:01:14 -0700 Subject: [PATCH 04/20] simple retry --- lemur/plugins/lemur_entrust/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lemur/plugins/lemur_entrust/plugin.py b/lemur/plugins/lemur_entrust/plugin.py index ffb5765d..4700c022 100644 --- a/lemur/plugins/lemur_entrust/plugin.py +++ b/lemur/plugins/lemur_entrust/plugin.py @@ -200,6 +200,7 @@ class EntrustIssuerPlugin(IssuerPlugin): return cert, chain, external_id + @retry(stop_max_attempt_number=3, wait_fixed=1000) def revoke_certificate(self, certificate, comments): """Revoke an Entrust certificate.""" base_url = current_app.config.get("ENTRUST_URL") @@ -216,6 +217,7 @@ class EntrustIssuerPlugin(IssuerPlugin): metrics.send("entrust_revoke_certificate", "counter", 1) return handle_response(response) + @retry(stop_max_attempt_number=3, wait_fixed=1000) def deactivate_certificate(self, certificate): """Deactivates an Entrust certificate.""" base_url = current_app.config.get("ENTRUST_URL") From 75bc3a5b20d6d0efcf0da236daf301fe29b12bdb Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Fri, 23 Oct 2020 18:02:05 -0700 Subject: [PATCH 05/20] refactoring and adding retry --- lemur/plugins/lemur_entrust/plugin.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/lemur/plugins/lemur_entrust/plugin.py b/lemur/plugins/lemur_entrust/plugin.py index 4700c022..52860049 100644 --- a/lemur/plugins/lemur_entrust/plugin.py +++ b/lemur/plugins/lemur_entrust/plugin.py @@ -121,6 +121,24 @@ def handle_response(my_response): else: # return data from the response return d +@retry(stop_max_attempt_number=3, wait_fixed=5000) +def get_certificate_order(session, url, data): + """ + Helper function place a cert order and downloading it + :param session: + :param url: Entrust endpoint url + :param data: CSR, and the required order details, such as validity length + :return: the cert chain + :raise Exception: + """ + try: + response = session.post(url, json=data, timeout=(15, 40)) + except requests.exceptions.Timeout: + raise Exception("Timeout for POST") + except requests.exceptions.RequestException as e: + raise Exception(f"Error for POST {e}") + + return handle_response(response) class EntrustIssuerPlugin(IssuerPlugin): @@ -178,14 +196,8 @@ class EntrustIssuerPlugin(IssuerPlugin): data = process_options(issuer_options) data["csr"] = csr - try: - response = self.session.post(url, json=data, timeout=(15, 40)) - except requests.exceptions.Timeout: - raise Exception("Timeout for POST") - except requests.exceptions.RequestException as e: - raise Exception(f"Error for POST {e}") + response_dict = get_certificate_order(self.session, url, data) - response_dict = handle_response(response) external_id = response_dict['trackingId'] cert = response_dict['endEntityCert'] if len(response_dict['chainCerts']) < 2: From 6891077501a9ca418b6747f0b13d57bc08a79759 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Fri, 23 Oct 2020 18:02:35 -0700 Subject: [PATCH 06/20] readability --- lemur/plugins/lemur_entrust/plugin.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lemur/plugins/lemur_entrust/plugin.py b/lemur/plugins/lemur_entrust/plugin.py index 52860049..0e769093 100644 --- a/lemur/plugins/lemur_entrust/plugin.py +++ b/lemur/plugins/lemur_entrust/plugin.py @@ -100,27 +100,29 @@ def handle_response(my_response): } try: - d = json.loads(my_response.content) + data = json.loads(my_response.content) except ValueError: # catch an empty jason object here - d = {'response': 'No detailed message'} - s = my_response.status_code - if s > 399: - raise Exception(f"ENTRUST error: {msg.get(s, s)}\n{d['errors']}") + data = {'response': 'No detailed message'} + status_code = my_response.status_code + if status_code > 399: + raise Exception(f"ENTRUST error: {msg.get(status_code, status_code)}\n{data['errors']}") log_data = { "function": f"{__name__}.{sys._getframe().f_code.co_name}", "message": "Response", - "status": s, - "response": d + "status": status_code, + "response": data } current_app.logger.info(log_data) - if d == {'response': 'No detailed message'}: + if data == {'response': 'No detailed message'}: # status if no data - return s + return status_code else: # return data from the response - return d + return data + + @retry(stop_max_attempt_number=3, wait_fixed=5000) def get_certificate_order(session, url, data): """ From 7e573d6d517d7f6189ff005ed072f321627b7b6f Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Fri, 23 Oct 2020 18:02:54 -0700 Subject: [PATCH 07/20] fixing typo --- lemur/plugins/lemur_entrust/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/plugins/lemur_entrust/plugin.py b/lemur/plugins/lemur_entrust/plugin.py index 0e769093..c785acc1 100644 --- a/lemur/plugins/lemur_entrust/plugin.py +++ b/lemur/plugins/lemur_entrust/plugin.py @@ -87,7 +87,7 @@ def process_options(options): def handle_response(my_response): """ Helper function for parsing responses from the Entrust API. - :param content: + :param my_response: :return: :raise Exception: """ msg = { From 9957120a7fd2c0befef39f166c6292f5b8f83e86 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Fri, 23 Oct 2020 18:03:07 -0700 Subject: [PATCH 08/20] adding missing import --- lemur/plugins/lemur_entrust/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/plugins/lemur_entrust/plugin.py b/lemur/plugins/lemur_entrust/plugin.py index c785acc1..02e0a2be 100644 --- a/lemur/plugins/lemur_entrust/plugin.py +++ b/lemur/plugins/lemur_entrust/plugin.py @@ -1,9 +1,9 @@ - import arrow import requests import json import sys from flask import current_app +from retrying import retry from lemur.plugins import lemur_entrust as entrust from lemur.plugins.bases import IssuerPlugin, SourcePlugin From 0e02abbb3791cbd844cd916812a99896c6823fce Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Fri, 23 Oct 2020 18:03:27 -0700 Subject: [PATCH 09/20] Entrust just looks into CSR for RSA/EC key type --- lemur/plugins/lemur_entrust/plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lemur/plugins/lemur_entrust/plugin.py b/lemur/plugins/lemur_entrust/plugin.py index 02e0a2be..fcb3e14f 100644 --- a/lemur/plugins/lemur_entrust/plugin.py +++ b/lemur/plugins/lemur_entrust/plugin.py @@ -78,7 +78,6 @@ def process_options(options): "eku": "SERVER_AND_CLIENT_AUTH", "certType": product_type, "certExpiryDate": validity_end, - # "keyType": "RSA", Entrust complaining about this parameter "tracking": tracking_data } return data From f6554a9a1e79b9a3e688ba59438708b559efd835 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Fri, 23 Oct 2020 18:03:55 -0700 Subject: [PATCH 10/20] typo, fixing abstract class complaints --- lemur/plugins/lemur_entrust/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/plugins/lemur_entrust/plugin.py b/lemur/plugins/lemur_entrust/plugin.py index fcb3e14f..8bb0710c 100644 --- a/lemur/plugins/lemur_entrust/plugin.py +++ b/lemur/plugins/lemur_entrust/plugin.py @@ -259,7 +259,7 @@ class EntrustIssuerPlugin(IssuerPlugin): def get_ordered_certificate(self, order_id): raise NotImplementedError("Not implemented\n", self, order_id) - def canceled_ordered_certificate(self, pending_cert, **kwargs): + def cancel_ordered_certificate(self, pending_cert, **kwargs): raise NotImplementedError("Not implemented\n", self, pending_cert, **kwargs) From d7478a5c5cb00dc619e7f8bc7afc2780a1cb3e1a Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sun, 25 Oct 2020 19:24:17 +0100 Subject: [PATCH 11/20] use an alternative logger for the upgrade --- docs/administration.rst | 7 +++++++ lemur/manage.py | 1 + lemur/migrations/versions/1db4f82bc780_.py | 16 ++++++++++++--- lemur/migrations/versions/c301c59688d2_.py | 23 +++++++++++++++++----- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/docs/administration.rst b/docs/administration.rst index c2f20362..62a22dd1 100644 --- a/docs/administration.rst +++ b/docs/administration.rst @@ -28,6 +28,13 @@ Basic Configuration LOG_FILE = "/logs/lemur/lemur-test.log" +.. data:: LOG_UPGRADE_FILE + :noindex: + + :: + + LOG_UPGRADE_FILE = "/logs/lemur/db_upgrade.log" + .. data:: DEBUG :noindex: diff --git a/lemur/manage.py b/lemur/manage.py index e53f8bd6..bff97535 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -120,6 +120,7 @@ METRIC_PROVIDERS = [] LOG_LEVEL = "DEBUG" LOG_FILE = "lemur.log" +LOG_UPGRADE_FILE = "db_upgrade.log" # Database diff --git a/lemur/migrations/versions/1db4f82bc780_.py b/lemur/migrations/versions/1db4f82bc780_.py index e6fb47f0..00b83ceb 100644 --- a/lemur/migrations/versions/1db4f82bc780_.py +++ b/lemur/migrations/versions/1db4f82bc780_.py @@ -10,11 +10,21 @@ Create Date: 2018-08-03 12:56:44.565230 revision = "1db4f82bc780" down_revision = "3adfdd6598df" -import logging - from alembic import op -log = logging.getLogger(__name__) +from flask import current_app +from logging import Formatter, FileHandler, getLogger + +log = getLogger(__name__) +handler = FileHandler(current_app.config.get("LOG_UPGRADE_FILE", "db_upgrade.log")) +handler.setFormatter( + Formatter( + "%(asctime)s %(levelname)s: %(message)s " "[in %(pathname)s:%(lineno)d]" + ) +) +handler.setLevel(current_app.config.get("LOG_LEVEL", "DEBUG")) +log.setLevel(current_app.config.get("LOG_LEVEL", "DEBUG")) +log.addHandler(handler) def upgrade(): diff --git a/lemur/migrations/versions/c301c59688d2_.py b/lemur/migrations/versions/c301c59688d2_.py index 8712d60c..d1a30650 100644 --- a/lemur/migrations/versions/c301c59688d2_.py +++ b/lemur/migrations/versions/c301c59688d2_.py @@ -31,12 +31,25 @@ down_revision = '434c29e40511' from alembic import op from sqlalchemy.sql import text -from lemur.common import utils import time import datetime +from flask import current_app + +from logging import Formatter, FileHandler, getLogger + +from lemur.common import utils + +log = getLogger(__name__) +handler = FileHandler(current_app.config.get("LOG_UPGRADE_FILE", "db_upgrade.log")) +handler.setFormatter( + Formatter( + "%(asctime)s %(levelname)s: %(message)s " "[in %(pathname)s:%(lineno)d]" + ) +) +handler.setLevel(current_app.config.get("LOG_LEVEL", "DEBUG")) +log.setLevel(current_app.config.get("LOG_LEVEL", "DEBUG")) +log.addHandler(handler) -import logging -log = logging.getLogger(__name__) def upgrade(): log.info("\n*** Starting new run(%s) ***\n" % datetime.datetime.now()) @@ -94,9 +107,9 @@ def update_key_type(): try: cert_key_type = utils.get_key_type_from_certificate(body) except ValueError as e: - log.info("Error in processing certificate - ID: %s Error: %s \n" % (cert_id, str(e))) + log.error("Error in processing certificate - ID: %s Error: %s \n" % (cert_id, str(e))) else: - log.info("Processing certificate - ID: %s key_type: %s\n" % (cert_id, cert_key_type)) + log.error("Processing certificate - ID: %s key_type: %s\n" % (cert_id, cert_key_type)) stmt = text( "update certificates set key_type=:key_type where id=:id" ) From 392725ff309609c7695de5860a409a9f794415d0 Mon Sep 17 00:00:00 2001 From: sayali Date: Mon, 26 Oct 2020 15:33:20 -0700 Subject: [PATCH 12/20] Add description check in reissue unit test --- lemur/tests/test_certificates.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py index c271a97e..583022eb 100644 --- a/lemur/tests/test_certificates.py +++ b/lemur/tests/test_certificates.py @@ -802,6 +802,7 @@ def test_reissue_certificate( assert new_cert.organization != certificate.organization # Check for default value since authority does not have cab_compliant option set assert new_cert.organization == LEMUR_DEFAULT_ORGANIZATION + assert new_cert.description.startswith(f"Reissued by Lemur for cert ID {certificate.id}") # update cab_compliant option to false for crypto_authority to maintain subject details update_options(crypto_authority.id, '[{"name": "cab_compliant","value":false}]') From 79647e33724ab107812737b5fe8647c8f7a2dfbd Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 27 Oct 2020 20:35:36 +0100 Subject: [PATCH 13/20] add reference to LOG_UPGRADE_FILE in toplevel comment --- lemur/migrations/versions/c301c59688d2_.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lemur/migrations/versions/c301c59688d2_.py b/lemur/migrations/versions/c301c59688d2_.py index d1a30650..f4656f20 100644 --- a/lemur/migrations/versions/c301c59688d2_.py +++ b/lemur/migrations/versions/c301c59688d2_.py @@ -7,8 +7,9 @@ the rest of the keys, the certificate body is parsed to determine the exact key_type information. Each individual DB change is explicitly committed, and the respective -log is added to a file named db_upgrade.log in the current working -directory. Any error encountered while parsing a certificate will +log is added to a file configured in LOG_UPGRADE_FILE or, by default, +to a file named db_upgrade.log in the current working directory. +Any error encountered while parsing a certificate will also be logged along with the certificate ID. If faced with any issue while running this upgrade, there is no harm in re-running the upgrade. Each run processes only rows for which key_type information is not yet From e9824a6808f4007f76df26427a4a10d76ab1b031 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 27 Oct 2020 20:38:00 +0100 Subject: [PATCH 14/20] change the log level to info if upgrade is successful --- lemur/migrations/versions/c301c59688d2_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/migrations/versions/c301c59688d2_.py b/lemur/migrations/versions/c301c59688d2_.py index f4656f20..4da91b7b 100644 --- a/lemur/migrations/versions/c301c59688d2_.py +++ b/lemur/migrations/versions/c301c59688d2_.py @@ -110,7 +110,7 @@ def update_key_type(): except ValueError as e: log.error("Error in processing certificate - ID: %s Error: %s \n" % (cert_id, str(e))) else: - log.error("Processing certificate - ID: %s key_type: %s\n" % (cert_id, cert_key_type)) + log.info("Processing certificate - ID: %s key_type: %s\n" % (cert_id, cert_key_type)) stmt = text( "update certificates set key_type=:key_type where id=:id" ) From c6a803489041f0e8564889af62d6b21d072ea459 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Tue, 27 Oct 2020 16:13:05 -0700 Subject: [PATCH 15/20] language --- lemur/plugins/lemur_entrust/plugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lemur/plugins/lemur_entrust/plugin.py b/lemur/plugins/lemur_entrust/plugin.py index 8bb0710c..d3324db0 100644 --- a/lemur/plugins/lemur_entrust/plugin.py +++ b/lemur/plugins/lemur_entrust/plugin.py @@ -123,9 +123,9 @@ def handle_response(my_response): @retry(stop_max_attempt_number=3, wait_fixed=5000) -def get_certificate_order(session, url, data): +def order_and_download_certificate(session, url, data): """ - Helper function place a cert order and downloading it + Helper function to place a certificacte order and download it :param session: :param url: Entrust endpoint url :param data: CSR, and the required order details, such as validity length @@ -197,7 +197,7 @@ class EntrustIssuerPlugin(IssuerPlugin): data = process_options(issuer_options) data["csr"] = csr - response_dict = get_certificate_order(self.session, url, data) + response_dict = order_and_download_certificate(self.session, url, data) external_id = response_dict['trackingId'] cert = response_dict['endEntityCert'] From c0bf111cb940dce4d1687558221b735631cdbd41 Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Wed, 28 Oct 2020 15:02:22 -0700 Subject: [PATCH 16/20] updating notification contact for travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 129d774b..67a1d0b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,4 +47,4 @@ after_success: notifications: email: - ccastrapel@netflix.com + lemur@netflix.com From 5e696f36bf8762da59583e85cb2ad4647417bca2 Mon Sep 17 00:00:00 2001 From: Jasmine Schladen Date: Wed, 28 Oct 2020 16:34:31 -0700 Subject: [PATCH 17/20] Add ability to override SourceArnn for SES --- docs/administration.rst | 10 ++++++++++ lemur/plugins/lemur_email/plugin.py | 18 +++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/administration.rst b/docs/administration.rst index 724b136f..a1eba56e 100644 --- a/docs/administration.rst +++ b/docs/administration.rst @@ -285,6 +285,16 @@ Lemur supports sending certificate expiration notifications through SES and SMTP you can send any mail. See: `Verifying Email Address in Amazon SES `_ +.. data:: LEMUR_SES_SOURCE_ARN + :noindex: + + Specifies an ARN to use as the SourceArn when sending emails via SES. + + .. note:: + This parameter is only required if you're using a sending authorization with SES. + See: `Using sending authorization with Amazon SES `_ + + .. data:: LEMUR_EMAIL :noindex: diff --git a/lemur/plugins/lemur_email/plugin.py b/lemur/plugins/lemur_email/plugin.py index 5b9c188e..39e76932 100644 --- a/lemur/plugins/lemur_email/plugin.py +++ b/lemur/plugins/lemur_email/plugin.py @@ -38,7 +38,7 @@ def render_html(template_name, options, certificates): def send_via_smtp(subject, body, targets): """ - Attempts to deliver email notification via SES service. + Attempts to deliver email notification via SMTP. :param subject: :param body: @@ -55,21 +55,25 @@ def send_via_smtp(subject, body, targets): def send_via_ses(subject, body, targets): """ - Attempts to deliver email notification via SMTP. + Attempts to deliver email notification via SES service. :param subject: :param body: :param targets: :return: """ client = boto3.client("ses", region_name="us-east-1") - client.send_email( - Source=current_app.config.get("LEMUR_EMAIL"), - Destination={"ToAddresses": targets}, - Message={ + source_arn = current_app.config.get("LEMUR_SES_SOURCE_ARN") + args = { + "Source": current_app.config.get("LEMUR_EMAIL"), + "Destination": {"ToAddresses": targets}, + "Message": { "Subject": {"Data": subject, "Charset": "UTF-8"}, "Body": {"Html": {"Data": body, "Charset": "UTF-8"}}, }, - ) + } + if source_arn: + args["SourceArn"] = source_arn + client.send_email(**args) class EmailNotificationPlugin(ExpirationNotificationPlugin): From 3e492e6310496e26c85ca1d42223b3e59c89322c Mon Sep 17 00:00:00 2001 From: Jasmine Schladen Date: Wed, 28 Oct 2020 17:09:54 -0700 Subject: [PATCH 18/20] Add ability to override SES region --- docs/administration.rst | 9 +++++++++ lemur/plugins/lemur_email/plugin.py | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/administration.rst b/docs/administration.rst index a1eba56e..7e94a4db 100644 --- a/docs/administration.rst +++ b/docs/administration.rst @@ -295,6 +295,15 @@ Lemur supports sending certificate expiration notifications through SES and SMTP See: `Using sending authorization with Amazon SES `_ +.. data:: LEMUR_SES_REGION + :noindex: + + Specifies a region for sending emails via SES. + + .. note:: + This parameter defaults to us-east-1 and is only required if you wish to use a different region. + + .. data:: LEMUR_EMAIL :noindex: diff --git a/lemur/plugins/lemur_email/plugin.py b/lemur/plugins/lemur_email/plugin.py index 39e76932..a9e35d16 100644 --- a/lemur/plugins/lemur_email/plugin.py +++ b/lemur/plugins/lemur_email/plugin.py @@ -61,7 +61,10 @@ def send_via_ses(subject, body, targets): :param targets: :return: """ - client = boto3.client("ses", region_name="us-east-1") + ses_region = current_app.config.get("LEMUR_SES_REGION") + if not ses_region: + ses_region = "us-east-1" + client = boto3.client("ses", region_name=ses_region) source_arn = current_app.config.get("LEMUR_SES_SOURCE_ARN") args = { "Source": current_app.config.get("LEMUR_EMAIL"), From 78afc060aeb1fdbc44890b3571746c9327587175 Mon Sep 17 00:00:00 2001 From: Jasmine Schladen Date: Thu, 29 Oct 2020 13:41:47 -0700 Subject: [PATCH 19/20] Add subject for SNS messages and correct date format --- lemur/plugins/lemur_aws/sns.py | 9 ++++++--- lemur/plugins/lemur_aws/tests/test_sns.py | 7 +++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lemur/plugins/lemur_aws/sns.py b/lemur/plugins/lemur_aws/sns.py index c98bbc0c..14109c11 100644 --- a/lemur/plugins/lemur_aws/sns.py +++ b/lemur/plugins/lemur_aws/sns.py @@ -15,16 +15,18 @@ from flask import current_app def publish(topic_arn, certificates, notification_type, **kwargs): sns_client = boto3.client("sns", **kwargs) message_ids = {} + subject = "Lemur: {0} Notification".format(notification_type.capitalize()) 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, subject) return message_ids -def publish_single(sns_client, topic_arn, certificate, notification_type): +def publish_single(sns_client, topic_arn, certificate, notification_type, subject): response = sns_client.publish( TopicArn=topic_arn, Message=format_message(certificate, notification_type), + Subject=subject, ) response_code = response["ResponseMetadata"]["HTTPStatusCode"] @@ -48,8 +50,9 @@ def format_message(certificate, notification_type): json_message = { "notification_type": notification_type, "certificate_name": certificate["name"], - "expires": arrow.get(certificate["validityEnd"]).format("YYYY-MM-ddTHH:mm:ss"), # 2047-12-T22:00:00 + "expires": arrow.get(certificate["validityEnd"]).format("YYYY-MM-DDTHH:mm:ss"), # 2047-12-31T22:00:00 "endpoints_detected": len(certificate["endpoints"]), + "owner": certificate["owner"], "details": create_certificate_url(certificate["name"]) } return json.dumps(json_message) diff --git a/lemur/plugins/lemur_aws/tests/test_sns.py b/lemur/plugins/lemur_aws/tests/test_sns.py index ce05c33c..59ef30f2 100644 --- a/lemur/plugins/lemur_aws/tests/test_sns.py +++ b/lemur/plugins/lemur_aws/tests/test_sns.py @@ -20,8 +20,9 @@ def test_format(certificate, endpoint): expected_message = { "notification_type": "expiration", "certificate_name": certificate["name"], - "expires": arrow.get(certificate["validityEnd"]).format("YYYY-MM-ddTHH:mm:ss"), + "expires": arrow.get(certificate["validityEnd"]).format("YYYY-MM-DDTHH:mm:ss"), "endpoints_detected": 0, + "owner": certificate["owner"], "details": "https://lemur.example.com/#/certificates/{name}".format(name=certificate["name"]) } assert expected_message == json.loads(format_message(certificate, "expiration")) @@ -57,7 +58,9 @@ def test_publish(certificate, endpoint): 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) - assert json.loads(actual_message["Body"])["Message"] == format_message(certificate, "expiration") + actual_json = json.loads(actual_message["Body"]) + assert actual_json["Message"] == format_message(certificate, "expiration") + assert actual_json["Subject"] == "Lemur: Expiration Notification" def get_options(): From 45cc9528d28416155197d72653996236c8843180 Mon Sep 17 00:00:00 2001 From: Jasmine Schladen Date: Thu, 29 Oct 2020 13:48:43 -0700 Subject: [PATCH 20/20] Cleaner syntax for default region --- lemur/plugins/lemur_email/plugin.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lemur/plugins/lemur_email/plugin.py b/lemur/plugins/lemur_email/plugin.py index a9e35d16..f380c82e 100644 --- a/lemur/plugins/lemur_email/plugin.py +++ b/lemur/plugins/lemur_email/plugin.py @@ -61,9 +61,7 @@ def send_via_ses(subject, body, targets): :param targets: :return: """ - ses_region = current_app.config.get("LEMUR_SES_REGION") - if not ses_region: - ses_region = "us-east-1" + ses_region = current_app.config.get("LEMUR_SES_REGION", "us-east-1") client = boto3.client("ses", region_name=ses_region) source_arn = current_app.config.get("LEMUR_SES_SOURCE_ARN") args = {