Support LetsEncrypt accounts

This commit is contained in:
Curtis Castrapel 2018-07-30 15:25:02 -07:00
parent b70885595f
commit 0889076d3b
6 changed files with 75 additions and 22 deletions

View File

@ -108,12 +108,13 @@ def fetch_all_acme():
error_log["message"] = "Deleting pending certificate"
send_pending_failure_notification(pending_cert, notify_owner=pending_cert.notify)
pending_certificate_service.delete_by_id(pending_cert.id)
current_app.logger.error(error_log)
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)
log_data["message"] = "Complete"
log_data["new"] = new
log_data["failed"] = failed

View File

@ -140,6 +140,19 @@ def setup_acme_client(authority):
tel = options.get('telephone', current_app.config.get('ACME_TEL'))
directory_url = options.get('acme_url', current_app.config.get('ACME_DIRECTORY_URL'))
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))
@ -196,6 +209,35 @@ def finalize_authorizations(acme_client, account_number, dns_provider, authoriza
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):
title = 'Acme'
slug = 'acme-issuer'
@ -333,12 +375,21 @@ class ACMEIssuerPlugin(IssuerPlugin):
"cert": 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)
certs.append({
"cert": False,
"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
def create_certificate(self, csr, issuer_options):

View File

@ -134,6 +134,7 @@ class TestAcme(unittest.TestCase):
mock_client.register = mock_registration
mock_client.agree_to_tos = Mock(return_value=True)
mock_acme.return_value = mock_client
mock_current_app.config = {}
result_client, result_registration = plugin.setup_acme_client(mock_authority)
assert result_client
assert result_registration

View File

@ -15,8 +15,8 @@ asyncpool==1.0
babel==2.6.0 # via sphinx
bcrypt==3.1.4
blinker==1.4
boto3==1.7.62
botocore==1.10.62
boto3==1.7.65
botocore==1.10.65
certifi==2018.4.16
cffi==1.11.5
chardet==3.0.4
@ -55,11 +55,11 @@ mock==2.0.0
ndg-httpsclient==0.5.1
packaging==17.1 # via sphinx
paramiko==2.4.1
pbr==4.1.1
pbr==4.2.0
pem==18.1.0
psycopg2==2.7.5
pyasn1-modules==0.2.2
pyasn1==0.4.3
pyasn1==0.4.4
pycparser==2.18
pygments==2.2.0 # via sphinx
pyjwt==1.6.4

View File

@ -8,9 +8,9 @@ asn1crypto==0.24.0 # via cryptography
atomicwrites==1.1.5 # via pytest
attrs==18.1.0 # via pytest
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
botocore==1.10.65 # via boto3, moto, s3transfer
botocore==1.10.66 # via boto3, moto, s3transfer
certifi==2018.4.16 # via requests
cffi==1.11.5 # via cryptography
chardet==3.0.4 # via requests
@ -37,14 +37,14 @@ more-itertools==4.2.0 # via pytest
moto==1.3.3
nose==1.3.7
pbr==4.2.0 # via mock
pluggy==0.6.0 # via pytest
pluggy==0.7.1 # via pytest
py==1.5.4 # via pytest
pyaml==17.12.1 # via moto
pycparser==2.18 # via cffi
pyflakes==2.0.0
pytest-flask==0.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
pytz==2018.5 # via moto
pyyaml==3.13 # via pyaml

View File

@ -13,8 +13,8 @@ asn1crypto==0.24.0 # via cryptography
asyncpool==1.0
bcrypt==3.1.4 # via flask-bcrypt, paramiko
blinker==1.4 # via flask-mail, flask-principal, raven
boto3==1.7.65
botocore==1.10.65 # via boto3, s3transfer
boto3==1.7.66
botocore==1.10.66 # via boto3, s3transfer
certifi==2018.4.16
cffi==1.11.5 # via bcrypt, cryptography, pynacl
chardet==3.0.4 # via requests