Adjusting the way that certificates are requested. (#643)
* Adjusting the way that certificates are requested. * Fixing tests.
This commit is contained in:
parent
08bb9c73a0
commit
cf6ad94509
|
@ -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'):
|
||||||
domains.append(name)
|
for name in options['extensions']['sub_alt_names']['names']:
|
||||||
|
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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue