From e64e2a41d512f9b19f8ffb2ecdf649841a16569b Mon Sep 17 00:00:00 2001 From: Mathias Petermann Date: Wed, 23 Sep 2020 16:36:59 +0200 Subject: [PATCH 1/9] Add update_options to authorities service --- lemur/authorities/service.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lemur/authorities/service.py b/lemur/authorities/service.py index c70c6fc5..c734f408 100644 --- a/lemur/authorities/service.py +++ b/lemur/authorities/service.py @@ -39,6 +39,21 @@ def update(authority_id, description, owner, active, roles): return database.update(authority) +def update_options(authority_id, options): + """ + Update an authority with new options. + + :param authority_id: + :param options: the new options to be saved into the authority + :return: + """ + + authority = get(authority_id) + + authority.options = options + + return database.update(authority) + def mint(**kwargs): """ Creates the authority based on the plugin provided. From 898b5da6613294403da6683f20c45abe3f4bd7f3 Mon Sep 17 00:00:00 2001 From: Mathias Petermann Date: Wed, 23 Sep 2020 16:38:38 +0200 Subject: [PATCH 2/9] Add store_account option to acme plugin --- lemur/plugins/lemur_acme/plugin.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lemur/plugins/lemur_acme/plugin.py b/lemur/plugins/lemur_acme/plugin.py index 16d61a0f..ec4a5b84 100644 --- a/lemur/plugins/lemur_acme/plugin.py +++ b/lemur/plugins/lemur_acme/plugin.py @@ -240,6 +240,7 @@ class AcmeHandler(object): existing_regr = options.get("acme_regr", current_app.config.get("ACME_REGR")) if existing_key and existing_regr: + current_app.logger.debug("Reusing existing ACME account") # Reuse the same account for each certificate issuance key = jose.JWK.json_loads(existing_key) regr = messages.RegistrationResource.json_loads(existing_regr) @@ -253,6 +254,7 @@ class AcmeHandler(object): # Create an account for each certificate issuance key = jose.JWKRSA(key=generate_private_key("RSA2048")) + current_app.logger.debug("Creating a new ACME account") current_app.logger.debug( "Connecting with directory at {0}".format(directory_url) ) @@ -447,6 +449,13 @@ class ACMEIssuerPlugin(IssuerPlugin): "validation": "/^-----BEGIN CERTIFICATE-----/", "helpMessage": "Certificate to use", }, + { + "name": "store_account", + "type": "bool", + "required": False, + "helpMessage": "Disable to create a new account for each ACME request", + "default": True, + } ] def __init__(self, *args, **kwargs): From eed628dbab1d423cd08ff99027957b06368039ae Mon Sep 17 00:00:00 2001 From: Mathias Petermann Date: Wed, 23 Sep 2020 16:38:57 +0200 Subject: [PATCH 3/9] Implement storage of acme account --- lemur/plugins/lemur_acme/plugin.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lemur/plugins/lemur_acme/plugin.py b/lemur/plugins/lemur_acme/plugin.py index ec4a5b84..b77b1765 100644 --- a/lemur/plugins/lemur_acme/plugin.py +++ b/lemur/plugins/lemur_acme/plugin.py @@ -32,6 +32,7 @@ from lemur.extensions import metrics, sentry from lemur.plugins import lemur_acme as acme from lemur.plugins.bases import IssuerPlugin from lemur.plugins.lemur_acme import cloudflare, dyn, route53, ultradns, powerdns +from lemur.authorities import service as authorities_service from retrying import retry @@ -264,6 +265,27 @@ class AcmeHandler(object): registration = client.new_account_and_tos( messages.NewRegistration.from_data(email=email) ) + + # if store_account is checked, add the private_key and registration resources to the options + if options['store_account']: + new_options = json.loads(authority.options) + # the key returned by fields_to_partial_json is missing the key type, so we add it manually + key_dict = key.fields_to_partial_json() + key_dict["kty"] = "RSA" + acme_private_key = { + "name": "acme_private_key", + "value": json.dumps(key_dict) + } + new_options.append(acme_private_key) + + acme_regr = { + "name": "acme_regr", + "value": json.dumps({"body": {}, "uri": registration.uri}) + } + new_options.append(acme_regr) + + authorities_service.update_options(authority.id, options=json.dumps(new_options)) + current_app.logger.debug("Connected: {0}".format(registration.uri)) return client, registration From 7e6fb740b3bf843a65730c60f813e759c37fc217 Mon Sep 17 00:00:00 2001 From: Mathias Petermann Date: Wed, 23 Sep 2020 16:57:48 +0200 Subject: [PATCH 4/9] Fix flake8/linting errors --- lemur/authorities/service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lemur/authorities/service.py b/lemur/authorities/service.py index c734f408..0913e629 100644 --- a/lemur/authorities/service.py +++ b/lemur/authorities/service.py @@ -54,6 +54,7 @@ def update_options(authority_id, options): return database.update(authority) + def mint(**kwargs): """ Creates the authority based on the plugin provided. From e0708410d0657da42f6acb08ddd3e9a3ecd05c3b Mon Sep 17 00:00:00 2001 From: Mathias Petermann Date: Wed, 23 Sep 2020 17:12:49 +0200 Subject: [PATCH 5/9] Add store_account value to options in test_setup_acme_client_success --- lemur/plugins/lemur_acme/tests/test_acme.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lemur/plugins/lemur_acme/tests/test_acme.py b/lemur/plugins/lemur_acme/tests/test_acme.py index 8320a2de..0356175c 100644 --- a/lemur/plugins/lemur_acme/tests/test_acme.py +++ b/lemur/plugins/lemur_acme/tests/test_acme.py @@ -169,7 +169,8 @@ class TestAcme(unittest.TestCase): @patch("lemur.plugins.lemur_acme.plugin.current_app") def test_setup_acme_client_success(self, mock_current_app, mock_acme): mock_authority = Mock() - mock_authority.options = '[{"name": "mock_name", "value": "mock_value"}]' + mock_authority.options = '[{"name": "mock_name", "value": "mock_value"}, ' \ + '{"name": "store_account", "value": false}] ' mock_client = Mock() mock_registration = Mock() mock_registration.uri = "http://test.com" From bf66de0bfd1a3cd8506a54e5d01304a388cbeb3b Mon Sep 17 00:00:00 2001 From: Mathias Petermann Date: Wed, 30 Sep 2020 12:59:52 +0200 Subject: [PATCH 6/9] Add Test for saving the accound details --- lemur/plugins/lemur_acme/tests/test_acme.py | 32 ++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/lemur/plugins/lemur_acme/tests/test_acme.py b/lemur/plugins/lemur_acme/tests/test_acme.py index 0356175c..b8d97489 100644 --- a/lemur/plugins/lemur_acme/tests/test_acme.py +++ b/lemur/plugins/lemur_acme/tests/test_acme.py @@ -165,12 +165,42 @@ class TestAcme(unittest.TestCase): with self.assertRaises(Exception): self.acme.setup_acme_client(mock_authority) + @patch("lemur.plugins.lemur_acme.plugin.jose.JWKRSA.fields_to_partial_json") + @patch("lemur.plugins.lemur_acme.plugin.authorities_service") + @patch("lemur.plugins.lemur_acme.plugin.BackwardsCompatibleClientV2") + @patch("lemur.plugins.lemur_acme.plugin.current_app") + def test_setup_acme_client_success_store_new_account(self, mock_current_app, mock_acme, mock_authorities_service, + mock_key_generation): + mock_authority = Mock() + mock_authority.id = 2 + mock_authority.options = '[{"name": "mock_name", "value": "mock_value"}, ' \ + '{"name": "store_account", "value": true}]' + mock_client = Mock() + mock_registration = Mock() + mock_registration.uri = "http://test.com" + mock_client.register = mock_registration + mock_client.agree_to_tos = Mock(return_value=True) + mock_client.new_account_and_tos.return_value = mock_registration + mock_acme.return_value = mock_client + mock_current_app.config = {} + + mock_key_generation.return_value = {"n": "PwIOkViO"} + + mock_authorities_service.update_options = Mock(return_value=True) + + self.acme.setup_acme_client(mock_authority) + + mock_authorities_service.update_options.assert_called_with(2, options='[{"name": "mock_name", "value": "mock_value"}, ' + '{"name": "store_account", "value": true}, ' + '{"name": "acme_private_key", "value": "{\\"n\\": \\"PwIOkViO\\", \\"kty\\": \\"RSA\\"}"}, ' + '{"name": "acme_regr", "value": "{\\"body\\": {}, \\"uri\\": \\"http://test.com\\"}"}]') + @patch("lemur.plugins.lemur_acme.plugin.BackwardsCompatibleClientV2") @patch("lemur.plugins.lemur_acme.plugin.current_app") def test_setup_acme_client_success(self, mock_current_app, mock_acme): mock_authority = Mock() mock_authority.options = '[{"name": "mock_name", "value": "mock_value"}, ' \ - '{"name": "store_account", "value": false}] ' + '{"name": "store_account", "value": false}]' mock_client = Mock() mock_registration = Mock() mock_registration.uri = "http://test.com" From 9abd3e97e7f4d90276a7ba78ca3c66792afdbb21 Mon Sep 17 00:00:00 2001 From: Mathias Petermann Date: Wed, 30 Sep 2020 13:21:34 +0200 Subject: [PATCH 7/9] Add test loading acme account from authority --- lemur/plugins/lemur_acme/tests/test_acme.py | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lemur/plugins/lemur_acme/tests/test_acme.py b/lemur/plugins/lemur_acme/tests/test_acme.py index b8d97489..8cd4783f 100644 --- a/lemur/plugins/lemur_acme/tests/test_acme.py +++ b/lemur/plugins/lemur_acme/tests/test_acme.py @@ -1,8 +1,10 @@ import unittest from unittest.mock import patch, Mock +import josepy as jose from cryptography.x509 import DNSName from lemur.plugins.lemur_acme import plugin +from lemur.common.utils import generate_private_key from mock import MagicMock @@ -165,6 +167,28 @@ class TestAcme(unittest.TestCase): with self.assertRaises(Exception): self.acme.setup_acme_client(mock_authority) + @patch("lemur.plugins.lemur_acme.plugin.jose.JWK.json_loads") + @patch("lemur.plugins.lemur_acme.plugin.BackwardsCompatibleClientV2") + @patch("lemur.plugins.lemur_acme.plugin.current_app") + def test_setup_acme_client_success_load_account_from_authority(self, mock_current_app, mock_acme, mock_key_json_load): + mock_authority = Mock() + mock_authority.id = 2 + mock_authority.options = '[{"name": "mock_name", "value": "mock_value"}, ' \ + '{"name": "store_account", "value": true},' \ + '{"name": "acme_private_key", "value": "{\\"n\\": \\"PwIOkViO\\", \\"kty\\": \\"RSA\\"}"}, ' \ + '{"name": "acme_regr", "value": "{\\"body\\": {}, \\"uri\\": \\"http://test.com\\"}"}]' + mock_client = Mock() + mock_acme.return_value = mock_client + mock_current_app.config = {} + + mock_key_json_load.return_value = jose.JWKRSA(key=generate_private_key("RSA2048")) + + result_client, result_registration = self.acme.setup_acme_client(mock_authority) + + mock_acme.new_account_and_tos.assert_not_called() + assert result_client + assert not result_registration + @patch("lemur.plugins.lemur_acme.plugin.jose.JWKRSA.fields_to_partial_json") @patch("lemur.plugins.lemur_acme.plugin.authorities_service") @patch("lemur.plugins.lemur_acme.plugin.BackwardsCompatibleClientV2") From 835339694081258f9e815a5b99d134a33ad55cba Mon Sep 17 00:00:00 2001 From: Mathias Petermann Date: Wed, 30 Sep 2020 13:42:16 +0200 Subject: [PATCH 8/9] Improve tests --- lemur/plugins/lemur_acme/tests/test_acme.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lemur/plugins/lemur_acme/tests/test_acme.py b/lemur/plugins/lemur_acme/tests/test_acme.py index 8cd4783f..ab246563 100644 --- a/lemur/plugins/lemur_acme/tests/test_acme.py +++ b/lemur/plugins/lemur_acme/tests/test_acme.py @@ -219,9 +219,10 @@ class TestAcme(unittest.TestCase): '{"name": "acme_private_key", "value": "{\\"n\\": \\"PwIOkViO\\", \\"kty\\": \\"RSA\\"}"}, ' '{"name": "acme_regr", "value": "{\\"body\\": {}, \\"uri\\": \\"http://test.com\\"}"}]') + @patch("lemur.plugins.lemur_acme.plugin.authorities_service") @patch("lemur.plugins.lemur_acme.plugin.BackwardsCompatibleClientV2") @patch("lemur.plugins.lemur_acme.plugin.current_app") - def test_setup_acme_client_success(self, mock_current_app, mock_acme): + def test_setup_acme_client_success(self, mock_current_app, mock_acme, mock_authorities_service): mock_authority = Mock() mock_authority.options = '[{"name": "mock_name", "value": "mock_value"}, ' \ '{"name": "store_account", "value": false}]' @@ -233,6 +234,7 @@ class TestAcme(unittest.TestCase): mock_acme.return_value = mock_client mock_current_app.config = {} result_client, result_registration = self.acme.setup_acme_client(mock_authority) + mock_authorities_service.update_options.assert_not_called() assert result_client assert result_registration From 57534d86cd03e3d8585ffe52d4d5dd58a5b007fe Mon Sep 17 00:00:00 2001 From: Mathias Petermann Date: Wed, 30 Sep 2020 17:46:14 +0200 Subject: [PATCH 9/9] Disable account saving by default --- lemur/plugins/lemur_acme/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/plugins/lemur_acme/plugin.py b/lemur/plugins/lemur_acme/plugin.py index b77b1765..8bc1485f 100644 --- a/lemur/plugins/lemur_acme/plugin.py +++ b/lemur/plugins/lemur_acme/plugin.py @@ -476,7 +476,7 @@ class ACMEIssuerPlugin(IssuerPlugin): "type": "bool", "required": False, "helpMessage": "Disable to create a new account for each ACME request", - "default": True, + "default": False, } ]