def perform( certificate: Certificate, storage: "StorageProtocol", acme_account_email: str, acme_directory_url: str, authenticators: typing.Sequence[AuthenticatorProtocol], ) -> None: client = setup_client( storage=storage, directory_url=acme_directory_url, account_email=acme_account_email, ) orderr = client.new_order( crypto_util.make_csr(certificate.private_key, certificate.domains)) auth_challs = select_challs(orderr, authenticators) for authenticator, challs in auth_challs: account_key = client.net.key authenticator.perform(challs, account_key) for challb, _ in challs: client.answer_challenge(challb, challb.response(account_key)) try: finalized_orderr = client.poll_and_finalize(orderr) fullchain_pem = finalized_orderr.fullchain_pem.encode("utf8") certificate.set_fullchain(fullchain_pem) storage.save_certificate(certificate) finally: for authenticator, challs in auth_challs: authenticator.cleanup(challs, account_key)
def _handle_challenges(self, client, account_key, authzr): """Handle challenges from the Letsencrypt provider. For each requested domain name we receive a list of challenges. We only have to do one from each list. HTTP challenges are the easiest, so do one of those if possible. We can do DNS challenges too. There are other types that we don't support. """ pending_responses = [] prefer_dns = (self.env["ir.config_parameter"].get_param( "letsencrypt.prefer_dns") == "True") for authorizations in authzr.authorizations: http_challenges = [ challenge for challenge in authorizations.body.challenges if challenge.chall.typ == TYPE_CHALLENGE_HTTP ] other_challenges = [ challenge for challenge in authorizations.body.challenges if challenge.chall.typ != TYPE_CHALLENGE_HTTP ] if prefer_dns: ordered_challenges = other_challenges + http_challenges else: ordered_challenges = http_challenges + other_challenges for challenge in ordered_challenges: if challenge.chall.typ == TYPE_CHALLENGE_HTTP: self._respond_challenge_http(challenge, account_key) client.answer_challenge(challenge, acme.challenges.HTTP01Response()) break elif challenge.chall.typ == TYPE_CHALLENGE_DNS: domain = authorizations.body.identifier.value token = challenge.validation(account_key) self._respond_challenge_dns(domain, token) # We delay this because we wait for each domain. # That takes less time if they've all already been changed. pending_responses.append( DNSUpdate(challenge=challenge, domain=domain, token=token)) break else: raise UserError( _("Could not respond to letsencrypt challenges.")) if pending_responses: for update in pending_responses: self._wait_for_record(update.domain, update.token) # 1 minute was not always enough during testing, even once records # were visible locally _logger.info( "All TXT records found, waiting 5 minutes more to make sure.") time.sleep(300) for update in pending_responses: client.answer_challenge(update.challenge, acme.challenges.DNSResponse())
def request_new_certificate(hostname, accnt_key, priv_key, tmp_chall_dict, directory_url): """Runs the entire process of ACME registration and certificate request""" client = create_v2_client(directory_url, accnt_key) try: client.net.account = client.new_account( messages.NewRegistration.from_data(terms_of_service_agreed=True)) except errors.ConflictError as error: existing_reg = messages.RegistrationResource(uri=error.location) existing_reg = client.query_registration(existing_reg) client.update_registration(existing_reg) csr = crypto_util.make_csr(priv_key, [hostname], False) order = client.new_order(csr) log.info('Created a new order for the issuance of a certificate for %s', hostname) challb = select_http01_chall(order) _, chall_tok = challb.response_and_validation(client.net.key) v = challb.chall.encode("token") log.info('Exposing challenge on %s', v) tmp_chall_dict.set(v, ChallTok(chall_tok)) cr = client.answer_challenge(challb, challb.response(client.net.key)) log.debug('Acme CA responded to challenge request with: %s', cr) order = client.poll_and_finalize(order) return split_certificate_chain(order.fullchain_pem)
def answer_challenge_http(domain, chall, validation_method, client, account, challg_body, log): # Create a challenge response object. resp = chall.response(account) # See if we've already installed the file at the right location # and the response validates. If it validates locally, submit # it to the ACME server. if validation_method.verify_first: try: # the 'port' argument is for unit testing only ok = resp.simple_verify(chall, domain, account.public_key(), port=validation_method.port) except: # assume any untrapped errors means something failed ok = False else: ok = True file_url = chall.uri(domain) if ok: log("Submitting challenge response file at %s." % file_url) return client.answer_challenge(challg_body, resp) else: log("Validation file is not present --- a file must be installed on the web server.") raise NeedToInstallFile( file_url, chall.validation(account), chall.encode("token"), # the filename )
def new_authorization(self, authz, client, key, domain): for combination in authz.combinations: if len(combination) == 1: challenger = authz.challenges[combination[0]] challenge = challenger.chall if isinstance(challenge, acme.challenges.DNS): # store (and deliver) needed response for challenge content = base64.b64encode(challenge.gen_validation(key).signature.signature) self.add_entry(challenge.validation_domain_name(domain) + '.', content.decode('utf-8')) # answer challenges / give ACME server go to check challenge resp = challenge.gen_response(key) client.answer_challenge(challenger, resp) # we can wait until this challenge is first requested ... raise exceptions.AuthorizationNotYetProcessed(datetime.now() + timedelta(seconds=2)) else: return False
def answer_challenges(client, challenger, *domains): """Answer the challenges for all domains """ authorizations = [] all_challenges = [] # first setup all challenges logger.info("Setting up challenges") for domain in domains: authorization = get_authorization(client, domain) authorizations.append(authorization) challb = get_challenge(authorization, challenge_type=challenger.challenge_type) response, validation = challb.response_and_validation(client.key) challenger.accomplish(domain, challb, validation) all_challenges.append({ 'authorization': authorization, 'response': response, 'challb': challb, 'domain': domain, }) challenger.all_accomplished() # then verify all responses logger.info("Verify all responses") for item in all_challenges: verified = item['response'].simple_verify( item['challb'].chall, item['domain'], client.key.public_key()) if not verified: raise RuntimeError( "Couldn't self-verify that validation " "is in place for %s" % item['domain']) # then answer all challenges logger.info("Answer challenges") for item in all_challenges: # tell acme server our validations are in place client.answer_challenge(item['challb'], item['response']) return authorizations
def new_authorization(self, authz, client, key, domain): for combination in authz.combinations: if len(combination) == 1: challenger = authz.challenges[combination[0]] challenge = challenger.chall if isinstance(challenge, acme.challenges.HTTP01): # store (and deliver) needed response for challenge content = challenge.validation(key) event = Event() self.responses.setdefault(domain, {}) self.responses[domain][challenge.path] = (content, event) # answer challenges / give ACME server go to check challenge resp = challenge.response(key) client.answer_challenge(challenger, resp) # we can wait until this challenge is first requested ... raise exceptions.AuthorizationNotYetRequested(event) else: return False
def run_acme_reg_to_finish(domain, accnt_key, priv_key, hostname, tmp_chall_dict, directory_url): """Runs the entire process of ACME registeration""" client = create_v2_client(directory_url, accnt_key) # First we need to create a registration with the email address provided # and accept the terms of service log.info("Using boulder server %s", directory_url) client.net.account = client.new_account( messages.NewRegistration.from_data( terms_of_service_agreed=True ) ) # Now we need to open an order and request our certificate # NOTE: We'll let ACME generate a CSR for our private key as there's # a lot of utility code it uses to generate the CSR in a specific # fashion. Better to use what LE provides than to roll our own as we # we doing with the v1 code # # This will also let us support multi-domain certificat requests in the # future, as well as mandate OCSP-Must-Staple if/when GL's HTTPS server # supports it csr = crypto_util.make_csr(priv_key, [hostname], False) order = client.new_order(csr) authzr = order.authorizations log.info('Created a new order for %s', hostname) # authrz is a list of Authorization resources, we need to find the # HTTP-01 challenge and use it for auth_req in authzr: # pylint: disable=not-an-iterable for chall_body in auth_req.body.challenges: if isinstance(chall_body.chall, challenges.HTTP01): challb = chall_body break if challb is None: raise Exception("HTTP01 challenge unavailable!") _, chall_tok = challb.response_and_validation(client.net.key) v = chall_body.chall.encode("token") log.info('Exposing challenge on %s', v) tmp_chall_dict.set(v, ChallTok(chall_tok)) cr = client.answer_challenge(challb, challb.response(client.net.key)) log.debug('Acme CA responded to challenge request with: %s', cr) order = client.poll_and_finalize(order) return split_certificate_chain(order.fullchain_pem)
def run_acme_reg_to_finish(domain, accnt_key, priv_key, hostname, tmp_chall_dict, directory_url): """Runs the entire process of ACME registeration""" client = create_v2_client(directory_url, accnt_key) # First we need to create a registration with the email address provided # and accept the terms of service log.info("Using boulder server %s", directory_url) client.net.account = client.new_account( messages.NewRegistration.from_data( terms_of_service_agreed=True ) ) # Now we need to open an order and request our certificate # NOTE: We'll let ACME generate a CSR for our private key as there's # a lot of utility code it uses to generate the CSR in a specific # fashion. Better to use what LE provides than to roll our own as we # we doing with the v1 code # # This will also let us support multi-domain certificat requests in the # future, as well as mandate OCSP-Must-Staple if/when GL's HTTPS server # supports it csr = crypto_util.make_csr(priv_key, [hostname], False) order = client.new_order(csr) authzr = order.authorizations log.info('Created a new order for %s', hostname) # authrz is a list of Authorization resources, we need to find the # HTTP-01 challenge and use it for auth_req in authzr: # pylint: disable=not-an-iterable for chall_body in auth_req.body.challenges: if isinstance(chall_body.chall, challenges.HTTP01): challb = chall_body break if challb is None: raise Exception("HTTP01 challenge unavailable!") response, chall_tok = challb.response_and_validation(client.net.key) v = chall_body.chall.encode("token") log.info('Exposing challenge on %s', v) tmp_chall_dict.set(v, ChallTok(chall_tok)) cr = client.answer_challenge(challb, challb.response(client.net.key)) log.debug('Acme CA responded to challenge request with: %s', cr) order = client.poll_and_finalize(order) return split_certificate_chain(order.fullchain_pem)
def answer_dns_challenge(client, domain, challenge): """ Compute the required answer and set it in the DNS record for the domain. """ authorization = "{}.{}".format( base64.urlsafe_b64encode( challenge.get("token")).decode("ascii").replace("=", ""), base64.urlsafe_b64encode( client.key.thumbprint()).decode("ascii").replace("=", "")) dns_response = base64.urlsafe_b64encode( hashlib.sha256( authorization.encode()).digest()).decode("ascii").replace("=", "") # Let's update the DNS on our R53 account r53 = route53.connect_to_region(exec_region) zone = r53.get_zone(domain['r53_zone']) if zone == None: LOG.error("Cannot find R53 zone {}, are you controling it ?".format( domain['r53_zone'])) exit(1) acme_domain = "_acme-challenge.{}".format(domain['name']) record = zone.find_records(name=acme_domain, type="TXT") if record: delete_status = zone.delete_record(record) add_status = zone.add_record("TXT", acme_domain, '"' + dns_response + '"') dns_updated = wait_until_sync(add_status) if dns_updated == False: LOG.error( "We updated R53 but the servers didn't sync within 10 seconds. Bailing out." ) exit(1) ## Now, let's tell the ACME server that we are ready challenge_response = challenges.DNS01Response( key_authorization=authorization) challenge_resource = client.answer_challenge(challenge, challenge_response)
def answer_challenge_simplehttp(domain, chall, client, account, challg_body, log): # Create a challenge response. resp = acme.challenges.SimpleHTTPResponse(tls=True) # See if we've already installed the file at the right location # and the response validates. If it validates locally, submit # it to the ACME server. try: ok = resp.simple_verify(chall, domain, account.public_key()) except: # invalid JSON data yields untrapped errors ok = False if ok: log("Submitting challenge response at %s." % resp.uri(domain, chall)) return client.answer_challenge(challg_body, resp) else: log("Validation file is not present.") raise NeedToInstallFile( resp.uri(domain, chall), resp.gen_validation(chall, account).json_dumps(), resp.CONTENT_TYPE, )
def _cron(self): ir_config_parameter = self.env['ir.config_parameter'] domains = self._get_altnames() domain = domains[0] cert_file = os.path.join(_get_data_dir(), 'domain.crt') domains = self._cascade_domains(domains) for dom in domains: self._validate_domain(dom) if not self._should_run(cert_file, domains): return account_key = josepy.JWKRSA.load(self._get_key('account.key')) domain_key = self._get_key('domain.key') client = self._create_client(account_key) new_reg = acme.messages.NewRegistration(key=account_key.public_key(), terms_of_service_agreed=True) try: client.new_account(new_reg) _logger.info("Successfully registered.") except acme.errors.ConflictError as err: reg = acme.messages.Registration(key=account_key.public_key()) reg_res = acme.messages.RegistrationResource(body=reg, uri=err.location) client.query_registration(reg_res) _logger.info("Reusing existing account.") _logger.info('Making CSR for the following domains: %s', domains) csr = acme.crypto_util.make_csr(private_key_pem=domain_key, domains=domains) authzr = client.new_order(csr) # For each requested domain name we receive a list of challenges. # We only have to do one from each list. # HTTP challenges are the easiest, so do one of those if possible. # We can do DNS challenges too. There are other types that we don't # support. pending_responses = [] prefer_dns = (self.env["ir.config_parameter"].get_param( "letsencrypt.prefer_dns") == "True") for authorizations in authzr.authorizations: http_challenges = [ challenge for challenge in authorizations.body.challenges if challenge.chall.typ == TYPE_CHALLENGE_HTTP ] other_challenges = [ challenge for challenge in authorizations.body.challenges if challenge.chall.typ != TYPE_CHALLENGE_HTTP ] if prefer_dns: ordered_challenges = other_challenges + http_challenges else: ordered_challenges = http_challenges + other_challenges for challenge in ordered_challenges: if challenge.chall.typ == TYPE_CHALLENGE_HTTP: self._respond_challenge_http(challenge, account_key) client.answer_challenge(challenge, acme.challenges.HTTP01Response()) break elif challenge.chall.typ == TYPE_CHALLENGE_DNS: domain = authorizations.body.identifier.value token = challenge.validation(account_key) self._respond_challenge_dns(domain, token) # We delay this because we wait for each domain. # That takes less time if they've all already been changed. pending_responses.append( DNSUpdate(challenge=challenge, domain=domain, token=token)) break else: raise UserError( _('Could not respond to letsencrypt challenges.')) if pending_responses: for update in pending_responses: self._wait_for_record(update.domain, update.token) # 1 minute was not always enough during testing, even once records # were visible locally _logger.info( "All TXT records found, waiting 5 minutes more to make sure.") time.sleep(300) for update in pending_responses: client.answer_challenge(update.challenge, acme.challenges.DNSResponse()) # let them know we are done and they should check backoff = int(ir_config_parameter.get_param('letsencrypt.backoff', 3)) deadline = datetime.now() + timedelta(minutes=backoff) try: order_resource = client.poll_and_finalize(authzr, deadline) except acme.errors.ValidationError as error: _logger.error("Let's Encrypt validation failed!") for authz in error.failed_authzrs: for challenge in authz.body.challenges: _logger.error(str(challenge.error)) raise with open(cert_file, 'w') as crt: crt.write(order_resource.fullchain_pem) _logger.info('SUCCESS: Certificate saved: %s', cert_file) reload_cmd = ir_config_parameter.get_param( 'letsencrypt.reload_command', '') if reload_cmd.strip(): self._call_cmdline(reload_cmd) else: _logger.warning("No reload command defined.")
def create_or_update_cert( kv_cert_name, *domains, use_prod=False, keyvault_url='https://ponti-certs-kvjwxwal2p6n.vault.azure.net/', dns_zone_resource_group='damienpontifex.com-rg', dns_zone_name='damienpontifex.com', registration_email='*****@*****.**', dns_subscription_id="fa2cbf67-1293-4f9c-8884-a0379a9e0c64"): # Get directory if use_prod: directory_url = 'https://acme-v02.api.letsencrypt.org/directory' user_key_name = 'acme' issuance_period_months = 3 else: directory_url = 'https://acme-staging-v02.api.letsencrypt.org/directory' user_key_name = 'acme-staging' issuance_period_months = 1 credential = DefaultAzureCredential() challenge_handler = functools.partial( dns_challenge_handler, credential=credential, subscription_id=dns_subscription_id, dns_zone_resource_group=dns_zone_resource_group, dns_zone_name=dns_zone_name) cert_client = CertificateClient(vault_url=keyvault_url, credential=credential) #%% key = KeyVaultRSAKey(credential, keyvault_url, user_key_name) account_key = josepy.JWKRSA(key=key) client_network = acme.client.ClientNetwork(account_key) directory = messages.Directory.from_json( client_network.get(directory_url).json()) client = acme.client.ClientV2(directory, client_network) new_regr = acme.messages.Registration.from_data( key=account_key, email=registration_email, terms_of_service_agreed=True) # Register or fetch account try: regr = client.new_account(new_regr) logger.info('Created new account') except acme.errors.ConflictError as e: regr = acme.messages.RegistrationResource(uri=e.location, body=new_regr) regr = client.query_registration(regr) logger.info('Got existing account') cert_policy = CertificatePolicy( issuer_name='Unknown', subject_name=f'CN={domains[0]}', exportable=True, key_type=KeyType.rsa, key_size=2048, content_type=CertificateContentType.pkcs12, san_dns_names=domains[1:] if len(domains) > 1 else [], validity_in_months=issuance_period_months) try: # Check an existing certificate operation isn't in progress cert_op = cert_client.get_certificate_operation( certificate_name=kv_cert_name) logger.info('Existing cert operation in progress') except ResourceNotFoundError: cert_op = cert_client.begin_create_certificate( certificate_name=kv_cert_name, policy=cert_policy) logger.info('New cert operation') # cert_op = kvclient.create_certificate(KEYVAULT_URL, certificate_name=kv_cert_name, certificate_policy=cert_policy) cert_op_res = cert_op.result() cert_op_r = cert_client.get_certificate_operation(kv_cert_name) logger.info('Created certificate request in key vault') # Wrap with header and footer for pem to show certificate request csr_pem = "-----BEGIN CERTIFICATE REQUEST-----\n" + base64.b64encode( cert_op_r.csr).decode() + "\n-----END CERTIFICATE REQUEST-----\n" # Submit order order_resource = client.new_order(csr_pem) logger.info('Submitted order') # Challenges from order # Respond to challenges challenges_to_respond_to = list( challenge_handler(authorizations=order_resource.authorizations, account_key=account_key)) for dns_challenge in challenges_to_respond_to: # Perform challenge auth_response = client.answer_challenge( dns_challenge, dns_challenge.chall.response(account_key)) logger.info('Answered challenges') # Poll for status # Finalize order # Download certificate final_order = client.poll_and_finalize(order_resource) logger.info('Finalised order') # Strip header and footer of BEGIN/END CERTIFICATE # with open('cert.pem', 'w') as f: # f.write(final_order.fullchain_pem) certificate_vals = [ val.replace('\n', '').encode() for val in final_order.fullchain_pem.split('-----') if 'CERTIFICATE' not in val and len(val.replace('\n', '')) != 0 ] cert_client.merge_certificate(name=kv_cert_name, x509_certificates=certificate_vals) logger.info('Merged certificate back to key vault')
def run_acme_reg_to_finish(domain, accnt_key, priv_key, hostname, tmp_chall_dict, directory_url): '''Runs the entire process of ACME registeration''' client = create_v2_client(directory_url, accnt_key) # First we need to create a registration with the email address provided # and accept the terms of service log.info("Using boulder server %s", directory_url) client.net.account = client.new_account( messages.NewRegistration.from_data(terms_of_service_agreed=True)) # Now we need to open an order and request our certificate # NOTE: We'll let ACME generate a CSR for our private key as there's # a lot of utility code it uses to generate the CSR in a specific # fashion. Better to use what LE provides than to roll our own as we # we doing with the v1 code # # This will also let us support multi-domain certificat requests in the # future, as well as mandate OCSP-Must-Staple if/when GL's HTTPS server # supports it csr = crypto_util.make_csr(priv_key, [hostname], False) order = client.new_order(csr) authzr = order.authorizations log.info('Created a new order for %s', hostname) # authrz is a list of Authorization resources, we need to find the # HTTP-01 challenge and use it for auth_req in authzr: # pylint: disable=not-an-iterable for chall_body in auth_req.body.challenges: if isinstance(chall_body.chall, challenges.HTTP01): challb = chall_body break if challb is None: raise Exception("HTTP01 challenge unavailable!") response, chall_tok = challb.response_and_validation(client.net.key) v = chall_body.chall.encode("token") log.info('Exposing challenge on %s', v) tmp_chall_dict.set(v, ChallTok(chall_tok)) cr = client.answer_challenge(challb, challb.response(client.net.key)) log.debug('Acme CA responded to challenge request with: %s', cr) # Wrap this step and log the failure particularly here because this is # the expected point of failure for applications that are not reachable # from the public internet. try: order = client.poll_and_finalize(order) except messages.Error as error: log.err("Failed in request issuance step %s", error) raise # ACME V2 returns a full chain certificate, and ACME doesn't ship with # helper functions out of the box. Fortunately, searching through cerbot # this is easily enough to do with pyOpenSSL cert = load_certificate(FILETYPE_PEM, order.fullchain_pem) cert_str = dump_certificate(FILETYPE_PEM, cert).decode() chain_str = order.fullchain_pem[len(cert_str):].lstrip() # pylint: disable=no-member expr_date = convert_asn1_date(cert.get_notAfter()) log.info('Retrieved cert using ACME that expires on %s', expr_date) return cert_str, chain_str
def answer_dns_challenge(conf, client, domain, challenge): """ Compute the required answer and set it in the DNS record for the domain. """ authorization = "{}.{}".format( base64.urlsafe_b64encode( challenge.get("token")).decode("ascii").replace("=", ""), base64.urlsafe_b64encode( client.key.thumbprint()).decode("ascii").replace("=", "")) dns_response = base64.urlsafe_b64encode( hashlib.sha256( authorization.encode()).digest()).decode("ascii").replace("=", "") # Let's update the DNS on our R53 account zone_id = get_route53_zone_id(conf, domain['r53_zone']) if zone_id == None: LOG.error("Cannot determine zone id for zone '{0}'".format( domain['r53_zone'])) return None LOG.info("Domain '{0}' has '{1}' for Id".format(domain['r53_zone'], zone_id)) zone_id = get_route53_zone_id(conf, domain['r53_zone']) if zone_id == None: LOG.error("Cannot find R53 zone {}, are you controling it ?".format( domain['r53_zone'])) return None acme_domain = "_acme-challenge.{}".format(domain['name']) res = reset_route53_letsencrypt_record(conf, zone_id, domain['name'], acme_domain) if res == None: LOG.error( "An error occured while trying to remove a previous resource record. Skipping domain {0}" .format(domain['name'])) return None add_status = create_route53_letsencrypt_record(conf, zone_id, domain['name'], acme_domain, 'TXT', '"' + dns_response + '"') if add_status == None: LOG.error( "An error occured while creating the dns record. Skipping domain {0}" .format(domain['name'])) return None add_status = wait_letsencrypt_record_insync(conf, add_status) if add_status == None: LOG.error( "Cannot determine if the dns record has been correctly created. Skipping domain {0}" .format(domain['name'])) return None if add_status == False: LOG.error( "We updated R53 but the servers didn't sync within 60 seconds. Skipping domain {0}" .format(domain['name'])) return None if add_status is not True: LOG.error( "An unexpected result code has been returned. Please report this bug. Skipping domain {0}" .format(domain['name'])) LOG.error("add_status={0}".format(add_status)) return None ## Now, let's tell the ACME server that we are ready challenge_response = challenges.DNS01Response( key_authorization=authorization) challenge_resource = client.answer_challenge(challenge, challenge_response) if challenge_resource.body.error != None: return False return True
def answer_dns_challenge(conf, client, domain, challenge): """ Compute the required answer and set it in the DNS record for the domain. """ zone_id = conf['r53_zone_id'] account_key = client.net.key authorization = "{}.{}".format( base64.urlsafe_b64encode( challenge.get("token")).decode("ascii").replace("=", ""), base64.urlsafe_b64encode( account_key.thumbprint()).decode("ascii").replace("=", "")) dns_response = base64.urlsafe_b64encode( hashlib.sha256( authorization.encode()).digest()).decode("ascii").replace("=", "") LOG.info("authorization ='{0}' dns_response= '{1}' for Id".format( authorization, dns_response)) #domain_name = '.'.join(domain['name'].split('.')[1:]) domain_name = domain['name'].replace( '*.', '') if domain['name'].startswith('*.') else domain['name'] acme_domain = "_acme-challenge.%s.%s" % (domain_name, conf['r53_zone']) res = reset_route53_letsencrypt_record(zone_id, acme_domain) if res is None: LOG.error( "An error occured while trying to remove a " "previous resource record. Skipping domain %s", domain_name) return None add_status = create_route53_letsencrypt_record(zone_id, acme_domain, '"' + dns_response + '"') if add_status is None: LOG.error( "An error occured while creating the dns record. " "Skipping domain %s", domain_name) return None add_status = wait_letsencrypt_record_insync(add_status) if add_status is None: LOG.error( "Cannot determine if the dns record has been correctly created. " "Skipping domain {0}".format(domain_name)) return None if add_status is False: LOG.error( "We updated R53 but the servers didn't sync within 60 seconds. " "Skipping domain {0}".format(domain_name)) return None if add_status is not True: LOG.error( "An unexpected result code has been returned. Please report this bug. " "Skipping domain {0}".format(domain_name)) LOG.error("add_status={0}".format(add_status)) return None challenge_response = challenges.DNS01Response( key_authorization=authorization) challenge_resource = client.answer_challenge(challenge, challenge_response) if challenge_resource.body.error is not None: return False return True