Adjusting the way that certificates are requested. (#643)

* Adjusting the way that certificates are requested.

* Fixing tests.
This commit is contained in:
kevgliss 2017-02-16 13:24:05 -08:00 committed by GitHub
parent 08bb9c73a0
commit cf6ad94509
3 changed files with 37 additions and 57 deletions

View File

@ -15,10 +15,10 @@ from flask import current_app
from acme.client import Client from acme.client import Client
from acme import jose from acme import jose
from acme import messages from acme import messages
from acme import challenges
from lemur.common.utils import generate_private_key from lemur.common.utils import generate_private_key
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import serialization
import OpenSSL.crypto import OpenSSL.crypto
@ -27,14 +27,14 @@ from lemur.common.utils import validate_conf
from lemur.plugins.bases import IssuerPlugin from lemur.plugins.bases import IssuerPlugin
from lemur.plugins import lemur_acme as acme from lemur.plugins import lemur_acme as acme
from .route53 import delete_txt_record, create_txt_record, wait_for_change from .route53 import delete_txt_record, create_txt_record, wait_for_r53_change
def find_dns_challenge(authz): def find_dns_challenge(authz):
for combo in authz.body.resolved_combinations: for combo in authz.body.resolved_combinations:
if ( if (
len(combo) == 1 and len(combo) == 1 and
isinstance(combo[0].chall, acme.challenges.DNS01) isinstance(combo[0].chall, challenges.DNS01)
): ):
yield combo[0] yield combo[0]
@ -47,17 +47,15 @@ class AuthorizationRecord(object):
self.change_id = change_id self.change_id = change_id
def start_dns_challenge(acme_client, host): def start_dns_challenge(acme_client, account_number, host):
authz = acme_client.request_domain_challenges( authz = acme_client.request_domain_challenges(host)
host, acme_client.directory.new_authz
)
[dns_challenge] = find_dns_challenge(authz) [dns_challenge] = find_dns_challenge(authz)
change_id = create_txt_record( change_id = create_txt_record(
dns_challenge.validation_domain_name(host), dns_challenge.validation_domain_name(host),
dns_challenge.validation(acme_client.key), dns_challenge.validation(acme_client.key),
account_number
) )
return AuthorizationRecord( return AuthorizationRecord(
@ -68,8 +66,8 @@ def start_dns_challenge(acme_client, host):
) )
def complete_dns_challenge(acme_client, authz_record): def complete_dns_challenge(acme_client, account_number, authz_record):
wait_for_change(authz_record.change_id) wait_for_r53_change(authz_record.change_id, account_number=account_number)
response = authz_record.dns_challenge.response(acme_client.key) response = authz_record.dns_challenge.response(acme_client.key)
@ -109,35 +107,21 @@ def request_certificate(acme_client, authorizations, csr):
def setup_acme_client(): def setup_acme_client():
key = current_app.config.get('ACME_PRIVATE_KEY').strip() email = current_app.config.get('ACME_EMAIL')
acme_email = current_app.config.get('ACME_EMAIL') tel = current_app.config.get('ACME_TEL')
acme_tel = current_app.config.get('ACME_TEL') directory_url = current_app.config.get('ACME_DIRECTORY_URL')
acme_directory_url = current_app.config.get('ACME_DIRECTORY_URL'), contact = ('mailto:{}'.format(email), 'tel:{}'.format(tel))
contact = ('mailto:{}'.format(acme_email), 'tel:{}'.format(acme_tel))
key = serialization.load_pem_private_key( key = jose.JWKRSA(key=generate_private_key('RSA2048'))
key, password=None, backend=default_backend()
)
return acme_client_for_private_key(acme_directory_url, key) client = Client(directory_url, key)
registration = client.register(
def acme_client_for_private_key(acme_directory_url, private_key):
return Client(
acme_directory_url, key=jose.JWKRSA(key=private_key)
)
def register(email):
private_key = generate_private_key('RSA2048')
acme_client = acme_client_for_private_key(current_app.config('ACME_DIRECTORY_URL'), private_key)
registration = acme_client.register(
messages.NewRegistration.from_data(email=email) messages.NewRegistration.from_data(email=email)
) )
acme_client.agree_to_tos(registration) client.agree_to_tos(registration)
return private_key return client, registration
def get_domains(options): def get_domains(options):
@ -147,27 +131,29 @@ def get_domains(options):
:return: :return:
""" """
domains = [options['common_name']] domains = [options['common_name']]
for name in options['extensions']['sub_alt_name']['names']: if options.get('extensions'):
for name in options['extensions']['sub_alt_names']['names']:
domains.append(name) domains.append(name)
return domains return domains
def get_authorizations(acme_client, domains): def get_authorizations(acme_client, account_number, domains):
authorizations = [] authorizations = []
try: try:
for domain in domains: for domain in domains:
authz_record = start_dns_challenge(acme_client, domain) authz_record = start_dns_challenge(acme_client, account_number, domain)
authorizations.append(authz_record) authorizations.append(authz_record)
for authz_record in authorizations: for authz_record in authorizations:
complete_dns_challenge(acme_client, authz_record) complete_dns_challenge(acme_client, account_number, authz_record)
finally: finally:
for authz_record in authorizations: for authz_record in authorizations:
dns_challenge = authz_record.dns_challenge dns_challenge = authz_record.dns_challenge
delete_txt_record( delete_txt_record(
authz_record.change_id, authz_record.change_id,
account_number,
dns_challenge.validation_domain_name(authz_record.host), dns_challenge.validation_domain_name(authz_record.host),
dns_challenge.validation(acme_client.key), dns_challenge.validation(acme_client.key)
) )
return authorizations return authorizations
@ -187,7 +173,7 @@ class ACMEIssuerPlugin(IssuerPlugin):
'ACME_DIRECTORY_URL', 'ACME_DIRECTORY_URL',
'ACME_TEL', 'ACME_TEL',
'ACME_EMAIL', 'ACME_EMAIL',
'ACME_PRIVATE_KEY', 'ACME_AWS_ACCOUNT_NUMBER',
'ACME_ROOT' 'ACME_ROOT'
] ]
@ -203,9 +189,10 @@ class ACMEIssuerPlugin(IssuerPlugin):
:return: :raise Exception: :return: :raise Exception:
""" """
current_app.logger.debug("Requesting a new acme certificate: {0}".format(issuer_options)) current_app.logger.debug("Requesting a new acme certificate: {0}".format(issuer_options))
acme_client = setup_acme_client() acme_client, registration = setup_acme_client()
account_number = current_app.config.get('ACME_AWS_ACCOUNT_NUMBER')
domains = get_domains(issuer_options) domains = get_domains(issuer_options)
authorizations = get_authorizations(acme_client, domains) authorizations = get_authorizations(acme_client, account_number, domains)
pem_certificate, pem_certificate_chain = request_certificate(acme_client, authorizations, csr) pem_certificate, pem_certificate_chain = request_certificate(acme_client, authorizations, csr)
return pem_certificate, pem_certificate_chain return pem_certificate, pem_certificate_chain

View File

@ -54,33 +54,24 @@ def change_txt_record(action, zone_id, domain, value, client=None):
return response["ChangeInfo"]["Id"] return response["ChangeInfo"]["Id"]
def create_txt_record(host, value): def create_txt_record(account_number, host, value):
zone_id = find_zone_id(host) zone_id = find_zone_id(host, account_number=account_number)
change_id = change_txt_record( change_id = change_txt_record(
"CREATE", "CREATE",
zone_id, zone_id,
host, host,
value, value,
account_number=account_number
) )
return zone_id, change_id return zone_id, change_id
def delete_txt_record(change_id, host, value): def delete_txt_record(change_id, account_number, host, value):
zone_id, _ = change_id zone_id, _ = change_id
change_txt_record( change_txt_record(
"DELETE", "DELETE",
zone_id, zone_id,
host, host,
value value,
account_number=account_number
) )
@sts_client('route53')
def wait_for_change(change_id, client=None):
_, change_id = change_id
while True:
response = client.get_change(Id=change_id)
if response["ChangeInfo"]["Status"] == "INSYNC":
return
time.sleep(5)

View File

@ -82,6 +82,8 @@ VERISIGN_LAST_NAME = 'Bob'
VERSIGN_EMAIL = 'jim@example.com' VERSIGN_EMAIL = 'jim@example.com'
ACME_AWS_ACCOUNT_NUMBER = '11111111111'
ACME_PRIVATE_KEY = ''' ACME_PRIVATE_KEY = '''
-----BEGIN RSA PRIVATE KEY----- -----BEGIN RSA PRIVATE KEY-----
MIIJJwIBAAKCAgEA0+jySNCc1i73LwDZEuIdSkZgRYQ4ZQVIioVf38RUhDElxy51 MIIJJwIBAAKCAgEA0+jySNCc1i73LwDZEuIdSkZgRYQ4ZQVIioVf38RUhDElxy51