def purge_expired_sessions(self): import time """ A file session contains a data separated by -- The first part is the hexadecimal representation of the encoded session data. Second is the epoch of the last write. If current epoch - file epoch is lower than the session life time then we need to delete this file. """ logger.debug("File handler looking for expired sessions.") self.engine.session_callback.stop() logging.debug("Session periodic callback stopped by the file " "handler.") purge_count = 0 purge_hiccup = False for dirname, dirnames, filenames in os.walk(self.path): for filename in filenames: file_path = os.path.join(self.path, filename) sess_id = filename.split(".")[0].split("_")[-1] timed_data = fs.read(file_path) last_write = timed_data.split("--")[1] age = int(time.time()) - int(last_write) if age > self.life_time: logging.debug("Session %s is expired. Removing file " "from the session path." % sess_id) os.remove(file_path) purge_count += 1 if purge_count == firenado.conf.session['purge_limit']: purge_hiccup = True logger.warning( "Expired 500 sessions. Exiting the call and waiting for " "purge hiccup.") break if purge_hiccup: self.engine.set_purge_hiccup() else: self.engine.set_purge_normal() self.engine.session_callback.start() logging.debug("Session periodic callback resumed by the file " "handler.")
def test_binary_fs_write(self): """ Write in a file using data as bytes. """ data = b"My text to write.\n" fs.write(self.file_to_write_path, data, True) self.assertTrue(os.path.exists(self.file_to_write_path)) self.assertEqual(data, fs.read(self.file_to_write_path, True))
def test_string_fs_write(self): """ Write in a file using data as string. """ data = "My text to write.\n" fs.write(self.file_to_write_path, data) self.assertTrue(os.path.exists(self.file_to_write_path)) self.assertEqual(data, fs.read(self.file_to_write_path))
def test_binary_fs_read(self): """ Read a file returning bytes as result. """ expected = b"Do not remove this file.\n" value = fs.read(self.file_to_read_path, True) self.assertIsInstance(value, bytes) self.assertEqual(expected, value)
def test_string_fs_read(self): """ Read a file returning string as result. """ expected = "Do not remove this file.\n" value = fs.read(self.file_to_read_path) self.assertIsInstance(value, str) self.assertEqual(expected, value)
def authorize(server, paths, account, domains, method, verbose=False): print("Candango Automatoes {}. Manuale replacement.\n\n".format( get_version())) current_path = paths['current'] orders_path = paths['orders'] domains_hash = hashlib.sha256( "_".join(domains).encode('ascii')).hexdigest() order_path = os.path.join(orders_path, domains_hash) order_file = os.path.join(order_path, "order.json".format(domains_hash)) if not os.path.exists(orders_path): if verbose: print("Orders path not found creating it at {}." "".format(orders_path)) os.mkdir(orders_path) os.chmod(orders_path, 0o770) else: if verbose: print("Orders path found at {}.".format(orders_path)) if not os.path.exists(order_path): if verbose: print("Current order {} path not found creating it at orders " "path.\n".format(domains_hash)) os.mkdir(order_path) os.chmod(order_path, 0o770) else: if verbose: print("Current order {} path found at orders path.\n".format( domains_hash)) method = method acme = AcmeV2(server, account) try: print("Authorizing {}.\n".format(", ".join(domains))) # Creating orders for domains if not existent if not os.path.exists(order_file): if verbose: print(" Order file not found creating it.") order = create_order(acme, domains, method, order_file) else: if verbose: print(" Found order file. Querying ACME server for current " "status.") order = Order.deserialize(fs.read(order_file)) server_order = acme.query_order(order) order.contents = server_order.contents update_order(order, order_file) if not order.expired and not order.invalid: if order.contents['status'] == 'valid': print(" Order is valid and expires at {}. Please run " "the issue " "command.\n".format(order.contents['expires'])) print(" {} domain(s) authorized. Let's Encrypt!".format( len(domains))) sys.exit(sysexits.EX_OK) else: if verbose: print(" Order still pending and expires " "at {}.\n".format(order.contents['expires'])) else: if order.invalid: print(" WARNING: Invalid order, renewing it.\n Just " "continue with the authorization when all " "verifications are in place.\n") else: print(" WARNING: Expired order. Renewing order.\n") os.remove(order_file) order = create_order(acme, domains, method, order_file) update_order(order, order_file) pending_challenges = [] for challenge in acme.get_order_challenges(order): print(" Requesting challenge for {}.".format(challenge.domain)) if challenge.status == 'valid': print(" {} is already authorized until {}.".format( challenge.domain, challenge.expires)) continue else: challenge_file = os.path.join(order_path, challenge.file_name) if verbose: print(" Creating challenge file {}.\n".format( challenge.file_name)) fs.write(challenge_file, challenge.serialize().decode()) pending_challenges.append(challenge) # Quit if nothing to authorize if not pending_challenges: print("\nAll domains are already authorized, exiting.") sys.exit(sysexits.EX_OK) files = set() if method == 'dns': print("\n DNS verification required. Make sure these TXT records" " are in place:\n") for challenge in pending_challenges: print(" _acme-challenge.{}. IN TXT " "\"{}\"".format(challenge.domain, challenge.key)) elif method == 'http': print("\n HTTP verification required. Make sure these files are " "in place:\n") for challenge in pending_challenges: token = challenge.contents['token'] # path sanity check assert (token and os.path.sep not in token and '.' not in token) files.add(token) fs.write(os.path.join(current_path, token), challenge.key) print(" http://{}/.well-known/acme-challenge/{}".format( challenge.domain, token)) print("\n The necessary files have been written to the current " "directory.\n") # Wait for the user to complete the challenges input("\nPress Enter to continue.\n") # Validate challenges done, failed, pending = set(), set(), set() for challenge in pending_challenges: print(" {}: waiting for verification. Checking in 5 " "seconds.".format(challenge.domain)) response = acme.verify_order_challenge(challenge, 5, 1) if response['status'] == "valid": print(" {}: OK! Authorization lasts until {}.".format( challenge.domain, challenge.expires)) done.add(challenge.domain) elif response['status'] == 'invalid': print(" {}: {} ({})".format(challenge.domain, response['error']['detail'], response['error']['type'])) failed.add(challenge.domain) break else: print("{}: Pending!".format(challenge.domain)) pending.add(challenge.domain) break challenge_file = os.path.join(order_path, challenge.file_name) # Print results if failed: print(" {} domain(s) authorized, {} failed.".format( len(done), len(failed), )) print(" Authorized: {}".format(' '.join(done) or "N/A")) print(" Failed: {}".format(' '.join(failed))) print(" WARNING: The current order will be invalidated. " "Try again.") if verbose: print(" Deleting invalid challenge file {}.\n".format( challenge.file_name)) clean_challenge_file(challenge_file) os.remove(order_file) os.rmdir(order_path) if method == 'http': print(files) clean_http_challenges(files) sys.exit(sysexits.EX_FATAL_ERROR) else: if pending: print(" {} domain(s) authorized, {} pending.".format( len(done), len(pending))) print(" Authorized: {}".format(' '.join(done) or "N/A")) print(" Pending: {}".format(' '.join(pending))) print(" Try again.") sys.exit(sysexits.EX_CANNOT_EXECUTE) else: if verbose: print(" Deleting valid challenge file {}.".format( challenge.file_name)) clean_challenge_file(challenge_file) if verbose: print(" Querying ACME server for current status.\n") server_order = acme.query_order(order) order.contents = server_order.contents update_order(order, order_file) print(" {} domain(s) authorized. Let's Encrypt!".format( len(done))) if method == 'http': clean_http_challenges(files) sys.exit(sysexits.EX_OK) except IOError as e: print("A connection or service error occurred. Aborting.") raise AutomatoesError(e)
def issue(server, paths, account, domains, key_size, key_file=None, csr_file=None, output_path=None, must_staple=False, verbose=False): print("Candango Automatoes {}. Manuale replacement.\n\n".format( get_version())) current_path = paths['current'] orders_path = paths['orders'] domains_hash = hashlib.sha256( "_".join(domains).encode('ascii')).hexdigest() order_path = os.path.join(orders_path, domains_hash) order_file = os.path.join(order_path, "order.json".format(domains_hash)) if not os.path.exists(orders_path): print(" ERROR: Orders path not found. Please run before: manuale " "authorize {}".format(" ".join(domains))) sys.exit(sysexits.EX_CANNOT_EXECUTE) else: if verbose: print("Orders path found at {}.".format(orders_path)) if verbose: print("Searching order file {}.".format(order_file)) if not os.path.exists(order_path): print(" ERROR: Order file not found. Please run before: manuale " "authorize {}".format(" ".join(domains))) sys.exit(sysexits.EX_CANNOT_EXECUTE) else: if verbose: print("Current order {} path found at orders path.\n".format( domains_hash)) acme = AcmeV2(server, account) order = Order.deserialize(fs.read(order_file)) if order.contents['status'] == "pending": if verbose: print("Querying ACME server for current status.") server_order = acme.query_order(order) order.contents = server_order.contents update_order(order, order_file) if order.contents['status'] in ["pending", "invalid"]: print(" ERROR: Order not ready or invalid. Please re-run: manuale" " authorize {}.".format(" ".join(domains))) sys.exit(sysexits.EX_CANNOT_EXECUTE) elif order.contents['status'] == "invalid": print(" ERROR: Invalid order. Please re-run: manuale authorize " "{}.".format(" ".join(domains))) sys.exit(sysexits.EX_CANNOT_EXECUTE) if not output_path or output_path == '.': output_path = os.getcwd() # Load key if given if key_file: try: with open(key_file, 'rb') as f: certificate_key = load_private_key(f.read()) order.key = export_private_key(certificate_key).decode('ascii') update_order(order, order_file) except (ValueError, AttributeError, TypeError, IOError) as e: print("ERROR: Couldn't read certificate key.") raise AutomatoesError(e) else: certificate_key = None # Load CSR or generate if csr_file: try: with open(csr_file, 'rb') as f: csr = export_csr_for_acme(load_csr(f.read())) except (ValueError, AttributeError, TypeError, IOError) as e: print("ERROR: Couldn't read CSR.") raise AutomatoesError(e) else: # Generate key if not key_file: if order.key is None: print("Generating a {} bit RSA key. This might take a " "second.".format(key_size)) certificate_key = generate_rsa_key(key_size) print(" Key generated.") order.key = export_private_key(certificate_key).decode('ascii') update_order(order, order_file) print(" Order updated with generated key.") else: print("Previous RSA key found in the order. Loading the key.") certificate_key = load_private_key(order.key.encode('ascii')) csr = create_csr(certificate_key, domains, must_staple=must_staple) try: logger.info("Requesting certificate issuance...") if order.contents['status'] == "ready": final_order = acme.finalize_order(order, csr) order.contents = final_order update_order(order, order_file) if final_order['status'] in ["processing", "valid"]: if verbose: print(" Order {} finalized. Certificate is being " "issued.".format(domains_hash)) else: print(" ERROR: Order not ready or invalid. Please re-run: " "manuale authorize {}.".format(" ".join(domains))) sys.exit(sysexits.EX_CANNOT_EXECUTE) elif order.contents['status'] in ["valid", "processing"]: print(" Order {} is already processing or valid. Downloading " "certificate.".format(domains_hash)) else: print(" ERROR: Order not ready or invalid. Please re-run: manuale " "authorize {}.".format(" ".join(domains))) sys.exit(sysexits.EX_CANNOT_EXECUTE) if order.certificate_uri is None: if verbose: print(" Checking order {} status.".format(domains_hash)) fulfillment = acme.await_for_order_fulfillment(order) if fulfillment['status'] == "valid": order.contents = fulfillment update_order(order, order_file) else: print(" ERROR: Order not ready or invalid. Please re-run: " "manuale authorize {}.".format(" ".join(domains))) sys.exit(sysexits.EX_CANNOT_EXECUTE) else: print(" We already know the certificate uri for order {}. " "Downloading certificate.".format(domains_hash)) result = acme.download_order_certificate(order) logger.info(" Certificate downloaded.") except IOError as e: print("Connection or service request failed. Aborting.") raise AutomatoesError(e) try: certificates = strip_certificates(result.content) certificate = load_pem_certificate(certificates[0]) # Print some neat info print(" Expires: {}".format( certificate.not_valid_after.strftime(EXPIRATION_FORMAT))) print(" SHA256: {}".format( binascii.hexlify(certificate.fingerprint( SHA256())).decode('ascii'))) # Write the key, certificate and full chain os.makedirs(output_path, exist_ok=True) cert_path = os.path.join(output_path, domains[0] + '.crt') chain_path = os.path.join(output_path, domains[0] + '.chain.crt') intermediate_path = os.path.join(output_path, domains[0] + '.intermediate.crt') key_path = os.path.join(output_path, domains[0] + '.pem') if order.key is not None: with open(key_path, 'wb') as f: os.chmod(key_path, 0o600) f.write(order.key.encode('ascii')) print("\n Wrote key to {}".format(f.name)) with open(cert_path, 'wb') as f: f.write(export_pem_certificate(certificate)) print(" Wrote certificate to {}".format(f.name)) with open(chain_path, 'wb') as f: f.write(export_pem_certificate(certificate)) if len(certificates) > 1: f.write( export_pem_certificate( load_pem_certificate(certificates[1]))) print(" Wrote certificate with intermediate to {}".format(f.name)) if len(certificates) > 1: with open(intermediate_path, 'wb') as f: f.write( export_pem_certificate( load_pem_certificate(certificates[1]))) print(" Wrote intermediate certificate to {}".format(f.name)) except IOError as e: print(" ERROR: Failed to write certificate or key. Going to print " "them for you instead.") if order.key is not None: for line in order.key.split('\n'): print("ERROR: {}".format(line)) for line in export_pem_certificate(certificate).decode('ascii').split( '\n'): print("ERROR: {}".format(line)) raise AutomatoesError(e)
def user_file_exists_at(context, certificate_path): real_certificate_path = get_absolute_path(certificate_path) context.tester.assertTrue(os.path.exists(real_certificate_path)) context.tester.assertTrue(os.path.isfile(real_certificate_path)) context.certificate = fs.read(real_certificate_path)
def read_stored_session(self, session_id): import binascii session_file = os.path.join(self.path, self.__get_filename(session_id)) timed_data = fs.read(session_file) return binascii.unhexlify(timed_data.split("--")[0])