def obtain_and_enroll_certificate(self, domains): """Obtain and enroll certificate. Get a new certificate for the specified domains using the specified authenticator and installer, and then create a new renewable lineage containing it. :param list domains: Domains to request. :param plugins: A PluginsFactory object. :returns: A new :class:`certbot.storage.RenewableCert` instance referred to the enrolled cert lineage, False if the cert could not be obtained, or None if doing a successful dry run. """ certr, chain, key, _ = self.obtain_certificate(domains) if (self.config.config_dir != constants.CLI_DEFAULTS["config_dir"] or self.config.work_dir != constants.CLI_DEFAULTS["work_dir"]): logger.warning( "Non-standard path(s), might not work with crontab installed " "by your operating system package manager") if self.config.dry_run: logger.debug("Dry run: Skipping creating new lineage for %s", domains[0]) return None else: return storage.RenewableCert.new_lineage( domains[0], OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped), key.pem, crypto_util.dump_pyopenssl_chain(chain), configuration.RenewerConfiguration(self.config.namespace))
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 setUp(self): self.tempdir = tempfile.mkdtemp() os.makedirs(os.path.join(self.tempdir, "renewal")) mock_namespace = mock.MagicMock( config_dir=self.tempdir, work_dir=self.tempdir, logs_dir=self.tempdir, ) self.cli_config = configuration.RenewerConfiguration( namespace=mock_namespace) self.domains = { "example.org": None, "other.com": os.path.join(self.tempdir, "specialarchive") } self.configs = dict( (domain, self._set_up_config(domain, self.domains[domain])) for domain in self.domains) # 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()
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.interfaces.IConfig` """ renewer_config = configuration.RenewerConfiguration(config) for renewal_file in renewal.renewal_conf_files(renewer_config): storage.RenewableCert(renewal_file, configuration.RenewerConfiguration(renewer_config), update_symlinks=True)
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, configuration.RenewerConfiguration(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, error.message) 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 setUp(self): super(RenameLineageTest, self).setUp() self.mock_config = configuration.RenewerConfiguration( namespace=mock.MagicMock( config_dir=self.tempdir, work_dir=self.tempdir, logs_dir=self.tempdir, certname="example.org", new_certname="after", ))
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 certificates(config): """Display information about certs configured with Certbot :param config: Configuration. :type config: :class:`certbot.interfaces.IConfig` """ renewer_config = configuration.RenewerConfiguration(config) parsed_certs = [] parse_failures = [] for renewal_file in renewal.renewal_conf_files(renewer_config): try: renewal_candidate = storage.RenewableCert( renewal_file, configuration.RenewerConfiguration(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(parsed_certs, parse_failures)
def rename_lineage(config): """Rename the specified lineage to the new name. :param config: Configuration. :type config: :class:`certbot.interfaces.IConfig` """ disp = zope.component.getUtility(interfaces.IDisplay) renewer_config = configuration.RenewerConfiguration(config) certname = config.certname if not certname: filenames = renewal.renewal_conf_files(renewer_config) choices = [ storage.lineagename_for_filename(name) for name in filenames ] if not choices: raise errors.Error("No existing certificates found.") code, index = disp.menu("Which certificate would you like to rename?", choices, ok_label="Select", flag="--cert-name") if code != display_util.OK or not index in range(0, len(choices)): raise errors.Error("User ended interaction.") certname = choices[index] new_certname = config.new_certname if not new_certname: code, new_certname = disp.input( "Enter the new name for certificate {0}".format(certname), flag="--updated-cert-name") if code != display_util.OK or not new_certname: raise errors.Error("User ended interaction.") lineage = lineage_for_certname(config, certname) if not lineage: raise errors.ConfigurationError("No existing certificate with name " "{0} found.".format(certname)) storage.rename_renewal_config(certname, new_certname, renewer_config) disp.notification("Successfully renamed {0} to {1}.".format( certname, new_certname), pause=False)
def renew_cert(config, le_client, lineage): "Renew a certificate lineage." renewal_params = lineage.configuration["renewalparams"] original_server = renewal_params.get("server", cli.flag_default("server")) _avoid_invalidating_lineage(config, lineage, original_server) new_certr, new_chain, new_key, _ = le_client.obtain_certificate(lineage.names()) if config.dry_run: logger.debug("Dry run: skipping updating lineage at %s", os.path.dirname(lineage.cert)) else: prior_version = lineage.latest_common_version() new_cert = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, new_certr.body.wrapped) new_chain = crypto_util.dump_pyopenssl_chain(new_chain) renewal_conf = configuration.RenewerConfiguration(config.namespace) # TODO: Check return value of save_successor lineage.save_successor(prior_version, new_cert, new_key.pem, new_chain, renewal_conf) lineage.update_all_links_to(lineage.latest_common_version()) hooks.renew_hook(config, lineage.names(), lineage.live_dir)
def setUp(self): from certbot import storage self.tempdir = tempfile.mkdtemp() self.cli_config = configuration.RenewerConfiguration( namespace=mock.MagicMock( config_dir=self.tempdir, work_dir=self.tempdir, logs_dir=self.tempdir, )) # TODO: maybe provide RenewerConfiguration.make_dirs? # TODO: main() should create those dirs, c.f. #902 os.makedirs(os.path.join(self.tempdir, "live", "example.org")) os.makedirs(os.path.join(self.tempdir, "archive", "example.org")) os.makedirs(os.path.join(self.tempdir, "renewal")) config = configobj.ConfigObj() for kind in ALL_FOUR: config[kind] = os.path.join(self.tempdir, "live", "example.org", kind + ".pem") 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 _search_lineages(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. """ cli_config = configuration.RenewerConfiguration(config) 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 renewal.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 renew_all_lineages(config): """Examine each lineage; renew if due and report results""" # This is trivially False if config.domains is empty if any(domain not in config.webroot_map for domain in config.domains): # If more plugins start using cli.add_domains, # we may want to only log a warning here raise errors.Error( "Currently, the renew verb is only capable of " "renewing all installed certificates that are due " "to be renewed; individual domains cannot be " "specified with this action. If you would like to " "renew specific certificates, use the certonly " "command. The renew verb may provide other options " "for selecting certificates to renew in the future.") renewer_config = configuration.RenewerConfiguration(config) renew_successes = [] renew_failures = [] renew_skipped = [] parse_failures = [] for renewal_file in renewal_conf_files(renewer_config): disp = zope.component.getUtility(interfaces.IDisplay) disp.notification("Processing " + renewal_file, pause=False) lineage_config = copy.deepcopy(config) # Note that this modifies config (to add back the configuration # elements from within the renewal configuration file). try: renewal_candidate = _reconstitute(lineage_config, renewal_file) 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) continue try: if renewal_candidate is None: parse_failures.append(renewal_file) else: # XXX: ensure that each call here replaces the previous one zope.component.provideUtility(lineage_config) if should_renew(lineage_config, renewal_candidate): plugins = plugins_disco.PluginsRegistry.find_all() from certbot import main main.obtain_cert(lineage_config, plugins, renewal_candidate) renew_successes.append(renewal_candidate.fullchain) else: renew_skipped.append(renewal_candidate.fullchain) except Exception as e: # pylint: disable=broad-except # obtain_cert (presumably) encountered an unanticipated problem. logger.warning( "Attempting to renew cert from %s produced an " "unexpected error: %s. Skipping.", renewal_file, e) logger.debug("Traceback was:\n%s", traceback.format_exc()) renew_failures.append(renewal_candidate.fullchain) # Describe all the results _renew_describe_results(config, renew_successes, renew_failures, renew_skipped, parse_failures) if renew_failures or parse_failures: raise errors.Error("{0} renew failure(s), {1} parse failure(s)".format( len(renew_failures), len(parse_failures))) else: logger.debug("no renewal failures")