Merge pull request #3153 from unic/feature/store-acme-account-details
Store ACME account details
This commit is contained in:
commit
896d1af0f7
|
@ -39,6 +39,22 @@ 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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
@ -240,6 +241,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 +255,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)
|
||||
)
|
||||
|
@ -262,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
|
||||
|
@ -447,6 +471,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": False,
|
||||
}
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -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,11 +167,65 @@ 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(self, mock_current_app, mock_acme):
|
||||
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.options = '[{"name": "mock_name", "value": "mock_value"}]'
|
||||
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")
|
||||
@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.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, mock_authorities_service):
|
||||
mock_authority = Mock()
|
||||
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"
|
||||
|
@ -178,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
|
||||
|
||||
|
|
Loading…
Reference in New Issue