Support LetsEncrypt accounts
This commit is contained in:
parent
46cd1a21f7
commit
7463d47057
|
@ -108,12 +108,13 @@ def fetch_all_acme():
|
||||||
error_log["message"] = "Deleting pending certificate"
|
error_log["message"] = "Deleting pending certificate"
|
||||||
send_pending_failure_notification(pending_cert, notify_owner=pending_cert.notify)
|
send_pending_failure_notification(pending_cert, notify_owner=pending_cert.notify)
|
||||||
pending_certificate_service.delete_by_id(pending_cert.id)
|
pending_certificate_service.delete_by_id(pending_cert.id)
|
||||||
|
else:
|
||||||
|
pending_certificate_service.increment_attempt(pending_cert)
|
||||||
|
pending_certificate_service.update(
|
||||||
|
cert.get("pending_cert").id,
|
||||||
|
status=str(cert.get("last_error"))[0:128]
|
||||||
|
)
|
||||||
current_app.logger.error(error_log)
|
current_app.logger.error(error_log)
|
||||||
pending_certificate_service.increment_attempt(pending_cert)
|
|
||||||
pending_certificate_service.update(
|
|
||||||
cert.get("pending_cert").id,
|
|
||||||
status=str(cert.get("last_error"))[0:128]
|
|
||||||
)
|
|
||||||
log_data["message"] = "Complete"
|
log_data["message"] = "Complete"
|
||||||
log_data["new"] = new
|
log_data["new"] = new
|
||||||
log_data["failed"] = failed
|
log_data["failed"] = failed
|
||||||
|
|
|
@ -140,14 +140,27 @@ def setup_acme_client(authority):
|
||||||
tel = options.get('telephone', current_app.config.get('ACME_TEL'))
|
tel = options.get('telephone', current_app.config.get('ACME_TEL'))
|
||||||
directory_url = options.get('acme_url', current_app.config.get('ACME_DIRECTORY_URL'))
|
directory_url = options.get('acme_url', current_app.config.get('ACME_DIRECTORY_URL'))
|
||||||
|
|
||||||
key = jose.JWKRSA(key=generate_private_key('RSA2048'))
|
existing_key = options.get('acme_private_key', current_app.config.get('ACME_PRIVATE_KEY'))
|
||||||
|
existing_regr = options.get('acme_regr', current_app.config.get('ACME_REGR'))
|
||||||
|
print(existing_key)
|
||||||
|
if existing_key and existing_regr:
|
||||||
|
# Reuse the same account for each certificate issuance
|
||||||
|
key = jose.JWK.json_loads(existing_key)
|
||||||
|
regr = messages.RegistrationResource.json_loads(existing_regr)
|
||||||
|
current_app.logger.debug("Connecting with directory at {0}".format(directory_url))
|
||||||
|
net = ClientNetwork(key, account=regr)
|
||||||
|
client = BackwardsCompatibleClientV2(net, key, directory_url)
|
||||||
|
return client, {}
|
||||||
|
else:
|
||||||
|
# Create an account for each certificate issuance
|
||||||
|
key = jose.JWKRSA(key=generate_private_key('RSA2048'))
|
||||||
|
|
||||||
current_app.logger.debug("Connecting with directory at {0}".format(directory_url))
|
current_app.logger.debug("Connecting with directory at {0}".format(directory_url))
|
||||||
|
|
||||||
net = ClientNetwork(key, account=None)
|
net = ClientNetwork(key, account=None)
|
||||||
client = BackwardsCompatibleClientV2(net, key, directory_url)
|
client = BackwardsCompatibleClientV2(net, key, directory_url)
|
||||||
registration = client.new_account_and_tos(messages.NewRegistration.from_data(email=email))
|
registration = client.new_account_and_tos(messages.NewRegistration.from_data(email=email))
|
||||||
current_app.logger.debug("Connected: {0}".format(registration.uri))
|
current_app.logger.debug("Connected: {0}".format(registration.uri))
|
||||||
|
|
||||||
return client, registration
|
return client, registration
|
||||||
|
|
||||||
|
@ -196,6 +209,35 @@ def finalize_authorizations(acme_client, account_number, dns_provider, authoriza
|
||||||
return authorizations
|
return authorizations
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_dns_challenges(acme_client, account_number, dns_provider, authorizations, dns_provider_options):
|
||||||
|
"""
|
||||||
|
Best effort attempt to delete DNS challenges that may not have been deleted previously. This is usually called
|
||||||
|
on an exception
|
||||||
|
|
||||||
|
:param acme_client:
|
||||||
|
:param account_number:
|
||||||
|
:param dns_provider:
|
||||||
|
:param authorizations:
|
||||||
|
:param dns_provider_options:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
for authz_record in authorizations:
|
||||||
|
dns_challenges = authz_record.dns_challenge
|
||||||
|
host_to_validate = maybe_remove_wildcard(authz_record.host)
|
||||||
|
host_to_validate = maybe_add_extension(host_to_validate, dns_provider_options)
|
||||||
|
for dns_challenge in dns_challenges:
|
||||||
|
try:
|
||||||
|
dns_provider.delete_txt_record(
|
||||||
|
authz_record.change_id,
|
||||||
|
account_number,
|
||||||
|
dns_challenge.validation_domain_name(host_to_validate),
|
||||||
|
dns_challenge.validation(acme_client.client.net.key)
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
# If this fails, it's most likely because the record doesn't exist or we're not authorized to modify it.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ACMEIssuerPlugin(IssuerPlugin):
|
class ACMEIssuerPlugin(IssuerPlugin):
|
||||||
title = 'Acme'
|
title = 'Acme'
|
||||||
slug = 'acme-issuer'
|
slug = 'acme-issuer'
|
||||||
|
@ -333,12 +375,21 @@ class ACMEIssuerPlugin(IssuerPlugin):
|
||||||
"cert": cert,
|
"cert": cert,
|
||||||
"pending_cert": entry["pending_cert"],
|
"pending_cert": entry["pending_cert"],
|
||||||
})
|
})
|
||||||
except (PollError, AcmeError, Exception):
|
except (PollError, AcmeError, Exception) as e:
|
||||||
current_app.logger.error("Unable to resolve pending cert: {}".format(pending_cert), exc_info=True)
|
current_app.logger.error("Unable to resolve pending cert: {}".format(pending_cert), exc_info=True)
|
||||||
certs.append({
|
certs.append({
|
||||||
"cert": False,
|
"cert": False,
|
||||||
"pending_cert": entry["pending_cert"],
|
"pending_cert": entry["pending_cert"],
|
||||||
|
"last_error": e,
|
||||||
})
|
})
|
||||||
|
# Ensure DNS records get deleted
|
||||||
|
cleanup_dns_challenges(
|
||||||
|
entry["acme_client"],
|
||||||
|
entry["account_number"],
|
||||||
|
entry["dns_provider_type"],
|
||||||
|
entry["authorizations"],
|
||||||
|
entry["dns_provider_options"],
|
||||||
|
)
|
||||||
return certs
|
return certs
|
||||||
|
|
||||||
def create_certificate(self, csr, issuer_options):
|
def create_certificate(self, csr, issuer_options):
|
||||||
|
|
|
@ -134,6 +134,7 @@ class TestAcme(unittest.TestCase):
|
||||||
mock_client.register = mock_registration
|
mock_client.register = mock_registration
|
||||||
mock_client.agree_to_tos = Mock(return_value=True)
|
mock_client.agree_to_tos = Mock(return_value=True)
|
||||||
mock_acme.return_value = mock_client
|
mock_acme.return_value = mock_client
|
||||||
|
mock_current_app.config = {}
|
||||||
result_client, result_registration = plugin.setup_acme_client(mock_authority)
|
result_client, result_registration = plugin.setup_acme_client(mock_authority)
|
||||||
assert result_client
|
assert result_client
|
||||||
assert result_registration
|
assert result_registration
|
||||||
|
|
|
@ -15,8 +15,8 @@ asyncpool==1.0
|
||||||
babel==2.6.0 # via sphinx
|
babel==2.6.0 # via sphinx
|
||||||
bcrypt==3.1.4
|
bcrypt==3.1.4
|
||||||
blinker==1.4
|
blinker==1.4
|
||||||
boto3==1.7.62
|
boto3==1.7.65
|
||||||
botocore==1.10.62
|
botocore==1.10.65
|
||||||
certifi==2018.4.16
|
certifi==2018.4.16
|
||||||
cffi==1.11.5
|
cffi==1.11.5
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
|
@ -55,11 +55,11 @@ mock==2.0.0
|
||||||
ndg-httpsclient==0.5.1
|
ndg-httpsclient==0.5.1
|
||||||
packaging==17.1 # via sphinx
|
packaging==17.1 # via sphinx
|
||||||
paramiko==2.4.1
|
paramiko==2.4.1
|
||||||
pbr==4.1.1
|
pbr==4.2.0
|
||||||
pem==18.1.0
|
pem==18.1.0
|
||||||
psycopg2==2.7.5
|
psycopg2==2.7.5
|
||||||
pyasn1-modules==0.2.2
|
pyasn1-modules==0.2.2
|
||||||
pyasn1==0.4.3
|
pyasn1==0.4.4
|
||||||
pycparser==2.18
|
pycparser==2.18
|
||||||
pygments==2.2.0 # via sphinx
|
pygments==2.2.0 # via sphinx
|
||||||
pyjwt==1.6.4
|
pyjwt==1.6.4
|
||||||
|
|
|
@ -8,9 +8,9 @@ asn1crypto==0.24.0 # via cryptography
|
||||||
atomicwrites==1.1.5 # via pytest
|
atomicwrites==1.1.5 # via pytest
|
||||||
attrs==18.1.0 # via pytest
|
attrs==18.1.0 # via pytest
|
||||||
aws-xray-sdk==0.95 # via moto
|
aws-xray-sdk==0.95 # via moto
|
||||||
boto3==1.7.65 # via moto
|
boto3==1.7.66 # via moto
|
||||||
boto==2.49.0 # via moto
|
boto==2.49.0 # via moto
|
||||||
botocore==1.10.65 # via boto3, moto, s3transfer
|
botocore==1.10.66 # via boto3, moto, s3transfer
|
||||||
certifi==2018.4.16 # via requests
|
certifi==2018.4.16 # via requests
|
||||||
cffi==1.11.5 # via cryptography
|
cffi==1.11.5 # via cryptography
|
||||||
chardet==3.0.4 # via requests
|
chardet==3.0.4 # via requests
|
||||||
|
@ -37,14 +37,14 @@ more-itertools==4.2.0 # via pytest
|
||||||
moto==1.3.3
|
moto==1.3.3
|
||||||
nose==1.3.7
|
nose==1.3.7
|
||||||
pbr==4.2.0 # via mock
|
pbr==4.2.0 # via mock
|
||||||
pluggy==0.6.0 # via pytest
|
pluggy==0.7.1 # via pytest
|
||||||
py==1.5.4 # via pytest
|
py==1.5.4 # via pytest
|
||||||
pyaml==17.12.1 # via moto
|
pyaml==17.12.1 # via moto
|
||||||
pycparser==2.18 # via cffi
|
pycparser==2.18 # via cffi
|
||||||
pyflakes==2.0.0
|
pyflakes==2.0.0
|
||||||
pytest-flask==0.10.0
|
pytest-flask==0.10.0
|
||||||
pytest-mock==1.10.0
|
pytest-mock==1.10.0
|
||||||
pytest==3.6.3
|
pytest==3.6.4
|
||||||
python-dateutil==2.6.1 # via botocore, faker, freezegun, moto
|
python-dateutil==2.6.1 # via botocore, faker, freezegun, moto
|
||||||
pytz==2018.5 # via moto
|
pytz==2018.5 # via moto
|
||||||
pyyaml==3.13 # via pyaml
|
pyyaml==3.13 # via pyaml
|
||||||
|
|
|
@ -13,8 +13,8 @@ asn1crypto==0.24.0 # via cryptography
|
||||||
asyncpool==1.0
|
asyncpool==1.0
|
||||||
bcrypt==3.1.4 # via flask-bcrypt, paramiko
|
bcrypt==3.1.4 # via flask-bcrypt, paramiko
|
||||||
blinker==1.4 # via flask-mail, flask-principal, raven
|
blinker==1.4 # via flask-mail, flask-principal, raven
|
||||||
boto3==1.7.65
|
boto3==1.7.66
|
||||||
botocore==1.10.65 # via boto3, s3transfer
|
botocore==1.10.66 # via boto3, s3transfer
|
||||||
certifi==2018.4.16
|
certifi==2018.4.16
|
||||||
cffi==1.11.5 # via bcrypt, cryptography, pynacl
|
cffi==1.11.5 # via bcrypt, cryptography, pynacl
|
||||||
chardet==3.0.4 # via requests
|
chardet==3.0.4 # via requests
|
||||||
|
|
Loading…
Reference in New Issue