Exemple #1
0
    def _get_runtime_cfg(self, ctl):  # pylint: disable=no-self-use
        """Get runtime configuration info.

        :returns: stdout from DUMP_RUN_CFG

        """
        try:
            proc = subprocess.Popen([ctl, "-D", "DUMP_RUN_CFG"],
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE)
            stdout, stderr = proc.communicate()

        except (OSError, ValueError):
            logger.error("Error accessing %s for runtime parameters!%s", ctl,
                         os.linesep)
            raise errors.MisconfigurationError(
                "Error accessing loaded Apache parameters: %s", ctl)
        # Small errors that do not impede
        if proc.returncode != 0:
            logger.warn("Error in checking parameter list: %s", stderr)
            raise errors.MisconfigurationError(
                "Apache is unable to check whether or not the module is "
                "loaded because Apache is misconfigured.")

        return stdout
Exemple #2
0
def nginx_restart(nginx_ctl, nginx_conf="/etc/nginx.conf"):
    """Restarts the Nginx Server.

    .. todo:: Nginx restart is fatal if the configuration references
        non-existent SSL cert/key files. Remove references to /etc/letsencrypt
        before restart.

    :param str nginx_ctl: Path to the Nginx binary.

    """
    try:
        proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf, "-s", "reload"],
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        stdout, stderr = proc.communicate()

        if proc.returncode != 0:
            # Maybe Nginx isn't running
            nginx_proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf],
                                          stdout=subprocess.PIPE,
                                          stderr=subprocess.PIPE)
            stdout, stderr = nginx_proc.communicate()

            if nginx_proc.returncode != 0:
                # Enter recovery routine...
                raise errors.MisconfigurationError(
                    "nginx restart failed:\n%s\n%s" % (stdout, stderr))

    except (OSError, ValueError):
        raise errors.MisconfigurationError("nginx restart failed")
    # Nginx can take a moment to recognize a newly added TLS SNI servername, so sleep
    # for a second. TODO: Check for expected servername and loop until it
    # appears or return an error if looping too long.
    time.sleep(1)
Exemple #3
0
    def mod_loaded(self, module):
        """Checks to see if mod_ssl is loaded

        Uses ``apache_ctl`` to get loaded module list. This also effectively
        serves as a config_test.

        :returns: If ssl_module is included and active in Apache
        :rtype: bool

        """
        try:
            proc = subprocess.Popen([self.conf("ctl"), "-M"],
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE)
            stdout, stderr = proc.communicate()

        except (OSError, ValueError):
            logger.error("Error accessing %s for loaded modules!",
                         self.conf("ctl"))
            raise errors.MisconfigurationError(
                "Error accessing loaded modules")
        # Small errors that do not impede
        if proc.returncode != 0:
            logger.warn("Error in checking loaded module list: %s", stderr)
            raise errors.MisconfigurationError(
                "Apache is unable to check whether or not the module is "
                "loaded because Apache is misconfigured.")

        if module in stdout:
            return True
        return False
Exemple #4
0
    def perform(self, achalls):  # pylint: disable=missing-docstring
        if any(util.already_listening(port) for port in
               (self.config.dvsni_port, self.config.simple_http_port)):
            raise errors.MisconfigurationError(
                "At least one of the (possibly) required ports is "
                "already taken.")

        try:
            return self.perform2(achalls)
        except errors.StandaloneBindError as error:
            display = zope.component.getUtility(interfaces.IDisplay)

            if error.socket_error.errno == socket.errno.EACCES:
                display.notification(
                    "Could not bind TCP port {0} because you don't have "
                    "the appropriate permissions (for example, you "
                    "aren't running this program as "
                    "root).".format(error.port))
            elif error.socket_error.errno == socket.errno.EADDRINUSE:
                display.notification(
                    "Could not bind TCP port {0} because it is already in "
                    "use by another process on this system (such as a web "
                    "server). Please stop the program in question and then "
                    "try again.".format(error.port))
            else:
                raise  # XXX: How to handle unknown errors in binding?
Exemple #5
0
    def get_all_certs_keys(self):
        """Find all existing keys, certs from configuration.

        Retrieve all certs and keys set in VirtualHosts on the Apache server

        :returns: list of tuples with form [(cert, key, path)]
            cert - str path to certificate file
            key - str path to associated key file
            path - File path to configuration file.
        :rtype: list

        """
        c_k = set()

        for vhost in self.vhosts:
            if vhost.ssl:
                cert_path = self.parser.find_dir(
                    parser.case_i("SSLCertificateFile"), None, vhost.path)
                key_path = self.parser.find_dir(
                    parser.case_i("SSLCertificateKeyFile"), None, vhost.path)

                # Can be removed once find directive can return ordered results
                if len(cert_path) != 1 or len(key_path) != 1:
                    logger.error("Too many cert or key directives in vhost %s",
                                 vhost.filep)
                    errors.MisconfigurationError(
                        "Too many cert/key directives in vhost")

                cert = os.path.abspath(self.aug.get(cert_path[0]))
                key = os.path.abspath(self.aug.get(key_path[0]))
                c_k.add((cert, key, get_file_path(cert_path[0])))

        return c_k
Exemple #6
0
def apache_restart(apache_init_script):
    """Restarts the Apache Server.

    :param str apache_init_script: Path to the Apache init script.

    .. todo:: Try to use reload instead. (This caused timing problems before)

    .. todo:: On failure, this should be a recovery_routine call with another
       restart.  This will confuse and inhibit developers from testing code
       though.  This change should happen after
       the ApacheConfigurator has been thoroughly tested.  The function will
       need to be moved into the class again.  Perhaps
       this version can live on... for testing purposes.

    """
    try:
        proc = subprocess.Popen([apache_init_script, "restart"],
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        stdout, stderr = proc.communicate()

        if proc.returncode != 0:
            # Enter recovery routine...
            logger.error("Apache Restart Failed!\n%s\n%s", stdout, stderr)
            return False

    except (OSError, ValueError):
        logger.fatal("Apache Restart Failed - Please Check the Configuration")
        raise errors.MisconfigurationError("Unable to restart Apache process")

    return True
Exemple #7
0
def _add_directives(block, directives, replace=False):
    """Adds or replaces directives in a block. If the directive doesn't exist in
    the entry already, raises a misconfiguration error.

    ..todo :: Find directives that are in included files.

    :param list block: The block to replace in
    :param list directives: The new directives.

    """
    if replace:
        for directive in directives:
            changed = False
            if len(directive) == 0:
                continue
            for index, line in enumerate(block):
                if len(line) > 0 and line[0] == directive[0]:
                    block[index] = directive
                    changed = True
            if not changed:
                raise errors.MisconfigurationError(
                    'LetsEncrypt expected directive for %s in the Nginx '
                    'config but did not find it.' % directive[0])
    else:
        block.extend(directives)
Exemple #8
0
def _add_directives(block, directives, replace=False):
    """Adds or replaces directives in a block. If the directive doesn't exist in
    the entry already, raises a misconfiguration error.

    ..todo :: Find directives that are in included files.

    :param list block: The block to replace in
    :param list directives: The new directives.

    """
    for directive in directives:
        if not replace:
            # We insert new directives at the top of the block, mostly
            # to work around https://trac.nginx.org/nginx/ticket/810
            # Only add directive if its not already in the block
            if directive not in block:
                block.insert(0, directive)
        else:
            changed = False
            if len(directive) == 0:
                continue
            for index, line in enumerate(block):
                if len(line) > 0 and line[0] == directive[0]:
                    block[index] = directive
                    changed = True
            if not changed:
                raise errors.MisconfigurationError(
                    'Let\'s Encrypt expected directive for %s in the Nginx '
                    'config but did not find it.' % directive[0])
Exemple #9
0
    def config_test(self):  # pylint: disable=no-self-use
        """Check the configuration of Nginx for errors.

        :raises .errors.MisconfigurationError: If config_test fails

        """
        try:
            le_util.run_script([self.conf('ctl'), "-c", self.nginx_conf, "-t"])
        except errors.SubprocessError as err:
            raise errors.MisconfigurationError(str(err))
Exemple #10
0
    def config_test(self):  # pylint: disable=no-self-use
        """Check the configuration of Apache for errors.

        :raises .errors.MisconfigurationError: If config_test fails

        """
        try:
            le_util.run_script([self.conf("ctl"), "configtest"])
        except errors.SubprocessError as err:
            raise errors.MisconfigurationError(str(err))
Exemple #11
0
    def _enable_mod_debian(self, mod_name, temp):
        """Assumes mods-available, mods-enabled layout."""
        # Generate reversal command.
        # Try to be safe here... check that we can probably reverse before
        # applying enmod command
        if not le_util.exe_exists(self.conf("dismod")):
            raise errors.MisconfigurationError(
                "Unable to find a2dismod, please make sure a2enmod and "
                "a2dismod are configured correctly for letsencrypt.")

        self.reverter.register_undo_command(
            temp, [self.conf("dismod"), mod_name])
        le_util.run_script([self.conf("enmod"), mod_name])
def _add_directive(block, directive, replace):
    """Adds or replaces a single directive in a config block.

    See _add_directives for more documentation.

    """
    location = -1
    # Find the index of a config line where the name of the directive matches
    # the name of the directive we want to add.
    for index, line in enumerate(block):
        if len(line) > 0 and line[0] == directive[0]:
            location = index
            break
    if replace:
        if location == -1:
            raise errors.MisconfigurationError(
                'expected directive for %s in the Nginx '
                'config but did not find it.' % directive[0])
        block[location] = directive
    else:
        # Append directive. Fail if the name is not a repeatable directive name,
        # and there is already a copy of that directive with a different value
        # in the config file.
        directive_name = directive[0]
        directive_value = directive[1]
        if location != -1 and directive_name.__str__(
        ) not in repeatable_directives:
            if block[location][1] == directive_value:
                # There's a conflict, but the existing value matches the one we
                # want to insert, so it's fine.
                pass
            else:
                raise errors.MisconfigurationError(
                    'tried to insert directive "%s" but found conflicting "%s".'
                    % (directive, block[location]))
        else:
            block.append(directive)
    def _mod_config(self, ll_addrs):
        """Modifies Nginx config to include challenge server blocks.

        :param list ll_addrs: list of lists of
            :class:`letsencrypt_nginx.obj.Addr` to apply

        :raises .MisconfigurationError:
            Unable to find a suitable HTTP block in which to include
            authenticator hosts.

        """
        # Add the 'include' statement for the challenges if it doesn't exist
        # already in the main config
        included = False
        include_directive = ['include', self.challenge_conf]
        root = self.configurator.parser.loc["root"]

        bucket_directive = ['server_names_hash_bucket_size', '128']

        main = self.configurator.parser.parsed[root]
        for key, body in main:
            if key == ['http']:
                found_bucket = False
                for k, _ in body:
                    if k == bucket_directive[0]:
                        found_bucket = True
                if not found_bucket:
                    body.insert(0, bucket_directive)
                if include_directive not in body:
                    body.insert(0, include_directive)
                included = True
                break
        if not included:
            raise errors.MisconfigurationError(
                'LetsEncrypt could not find an HTTP block to include '
                'TLS-SNI-01 challenges in %s.' % root)

        config = [
            self._make_server_block(pair[0], pair[1])
            for pair in itertools.izip(self.achalls, ll_addrs)
        ]

        self.configurator.reverter.register_file_creation(
            True, self.challenge_conf)

        with open(self.challenge_conf, "w") as new_conf:
            nginxparser.dump(config, new_conf)
Exemple #14
0
    def enable_mod(self, mod_name):
        """Enables module in Apache.

        Both enables and restarts Apache so module is active.

        :param str mod_name: Name of the module to enable.

        """
        try:
            # Use check_output so the command will finish before reloading
            # TODO: a2enmod is debian specific...
            subprocess.check_call([self.conf("enmod"), mod_name],
                                  stdout=open("/dev/null", "w"),
                                  stderr=open("/dev/null", "w"))
            apache_restart(self.conf("init-script"))
        except (OSError, subprocess.CalledProcessError):
            logger.exception("Error enabling mod_%s", mod_name)
            raise errors.MisconfigurationError(
                "Missing enable_mod binary or lack privileges")
Exemple #15
0
    def _mod_config(self, ll_addrs):
        """Modifies Nginx config to include challenge server blocks.

        :param list ll_addrs: list of lists of
            :class:`letsencrypt_nginx.obj.Addr` to apply

        :raises .MisconfigurationError:
            Unable to find a suitable HTTP block to include DVSNI hosts.

        """
        # Add the 'include' statement for the challenges if it doesn't exist
        # already in the main config
        included = False
        directive = ['include', self.challenge_conf]
        root = self.configurator.parser.loc["root"]
        main = self.configurator.parser.parsed[root]
        for entry in main:
            if entry[0] == ['http']:
                body = entry[1]
                if directive not in body:
                    body.append(directive)
                included = True
                break
        if not included:
            raise errors.MisconfigurationError(
                'LetsEncrypt could not find an HTTP block to include DVSNI '
                'challenges in %s.' % root)

        config = [
            self._make_server_block(pair[0], pair[1])
            for pair in itertools.izip(self.achalls, ll_addrs)
        ]

        self.configurator.reverter.register_file_creation(
            True, self.challenge_conf)

        with open(self.challenge_conf, "w") as new_conf:
            nginxparser.dump(config, new_conf)
    def add_server_directives(self, filename, names, directives, replace):
        """Add or replace directives in the first server block with names.

        ..note :: If replace is True, this raises a misconfiguration error
        if the directive does not already exist.
        ..note :: If replace is False nothing gets added if an identical
        block exists already.

        ..todo :: Doesn't match server blocks whose server_name directives are
            split across multiple conf files.

        :param str filename: The absolute filename of the config file
        :param set names: The server_name to match
        :param list directives: The directives to add
        :param bool replace: Whether to only replace existing directives

        """
        try:
            _do_for_subarray(self.parsed[filename],
                             lambda x: self._has_server_names(x, names),
                             lambda x: _add_directives(x, directives, replace))
        except errors.MisconfigurationError as err:
            raise errors.MisconfigurationError("Problem in %s: %s" %
                                               (filename, err.message))