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 _search_lineages(cli_config, func, initial_rv, *args): """Iterate func over unbroken lineages, allowing custom return conditions. Allows flexible customization of return values, including multiple return values and complex checks. :param `configuration.NamespaceConfig` cli_config: parsed command line arguments :param function func: function used while searching over lineages :param initial_rv: initial return value of the function (any type) :returns: Whatever was specified by `func` if a match is found. """ configs_dir = cli_config.renewal_configs_dir # Verify the directory is there util.make_or_verify_dir(configs_dir, mode=0o755) rv = initial_rv for renewal_file in storage.renewal_conf_files(cli_config): try: candidate_lineage = storage.RenewableCert(renewal_file, cli_config) except (errors.CertStorageError, IOError): logger.debug("Renewal conf file %s is broken. Skipping.", renewal_file) logger.debug("Traceback was:\n%s", traceback.format_exc()) continue rv = func(candidate_lineage, rv, *args) return rv
def _reconstitute(config, full_path): """Try to instantiate a RenewableCert, updating config with relevant items. This is specifically for use in renewal and enforces several checks and policies to ensure that we can try to proceed with the renewal request. The config argument is modified by including relevant options read from the renewal configuration file. :param configuration.NamespaceConfig config: configuration for the current lineage :param str full_path: Absolute path to the configuration file that defines this lineage :returns: the RenewableCert object or None if a fatal error occurred :rtype: `storage.RenewableCert` or NoneType """ try: renewal_candidate = storage.RenewableCert(full_path, config) except (errors.CertStorageError, IOError) as exc: logger.warning(exc) logger.warning("Renewal configuration file %s is broken. Skipping.", full_path) logger.debug("Traceback was:\n%s", traceback.format_exc()) return None if "renewalparams" not in renewal_candidate.configuration: logger.warning( "Renewal configuration file %s lacks " "renewalparams. Skipping.", full_path) return None renewalparams = renewal_candidate.configuration["renewalparams"] if "authenticator" not in renewalparams: logger.warning( "Renewal configuration file %s does not specify " "an authenticator. Skipping.", full_path) return None # Now restore specific values along with their data types, if # those elements are present. try: restore_required_config_elements(config, renewalparams) _restore_plugin_configs(config, renewalparams) except (ValueError, errors.Error) as error: logger.warning( "An error occurred while parsing %s. The error was %s. " "Skipping the file.", full_path, str(error)) logger.debug("Traceback was:\n%s", traceback.format_exc()) return None try: config.domains = [ util.enforce_domain_sanity(d) for d in renewal_candidate.names() ] except errors.ConfigurationError as error: logger.warning( "Renewal configuration file %s references a cert " "that contains an invalid domain name. The problem " "was: %s. Skipping.", full_path, error) return None return renewal_candidate
def test_no_renewal_version(self): from certbot import storage self._write_out_ex_kinds() self.assertTrue("version" not in self.config_file) with mock.patch("certbot.storage.logger") as mock_logger: storage.RenewableCert(self.config_file.filename, self.config) self.assertFalse(mock_logger.warning.called)
def test_ancient_webroot_renewal_conf(self, mock_set_by_cli): mock_set_by_cli.return_value = False rc_path = self._make_test_renewal_conf('sample-renewal-ancient.conf') args = mock.MagicMock(account=None, email=None, webroot_path=None) config = configuration.NamespaceConfig(args) lineage = storage.RenewableCert( rc_path, configuration.RenewerConfiguration(config)) renewalparams = lineage.configuration["renewalparams"] # pylint: disable=protected-access renewal._restore_webroot_config(config, renewalparams) self.assertEqual(config.webroot_path, ["/var/www/"])
def test_renewal_newer_version(self): from certbot import storage self._write_out_ex_kinds() self.config_file["version"] = "99.99.99" self.config_file.write() with mock.patch("certbot.storage.logger") as mock_logger: storage.RenewableCert(self.config_file.filename, self.config) self.assertTrue(mock_logger.info.called) self.assertTrue("version" in mock_logger.info.call_args[0][0])
def test_ancient_webroot_renewal_conf(self, mock_set_by_cli): mock_set_by_cli.return_value = False rc_path = util.make_lineage(self, 'sample-renewal-ancient.conf') args = mock.MagicMock(account=None, email=None, webroot_path=None) config = configuration.NamespaceConfig(args) lineage = storage.RenewableCert(rc_path, config) renewalparams = lineage.configuration['renewalparams'] # pylint: disable=protected-access from certbot import renewal renewal._restore_webroot_config(config, renewalparams) self.assertEqual(config.webroot_path, ['/var/www/'])
def lineage_for_certname(cli_config, certname): """Find a lineage object with name certname.""" configs_dir = cli_config.renewal_configs_dir # Verify the directory is there util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid()) renewal_file = storage.renewal_file_for_certname(cli_config, certname) try: return storage.RenewableCert(renewal_file, cli_config) except (errors.CertStorageError, IOError): logger.debug("Renewal conf file %s is broken.", renewal_file) logger.debug("Traceback was:\n%s", traceback.format_exc()) return None
def update_live_symlinks(config): """Update the certificate file family symlinks to use archive_dir. Use the information in the config file to make symlinks point to the correct archive directory. .. note:: This assumes that the installation is using a Reverter object. :param config: Configuration. :type config: :class:`certbot.configuration.NamespaceConfig` """ for renewal_file in storage.renewal_conf_files(config): storage.RenewableCert(renewal_file, config, update_symlinks=True)
def test_update_symlinks(self): from certbot import storage archive_dir_path = os.path.join(self.config.config_dir, "archive", "example.org") for kind in ALL_FOUR: live_path = self.config_file[kind] basename = kind + "1.pem" archive_path = os.path.join(archive_dir_path, basename) open(archive_path, 'a').close() os.symlink(os.path.join(self.config.config_dir, basename), live_path) self.assertRaises(errors.CertStorageError, storage.RenewableCert, self.config_file.filename, self.config) storage.RenewableCert(self.config_file.filename, self.config, update_symlinks=True)
def setUp(self): from certbot import storage super(BaseRenewableCertTest, self).setUp() self.cli_config = configuration.NamespaceConfig( namespace=mock.MagicMock( config_dir=self.tempdir, work_dir=self.tempdir, logs_dir=self.tempdir, )) # TODO: maybe provide NamespaceConfig.make_dirs? # TODO: main() should create those dirs, c.f. #902 os.makedirs(os.path.join(self.tempdir, "live", "example.org")) archive_path = os.path.join(self.tempdir, "archive", "example.org") os.makedirs(archive_path) os.makedirs(os.path.join(self.tempdir, "renewal")) config = configobj.ConfigObj() for kind in ALL_FOUR: kind_path = os.path.join(self.tempdir, "live", "example.org", kind + ".pem") config[kind] = kind_path with open(os.path.join(self.tempdir, "live", "example.org", "README"), 'a'): pass config["archive"] = archive_path config.filename = os.path.join(self.tempdir, "renewal", "example.org.conf") config.write() self.config = config # We also create a file that isn't a renewal config in the same # location to test that logic that reads in all-and-only renewal # configs will ignore it and NOT attempt to parse it. junk = open(os.path.join(self.tempdir, "renewal", "IGNORE.THIS"), "w") junk.write("This file should be ignored!") junk.close() self.defaults = configobj.ConfigObj() with mock.patch( "certbot.storage.RenewableCert._check_symlinks") as check: check.return_value = True self.test_rc = storage.RenewableCert(config.filename, self.cli_config)
def certificates(config): """Display information about certs configured with Certbot :param config: Configuration. :type config: :class:`certbot.configuration.NamespaceConfig` """ parsed_certs = [] parse_failures = [] for renewal_file in storage.renewal_conf_files(config): try: renewal_candidate = storage.RenewableCert(renewal_file, config) parsed_certs.append(renewal_candidate) except Exception as e: # pylint: disable=broad-except logger.warning("Renewal configuration file %s produced an " "unexpected error: %s. Skipping.", renewal_file, e) logger.debug("Traceback was:\n%s", traceback.format_exc()) parse_failures.append(renewal_file) # Describe all the certs _describe_certs(config, parsed_certs, parse_failures)
def _search_lineages(cli_config, func, initial_rv): """Iterate func over unbroken lineages, allowing custom return conditions. Allows flexible customization of return values, including multiple return values and complex checks. """ configs_dir = cli_config.renewal_configs_dir # Verify the directory is there util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid()) rv = initial_rv for renewal_file in storage.renewal_conf_files(cli_config): try: candidate_lineage = storage.RenewableCert(renewal_file, cli_config) except (errors.CertStorageError, IOError): logger.debug("Renewal conf file %s is broken. Skipping.", renewal_file) logger.debug("Traceback was:\n%s", traceback.format_exc()) continue rv = func(candidate_lineage, rv) return rv
def _delete_if_appropriate(config): # pylint: disable=too-many-locals,too-many-branches """Does the user want to delete their now-revoked certs? If run in non-interactive mode, deleting happens automatically, unless if both `--cert-name` and `--cert-path` were specified with conflicting values. :param config: parsed command line arguments :type config: interfaces.IConfig :returns: `None` :rtype: None :raises errors.Error: If anything goes wrong, including bad user input, if an overlapping archive dir is found for the specified lineage, etc ... """ display = zope.component.getUtility(interfaces.IDisplay) reporter_util = zope.component.getUtility(interfaces.IReporter) attempt_deletion = config.delete_after_revoke if attempt_deletion is None: msg = ("Would you like to delete the cert(s) you just revoked?") attempt_deletion = display.yesno(msg, yes_label="Yes (recommended)", no_label="No", force_interactive=True, default=True) if not attempt_deletion: reporter_util.add_message("Not deleting revoked certs.", reporter_util.LOW_PRIORITY) return if not (config.certname or config.cert_path): raise errors.Error('At least one of --cert-path or --cert-name must be specified.') if config.certname and config.cert_path: # first, check if certname and cert_path imply the same certs implied_cert_name = cert_manager.cert_path_to_lineage(config) if implied_cert_name != config.certname: cert_path_implied_cert_name = cert_manager.cert_path_to_lineage(config) cert_path_implied_conf = storage.renewal_file_for_certname(config, cert_path_implied_cert_name) cert_path_cert = storage.RenewableCert(cert_path_implied_conf, config) cert_path_info = cert_manager.human_readable_cert_info(config, cert_path_cert, skip_filter_checks=True) cert_name_implied_conf = storage.renewal_file_for_certname(config, config.certname) cert_name_cert = storage.RenewableCert(cert_name_implied_conf, config) cert_name_info = cert_manager.human_readable_cert_info(config, cert_name_cert) msg = ("You specified conflicting values for --cert-path and --cert-name. " "Which did you mean to select?") choices = [cert_path_info, cert_name_info] try: code, index = display.menu(msg, choices, ok_label="Select", force_interactive=True) except errors.MissingCommandlineFlag: error_msg = ('To run in non-interactive mode, you must either specify only one of ' '--cert-path or --cert-name, or both must point to the same certificate lineages.') raise errors.Error(error_msg) if code != display_util.OK or not index in range(0, len(choices)): raise errors.Error("User ended interaction.") if index == 0: config.certname = cert_path_implied_cert_name else: config.cert_path = storage.cert_path_for_cert_name(config, config.certname) elif config.cert_path: config.certname = cert_manager.cert_path_to_lineage(config) else: # if only config.certname was specified config.cert_path = storage.cert_path_for_cert_name(config, config.certname) # don't delete if the archive_dir is used by some other lineage archive_dir = storage.full_archive_path( configobj.ConfigObj(storage.renewal_file_for_certname(config, config.certname)), config, config.certname) try: cert_manager.match_and_check_overlaps(config, [lambda x: archive_dir], lambda x: x.archive_dir, lambda x: x) except errors.OverlappingMatchFound: msg = ('Not deleting revoked certs due to overlapping archive dirs. More than ' 'one lineage is using {0}'.format(archive_dir)) reporter_util.add_message(''.join(msg), reporter_util.MEDIUM_PRIORITY) return except Exception as e: msg = ('config.default_archive_dir: {0}, config.live_dir: {1}, archive_dir: {2},' 'original exception: {3}') msg = msg.format(config.default_archive_dir, config.live_dir, archive_dir, e) raise errors.Error(msg) cert_manager.delete(config)