Exemplo n.º 1
0
    def test_report_renewal_status(self, mock_zope):
        # pylint: disable=protected-access
        cert = mock.MagicMock()
        cert.configuration = configobj.ConfigObj()
        cert.cli_config = configuration.RenewerConfiguration(self.config)

        cert.configuration["autorenew"] = "True"
        cert.configuration["autodeploy"] = "True"
        self.client._report_renewal_status(cert)
        msg = mock_zope().add_message.call_args[0][0]
        self.assertTrue("renewal and deployment has been" in msg)
        self.assertTrue(cert.cli_config.renewal_configs_dir in msg)

        cert.configuration["autorenew"] = "False"
        self.client._report_renewal_status(cert)
        msg = mock_zope().add_message.call_args[0][0]
        self.assertTrue("deployment but not automatic renewal" in msg)
        self.assertTrue(cert.cli_config.renewal_configs_dir in msg)

        cert.configuration["autodeploy"] = "False"
        self.client._report_renewal_status(cert)
        msg = mock_zope().add_message.call_args[0][0]
        self.assertTrue("renewal and deployment has not" in msg)
        self.assertTrue(cert.cli_config.renewal_configs_dir in msg)

        cert.configuration["autorenew"] = "True"
        self.client._report_renewal_status(cert)
        msg = mock_zope().add_message.call_args[0][0]
        self.assertTrue("renewal but not automatic deployment" in msg)
        self.assertTrue(cert.cli_config.renewal_configs_dir in msg)
Exemplo n.º 2
0
    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:`letsencrypt.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.info("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))
Exemplo n.º 3
0
    def setUp(self):
        from letsencrypt 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

        self.defaults = configobj.ConfigObj()
        self.test_rc = storage.RenewableCert(config.filename, self.cli_config)
Exemplo n.º 4
0
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
    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
Exemplo n.º 5
0
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
Exemplo n.º 6
0
def renew_all_lineages(config):
    """Examine each lineage; renew if due and report results"""

    if config.domains != []:
        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):
        print("Processing " + renewal_file)
        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 letsencrypt 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")
Exemplo n.º 7
0
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
Exemplo n.º 8
0
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 renwal
    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):
        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:
        for d in renewal_candidate.names():
            cli.process_domain(config, d)
    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
Exemplo n.º 9
0
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!")
Exemplo n.º 10
0
    def obtain_and_enroll_certificate(self, domains, authenticator, installer,
                                      plugins):
        """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 authenticator: The authenticator to use.
        :type authenticator: :class:`letsencrypt.interfaces.IAuthenticator`

        :param installer: The installer to use.
        :type installer: :class:`letsencrypt.interfaces.IInstaller`

        :param plugins: A PluginsFactory object.

        :returns: A new :class:`letsencrypt.storage.RenewableCert` instance
            referred to the enrolled cert lineage, or False if the cert could
            not be obtained.

        """
        certr, chain, key, _ = self.obtain_certificate(domains)

        # TODO: remove this dirty hack
        self.config.namespace.authenticator = plugins.find_init(
            authenticator).name
        if installer is not None:
            self.config.namespace.installer = plugins.find_init(installer).name

        # XXX: We clearly need a more general and correct way of getting
        # options into the configobj for the RenewableCert instance.
        # This is a quick-and-dirty way to do it to allow integration
        # testing to start.  (Note that the config parameter to new_lineage
        # ideally should be a ConfigObj, but in this case a dict will be
        # accepted in practice.)
        params = vars(self.config.namespace)
        config = {}
        cli_config = configuration.RenewerConfiguration(self.config.namespace)

        if (cli_config.config_dir != constants.CLI_DEFAULTS["config_dir"]
                or cli_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")

        # XXX: just to stop RenewableCert from complaining; this is
        # probably not a good solution
        chain_pem = "" if chain is None else OpenSSL.crypto.dump_certificate(
            OpenSSL.crypto.FILETYPE_PEM, chain)
        lineage = storage.RenewableCert.new_lineage(
            domains[0],
            OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                            certr.body), key.pem, chain_pem,
            params, config, cli_config)
        self._report_renewal_status(lineage)
        return lineage
Exemplo n.º 11
0
def _auth_from_domains(le_client, config, domains, lineage=None):
    """Authenticate and enroll certificate."""
    # Note: This can raise errors... caught above us though. This is now
    # a three-way case: reinstall (which results in a no-op here because
    # although there is a relevant lineage, we don't do anything to it
    # inside this function -- we don't obtain a new certificate), renew
    # (which results in treating the request as a renewal), or newcert
    # (which results in treating the request as a new certificate request).

    # If lineage is specified, use that one instead of looking around for
    # a matching one.
    if lineage is None:
        # This will find a relevant matching lineage that exists
        action, lineage = _treat_as_renewal(config, domains)
    else:
        # Renewal, where we already know the specific lineage we're
        # interested in
        action = "renew"

    if action == "reinstall":
        # The lineage already exists; allow the caller to try installing
        # it without getting a new certificate at all.
        return lineage, "reinstall"
    elif action == "renew":
        original_server = lineage.configuration["renewalparams"]["server"]
        _avoid_invalidating_lineage(config, lineage, original_server)
        # TODO: schoen wishes to reuse key - discussion
        # https://github.com/letsencrypt/letsencrypt/pull/777/files#r40498574
        new_certr, new_chain, new_key, _ = le_client.obtain_certificate(
            domains)
        # TODO: Check whether it worked! <- or make sure errors are thrown (jdk)
        if config.dry_run:
            logger.info("Dry run: skipping updating lineage at %s",
                        os.path.dirname(lineage.cert))
        else:
            lineage.save_successor(
                lineage.latest_common_version(),
                OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                                new_certr.body.wrapped),
                new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain),
                configuration.RenewerConfiguration(config.namespace))
            lineage.update_all_links_to(lineage.latest_common_version())
        # TODO: Check return value of save_successor
        # TODO: Also update lineage renewal config with any relevant
        #       configuration values from this attempt? <- Absolutely (jdkasten)
    elif action == "newcert":
        # TREAT AS NEW REQUEST
        lineage = le_client.obtain_and_enroll_certificate(domains)
        if lineage is False:
            raise errors.Error("Certificate could not be obtained")

    if not config.dry_run and not config.verb == "renew":
        _report_new_cert(lineage.cert, lineage.fullchain)

    return lineage, action
Exemplo n.º 12
0
 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/"])
Exemplo n.º 13
0
def renew_cert(config, domains, le_client, lineage):
    "Renew a certificate lineage."
    original_server = lineage.configuration["renewalparams"]["server"]
    _avoid_invalidating_lineage(config, lineage, original_server)
    new_certr, new_chain, new_key, _ = le_client.obtain_certificate(domains)
    if config.dry_run:
        logger.info("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)
        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, domains, lineage.live_dir)
Exemplo n.º 14
0
    def setUp(self):
        from letsencrypt import storage
        self.tempdir = tempfile.mkdtemp()

        self.cli_config = configuration.RenewerConfiguration(
            namespace=mock.MagicMock(config_dir=self.tempdir))
        # TODO: maybe provide RenewerConfiguration.make_dirs?
        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, "configs"))

        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, "configs",
                                       "example.org.conf")

        self.defaults = configobj.ConfigObj()
        self.test_rc = storage.RenewableCert(config, self.defaults,
                                             self.cli_config)
Exemplo n.º 15
0
    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:`letsencrypt.storage.RenewableCert` instance
            referred to the enrolled cert lineage, or False if the cert could
            not be obtained.

        """
        certr, chain, key, _ = self.obtain_certificate(domains)

        # XXX: We clearly need a more general and correct way of getting
        # options into the configobj for the RenewableCert instance.
        # This is a quick-and-dirty way to do it to allow integration
        # testing to start.  (Note that the config parameter to new_lineage
        # ideally should be a ConfigObj, but in this case a dict will be
        # accepted in practice.)
        params = vars(self.config.namespace)
        config = {}
        cli_config = configuration.RenewerConfiguration(self.config.namespace)

        if (cli_config.config_dir != constants.CLI_DEFAULTS["config_dir"]
                or cli_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")

        lineage = storage.RenewableCert.new_lineage(
            domains[0],
            OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                            certr.body), key.pem,
            crypto_util.dump_pyopenssl_chain(chain), params, config,
            cli_config)
        return lineage
Exemplo n.º 16
0
    def setUp(self):
        from letsencrypt 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(
                "letsencrypt.storage.RenewableCert._check_symlinks") as check:
            check.return_value = True
            self.test_rc = storage.RenewableCert(config.filename,
                                                 self.cli_config)
Exemplo n.º 17
0
def _treat_as_renewal(config, domains):
    """Determine whether or not the call should be treated as a renewal.

    :returns: RenewableCert or None if renewal shouldn't occur.
    :rtype: :class:`.storage.RenewableCert`

    :raises .Error: If the user would like to rerun the client again.

    """
    renewal = False

    # Considering the possibility that the requested certificate is
    # related to an existing certificate.  (config.duplicate, which
    # is set with --duplicate, skips all of this logic and forces any
    # kind of certificate to be obtained with renewal = False.)
    if not config.duplicate:
        ident_names_cert, subset_names_cert = _find_duplicative_certs(
            domains, config, configuration.RenewerConfiguration(config))
        # I am not sure whether that correctly reads the systemwide
        # configuration file.
        question = None
        if ident_names_cert is not None:
            question = (
                "You have an existing certificate that contains exactly the "
                "same domains you requested (ref: {0}){br}{br}Do you want to "
                "renew and replace this certificate with a newly-issued one?"
            ).format(ident_names_cert.configfile.filename, br=os.linesep)
        elif subset_names_cert is not None:
            question = (
                "You have an existing certificate that contains a portion of "
                "the domains you requested (ref: {0}){br}{br}It contains these "
                "names: {1}{br}{br}You requested these names for the new "
                "certificate: {2}.{br}{br}Do you want to replace this existing "
                "certificate with the new certificate?"
            ).format(subset_names_cert.configfile.filename,
                     ", ".join(subset_names_cert.names()),
                     ", ".join(domains),
                     br=os.linesep)
        if question is None:
            # We aren't in a duplicative-names situation at all, so we don't
            # have to tell or ask the user anything about this.
            pass
        elif config.renew_by_default or zope.component.getUtility(
                interfaces.IDisplay).yesno(question, "Replace", "Cancel"):
            renewal = True
        else:
            reporter_util = zope.component.getUtility(interfaces.IReporter)
            reporter_util.add_message(
                "To obtain a new certificate that {0} an existing certificate "
                "in its domain-name coverage, you must use the --duplicate "
                "option.{br}{br}For example:{br}{br}{1} --duplicate {2}".format(
                    "duplicates" if ident_names_cert is not None else
                    "overlaps with",
                    sys.argv[0], " ".join(sys.argv[1:]),
                    br=os.linesep
                ),
                reporter_util.HIGH_PRIORITY)
            raise Error(
                "User did not use proper CLI and would like "
                "to reinvoke the client.")

        if renewal:
            return ident_names_cert if ident_names_cert is not None else subset_names_cert

    return None
Exemplo n.º 18
0
def main(config=None, 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.)

    zope.component.provideUtility(display_util.FileDisplay(sys.stdout))

    cli_config = configuration.RenewerConfiguration(
        _create_parser().parse_args(args))

    config = storage.config_with_defaults(config)
    # Now attempt to read the renewer config file and augment or replace
    # the renewer defaults with any options contained in that file.  If
    # renewer_config_file is undefined or if the file is nonexistent or
    # empty, this .merge() will have no effect.  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 this one.
    config.merge(configobj.ConfigObj(cli_config.renewer_config_file))

    for i in os.listdir(cli_config.renewal_configs_dir):
        print "Processing", i
        if not i.endswith(".conf"):
            continue
        rc_config = configobj.ConfigObj(cli_config.renewer_config_file)
        rc_config.merge(
            configobj.ConfigObj(os.path.join(cli_config.renewal_configs_dir,
                                             i)))
        # TODO: this is a dirty hack!
        rc_config.filename = os.path.join(cli_config.renewal_configs_dir, i)
        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(rc_config, cli_config=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!")
Exemplo n.º 19
0
def run(args, config, plugins):  # pylint: disable=too-many-branches,too-many-locals
    """Obtain a certificate and install."""
    if args.configurator is not None and (args.installer is not None
                                          or args.authenticator is not None):
        return ("Either --configurator or --authenticator/--installer"
                "pair, but not both, is allowed")

    if args.authenticator is not None or args.installer is not None:
        installer = display_ops.pick_installer(config, args.installer, plugins)
        authenticator = display_ops.pick_authenticator(config,
                                                       args.authenticator,
                                                       plugins)
    else:
        # TODO: this assumes that user doesn't want to pick authenticator
        #       and installer separately...
        authenticator = installer = display_ops.pick_configurator(
            config, args.configurator, plugins)

    if installer is None or authenticator is None:
        return "Configurator could not be determined"

    domains = _find_domains(args, installer)

    treat_as_renewal = False

    # Considering the possibility that the requested certificate is
    # related to an existing certificate.  (config.duplicate, which
    # is set with --duplicate, skips all of this logic and forces any
    # kind of certificate to be obtained with treat_as_renewal = False.)
    if not config.duplicate:
        identical_names_cert, subset_names_cert = _find_duplicative_certs(
            domains, config, configuration.RenewerConfiguration(config))
        # I am not sure whether that correctly reads the systemwide
        # configuration file.
        question = None
        if identical_names_cert is not None:
            question = (
                "You have an existing certificate that contains exactly the "
                "same domains you requested (ref: {0})\n\nDo you want to "
                "renew and replace this certificate with a newly-issued one?"
            ).format(identical_names_cert.configfile.filename)
        elif subset_names_cert is not None:
            question = (
                "You have an existing certificate that contains a portion of "
                "the domains you requested (ref: {0})\n\nIt contains these "
                "names: {1}\n\nYou requested these names for the new "
                "certificate: {2}.\n\nDo you want to replace this existing "
                "certificate with the new certificate?").format(
                    subset_names_cert.configfile.filename,
                    ", ".join(subset_names_cert.names()), ", ".join(domains))
        if question is None:
            # We aren't in a duplicative-names situation at all, so we don't
            # have to tell or ask the user anything about this.
            pass
        elif zope.component.getUtility(interfaces.IDisplay).yesno(
                question, "Replace", "Cancel"):
            treat_as_renewal = True
        else:
            reporter_util = zope.component.getUtility(interfaces.IReporter)
            reporter_util.add_message(
                "To obtain a new certificate that {0} an existing certificate "
                "in its domain-name coverage, you must use the --duplicate "
                "option.\n\nFor example:\n\n{1} --duplicate {2}".format(
                    "duplicates" if identical_names_cert is not None else
                    "overlaps with", sys.argv[0], " ".join(sys.argv[1:])),
                reporter_util.HIGH_PRIORITY)
            return 1

    # Attempting to obtain the certificate
    # TODO: Handle errors from _init_le_client?
    le_client = _init_le_client(args, config, authenticator, installer)
    if treat_as_renewal:
        lineage = identical_names_cert if identical_names_cert is not None else subset_names_cert
        # TODO: Use existing privkey instead of generating a new one
        new_certr, new_chain, new_key, _ = le_client.obtain_certificate(
            domains)
        # TODO: Check whether it worked!
        lineage.save_successor(
            lineage.latest_common_version(),
            OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                            new_certr.body), new_key.pem,
            crypto_util.dump_pyopenssl_chain(new_chain))

        lineage.update_all_links_to(lineage.latest_common_version())
        # TODO: Check return value of save_successor
        # TODO: Also update lineage renewal config with any relevant
        #       configuration values from this attempt?
        le_client.deploy_certificate(domains, lineage.privkey, lineage.cert,
                                     lineage.chain)
        display_ops.success_renewal(domains)
    else:
        # TREAT AS NEW REQUEST
        lineage = le_client.obtain_and_enroll_certificate(
            domains, authenticator, installer, plugins)
        if not lineage:
            return "Certificate could not be obtained"
        # TODO: This treats the key as changed even when it wasn't
        # TODO: We also need to pass the fullchain (for Nginx)
        le_client.deploy_certificate(domains, lineage.privkey, lineage.cert,
                                     lineage.chain)
        le_client.enhance_config(domains, args.redirect)
        display_ops.success_installation(domains)