def init_save_csr(privkey, names, path, csrname="csr-letsencrypt.pem"): """Initialize a CSR with the given private key. :param privkey: Key to include in the CSR :type privkey: :class:`letsencrypt.le_util.Key` :param set names: `str` names to include in the CSR :param str path: Certificate save directory. :returns: CSR :rtype: :class:`letsencrypt.le_util.CSR` """ csr_pem, csr_der = make_csr(privkey.pem, names) config = zope.component.getUtility(interfaces.IConfig) # Save CSR le_util.make_or_verify_dir(path, 0o755, os.geteuid(), config.strict_permissions) csr_f, csr_filename = le_util.unique_file( os.path.join(path, csrname), 0o644) csr_f.write(csr_pem) csr_f.close() logger.info("Creating CSR: %s", csr_filename) return le_util.CSR(csr_filename, csr_der, "der")
def init_save_csr(privkey, names, cert_dir, csrname="csr-letsencrypt.pem"): """Initialize a CSR with the given private key. :param privkey: Key to include in the CSR :type privkey: :class:`letsencrypt.le_util.Key` :param set names: `str` names to include in the CSR :param str cert_dir: Certificate save directory. :returns: CSR :rtype: :class:`letsencrypt.le_util.CSR` """ csr_pem, csr_der = make_csr(privkey.pem, names) # Save CSR le_util.make_or_verify_dir(cert_dir, 0o755) csr_f, csr_filename = le_util.unique_file( os.path.join(cert_dir, csrname), 0o644) csr_f.write(csr_pem) csr_f.close() logging.info("Creating CSR: %s", csr_filename) return le_util.CSR(csr_filename, csr_der, "der")
def main(cli_args=sys.argv[1:]): """Command line argument parsing and main script execution.""" # note: arg parser internally handles --help (and exits afterwards) plugins = plugins_disco.PluginsRegistry.find_all() args = create_parser(plugins, cli_args).parse_args(cli_args) config = configuration.NamespaceConfig(args) # Setup logging ASAP, otherwise "No handlers could be found for # logger ..." TODO: this should be done before plugins discovery for directory in config.config_dir, config.work_dir: le_util.make_or_verify_dir(directory, constants.CONFIG_DIRS_MODE, os.geteuid()) # TODO: logs might contain sensitive data such as contents of the # private key! #525 le_util.make_or_verify_dir(args.logs_dir, 0o700, os.geteuid()) _setup_logging(args) def handle_exception_common(): """Logs the exception and reraises it if in debug mode.""" logger.debug("Exiting abnormally", exc_info=True) if args.debug: raise try: return main2(cli_args, args, config, plugins) except errors.Error as error: handle_exception_common() return error except Exception: # pylint: disable=broad-except handle_exception_common() return "An unexpected error occured. Please see the logfiles in {0} " "for more details.".format(args.logs_dir)
def init_save_key(key_size, key_dir, keyname="key-letsencrypt.pem"): """Initializes and saves a privkey. Inits key and saves it in PEM format on the filesystem. .. note:: keyname is the attempted filename, it may be different if a file already exists at the path. :param int key_size: RSA key size in bits :param str key_dir: Key save directory. :param str keyname: Filename of key :returns: Key :rtype: :class:`letsencrypt.le_util.Key` :raises ValueError: If unable to generate the key given key_size. """ try: key_pem = make_key(key_size) except ValueError as err: logging.fatal(str(err)) raise err # Save file le_util.make_or_verify_dir(key_dir, 0o700, os.geteuid()) key_f, key_path = le_util.unique_file( os.path.join(key_dir, keyname), 0o600) key_f.write(key_pem) key_f.close() logging.info("Generating key (%d bits): %s", key_size, key_path) return le_util.Key(key_path, key_pem)
def save(self): """Save account to disk.""" le_util.make_or_verify_dir( self.config.accounts_dir, 0o700, os.geteuid()) acc_config = configobj.ConfigObj() acc_config.filename = os.path.join( self.config.accounts_dir, self._get_config_filename(self.email)) acc_config.initial_comment = [ "DO NOT EDIT THIS FILE", "Account information for %s under %s" % ( self._get_config_filename(self.email), self.config.server), ] acc_config["key"] = self.key.file acc_config["phone"] = self.phone if self.regr is not None: acc_config["RegistrationResource"] = {} acc_config["RegistrationResource"]["uri"] = self.uri acc_config["RegistrationResource"]["new_authzr_uri"] = ( self.new_authzr_uri) acc_config["RegistrationResource"]["terms_of_service"] = ( self.terms_of_service) regr_dict = self.regr.body.to_json() acc_config["RegistrationResource"]["body"] = regr_dict acc_config.write()
def _find_duplicative_certs(config, domains): """Find existing certs that duplicate the request.""" identical_names_cert, subset_names_cert = None, None cli_config = configuration.RenewerConfiguration(config) configs_dir = cli_config.renewal_configs_dir # Verify the directory is there le_util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid()) for renewal_file in os.listdir(configs_dir): try: full_path = os.path.join(configs_dir, renewal_file) candidate_lineage = storage.RenewableCert(full_path, cli_config) except (CertStorageError, IOError): logger.warning( "Renewal configuration file %s is broken. " "Skipping.", full_path) continue # TODO: Handle these differently depending on whether they are # expired or still valid? candidate_names = set(candidate_lineage.names()) if candidate_names == set(domains): identical_names_cert = candidate_lineage elif candidate_names.issubset(set(domains)): subset_names_cert = candidate_lineage return identical_names_cert, subset_names_cert
def copy_key(input_key, key_dir, keyname="key-letsencrypt.pem"): """ Copies the given key to into the key directory with the data format. .. note:: keyname is the attempted filename, it may be different if a file already exists at the path. :param le_util.Key input_key: The key which shoudl be copied :param str key_dir: Key save directory. :param str keyname: Filename of key :returns: Key :rtype: :class:`letsencrypt.le_util.Key` :raises ValueError: If unable to generate the key given key_size. """ config = zope.component.getUtility(interfaces.IConfig) # Save file le_util.make_or_verify_dir(key_dir, 0o700, os.geteuid(), config.strict_permissions) key_f, key_path = le_util.unique_file( os.path.join(key_dir, keyname), 0o600) if isinstance(input_key, basestring): key_pem = input_key else: key_pem = input_key[1] key_f.write(key_pem) key_f.close() logger.info("Copied key: %s", key_path) return le_util.Key(key_path, key_pem)
def main(cli_args=sys.argv[1:]): """Command line argument parsing and main script execution.""" sys.excepthook = functools.partial(_handle_exception, args=None) # note: arg parser internally handles --help (and exits afterwards) plugins = plugins_disco.PluginsRegistry.find_all() parser, tweaked_cli_args = create_parser(plugins, cli_args) args = parser.parse_args(tweaked_cli_args) config = configuration.NamespaceConfig(args) zope.component.provideUtility(config) # Setup logging ASAP, otherwise "No handlers could be found for # logger ..." TODO: this should be done before plugins discovery for directory in config.config_dir, config.work_dir: le_util.make_or_verify_dir( directory, constants.CONFIG_DIRS_MODE, os.geteuid(), "--strict-permissions" in cli_args) # TODO: logs might contain sensitive data such as contents of the # private key! #525 le_util.make_or_verify_dir( args.logs_dir, 0o700, os.geteuid(), "--strict-permissions" in cli_args) setup_logging(args, _cli_log_handler, logfile='letsencrypt.log') # do not log `args`, as it contains sensitive data (e.g. revoke --key)! logger.debug("Arguments: %r", cli_args) logger.debug("Discovered plugins: %r", plugins) sys.excepthook = functools.partial(_handle_exception, args=args) # Displayer if args.text_mode: displayer = display_util.FileDisplay(sys.stdout) else: displayer = display_util.NcursesDisplay() zope.component.provideUtility(displayer) # Reporter report = reporter.Reporter() zope.component.provideUtility(report) atexit.register(report.atexit_print_messages) # TODO: remove developer EULA prompt for the launch if not config.eula: eula = pkg_resources.resource_string("letsencrypt", "EULA") if not zope.component.getUtility(interfaces.IDisplay).yesno( eula, "Agree", "Cancel"): raise errors.Error("Must agree to TOS") if not os.geteuid() == 0: logger.warning( "Root (sudo) is required to run most of letsencrypt functionality.") # check must be done after arg parsing as --help should work # w/o root; on the other hand, e.g. "letsencrypt run # --authenticator dns" or "letsencrypt plugins" does not # require root as well #return ( # "{0}Root is required to run letsencrypt. Please use sudo.{0}" # .format(os.linesep)) return args.func(args, config, plugins)
def _find_duplicative_certs(config, domains): """Find existing certs that duplicate the request.""" identical_names_cert, subset_names_cert = None, None cli_config = configuration.RenewerConfiguration(config) configs_dir = cli_config.renewal_configs_dir # Verify the directory is there le_util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid()) for renewal_file in renewal.renewal_conf_files(cli_config): try: candidate_lineage = storage.RenewableCert(renewal_file, cli_config) except (errors.CertStorageError, IOError): logger.warning("Renewal conf file %s is broken. Skipping.", renewal_file) logger.debug("Traceback was:\n%s", traceback.format_exc()) continue # TODO: Handle these differently depending on whether they are # expired or still valid? candidate_names = set(candidate_lineage.names()) if candidate_names == set(domains): identical_names_cert = candidate_lineage elif candidate_names.issubset(set(domains)): # This logic finds and returns the largest subset-names cert # in the case where there are several available. if subset_names_cert is None: subset_names_cert = candidate_lineage elif len(candidate_names) > len(subset_names_cert.names()): subset_names_cert = candidate_lineage return identical_names_cert, subset_names_cert
def main(cli_args=sys.argv[1:]): """Command line argument parsing and main script execution.""" sys.excepthook = functools.partial(_handle_exception, args=None) # note: arg parser internally handles --help (and exits afterwards) plugins = plugins_disco.PluginsRegistry.find_all() parser, tweaked_cli_args = create_parser(plugins, cli_args) args = parser.parse_args(tweaked_cli_args) config = configuration.NamespaceConfig(args) zope.component.provideUtility(config) # Setup logging ASAP, otherwise "No handlers could be found for # logger ..." TODO: this should be done before plugins discovery for directory in config.config_dir, config.work_dir: le_util.make_or_verify_dir( directory, constants.CONFIG_DIRS_MODE, os.geteuid(), "--strict-permissions" in cli_args) # TODO: logs might contain sensitive data such as contents of the # private key! #525 le_util.make_or_verify_dir( args.logs_dir, 0o700, os.geteuid(), "--strict-permissions" in cli_args) setup_logging(args, _cli_log_handler, logfile='letsencrypt.log') # do not log `args`, as it contains sensitive data (e.g. revoke --key)! logger.debug("Arguments: %r", cli_args) logger.debug("Discovered plugins: %r", plugins) sys.excepthook = functools.partial(_handle_exception, args=args) # Displayer if args.text_mode: displayer = display_util.FileDisplay(sys.stdout) else: displayer = display_util.NcursesDisplay() zope.component.provideUtility(displayer) # Reporter report = reporter.Reporter() zope.component.provideUtility(report) atexit.register(report.atexit_print_messages) # TODO: remove developer EULA prompt for the launch if not config.eula: eula = pkg_resources.resource_string("letsencrypt", "EULA") if not zope.component.getUtility(interfaces.IDisplay).yesno( eula, "Agree", "Cancel"): raise Error("Must agree to TOS") if not os.geteuid() == 0: logger.warning( "Root (sudo) is required to run most of letsencrypt functionality.") # check must be done after arg parsing as --help should work # w/o root; on the other hand, e.g. "letsencrypt run # --authenticator dns" or "letsencrypt plugins" does not # require root as well #return ( # "{0}Root is required to run letsencrypt. Please use sudo.{0}" # .format(os.linesep)) return args.func(args, config, plugins)
def init_save_key(key_size, key_dir, keyname="key-letsencrypt.pem"): """Initializes and saves a privkey. Inits key and saves it in PEM format on the filesystem. .. note:: keyname is the attempted filename, it may be different if a file already exists at the path. :param int key_size: RSA key size in bits :param str key_dir: Key save directory. :param str keyname: Filename of key :returns: Key :rtype: :class:`letsencrypt.le_util.Key` :raises ValueError: If unable to generate the key given key_size. """ try: key_pem = make_key(key_size) except ValueError as err: logger.exception(err) raise err config = zope.component.getUtility(interfaces.IConfig) # Save file le_util.make_or_verify_dir(key_dir, 0o700, os.geteuid(), config.strict_permissions) key_f, key_path = le_util.unique_file(os.path.join(key_dir, keyname), 0o600) with key_f: key_f.write(key_pem) logger.info("Generating key (%d bits): %s", key_size, key_path) return le_util.Key(key_path, key_pem)
def init_save_key(key_size, key_dir, keyname="key-letsencrypt.pem"): """Initializes and saves a privkey. Inits key and saves it in PEM format on the filesystem. .. note:: keyname is the attempted filename, it may be different if a file already exists at the path. :param int key_size: RSA key size in bits :param str key_dir: Key save directory. :param str keyname: Filename of key :returns: Key :rtype: :class:`letsencrypt.le_util.Key` :raises ValueError: If unable to generate the key given key_size. """ try: key_pem = make_key(key_size) except ValueError as err: logger.exception(err) raise err # Save file le_util.make_or_verify_dir(key_dir, 0o700, os.geteuid()) key_f, key_path = le_util.unique_file(os.path.join(key_dir, keyname), 0o600) key_f.write(key_pem) key_f.close() logger.info("Generating key (%d bits): %s", key_size, key_path) return le_util.Key(key_path, key_pem)
def init_save_csr(privkey, names, path, csrname="csr-letsencrypt.pem"): """Initialize a CSR with the given private key. :param privkey: Key to include in the CSR :type privkey: :class:`letsencrypt.le_util.Key` :param set names: `str` names to include in the CSR :param str path: Certificate save directory. :returns: CSR :rtype: :class:`letsencrypt.le_util.CSR` """ csr_pem, csr_der = make_csr(privkey.pem, names) # Save CSR le_util.make_or_verify_dir(path, 0o755, os.geteuid()) csr_f, csr_filename = le_util.unique_file(os.path.join(path, csrname), 0o644) csr_f.write(csr_pem) csr_f.close() logger.info("Creating CSR: %s", csr_filename) return le_util.CSR(csr_filename, csr_der, "der")
def _find_duplicative_certs(domains, config, renew_config): """Find existing certs that duplicate the request.""" identical_names_cert, subset_names_cert = None, None configs_dir = renew_config.renewal_configs_dir # Verify the directory is there le_util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid()) cli_config = configuration.RenewerConfiguration(config) for renewal_file in os.listdir(configs_dir): try: full_path = os.path.join(configs_dir, renewal_file) rc_config = configobj.ConfigObj(renew_config.renewer_config_file) rc_config.merge(configobj.ConfigObj(full_path)) rc_config.filename = full_path candidate_lineage = storage.RenewableCert( rc_config, config_opts=None, cli_config=cli_config) except (configobj.ConfigObjError, errors.CertStorageError, IOError): logger.warning("Renewal configuration file %s is broken. " "Skipping.", full_path) continue # TODO: Handle these differently depending on whether they are # expired or still valid? candidate_names = set(candidate_lineage.names()) if candidate_names == set(domains): identical_names_cert = candidate_lineage elif candidate_names.issubset(set(domains)): subset_names_cert = candidate_lineage return identical_names_cert, subset_names_cert
def save(self): """Save account to disk.""" le_util.make_or_verify_dir(self.config.accounts_dir, 0o700, os.geteuid()) acc_config = configobj.ConfigObj() acc_config.filename = os.path.join( self.config.accounts_dir, self._get_config_filename(self.email)) acc_config.initial_comment = [ "DO NOT EDIT THIS FILE", "Account information for %s under %s" % (self._get_config_filename(self.email), self.config.server), ] acc_config["key"] = self.key.file acc_config["phone"] = self.phone if self.regr is not None: acc_config["RegistrationResource"] = {} acc_config["RegistrationResource"]["uri"] = self.uri acc_config["RegistrationResource"]["new_authzr_uri"] = ( self.new_authzr_uri) acc_config["RegistrationResource"]["terms_of_service"] = ( self.terms_of_service) regr_dict = self.regr.body.to_json() acc_config["RegistrationResource"]["body"] = regr_dict acc_config.write()
def main(cli_args=sys.argv[1:]): """Main function for autorenewer script.""" # TODO: Distinguish automated invocation from manual invocation, # perhaps by looking at sys.argv[0] and inhibiting automated # invocations if /etc/letsencrypt/renewal.conf defaults have # turned it off. (The boolean parameter should probably be # called renewer_enabled.) # TODO: When we have a more elaborate renewer command line, we will # presumably also be able to specify a config file on the # command line, which, if provided, should take precedence over # te default config files zope.component.provideUtility(display_util.FileDisplay(sys.stdout)) args = _create_parser().parse_args(cli_args) uid = os.geteuid() le_util.make_or_verify_dir(args.logs_dir, 0o700, uid) cli.setup_logging(args, _cli_log_handler, logfile='renewer.log') cli_config = configuration.RenewerConfiguration(args) # Ensure that all of the needed folders have been created before continuing le_util.make_or_verify_dir(cli_config.work_dir, constants.CONFIG_DIRS_MODE, uid) for renewal_file in os.listdir(cli_config.renewal_configs_dir): print "Processing", renewal_file try: # TODO: Before trying to initialize the RenewableCert object, # we could check here whether the combination of the config # and the rc_config together disables all autorenewal and # autodeployment applicable to this cert. In that case, we # can simply continue and don't need to instantiate a # RenewableCert object for this cert at all, which could # dramatically improve performance for large deployments # where autorenewal is widely turned off. cert = storage.RenewableCert(renewal_file, cli_config) except errors.CertStorageError: # This indicates an invalid renewal configuration file, such # as one missing a required parameter (in the future, perhaps # also one that is internally inconsistent or is missing a # required parameter). As a TODO, maybe we should warn the # user about the existence of an invalid or corrupt renewal # config rather than simply ignoring it. continue if cert.should_autorenew(): # Note: not cert.current_version() because the basis for # the renewal is the latest version, even if it hasn't been # deployed yet! old_version = cert.latest_common_version() renew(cert, old_version) notify.notify("Autorenewed a cert!!!", "root", "It worked!") # TODO: explain what happened if cert.should_autodeploy(): cert.update_all_links_to(cert.latest_common_version()) # TODO: restart web server (invoke IInstaller.restart() method) notify.notify("Autodeployed a cert!!!", "root", "It worked!")
def _get_cp_dir(self, temporary): """Return the proper reverter directory.""" if temporary: cp_dir = self.config.temp_checkpoint_dir else: cp_dir = self.config.in_progress_dir le_util.make_or_verify_dir( cp_dir, constants.CONFIG_DIRS_MODE, os.geteuid()) return cp_dir
def __init__(self, config, key, email=None, phone=None, regr=None): le_util.make_or_verify_dir(config.accounts_dir, 0o700, os.geteuid()) self.key = key self.config = config if email is not None and self.safe_email(email): self.email = email else: self.email = None self.phone = phone self.regr = regr
def store_token(self, domain, token): """Store token for later automatic use. :param str domain: domain associated with the token :param str token: token from authorization """ le_util.make_or_verify_dir(self.token_dir, 0o700, os.geteuid()) with open(os.path.join(self.token_dir, domain), "w") as token_fd: token_fd.write(str(token))
def __init__(self, config, key, email=None, phone=None, regr=None): le_util.make_or_verify_dir( config.accounts_dir, 0o700, os.geteuid()) self.key = key self.config = config if email is not None and self.safe_email(email): self.email = email else: self.email = None self.phone = phone self.regr = regr
def __init__(self, installer, config, no_confirm=False): self.network = network.Network(config.server) self.installer = installer self.config = config self.no_confirm = no_confirm le_util.make_or_verify_dir(config.cert_key_backup, 0o700, os.geteuid()) # TODO: Find a better solution for this... self.list_path = os.path.join(config.cert_key_backup, "LIST") # Make sure that the file is available for use for rest of class open(self.list_path, "a").close()
def save(self, account): account_dir_path = self._account_dir_path(account.id) le_util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid()) try: with open(self._regr_path(account_dir_path), "w") as regr_file: regr_file.write(account.regr.json_dumps()) with le_util.safe_open(self._key_path(account_dir_path), "w", chmod=0o400) as key_file: key_file.write(account.key.json_dumps()) with open(self._metadata_path(account_dir_path), "w") as metadata_file: metadata_file.write(account.meta.json_dumps()) except IOError as error: raise errors.AccountStorageError(error)
def save_certificate(self, certr, chain_cert, cert_path, chain_path): # pylint: disable=no-self-use """Saves the certificate received from the ACME server. :param certr: ACME "certificate" resource. :type certr: :class:`acme.messages.Certificate` :param chain_cert: :param str cert_path: Candidate path to a certificate. :param str chain_path: Candidate path to a certificate chain. :returns: cert_path, chain_path (absolute paths to the actual files) :rtype: `tuple` of `str` :raises IOError: If unable to find room to write the cert files """ for path in cert_path, chain_path: le_util.make_or_verify_dir( os.path.dirname(path), 0o755, os.geteuid()) # try finally close cert_chain_abspath = None cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644) # TODO: Except cert_pem = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, certr.body) try: cert_file.write(cert_pem) finally: cert_file.close() logger.info("Server issued certificate; certificate written to %s", act_cert_path) if chain_cert is not None: chain_file, act_chain_path = le_util.unique_file( chain_path, 0o644) # TODO: Except chain_pem = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, chain_cert) try: chain_file.write(chain_pem) finally: chain_file.close() logger.info("Cert chain written to %s", act_chain_path) # This expects a valid chain file cert_chain_abspath = os.path.abspath(act_chain_path) return os.path.abspath(act_cert_path), cert_chain_abspath
def main(cli_args=sys.argv[1:]): """Command line argument parsing and main script execution.""" sys.excepthook = functools.partial(_handle_exception, config=None) plugins = plugins_disco.PluginsRegistry.find_all() # note: arg parser internally handles --help (and exits afterwards) args = cli.prepare_and_parse_args(plugins, cli_args) config = configuration.NamespaceConfig(args) zope.component.provideUtility(config) # Setup logging ASAP, otherwise "No handlers could be found for # logger ..." TODO: this should be done before plugins discovery for directory in config.config_dir, config.work_dir: le_util.make_or_verify_dir(directory, constants.CONFIG_DIRS_MODE, os.geteuid(), "--strict-permissions" in cli_args) # TODO: logs might contain sensitive data such as contents of the # private key! #525 le_util.make_or_verify_dir(config.logs_dir, 0o700, os.geteuid(), "--strict-permissions" in cli_args) setup_logging(config, _cli_log_handler, logfile='letsencrypt.log') logger.debug("letsencrypt version: %s", letsencrypt.__version__) # do not log `config`, as it contains sensitive data (e.g. revoke --key)! logger.debug("Arguments: %r", cli_args) logger.debug("Discovered plugins: %r", plugins) sys.excepthook = functools.partial(_handle_exception, config=config) # Displayer if config.quiet: config.noninteractive_mode = True displayer = display_util.NoninteractiveDisplay(open(os.devnull, "w")) elif config.noninteractive_mode: displayer = display_util.NoninteractiveDisplay(sys.stdout) elif config.text_mode: displayer = display_util.FileDisplay(sys.stdout) elif config.verb == "renew": config.noninteractive_mode = True displayer = display_util.NoninteractiveDisplay(sys.stdout) else: displayer = display_util.NcursesDisplay() zope.component.provideUtility(displayer) # Reporter report = reporter.Reporter(config) zope.component.provideUtility(report) atexit.register(report.atexit_print_messages) return config.func(config, plugins)
def store_cert_key(cls, cert_path, key_path, config): """Store certificate key. (Used to allow quick revocation) :param str cert_path: Path to a certificate file. :param str key_path: Path to authorized key for certificate :ivar config: Configuration. :type config: :class:`~letsencrypt.interfaces.IConfig` """ list_path = os.path.join(config.cert_key_backup, "LIST") le_util.make_or_verify_dir(config.cert_key_backup, 0o700, os.geteuid()) cls._catalog_files(config.cert_key_backup, cert_path, key_path, list_path)
def main(cli_args=sys.argv[1:]): """Command line argument parsing and main script execution.""" sys.excepthook = functools.partial(_handle_exception, config=None) plugins = plugins_disco.PluginsRegistry.find_all() # note: arg parser internally handles --help (and exits afterwards) args = cli.prepare_and_parse_args(plugins, cli_args) config = configuration.NamespaceConfig(args) zope.component.provideUtility(config) # Setup logging ASAP, otherwise "No handlers could be found for # logger ..." TODO: this should be done before plugins discovery for directory in config.config_dir, config.work_dir: le_util.make_or_verify_dir( directory, constants.CONFIG_DIRS_MODE, os.geteuid(), "--strict-permissions" in cli_args) # TODO: logs might contain sensitive data such as contents of the # private key! #525 le_util.make_or_verify_dir( config.logs_dir, 0o700, os.geteuid(), "--strict-permissions" in cli_args) setup_logging(config, _cli_log_handler, logfile='letsencrypt.log') logger.debug("letsencrypt version: %s", letsencrypt.__version__) # do not log `config`, as it contains sensitive data (e.g. revoke --key)! logger.debug("Arguments: %r", cli_args) logger.debug("Discovered plugins: %r", plugins) sys.excepthook = functools.partial(_handle_exception, config=config) # Displayer if config.quiet: config.noninteractive_mode = True displayer = display_util.NoninteractiveDisplay(open(os.devnull, "w")) elif config.noninteractive_mode: displayer = display_util.NoninteractiveDisplay(sys.stdout) elif config.text_mode: displayer = display_util.FileDisplay(sys.stdout) elif config.verb == "renew": config.noninteractive_mode = True displayer = display_util.NoninteractiveDisplay(sys.stdout) else: displayer = display_util.NcursesDisplay() zope.component.provideUtility(displayer) # Reporter report = reporter.Reporter(config) zope.component.provideUtility(report) atexit.register(report.atexit_print_messages) return config.func(config, plugins)
def save_certificate(self, certr, chain_cert, cert_path, chain_path): # pylint: disable=no-self-use """Saves the certificate received from the ACME server. :param certr: ACME "certificate" resource. :type certr: :class:`acme.messages.Certificate` :param list chain_cert: :param str cert_path: Candidate path to a certificate. :param str chain_path: Candidate path to a certificate chain. :returns: cert_path, chain_path (absolute paths to the actual files) :rtype: `tuple` of `str` :raises IOError: If unable to find room to write the cert files """ for path in cert_path, chain_path: le_util.make_or_verify_dir(os.path.dirname(path), 0o755, os.geteuid(), self.config.strict_permissions) # try finally close cert_chain_abspath = None cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644) # TODO: Except cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, certr.body) try: cert_file.write(cert_pem) finally: cert_file.close() logger.info("Server issued certificate; certificate written to %s", act_cert_path) if chain_cert: chain_file, act_chain_path = le_util.unique_file(chain_path, 0o644) # TODO: Except chain_pem = crypto_util.dump_pyopenssl_chain(chain_cert) try: chain_file.write(chain_pem) finally: chain_file.close() logger.info("Cert chain written to %s", act_chain_path) # This expects a valid chain file cert_chain_abspath = os.path.abspath(act_chain_path) return os.path.abspath(act_cert_path), cert_chain_abspath
def __init__(self, installer, config, no_confirm=False): # XXX self.acme = acme_client.Client(directory=None, key=None, alg=None) self.installer = installer self.config = config self.no_confirm = no_confirm le_util.make_or_verify_dir(config.cert_key_backup, 0o700, os.geteuid()) # TODO: Find a better solution for this... self.list_path = os.path.join(config.cert_key_backup, "LIST") # Make sure that the file is available for use for rest of class open(self.list_path, "a").close()
def __init__(self, installer, config, no_confirm=False): # XXX self.acme = acme_client.Client(new_reg_uri=None, key=None, alg=None) self.installer = installer self.config = config self.no_confirm = no_confirm le_util.make_or_verify_dir(config.cert_key_backup, 0o700, os.geteuid()) # TODO: Find a better solution for this... self.list_path = os.path.join(config.cert_key_backup, "LIST") # Make sure that the file is available for use for rest of class open(self.list_path, "a").close()
def _add_to_checkpoint_dir(self, cp_dir, save_files, save_notes): """Add save files to checkpoint directory. :param str cp_dir: Checkpoint directory filepath :param set save_files: set of files to save :param str save_notes: notes about changes made during the save :raises IOError: If unable to open cp_dir + FILEPATHS file :raises letsencrypt.errors.LetsEncryptReverterError: If unable to add checkpoint """ le_util.make_or_verify_dir(cp_dir, constants.CONFIG_DIRS_MODE, os.geteuid()) op_fd, existing_filepaths = self._read_and_append( os.path.join(cp_dir, "FILEPATHS")) idx = len(existing_filepaths) for filename in save_files: # No need to copy/index already existing files # The oldest copy already exists in the directory... if filename not in existing_filepaths: # Tag files with index so multiple files can # have the same filename logging.debug("Creating backup of %s", filename) try: shutil.copy2( filename, os.path.join( cp_dir, os.path.basename(filename) + "_" + str(idx))) op_fd.write(filename + os.linesep) # http://stackoverflow.com/questions/4726260/effective-use-of-python-shutil-copy2 except IOError: op_fd.close() logging.error("Unable to add file %s to checkpoint %s", filename, cp_dir) raise errors.LetsEncryptReverterError( "Unable to add file {0} to checkpoint " "{1}".format(filename, cp_dir)) idx += 1 op_fd.close() with open(os.path.join(cp_dir, "CHANGES_SINCE"), "a") as notes_fd: notes_fd.write(save_notes)
def _account_init(args, config): le_util.make_or_verify_dir(config.config_dir, constants.CONFIG_DIRS_MODE, os.geteuid()) # Prepare for init of Client if args.email is None: return client.determine_account(config) else: try: # The way to get the default would be args.email = "" # First try existing account return account.Account.from_existing_account(config, args.email) except errors.LetsEncryptClientError: try: # Try to make an account based on the email address return account.Account.from_email(config, args.email) except errors.LetsEncryptClientError: return None
def register_file_creation(self, temporary, *files): r"""Register the creation of all files during letsencrypt execution. Call this method before writing to the file to make sure that the file will be cleaned up if the program exits unexpectedly. (Before a save occurs) :param bool temporary: If the file creation registry is for a temp or permanent save. :param \*files: file paths (str) to be registered :raises letsencrypt.errors.LetsEncryptReverterError: If call does not contain necessary parameters or if the file creation is unable to be registered. """ # Make sure some files are provided... as this is an error # Made this mistake in my initial implementation of apache.dvsni.py if not files: raise errors.LetsEncryptReverterError( "Forgot to provide files to registration call") if temporary: cp_dir = self.config.temp_checkpoint_dir else: cp_dir = self.config.in_progress_dir le_util.make_or_verify_dir(cp_dir, constants.CONFIG_DIRS_MODE, os.geteuid()) # Append all new files (that aren't already registered) new_fd = None try: new_fd, ex_files = self._read_and_append( os.path.join(cp_dir, "NEW_FILES")) for path in files: if path not in ex_files: new_fd.write("{0}{1}".format(path, os.linesep)) except (IOError, OSError): logging.error("Unable to register file creation(s) - %s", files) raise errors.LetsEncryptReverterError( "Unable to register file creation(s) - {0}".format(files)) finally: if new_fd is not None: new_fd.close()
def register_file_creation(self, temporary, *files): r"""Register the creation of all files during letsencrypt execution. Call this method before writing to the file to make sure that the file will be cleaned up if the program exits unexpectedly. (Before a save occurs) :param bool temporary: If the file creation registry is for a temp or permanent save. :param \*files: file paths (str) to be registered :raises letsencrypt.errors.ReverterError: If call does not contain necessary parameters or if the file creation is unable to be registered. """ # Make sure some files are provided... as this is an error # Made this mistake in my initial implementation of apache.dvsni.py if not files: raise errors.ReverterError( "Forgot to provide files to registration call") if temporary: cp_dir = self.config.temp_checkpoint_dir else: cp_dir = self.config.in_progress_dir le_util.make_or_verify_dir( cp_dir, constants.CONFIG_DIRS_MODE, os.geteuid()) # Append all new files (that aren't already registered) new_fd = None try: new_fd, ex_files = self._read_and_append( os.path.join(cp_dir, "NEW_FILES")) for path in files: if path not in ex_files: new_fd.write("{0}{1}".format(path, os.linesep)) except (IOError, OSError): logging.error("Unable to register file creation(s) - %s", files) raise errors.ReverterError( "Unable to register file creation(s) - {0}".format(files)) finally: if new_fd is not None: new_fd.close()
def _add_to_checkpoint_dir(self, cp_dir, save_files, save_notes): """Add save files to checkpoint directory. :param str cp_dir: Checkpoint directory filepath :param set save_files: set of files to save :param str save_notes: notes about changes made during the save :raises IOError: if unable to open cp_dir + FILEPATHS file :raises .ReverterError: if unable to add checkpoint """ le_util.make_or_verify_dir( cp_dir, constants.CONFIG_DIRS_MODE, os.geteuid(), self.config.strict_permissions) op_fd, existing_filepaths = self._read_and_append( os.path.join(cp_dir, "FILEPATHS")) idx = len(existing_filepaths) for filename in save_files: # No need to copy/index already existing files # The oldest copy already exists in the directory... if filename not in existing_filepaths: # Tag files with index so multiple files can # have the same filename logger.debug("Creating backup of %s", filename) try: shutil.copy2(filename, os.path.join( cp_dir, os.path.basename(filename) + "_" + str(idx))) op_fd.write(filename + os.linesep) # http://stackoverflow.com/questions/4726260/effective-use-of-python-shutil-copy2 except IOError: op_fd.close() logger.error( "Unable to add file %s to checkpoint %s", filename, cp_dir) raise errors.ReverterError( "Unable to add file {0} to checkpoint " "{1}".format(filename, cp_dir)) idx += 1 op_fd.close() with open(os.path.join(cp_dir, "CHANGES_SINCE"), "a") as notes_fd: notes_fd.write(save_notes)
def _account_init(args, config): le_util.make_or_verify_dir( config.config_dir, constants.CONFIG_DIRS_MODE, os.geteuid()) # Prepare for init of Client if args.email is None: return client.determine_account(config) else: try: # The way to get the default would be args.email = "" # First try existing account return account.Account.from_existing_account(config, args.email) except errors.LetsEncryptClientError: try: # Try to make an account based on the email address return account.Account.from_email(config, args.email) except errors.LetsEncryptClientError: return None
def save_certificate(self, certr, chain_cert, cert_path, chain_path, fullchain_path): """Saves the certificate received from the ACME server. :param certr: ACME "certificate" resource. :type certr: :class:`acme.messages.Certificate` :param list chain_cert: :param str cert_path: Candidate path to a certificate. :param str chain_path: Candidate path to a certificate chain. :param str fullchain_path: Candidate path to a full cert chain. :returns: cert_path, chain_path, and fullchain_path as absolute paths to the actual files :rtype: `tuple` of `str` :raises IOError: If unable to find room to write the cert files """ for path in cert_path, chain_path, fullchain_path: le_util.make_or_verify_dir( os.path.dirname(path), 0o755, os.geteuid(), self.config.strict_permissions) cert_pem = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, certr.body) cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644) try: cert_file.write(cert_pem) finally: cert_file.close() logger.info("Server issued certificate; certificate written to %s", act_cert_path) cert_chain_abspath = None fullchain_abspath = None if chain_cert: chain_pem = crypto_util.dump_pyopenssl_chain(chain_cert) cert_chain_abspath = _save_chain(chain_pem, chain_path) fullchain_abspath = _save_chain(cert_pem + chain_pem, fullchain_path) return os.path.abspath(act_cert_path), cert_chain_abspath, fullchain_abspath
def from_email(cls, config, email): """Generate a new account from an email address. :param config: Configuration :type config: :class:`letsencrypt.interfaces.IConfig` :param str email: Email address :raises .errors.Error: If invalid email address is given. """ if not email or cls.safe_email(email): email = email if email else None le_util.make_or_verify_dir( config.account_keys_dir, 0o700, os.geteuid()) key = crypto_util.init_save_key( config.rsa_key_size, config.account_keys_dir, cls._get_config_filename(email)) return cls(config, key, email) raise errors.Error("Invalid email address.")
def main(cli_args=sys.argv[1:]): """Command line argument parsing and main script execution.""" # note: arg parser internally handles --help (and exits afterwards) plugins = plugins_disco.PluginsRegistry.find_all() args = create_parser(plugins, cli_args).parse_args(cli_args) config = configuration.NamespaceConfig(args) # Setup logging ASAP, otherwise "No handlers could be found for # logger ..." TODO: this should be done before plugins discovery for directory in config.config_dir, config.work_dir: le_util.make_or_verify_dir( directory, constants.CONFIG_DIRS_MODE, os.geteuid()) # TODO: logs might contain sensitive data such as contents of the # private key! #525 le_util.make_or_verify_dir(args.logs_dir, 0o700, os.geteuid()) _setup_logging(args) def handle_exception_common(): """Logs the exception and reraises it if in debug mode.""" logger.debug("Exiting abnormally", exc_info=True) if args.debug: raise try: return main2(cli_args, args, config, plugins) except errors.Error as error: handle_exception_common() return error except KeyboardInterrupt: handle_exception_common() # Ensures a new line is printed return "" except: # pylint: disable=bare-except handle_exception_common() return ("An unexpected error occured. Please see the logfiles in {0} " "for more details.".format(args.logs_dir))
def _verify_setup(self): """Verify the setup to ensure safe operating environment. Make sure that files/directories are setup with appropriate permissions Aim for defensive coding... make sure all input files have permissions of root. """ uid = os.geteuid() le_util.make_or_verify_dir(self.config.work_dir, core_constants.CONFIG_DIRS_MODE, uid) le_util.make_or_verify_dir(self.config.backup_dir, core_constants.CONFIG_DIRS_MODE, uid) le_util.make_or_verify_dir(self.config.config_dir, core_constants.CONFIG_DIRS_MODE, uid)
def _call(self, directory, mode): from letsencrypt.le_util import make_or_verify_dir return make_or_verify_dir(directory, mode, self.uid, strict=True)
def __init__(self, config): self.config = config le_util.make_or_verify_dir(config.accounts_dir, 0o700, os.geteuid(), self.config.strict_permissions)
def __init__(self, config): le_util.make_or_verify_dir(config.accounts_dir, 0o700, os.geteuid()) self.config = config
def __init__(self, config): self.config = config le_util.make_or_verify_dir( config.backup_dir, constants.CONFIG_DIRS_MODE, os.geteuid(), self.config.strict_permissions)
def __init__(self, config): self.config = config le_util.make_or_verify_dir(config.backup_dir, constants.CONFIG_DIRS_MODE, os.geteuid(), self.config.strict_permissions)
def _call(self, directory, mode): from letsencrypt.le_util import make_or_verify_dir return make_or_verify_dir(directory, mode, self.uid)