From ac0282529ef9ec60050be4681c1ce278cb0eab04 Mon Sep 17 00:00:00 2001 From: csine-nflx Date: Mon, 3 Feb 2020 11:05:20 -0800 Subject: [PATCH 01/19] adding basic logging on success --- lemur/plugins/lemur_acme/plugin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lemur/plugins/lemur_acme/plugin.py b/lemur/plugins/lemur_acme/plugin.py index e38870d8..54493f97 100644 --- a/lemur/plugins/lemur_acme/plugin.py +++ b/lemur/plugins/lemur_acme/plugin.py @@ -183,6 +183,10 @@ class AcmeHandler(object): else: raise + current_app.logger.debug( + f"Successfully resolved Acme order: {order.uri}", exc_info=True + ) + pem_certificate = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.load_certificate( From ca8e73286f6f0ade5ffb130e276397e9d6b96273 Mon Sep 17 00:00:00 2001 From: csine-nflx Date: Wed, 12 Feb 2020 15:10:24 -0800 Subject: [PATCH 02/19] fixed get_domains() to remove duplicate entries, updated usage and tests --- lemur/plugins/lemur_acme/plugin.py | 14 ++++---------- lemur/plugins/lemur_acme/tests/test_acme.py | 21 +++++++++++++++++++-- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/lemur/plugins/lemur_acme/plugin.py b/lemur/plugins/lemur_acme/plugin.py index 8991efdf..95689a13 100644 --- a/lemur/plugins/lemur_acme/plugin.py +++ b/lemur/plugins/lemur_acme/plugin.py @@ -254,8 +254,9 @@ class AcmeHandler(object): domains = [options["common_name"]] if options.get("extensions"): - for name in options["extensions"]["sub_alt_names"]["names"]: - domains.append(name) + for dns_name in options["extensions"]["sub_alt_names"]["names"]: + if dns_name.value not in domains: + domains.append(dns_name.value) current_app.logger.debug("Got these domains: {0}".format(domains)) return domains @@ -640,15 +641,8 @@ class ACMEIssuerPlugin(IssuerPlugin): domains = self.acme.get_domains(issuer_options) if not create_immediately: # Create pending authorizations that we'll need to do the creation - authz_domains = [] - for d in domains: - if type(d) == str: - authz_domains.append(d) - else: - authz_domains.append(d.value) - dns_authorization = authorization_service.create( - account_number, authz_domains, provider_type + account_number, domains, provider_type ) # Return id of the DNS Authorization return None, None, dns_authorization.id diff --git a/lemur/plugins/lemur_acme/tests/test_acme.py b/lemur/plugins/lemur_acme/tests/test_acme.py index 04997ace..990a556e 100644 --- a/lemur/plugins/lemur_acme/tests/test_acme.py +++ b/lemur/plugins/lemur_acme/tests/test_acme.py @@ -1,4 +1,6 @@ import unittest + +from cryptography.x509 import DNSName from requests.models import Response from mock import MagicMock, Mock, patch @@ -74,12 +76,14 @@ class TestAcme(unittest.TestCase): @patch("acme.client.Client") @patch("lemur.plugins.lemur_acme.plugin.current_app") @patch("lemur.plugins.lemur_acme.cloudflare.wait_for_dns_change") + @patch("time.sleep") def test_complete_dns_challenge_success( - self, mock_wait_for_dns_change, mock_current_app, mock_acme + self, mock_sleep, mock_wait_for_dns_change, mock_current_app, mock_acme ): mock_dns_provider = Mock() mock_dns_provider.wait_for_dns_change = Mock(return_value=True) mock_authz = Mock() + mock_sleep.return_value = False mock_authz.dns_challenge.response = Mock() mock_authz.dns_challenge.response.simple_verify = Mock(return_value=True) mock_authz.authz = [] @@ -179,7 +183,7 @@ class TestAcme(unittest.TestCase): options = { "common_name": "test.netflix.net", "extensions": { - "sub_alt_names": {"names": ["test2.netflix.net", "test3.netflix.net"]} + "sub_alt_names": {"names": [DNSName("test2.netflix.net"), DNSName("test3.netflix.net")]} }, } result = self.acme.get_domains(options) @@ -187,6 +191,19 @@ class TestAcme(unittest.TestCase): result, [options["common_name"], "test2.netflix.net", "test3.netflix.net"] ) + @patch("lemur.plugins.lemur_acme.plugin.current_app") + def test_get_domains_san(self, mock_current_app): + options = { + "common_name": "test.netflix.net", + "extensions": { + "sub_alt_names": {"names": [DNSName("test.netflix.net"), DNSName("test2.netflix.net")]} + }, + } + result = self.acme.get_domains(options) + self.assertEqual( + result, [options["common_name"], "test2.netflix.net"] + ) + @patch( "lemur.plugins.lemur_acme.plugin.AcmeHandler.start_dns_challenge", return_value="test", From 6c7bb5f9b73fe9bca34bfadb2f8a1eae01a5bcd4 Mon Sep 17 00:00:00 2001 From: sirferl <41906265+sirferl@users.noreply.github.com> Date: Thu, 13 Feb 2020 07:35:35 +0100 Subject: [PATCH 03/19] Fixed TLS secret format ( #2913 ) The Plugin handled the TLS secret format wrong: it sent chain certificate instead of requested public certificate #2913 --- lemur/plugins/lemur_kubernetes/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/plugins/lemur_kubernetes/plugin.py b/lemur/plugins/lemur_kubernetes/plugin.py index 62ffffda..f7ff00f7 100644 --- a/lemur/plugins/lemur_kubernetes/plugin.py +++ b/lemur/plugins/lemur_kubernetes/plugin.py @@ -96,7 +96,7 @@ def build_secret(secret_format, secret_name, body, private_key, cert_chain): if secret_format == "TLS": secret["type"] = "kubernetes.io/tls" secret["data"] = { - "tls.crt": base64encode(cert_chain), + "tls.crt": base64encode(body), "tls.key": base64encode(private_key), } if secret_format == "Certificate": From 571c8bf42d83f32ee808840edd2287a665a2f6fc Mon Sep 17 00:00:00 2001 From: sirferl <41906265+sirferl@users.noreply.github.com> Date: Thu, 13 Feb 2020 07:38:04 +0100 Subject: [PATCH 04/19] Error when validity_end date is empty #2905 this lines of code (114ff) in threw an error, when the validity_end date was empty: if options.get("validity_end") > arrow.utcnow().shift(years=2): raise Exception( "Verisign issued certificates cannot exceed two years in validity" ) Actually, they are not needed, because immidiately following is a check for an empty validity_end and for the length of the entered period. When I commented it out for testing, the error was gone and everything worked as expected. --- lemur/plugins/lemur_verisign/plugin.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index 7bf517b7..6a49364f 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -111,11 +111,7 @@ def process_options(options): data["subject_alt_names"] = ",".join(get_additional_names(options)) - if options.get("validity_end") > arrow.utcnow().shift(years=2): - raise Exception( - "Verisign issued certificates cannot exceed two years in validity" - ) - + if options.get("validity_end"): # VeriSign (Symantec) only accepts strictly smaller than 2 year end date if options.get("validity_end") < arrow.utcnow().shift(years=2, days=-1): From 2b849a65205a00ea129247263c4ac300f8ebd25d Mon Sep 17 00:00:00 2001 From: Hossein Shafagh Date: Thu, 13 Feb 2020 15:58:07 -0800 Subject: [PATCH 05/19] Update plugin.py making lint happy --- lemur/plugins/lemur_verisign/plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index 6a49364f..3e7c383f 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -110,7 +110,6 @@ def process_options(options): } data["subject_alt_names"] = ",".join(get_additional_names(options)) - if options.get("validity_end"): # VeriSign (Symantec) only accepts strictly smaller than 2 year end date From af21225918929e936ed5781246c3cca05fe44d4d Mon Sep 17 00:00:00 2001 From: csine-nflx Date: Thu, 13 Feb 2020 16:38:33 -0800 Subject: [PATCH 06/19] adding logging on sucess and metric submission of URL for certificate issuance --- lemur/plugins/lemur_acme/plugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lemur/plugins/lemur_acme/plugin.py b/lemur/plugins/lemur_acme/plugin.py index 54493f97..179285b3 100644 --- a/lemur/plugins/lemur_acme/plugin.py +++ b/lemur/plugins/lemur_acme/plugin.py @@ -172,7 +172,7 @@ class AcmeHandler(object): except (AcmeError, TimeoutError): sentry.captureException(extra={"order_url": str(order.uri)}) - metrics.send("request_certificate_error", "counter", 1) + metrics.send("request_certificate_error", "counter", 1, metric_tags={"uri": order.uri}) current_app.logger.error( f"Unable to resolve Acme order: {order.uri}", exc_info=True ) @@ -183,7 +183,8 @@ class AcmeHandler(object): else: raise - current_app.logger.debug( + metrics.send("request_certificate_success", "counter", 1, metric_tags={"uri": order.uri}) + current_app.logger.info( f"Successfully resolved Acme order: {order.uri}", exc_info=True ) From 8e3cc93d6ae64bd6b75b1d54a7903d87052bb96f Mon Sep 17 00:00:00 2001 From: sirferl <41906265+sirferl@users.noreply.github.com> Date: Fri, 14 Feb 2020 07:50:18 +0100 Subject: [PATCH 07/19] Whitespaces in empty line 113 removed --- lemur/plugins/lemur_verisign/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index 3e7c383f..a0e2d1cb 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -110,7 +110,7 @@ def process_options(options): } data["subject_alt_names"] = ",".join(get_additional_names(options)) - + if options.get("validity_end"): # VeriSign (Symantec) only accepts strictly smaller than 2 year end date if options.get("validity_end") < arrow.utcnow().shift(years=2, days=-1): From fabcad1e46c44d86c7dfe69204fdf45cbf1c189c Mon Sep 17 00:00:00 2001 From: sirferl <41906265+sirferl@users.noreply.github.com> Date: Sat, 15 Feb 2020 15:52:24 +0100 Subject: [PATCH 08/19] New variable VERISIGN_PRODUCT_(authority.name) If there is a config variable with VERISIGN_PRODUCT_ take the value as Cert product-type else default to "Server", to be compatoible with former versions. This enables the use of different Verisign authorities for differnt cert-products eg. EV or Standard Certs --- lemur/plugins/lemur_verisign/plugin.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index a0e2d1cb..c74a71f1 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -98,10 +98,18 @@ def process_options(options): :param options: :return: dict or valid verisign options """ + + # if there is a config variable with VERISIGN_PRODUCT_ take the value as Cert product-type + # else default to "Server", to be compatoible with former versions + authority = options.get("authority").name.upper() + product_type = current_app.config.get("VERISIGN_PRODUCT_{0}".format(authority)) + if product_type is None: + product_type ="Server" + data = { "challenge": get_psuedo_random_string(), "serverType": "Apache", - "certProductType": "Server", + "certProductType": product_type, "firstName": current_app.config.get("VERISIGN_FIRST_NAME"), "lastName": current_app.config.get("VERISIGN_LAST_NAME"), "signatureAlgorithm": "sha256WithRSAEncryption", From bfa953270d3840b9d96807b69c30466138f89b39 Mon Sep 17 00:00:00 2001 From: sirferl <41906265+sirferl@users.noreply.github.com> Date: Sat, 15 Feb 2020 16:04:44 +0100 Subject: [PATCH 09/19] Fixed whitespace error --- lemur/plugins/lemur_verisign/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index c74a71f1..cd716e84 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -104,7 +104,7 @@ def process_options(options): authority = options.get("authority").name.upper() product_type = current_app.config.get("VERISIGN_PRODUCT_{0}".format(authority)) if product_type is None: - product_type ="Server" + product_type = "Server" data = { "challenge": get_psuedo_random_string(), From 3693bc2d8be12cafce118c196fadf72e8606d672 Mon Sep 17 00:00:00 2001 From: sirferl <41906265+sirferl@users.noreply.github.com> Date: Sat, 15 Feb 2020 16:09:25 +0100 Subject: [PATCH 10/19] removed whitespaces inserted by online editor --- lemur/plugins/lemur_verisign/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index cd716e84..ca606baf 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -98,14 +98,14 @@ def process_options(options): :param options: :return: dict or valid verisign options """ - + # if there is a config variable with VERISIGN_PRODUCT_ take the value as Cert product-type # else default to "Server", to be compatoible with former versions authority = options.get("authority").name.upper() product_type = current_app.config.get("VERISIGN_PRODUCT_{0}".format(authority)) if product_type is None: product_type = "Server" - + data = { "challenge": get_psuedo_random_string(), "serverType": "Apache", From a70a49e4e9cffa757a608022e51e14646d766513 Mon Sep 17 00:00:00 2001 From: sirferl <41906265+sirferl@users.noreply.github.com> Date: Sat, 15 Feb 2020 16:11:58 +0100 Subject: [PATCH 11/19] Update plugin.py --- lemur/plugins/lemur_verisign/plugin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index ca606baf..6d9182df 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -98,14 +98,12 @@ def process_options(options): :param options: :return: dict or valid verisign options """ - # if there is a config variable with VERISIGN_PRODUCT_ take the value as Cert product-type # else default to "Server", to be compatoible with former versions authority = options.get("authority").name.upper() product_type = current_app.config.get("VERISIGN_PRODUCT_{0}".format(authority)) if product_type is None: product_type = "Server" - data = { "challenge": get_psuedo_random_string(), "serverType": "Apache", From 1815c8997064130e903e172a7afc4a13df23f714 Mon Sep 17 00:00:00 2001 From: sirferl <41906265+sirferl@users.noreply.github.com> Date: Sun, 16 Feb 2020 09:28:52 +0100 Subject: [PATCH 12/19] Made the change more elegant As suggested by @hosseinsh. This is of course more elegant. --- lemur/plugins/lemur_verisign/plugin.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index 6d9182df..0864657a 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -101,9 +101,7 @@ def process_options(options): # if there is a config variable with VERISIGN_PRODUCT_ take the value as Cert product-type # else default to "Server", to be compatoible with former versions authority = options.get("authority").name.upper() - product_type = current_app.config.get("VERISIGN_PRODUCT_{0}".format(authority)) - if product_type is None: - product_type = "Server" + product_type = current_app.config.get("VERISIGN_PRODUCT_{0}".format(authority), "Server") data = { "challenge": get_psuedo_random_string(), "serverType": "Apache", From 3fd0d3e1416fcb79aedb122c83c8c23213967e11 Mon Sep 17 00:00:00 2001 From: sirferl <41906265+sirferl@users.noreply.github.com> Date: Mon, 17 Feb 2020 12:40:36 +0100 Subject: [PATCH 13/19] Added VERISIGN_INTERMEDIATE_ parameter When using the VERISIGN_PRODUCT_ Parameter one also has to add this parameter: VERISIGN_INTERMEDIATE_ = """ """ While doing this, I also added code, so the external_id field is filled with data from CA-Answer --- lemur/plugins/lemur_verisign/plugin.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index 0864657a..2d108071 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -209,7 +209,7 @@ class VerisignIssuerPlugin(IssuerPlugin): response = self.session.post(url, data=data) try: - cert = handle_response(response.content)["Response"]["Certificate"] + response_dict = handle_response(response.content) except KeyError: metrics.send( "verisign_create_certificate_error", @@ -221,8 +221,12 @@ class VerisignIssuerPlugin(IssuerPlugin): extra={"common_name": issuer_options.get("common_name", "")} ) raise Exception(f"Error with Verisign: {response.content}") - # TODO add external id - return cert, current_app.config.get("VERISIGN_INTERMEDIATE"), None + # DONE: TODO add external id + authority = issuer_options.get("authority").name.upper() + cert = response_dict['Response']['Certificate'] + external_id = response_dict['Response']['Transaction_ID'] + chain = current_app.config.get("VERISIGN_INTERMEDIATE_{0}".format(authority), current_app.config.get("VERISIGN_INTERMEDIATE")) + return cert, chain, external_id @staticmethod def create_authority(options): From ed3472d029c88b678e1e9e5104151bd888b207b4 Mon Sep 17 00:00:00 2001 From: sirferl <41906265+sirferl@users.noreply.github.com> Date: Mon, 17 Feb 2020 15:21:29 +0100 Subject: [PATCH 14/19] Update plugin.py --- lemur/plugins/lemur_verisign/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index 2d108071..479f3215 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -209,7 +209,7 @@ class VerisignIssuerPlugin(IssuerPlugin): response = self.session.post(url, data=data) try: - response_dict = handle_response(response.content) + response_dict = handle_response(response.content) except KeyError: metrics.send( "verisign_create_certificate_error", @@ -225,7 +225,7 @@ class VerisignIssuerPlugin(IssuerPlugin): authority = issuer_options.get("authority").name.upper() cert = response_dict['Response']['Certificate'] external_id = response_dict['Response']['Transaction_ID'] - chain = current_app.config.get("VERISIGN_INTERMEDIATE_{0}".format(authority), current_app.config.get("VERISIGN_INTERMEDIATE")) + chain = current_app.config.get("VERISIGN_INTERMEDIATE_{0}".format(authority), current_app.config.get("VERISIGN_INTERMEDIATE")) return cert, chain, external_id @staticmethod From e75df1ddc92a79dc197c698d38078f8c3d80157a Mon Sep 17 00:00:00 2001 From: sirferl <41906265+sirferl@users.noreply.github.com> Date: Mon, 17 Feb 2020 19:04:20 +0100 Subject: [PATCH 15/19] Update plugin.py --- lemur/plugins/lemur_verisign/plugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index 479f3215..f913861c 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -221,10 +221,11 @@ class VerisignIssuerPlugin(IssuerPlugin): extra={"common_name": issuer_options.get("common_name", "")} ) raise Exception(f"Error with Verisign: {response.content}") - # DONE: TODO add external id authority = issuer_options.get("authority").name.upper() cert = response_dict['Response']['Certificate'] - external_id = response_dict['Response']['Transaction_ID'] + external_id = None + if 'Transaction_ID' in response_dict['Response'].keys(): + external_id = response_dict['Response']['Transaction_ID'] chain = current_app.config.get("VERISIGN_INTERMEDIATE_{0}".format(authority), current_app.config.get("VERISIGN_INTERMEDIATE")) return cert, chain, external_id From 64a7faffd982213acf566f8b9b79229ba559c2a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2020 18:30:10 +0000 Subject: [PATCH 16/19] Bump bleach from 3.1.0 to 3.1.1 Bumps [bleach](https://github.com/mozilla/bleach) from 3.1.0 to 3.1.1. - [Release notes](https://github.com/mozilla/bleach/releases) - [Changelog](https://github.com/mozilla/bleach/blob/master/CHANGES) - [Commits](https://github.com/mozilla/bleach/compare/v3.1.0...v3.1.1) Signed-off-by: dependabot[bot] --- requirements-dev.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d1423888..224789f6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,37 +5,39 @@ # pip-compile --no-index --output-file=requirements-dev.txt requirements-dev.in # aspy.yaml==1.3.0 # via pre-commit -bleach==3.1.0 # via readme-renderer +bleach==3.1.1 # via readme-renderer certifi==2019.11.28 # via requests +cffi==1.14.0 # via cryptography cfgv==2.0.1 # via pre-commit chardet==3.0.4 # via requests +cryptography==2.8 # via secretstorage docutils==0.15.2 # via readme-renderer flake8==3.5.0 identify==1.4.9 # via pre-commit idna==2.8 # via requests -importlib-metadata==1.3.0 # via keyring, pre-commit, twine invoke==1.3.0 +jeepney==0.4.2 # via secretstorage keyring==21.0.0 # via twine mccabe==0.6.1 # via flake8 -more-itertools==8.0.2 # via zipp nodeenv==1.3.3 pkginfo==1.5.0.1 # via twine pre-commit==1.21.0 pycodestyle==2.3.1 # via flake8 +pycparser==2.19 # via cffi pyflakes==1.6.0 # via flake8 pygments==2.5.2 # via readme-renderer pyyaml==5.2 readme-renderer==24.0 # via twine requests-toolbelt==0.9.1 # via twine requests==2.22.0 # via requests-toolbelt, twine -six==1.13.0 # via bleach, cfgv, pre-commit, readme-renderer +secretstorage==3.1.2 # via keyring +six==1.13.0 # via bleach, cfgv, cryptography, pre-commit, readme-renderer toml==0.10.0 # via pre-commit tqdm==4.41.1 # via twine twine==3.1.1 urllib3==1.25.7 # via requests virtualenv==16.7.9 # via pre-commit webencodings==0.5.1 # via bleach -zipp==0.6.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # setuptools From 318292704d3c2167c9291bbb26e77ca2df8c543e Mon Sep 17 00:00:00 2001 From: csine-nflx Date: Tue, 3 Mar 2020 14:29:17 -0800 Subject: [PATCH 17/19] fixing default/max DigiCert validity values --- lemur/plugins/lemur_digicert/plugin.py | 88 +++--- .../lemur_digicert/tests/test_digicert.py | 266 ++++++++++-------- 2 files changed, 202 insertions(+), 152 deletions(-) diff --git a/lemur/plugins/lemur_digicert/plugin.py b/lemur/plugins/lemur_digicert/plugin.py index 88ea5b6b..b9508357 100644 --- a/lemur/plugins/lemur_digicert/plugin.py +++ b/lemur/plugins/lemur_digicert/plugin.py @@ -14,21 +14,17 @@ .. moduleauthor:: Kevin Glisson """ import json + import arrow -import requests - import pem -from retrying import retry - -from flask import current_app - +import requests from cryptography import x509 - -from lemur.extensions import metrics +from flask import current_app from lemur.common.utils import validate_conf -from lemur.plugins.bases import IssuerPlugin, SourcePlugin - +from lemur.extensions import metrics from lemur.plugins import lemur_digicert as digicert +from lemur.plugins.bases import IssuerPlugin, SourcePlugin +from retrying import retry def log_status_code(r, *args, **kwargs): @@ -64,24 +60,38 @@ def signature_hash(signing_algorithm): raise Exception("Unsupported signing algorithm.") -def determine_validity_years(end_date): +def determine_validity_years(years): """Given an end date determine how many years into the future that date is. + :param years: + :return: validity in years + """ + default_years = current_app.config.get("DIGICERT_DEFAULT_VALIDITY", 1) + max_years = current_app.config.get("DIGICERT_MAX_VALIDITY", default_years) + + if years > max_years: + return max_years + if years not in [1, 2, 3]: + return default_years + else: + return years + + +def determine_end_date(end_date): + """ + Determine appropriate end date :param end_date: - :return: str validity in years + :return: validity_end """ - now = arrow.utcnow() + default_years = current_app.config.get("DIGICERT_DEFAULT_VALIDITY", 1) + max_validity_end = arrow.utcnow().shift(years=current_app.config.get("DIGICERT_MAX_VALIDITY", default_years)) - if end_date < now.shift(years=+1): - return 1 - elif end_date < now.shift(years=+2): - return 2 - elif end_date < now.shift(years=+3): - return 3 + if not end_date: + end_date = arrow.utcnow().shift(years=default_years) - raise Exception( - "DigiCert issued certificates cannot exceed three" " years in validity" - ) + if end_date > max_validity_end: + end_date = max_validity_end + return end_date def get_additional_names(options): @@ -107,12 +117,6 @@ def map_fields(options, csr): :param csr: :return: dict or valid DigiCert options """ - if not options.get("validity_years"): - if not options.get("validity_end"): - options["validity_years"] = current_app.config.get( - "DIGICERT_DEFAULT_VALIDITY", 1 - ) - data = dict( certificate={ "common_name": options["common_name"], @@ -125,9 +129,11 @@ def map_fields(options, csr): data["certificate"]["dns_names"] = get_additional_names(options) if options.get("validity_years"): - data["validity_years"] = options["validity_years"] + data["validity_years"] = determine_validity_years(options.get("validity_years")) + elif options.get("validity_end"): + data["custom_expiration_date"] = determine_end_date(options.get("validity_end")).format("YYYY-MM-DD") else: - data["custom_expiration_date"] = options["validity_end"].format("YYYY-MM-DD") + data["validity_years"] = determine_validity_years(0) if current_app.config.get("DIGICERT_PRIVATE", False): if "product" in data: @@ -144,18 +150,15 @@ def map_cis_fields(options, csr): :param options: :param csr: - :return: + :return: data """ - if not options.get("validity_years"): - if not options.get("validity_end"): - options["validity_end"] = arrow.utcnow().shift( - years=current_app.config.get("DIGICERT_DEFAULT_VALIDITY", 1) - ) - options["validity_years"] = determine_validity_years(options["validity_end"]) + + if options.get("validity_years"): + validity_end = determine_end_date(arrow.utcnow().shift(years=options["validity_years"])) + elif options.get("validity_end"): + validity_end = determine_end_date(options.get("validity_end")) else: - options["validity_end"] = arrow.utcnow().shift( - years=options["validity_years"] - ) + validity_end = determine_end_date(False) data = { "profile_name": current_app.config.get("DIGICERT_CIS_PROFILE_NAMES", {}).get(options['authority'].name), @@ -164,7 +167,7 @@ def map_cis_fields(options, csr): "csr": csr, "signature_hash": signature_hash(options.get("signing_algorithm")), "validity": { - "valid_to": options["validity_end"].format("YYYY-MM-DDTHH:MM") + "Z" + "valid_to": validity_end.format("YYYY-MM-DDTHH:MM") + "Z" }, "organization": { "name": options["organization"], @@ -173,7 +176,8 @@ def map_cis_fields(options, csr): } # possibility to default to a SIGNING_ALGORITHM for a given profile if current_app.config.get("DIGICERT_CIS_SIGNING_ALGORITHMS", {}).get(options['authority'].name): - data["signature_hash"] = current_app.config.get("DIGICERT_CIS_SIGNING_ALGORITHMS", {}).get(options['authority'].name) + data["signature_hash"] = current_app.config.get("DIGICERT_CIS_SIGNING_ALGORITHMS", {}).get( + options['authority'].name) return data diff --git a/lemur/plugins/lemur_digicert/tests/test_digicert.py b/lemur/plugins/lemur_digicert/tests/test_digicert.py index 77b0a1fa..b9576f51 100644 --- a/lemur/plugins/lemur_digicert/tests/test_digicert.py +++ b/lemur/plugins/lemur_digicert/tests/test_digicert.py @@ -1,117 +1,125 @@ -import pytest -import arrow import json -from unittest.mock import patch - -from freezegun import freeze_time - -from lemur.tests.vectors import CSR_STR +import arrow +import pytest from cryptography import x509 +from freezegun import freeze_time +from lemur.plugins.lemur_digicert import plugin +from lemur.tests.vectors import CSR_STR +from mock import Mock, patch -def test_map_fields_with_validity_end_and_start(app): - from lemur.plugins.lemur_digicert.plugin import map_fields - - names = [u"one.example.com", u"two.example.com", u"three.example.com"] - - options = { - "common_name": "example.com", - "owner": "bob@example.com", - "description": "test certificate", - "extensions": {"sub_alt_names": {"names": [x509.DNSName(x) for x in names]}}, - "validity_end": arrow.get(2017, 5, 7), - "validity_start": arrow.get(2016, 10, 30), - } - - data = map_fields(options, CSR_STR) - - assert data == { - "certificate": { - "csr": CSR_STR, - "common_name": "example.com", - "dns_names": names, - "signature_hash": "sha256", - }, - "organization": {"id": 111111}, - "custom_expiration_date": arrow.get(2017, 5, 7).format("YYYY-MM-DD"), +def config_mock(*args): + values = { + "DIGICERT_ORG_ID": 111111, + "DIGICERT_PRIVATE": False, + "DIGICERT_DEFAULT_SIGNING_ALGORITHM": "sha256", + "DIGICERT_DEFAULT_VALIDITY": 1, + "DIGICERT_MAX_VALIDITY": 2, + "DIGICERT_CIS_PROFILE_NAMES": {"digicert": 'digicert'}, + "DIGICERT_CIS_SIGNING_ALGORITHMS": {"verisign-sha1-intermediary": 'sha1'}, } + return values[args[0]] -def test_map_fields_with_validity_years(app): - from lemur.plugins.lemur_digicert.plugin import map_fields - - names = [u"one.example.com", u"two.example.com", u"three.example.com"] - - options = { - "common_name": "example.com", - "owner": "bob@example.com", - "description": "test certificate", - "extensions": {"sub_alt_names": {"names": [x509.DNSName(x) for x in names]}}, - "validity_years": 2, - "validity_end": arrow.get(2017, 10, 30), - } - - data = map_fields(options, CSR_STR) - - assert data == { - "certificate": { - "csr": CSR_STR, - "common_name": "example.com", - "dns_names": names, - "signature_hash": "sha256", - }, - "organization": {"id": 111111}, - "validity_years": 2, - } +@patch("lemur.plugins.lemur_digicert.plugin.current_app") +def test_determine_validity_years(mock_current_app): + mock_current_app.config.get = Mock(return_value=2) + assert plugin.determine_validity_years(1) == 1 + assert plugin.determine_validity_years(0) == 2 + assert plugin.determine_validity_years(3) == 2 -def test_map_cis_fields(app, authority): - from lemur.plugins.lemur_digicert.plugin import map_cis_fields - - names = [u"one.example.com", u"two.example.com", u"three.example.com"] - - options = { - "common_name": "example.com", - "owner": "bob@example.com", - "description": "test certificate", - "extensions": {"sub_alt_names": {"names": [x509.DNSName(x) for x in names]}}, - "organization": "Example, Inc.", - "organizational_unit": "Example Org", - "validity_end": arrow.get(2017, 5, 7), - "validity_start": arrow.get(2016, 10, 30), - "authority": authority, - } - - data = map_cis_fields(options, CSR_STR) - - assert data == { - "common_name": "example.com", - "csr": CSR_STR, - "additional_dns_names": names, - "signature_hash": "sha256", - "organization": {"name": "Example, Inc.", "units": ["Example Org"]}, - "validity": { - "valid_to": arrow.get(2017, 5, 7).format("YYYY-MM-DDTHH:MM") + "Z" - }, - "profile_name": None, - } - - options = { - "common_name": "example.com", - "owner": "bob@example.com", - "description": "test certificate", - "extensions": {"sub_alt_names": {"names": [x509.DNSName(x) for x in names]}}, - "organization": "Example, Inc.", - "organizational_unit": "Example Org", - "validity_years": 2, - "authority": authority, - } - +@patch("lemur.plugins.lemur_digicert.plugin.current_app") +def test_determine_end_date(mock_current_app): + mock_current_app.config.get = Mock(return_value=2) with freeze_time(time_to_freeze=arrow.get(2016, 11, 3).datetime): - data = map_cis_fields(options, CSR_STR) + assert arrow.get(2018, 11, 3) == plugin.determine_end_date(0) + assert arrow.get(2018, 5, 7) == plugin.determine_end_date(arrow.get(2018, 5, 7)) + assert arrow.get(2018, 11, 3) == plugin.determine_end_date(arrow.get(2020, 5, 7)) - assert data == { + +@patch("lemur.plugins.lemur_digicert.plugin.current_app") +def test_map_fields_with_validity_years(mock_current_app): + mock_current_app.config.get = Mock(side_effect=config_mock) + + with patch('lemur.plugins.lemur_digicert.plugin.signature_hash') as mock_signature_hash: + mock_signature_hash.return_value = "sha256" + + names = [u"one.example.com", u"two.example.com", u"three.example.com"] + options = { + "common_name": "example.com", + "owner": "bob@example.com", + "description": "test certificate", + "extensions": {"sub_alt_names": {"names": [x509.DNSName(x) for x in names]}}, + "validity_years": 2 + } + expected = { + "certificate": { + "csr": CSR_STR, + "common_name": "example.com", + "dns_names": names, + "signature_hash": "sha256", + }, + "organization": {"id": 111111}, + "validity_years": 2, + } + assert expected == plugin.map_fields(options, CSR_STR) + + +@patch("lemur.plugins.lemur_digicert.plugin.current_app") +def test_map_fields_with_validity_end_and_start(mock_current_app): + mock_current_app.config.get = Mock(side_effect=config_mock) + plugin.determine_end_date = Mock(return_value=arrow.get(2017, 5, 7)) + + with patch('lemur.plugins.lemur_digicert.plugin.signature_hash') as mock_signature_hash: + mock_signature_hash.return_value = "sha256" + + names = [u"one.example.com", u"two.example.com", u"three.example.com"] + options = { + "common_name": "example.com", + "owner": "bob@example.com", + "description": "test certificate", + "extensions": {"sub_alt_names": {"names": [x509.DNSName(x) for x in names]}}, + "validity_end": arrow.get(2017, 5, 7), + "validity_start": arrow.get(2016, 10, 30), + } + + expected = { + "certificate": { + "csr": CSR_STR, + "common_name": "example.com", + "dns_names": names, + "signature_hash": "sha256", + }, + "organization": {"id": 111111}, + "custom_expiration_date": arrow.get(2017, 5, 7).format("YYYY-MM-DD"), + } + + assert expected == plugin.map_fields(options, CSR_STR) + + +@patch("lemur.plugins.lemur_digicert.plugin.current_app") +def test_map_cis_fields_with_validity_years(mock_current_app, authority): + mock_current_app.config.get = Mock(side_effect=config_mock) + plugin.determine_end_date = Mock(return_value=arrow.get(2018, 11, 3)) + + with patch('lemur.plugins.lemur_digicert.plugin.signature_hash') as mock_signature_hash: + mock_signature_hash.return_value = "sha256" + + names = [u"one.example.com", u"two.example.com", u"three.example.com"] + options = { + "common_name": "example.com", + "owner": "bob@example.com", + "description": "test certificate", + "extensions": {"sub_alt_names": {"names": [x509.DNSName(x) for x in names]}}, + "organization": "Example, Inc.", + "organizational_unit": "Example Org", + "validity_years": 2, + "authority": authority, + } + + expected = { "common_name": "example.com", "csr": CSR_STR, "additional_dns_names": names, @@ -123,21 +131,59 @@ def test_map_cis_fields(app, authority): "profile_name": None, } + assert expected == plugin.map_cis_fields(options, CSR_STR) -def test_signature_hash(app): - from lemur.plugins.lemur_digicert.plugin import signature_hash - assert signature_hash(None) == "sha256" - assert signature_hash("sha256WithRSA") == "sha256" - assert signature_hash("sha384WithRSA") == "sha384" - assert signature_hash("sha512WithRSA") == "sha512" +@patch("lemur.plugins.lemur_digicert.plugin.current_app") +def test_map_cis_fields_with_validity_end_and_start(mock_current_app, app, authority): + mock_current_app.config.get = Mock(side_effect=config_mock) + plugin.determine_end_date = Mock(return_value=arrow.get(2017, 5, 7)) + + with patch('lemur.plugins.lemur_digicert.plugin.signature_hash') as mock_signature_hash: + mock_signature_hash.return_value = "sha256" + + names = [u"one.example.com", u"two.example.com", u"three.example.com"] + options = { + "common_name": "example.com", + "owner": "bob@example.com", + "description": "test certificate", + "extensions": {"sub_alt_names": {"names": [x509.DNSName(x) for x in names]}}, + "organization": "Example, Inc.", + "organizational_unit": "Example Org", + "validity_end": arrow.get(2017, 5, 7), + "validity_start": arrow.get(2016, 10, 30), + "authority": authority + } + + expected = { + "common_name": "example.com", + "csr": CSR_STR, + "additional_dns_names": names, + "signature_hash": "sha256", + "organization": {"name": "Example, Inc.", "units": ["Example Org"]}, + "validity": { + "valid_to": arrow.get(2017, 5, 7).format("YYYY-MM-DDTHH:MM") + "Z" + }, + "profile_name": None, + } + + assert expected == plugin.map_cis_fields(options, CSR_STR) + + +@patch("lemur.plugins.lemur_digicert.plugin.current_app") +def test_signature_hash(mock_current_app, app): + mock_current_app.config.get = Mock(side_effect=config_mock) + assert plugin.signature_hash(None) == "sha256" + assert plugin.signature_hash("sha256WithRSA") == "sha256" + assert plugin.signature_hash("sha384WithRSA") == "sha384" + assert plugin.signature_hash("sha512WithRSA") == "sha512" with pytest.raises(Exception): - signature_hash("sdfdsf") + plugin.signature_hash("sdfdsf") def test_issuer_plugin_create_certificate( - certificate_="""\ + certificate_="""\ -----BEGIN CERTIFICATE----- abc -----END CERTIFICATE----- From 6c46481ffd9dd3dc0479a6de351b51fceb653091 Mon Sep 17 00:00:00 2001 From: csine-nflx Date: Tue, 3 Mar 2020 14:40:50 -0800 Subject: [PATCH 18/19] simplifying return statement for validity years --- lemur/plugins/lemur_digicert/plugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lemur/plugins/lemur_digicert/plugin.py b/lemur/plugins/lemur_digicert/plugin.py index b9508357..e5c4b2ce 100644 --- a/lemur/plugins/lemur_digicert/plugin.py +++ b/lemur/plugins/lemur_digicert/plugin.py @@ -72,8 +72,7 @@ def determine_validity_years(years): return max_years if years not in [1, 2, 3]: return default_years - else: - return years + return years def determine_end_date(end_date): From fdc1e20c234303adc27ea7768b72978d6bf5d80b Mon Sep 17 00:00:00 2001 From: csine-nflx Date: Tue, 3 Mar 2020 17:27:15 -0800 Subject: [PATCH 19/19] updating config_mock defaults --- lemur/plugins/lemur_digicert/tests/test_digicert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/plugins/lemur_digicert/tests/test_digicert.py b/lemur/plugins/lemur_digicert/tests/test_digicert.py index b9576f51..1e9ebca4 100644 --- a/lemur/plugins/lemur_digicert/tests/test_digicert.py +++ b/lemur/plugins/lemur_digicert/tests/test_digicert.py @@ -17,7 +17,7 @@ def config_mock(*args): "DIGICERT_DEFAULT_VALIDITY": 1, "DIGICERT_MAX_VALIDITY": 2, "DIGICERT_CIS_PROFILE_NAMES": {"digicert": 'digicert'}, - "DIGICERT_CIS_SIGNING_ALGORITHMS": {"verisign-sha1-intermediary": 'sha1'}, + "DIGICERT_CIS_SIGNING_ALGORITHMS": {"digicert": 'digicert'}, } return values[args[0]]