diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt/client/plugins/nginx/configurator.py index bb1bb8a34..51275a6ee 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt/client/plugins/nginx/configurator.py @@ -68,7 +68,7 @@ class NginxConfigurator(object): self.parser = None self.version = version self.vhosts = None - self._enhance_func = {"redirect": self._enable_redirect} + self._enhance_func = {} # TODO: Support at least redirects def prepare(self): """Prepare the authenticator/installer.""" @@ -345,7 +345,7 @@ class NginxConfigurator(object): def supported_enhancements(self): # pylint: disable=no-self-use """Returns currently supported enhancements.""" - return ["redirect"] + return [] def enhance(self, domain, enhancement, options=None): """Enhance configuration. @@ -367,270 +367,6 @@ class NginxConfigurator(object): except errors.LetsEncryptConfiguratorError: logging.warn("Failed %s for %s", enhancement, domain) - def _enable_redirect(self, ssl_vhost, unused_options): - """Redirect all equivalent HTTP traffic to ssl_vhost. - - .. todo:: This enhancement should be rewritten and will - unfortunately require lots of debugging by hand. - - Adds Redirect directive to the port 80 equivalent of ssl_vhost - First the function attempts to find the vhost with equivalent - ip addresses that serves on non-ssl ports - The function then adds the directive - - .. note:: This function saves the configuration - - :param ssl_vhost: Destination of traffic, an ssl enabled vhost - :type ssl_vhost: - :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost` - - :param unused_options: Not currently used - :type unused_options: Not Available - - :returns: Success, general_vhost (HTTP vhost) - :rtype: (bool, - :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost`) - - """ - if not mod_loaded("rewrite_module", self.config.nginx_ctl): - enable_mod("rewrite", self.config.nginx_init_script, - self.config.nginx_enmod) - - general_v = self._general_vhost(ssl_vhost) - if general_v is None: - # Add virtual_server with redirect - logging.debug( - "Did not find http version of ssl virtual host... creating") - return self._create_redirect_vhost(ssl_vhost) - else: - # Check if redirection already exists - exists, code = self._existing_redirect(general_v) - if exists: - if code == 0: - logging.debug("Redirect already added") - logging.info( - "Configuration is already redirecting traffic to HTTPS") - return - else: - logging.info("Unknown redirect exists for this vhost") - raise errors.LetsEncryptConfiguratorError( - "Unknown redirect already exists " - "in {}".format(general_v.filep)) - # Add directives to server - self.parser.add_dir(general_v.path, "RewriteEngine", "On") - self.parser.add_dir(general_v.path, "RewriteRule", - constants.APACHE_REWRITE_HTTPS_ARGS) - self.save_notes += ("Redirecting host in %s to ssl vhost in %s\n" % - (general_v.filep, ssl_vhost.filep)) - self.save() - - logging.info("Redirecting vhost in %s to ssl vhost in %s", - general_v.filep, ssl_vhost.filep) - - def _existing_redirect(self, vhost): - """Checks to see if existing redirect is in place. - - Checks to see if virtualhost already contains a rewrite or redirect - returns boolean, integer - The boolean indicates whether the redirection exists... - The integer has the following code: - 0 - Existing letsencrypt https rewrite rule is appropriate and in place - 1 - Virtual host contains a Redirect directive - 2 - Virtual host contains an unknown RewriteRule - - -1 is also returned in case of no redirection/rewrite directives - - :param vhost: vhost to check - :type vhost: :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost` - - :returns: Success, code value... see documentation - :rtype: bool, int - - """ - rewrite_path = self.parser.find_dir( - parser.case_i("RewriteRule"), None, vhost.path) - redirect_path = self.parser.find_dir( - parser.case_i("Redirect"), None, vhost.path) - - if redirect_path: - # "Existing Redirect directive for virtualhost" - return True, 1 - if not rewrite_path: - # "No existing redirection for virtualhost" - return False, -1 - if len(rewrite_path) == len(constants.APACHE_REWRITE_HTTPS_ARGS): - for idx, match in enumerate(rewrite_path): - if (self.aug.get(match) != - constants.APACHE_REWRITE_HTTPS_ARGS[idx]): - # Not a letsencrypt https rewrite - return True, 2 - # Existing letsencrypt https rewrite rule is in place - return True, 0 - # Rewrite path exists but is not a letsencrypt https rule - return True, 2 - - def _create_redirect_vhost(self, ssl_vhost): - """Creates an http_vhost specifically to redirect for the ssl_vhost. - - :param ssl_vhost: ssl vhost - :type ssl_vhost: - :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost` - - :returns: tuple of the form - (`success`, - :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost`) - :rtype: tuple - - """ - # Consider changing this to a dictionary check - # Make sure adding the vhost will be safe - conflict, host_or_addrs = self._conflicting_host(ssl_vhost) - if conflict: - raise errors.LetsEncryptConfiguratorError( - "Unable to create a redirection vhost " - "- {}".format(host_or_addrs)) - - redirect_addrs = host_or_addrs - - # get servernames and serveraliases - serveralias = "" - servername = "" - size_n = len(ssl_vhost.names) - if size_n > 0: - servername = "ServerName " + ssl_vhost.names[0] - if size_n > 1: - serveralias = " ".join(ssl_vhost.names[1:size_n]) - serveralias = "ServerAlias " + serveralias - redirect_file = ("\n" - "%s \n" - "%s \n" - "ServerSignature Off\n" - "\n" - "RewriteEngine On\n" - "RewriteRule %s\n" - "\n" - "ErrorLog /var/log/nginx2/redirect.error.log\n" - "LogLevel warn\n" - "\n" - % (servername, serveralias, - " ".join(constants.APACHE_REWRITE_HTTPS_ARGS))) - - # Write out the file - # This is the default name - redirect_filename = "le-redirect.conf" - - # See if a more appropriate name can be applied - if len(ssl_vhost.names) > 0: - # Sanity check... - # make sure servername doesn't exceed filename length restriction - if ssl_vhost.names[0] < (255-23): - redirect_filename = "le-redirect-%s.conf" % ssl_vhost.names[0] - - redirect_filepath = os.path.join( - self.parser.root, "sites-available", redirect_filename) - - # Register the new file that will be created - # Note: always register the creation before writing to ensure file will - # be removed in case of unexpected program exit - self.reverter.register_file_creation(False, redirect_filepath) - - # Write out file - with open(redirect_filepath, "w") as redirect_fd: - redirect_fd.write(redirect_file) - logging.info("Created redirect file: %s", redirect_filename) - - self.aug.load() - # Make a new vhost data structure and add it to the lists - new_vhost = self._create_vhost(parser.get_aug_path(redirect_filepath)) - self.vhosts.append(new_vhost) - - # Finally create documentation for the change - self.save_notes += ("Created a port 80 vhost, %s, for redirection to " - "ssl vhost %s\n" % - (new_vhost.filep, ssl_vhost.filep)) - - def _conflicting_host(self, ssl_vhost): - """Checks for conflicting HTTP vhost for ssl_vhost. - - Checks for a conflicting host, such that a new port 80 host could not - be created without ruining the nginx config - Used with redirection - - returns: conflict, host_or_addrs - boolean - if conflict: returns conflicting vhost - if not conflict: returns space separated list of new host addrs - - :param ssl_vhost: SSL Vhost to check for possible port 80 redirection - :type ssl_vhost: - :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost` - - :returns: TODO - :rtype: TODO - - """ - # Consider changing this to a dictionary check - redirect_addrs = "" - for ssl_a in ssl_vhost.addrs: - # Add space on each new addr, combine "VirtualHost"+redirect_addrs - redirect_addrs = redirect_addrs + " " - ssl_a_vhttp = ssl_a.get_addr_obj("80") - # Search for a conflicting host... - for vhost in self.vhosts: - if vhost.enabled: - if (ssl_a_vhttp in vhost.addrs or - ssl_a.get_addr_obj("") in vhost.addrs or - ssl_a.get_addr_obj("*") in vhost.addrs): - # We have found a conflicting host... just return - return True, vhost - - redirect_addrs = redirect_addrs + ssl_a_vhttp - - return False, redirect_addrs - - def _general_vhost(self, ssl_vhost): - """Find appropriate HTTP vhost for ssl_vhost. - - Function needs to be thoroughly tested and perhaps improved - Will not do well with malformed configurations - Consider changing this into a dict check - - :param ssl_vhost: ssl vhost to check - :type ssl_vhost: - :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost` - - :returns: HTTP vhost or None if unsuccessful - :rtype: :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost` - or None - - """ - # _default_:443 check - # Instead... should look for vhost of the form *:80 - # Should we prompt the user? - ssl_addrs = ssl_vhost.addrs - if ssl_addrs == obj.Addr.fromstring("_default_:443"): - ssl_addrs = [obj.Addr.fromstring("*:443")] - - for vhost in self.vhosts: - found = 0 - # Not the same vhost, and same number of addresses - if vhost != ssl_vhost and len(vhost.addrs) == len(ssl_vhost.addrs): - # Find each address in ssl_host in test_host - for ssl_a in ssl_addrs: - for test_a in vhost.addrs: - if test_a.get_addr() == ssl_a.get_addr(): - # Check if found... - if (test_a.get_port() == "80" or - test_a.get_port() == "" or - test_a.get_port() == "*"): - found += 1 - break - # Check to make sure all addresses were found - # and names are equal - if (found == len(ssl_vhost.addrs) and - vhost.names == ssl_vhost.names): - return vhost - return None - def get_all_certs_keys(self): """Find all existing keys, certs from configuration. @@ -717,7 +453,7 @@ class NginxConfigurator(object): :rtype: bool """ - return nginx_restart(self.config.nginx_init_script) + return nginx_restart(self.config.nginx_ctl) def config_test(self): # pylint: disable=no-self-use """Check the configuration of Nginx for errors. @@ -863,82 +599,14 @@ class NginxConfigurator(object): self.restart() -def enable_mod(mod_name, nginx_init_script, nginx_enmod): - """Enables module in Nginx. - - Both enables and restarts Nginx so module is active. - - :param str mod_name: Name of the module to enable. - :param str nginx_init_script: Path to the Nginx init script. - :param str nginx_enmod: Path to the Nginx a2enmod script. - - """ - try: - # Use check_output so the command will finish before reloading - # TODO: a2enmod is debian specific... - subprocess.check_call(["sudo", nginx_enmod, mod_name], # TODO: sudo? - stdout=open("/dev/null", "w"), - stderr=open("/dev/null", "w")) - nginx_restart(nginx_init_script) - except (OSError, subprocess.CalledProcessError) as err: - logging.error("Error enabling mod_%s", mod_name) - logging.error("Exception: %s", err) - sys.exit(1) - - -def mod_loaded(module, nginx_ctl): - """Checks to see if mod_ssl is loaded - - Uses ``nginx_ctl`` to get loaded module list. This also effectively - serves as a config_test. - - :param str nginx_ctl: Path to nginx2ctl binary. - - :returns: If ssl_module is included and active in Nginx - :rtype: bool - - """ - try: - proc = subprocess.Popen( - [nginx_ctl, "-M"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout, stderr = proc.communicate() - - except (OSError, ValueError): - logging.error( - "Error accessing %s for loaded modules!", nginx_ctl) - raise errors.LetsEncryptConfiguratorError( - "Error accessing loaded modules") - # Small errors that do not impede - if proc.returncode != 0: - logging.warn("Error in checking loaded module list: %s", stderr) - raise errors.LetsEncryptMisconfigurationError( - "Nginx is unable to check whether or not the module is " - "loaded because Nginx is misconfigured.") - - if module in stdout: - return True - return False - - -def nginx_restart(nginx_init_script): +def nginx_restart(nginx_ctl): """Restarts the Nginx Server. - :param str nginx_init_script: Path to the Nginx 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 NginxConfigurator has been thoroughly tested. The function will - need to be moved into the class again. Perhaps - this version can live on... for testing purposes. + :param str nginx_ctl: Path to the Nginx binary. """ try: - proc = subprocess.Popen([nginx_init_script, "restart"], + proc = subprocess.Popen([nginx_ctl, "-s", "reload"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = proc.communicate()