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)
|
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):
|
def mint(**kwargs):
|
||||||
"""
|
"""
|
||||||
Creates the authority based on the plugin provided.
|
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 import lemur_acme as acme
|
||||||
from lemur.plugins.bases import IssuerPlugin
|
from lemur.plugins.bases import IssuerPlugin
|
||||||
from lemur.plugins.lemur_acme import cloudflare, dyn, route53, ultradns, powerdns
|
from lemur.plugins.lemur_acme import cloudflare, dyn, route53, ultradns, powerdns
|
||||||
|
from lemur.authorities import service as authorities_service
|
||||||
from retrying import retry
|
from retrying import retry
|
||||||
|
|
||||||
|
|
||||||
|
@ -240,6 +241,7 @@ class AcmeHandler(object):
|
||||||
existing_regr = options.get("acme_regr", current_app.config.get("ACME_REGR"))
|
existing_regr = options.get("acme_regr", current_app.config.get("ACME_REGR"))
|
||||||
|
|
||||||
if existing_key and existing_regr:
|
if existing_key and existing_regr:
|
||||||
|
current_app.logger.debug("Reusing existing ACME account")
|
||||||
# Reuse the same account for each certificate issuance
|
# Reuse the same account for each certificate issuance
|
||||||
key = jose.JWK.json_loads(existing_key)
|
key = jose.JWK.json_loads(existing_key)
|
||||||
regr = messages.RegistrationResource.json_loads(existing_regr)
|
regr = messages.RegistrationResource.json_loads(existing_regr)
|
||||||
|
@ -253,6 +255,7 @@ class AcmeHandler(object):
|
||||||
# Create an account for each certificate issuance
|
# Create an account for each certificate issuance
|
||||||
key = jose.JWKRSA(key=generate_private_key("RSA2048"))
|
key = jose.JWKRSA(key=generate_private_key("RSA2048"))
|
||||||
|
|
||||||
|
current_app.logger.debug("Creating a new ACME account")
|
||||||
current_app.logger.debug(
|
current_app.logger.debug(
|
||||||
"Connecting with directory at {0}".format(directory_url)
|
"Connecting with directory at {0}".format(directory_url)
|
||||||
)
|
)
|
||||||
|
@ -262,6 +265,27 @@ class AcmeHandler(object):
|
||||||
registration = client.new_account_and_tos(
|
registration = client.new_account_and_tos(
|
||||||
messages.NewRegistration.from_data(email=email)
|
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))
|
current_app.logger.debug("Connected: {0}".format(registration.uri))
|
||||||
|
|
||||||
return client, registration
|
return client, registration
|
||||||
|
@ -447,6 +471,13 @@ class ACMEIssuerPlugin(IssuerPlugin):
|
||||||
"validation": "/^-----BEGIN CERTIFICATE-----/",
|
"validation": "/^-----BEGIN CERTIFICATE-----/",
|
||||||
"helpMessage": "Certificate to use",
|
"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):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch, Mock
|
from unittest.mock import patch, Mock
|
||||||
|
|
||||||
|
import josepy as jose
|
||||||
from cryptography.x509 import DNSName
|
from cryptography.x509 import DNSName
|
||||||
from lemur.plugins.lemur_acme import plugin
|
from lemur.plugins.lemur_acme import plugin
|
||||||
|
from lemur.common.utils import generate_private_key
|
||||||
from mock import MagicMock
|
from mock import MagicMock
|
||||||
|
|
||||||
|
|
||||||
|
@ -165,11 +167,65 @@ class TestAcme(unittest.TestCase):
|
||||||
with self.assertRaises(Exception):
|
with self.assertRaises(Exception):
|
||||||
self.acme.setup_acme_client(mock_authority)
|
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.BackwardsCompatibleClientV2")
|
||||||
@patch("lemur.plugins.lemur_acme.plugin.current_app")
|
@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 = 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_client = Mock()
|
||||||
mock_registration = Mock()
|
mock_registration = Mock()
|
||||||
mock_registration.uri = "http://test.com"
|
mock_registration.uri = "http://test.com"
|
||||||
|
@ -178,6 +234,7 @@ class TestAcme(unittest.TestCase):
|
||||||
mock_acme.return_value = mock_client
|
mock_acme.return_value = mock_client
|
||||||
mock_current_app.config = {}
|
mock_current_app.config = {}
|
||||||
result_client, result_registration = self.acme.setup_acme_client(mock_authority)
|
result_client, result_registration = self.acme.setup_acme_client(mock_authority)
|
||||||
|
mock_authorities_service.update_options.assert_not_called()
|
||||||
assert result_client
|
assert result_client
|
||||||
assert result_registration
|
assert result_registration
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue