diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt/client/plugins/nginx/configurator.py index 624d24ca9..64d07d717 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt/client/plugins/nginx/configurator.py @@ -35,6 +35,12 @@ class NginxConfigurator(object): :ivar parser: Handles low level parsing :type parser: :class:`~letsencrypt.client.plugins.nginx.parser` + :ivar set save_files: Files that need to be saved + :ivar str save_notes: Human-readable config change notes + + :ivar reverter: saves and reverts checkpoints + :type reverter: :class:`letsencrypt.client.reverter.Reverter` + :ivar tup version: version of Nginx :ivar list vhosts: All vhosts found in the configuration (:class:`list` of @@ -55,11 +61,14 @@ class NginxConfigurator(object): """ self.config = config - self.save_notes = "" # Verify that all directories and files exist with proper permissions if os.geteuid() == 0: - self.verify_setup() + self._verify_setup() + + # Files to save + self.save_files = set() + self.save_notes = "" # Add name_server association dict self.assoc = dict() @@ -76,6 +85,7 @@ class NginxConfigurator(object): self.reverter = reverter.Reverter(config) self.reverter.recovery_routine() + # This is called in determine_authenticator and determine_installer def prepare(self): """Prepare the authenticator/installer.""" self.parser = parser.NginxParser( @@ -84,13 +94,14 @@ class NginxConfigurator(object): # Set Version if self.version is None: - self.version = self.get_version() + self.version = self._get_version() # Get all of the available vhosts - self.vhosts = self.get_virtual_hosts() + self.vhosts = self._get_vhosts() temp_install(self.config.nginx_mod_ssl_conf) + # Entry point in main.py for installing cert def deploy_cert(self, domain, cert, key, cert_chain=None): """Deploys certificate to specified virtual host. @@ -154,7 +165,7 @@ class NginxConfigurator(object): # Make sure vhost is enabled if not vhost.enabled: - self.enable_site(vhost) + self._enable_site(vhost) ####################### # Vhost parsing methods @@ -172,7 +183,6 @@ class NginxConfigurator(object): """ # Allows for domain names to be associated with a virtual host - # Client isn't using create_dn_server_assoc(self, dn, vh) yet if target_name in self.assoc: return self.assoc[target_name] # Check for servernames/aliases for ssl hosts @@ -191,7 +201,7 @@ class NginxConfigurator(object): # Check for non ssl vhosts with servernames/aliases == "name" for vhost in self.vhosts: if not vhost.ssl and target_name in vhost.names: - vhost = self.make_vhost_ssl(vhost) + vhost = self._make_vhost_ssl(vhost) self.assoc[target_name] = vhost return vhost @@ -201,19 +211,6 @@ class NginxConfigurator(object): return vhost return None - def create_dn_server_assoc(self, domain, vhost): - """Create an association between a domain name and virtual host. - - Helps to choose an appropriate vhost - - :param str domain: domain name to associate - - :param vhost: virtual host to associate with domain - :type vhost: :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost` - - """ - self.assoc[domain] = vhost - def get_all_names(self): """Returns all names found in the Nginx Configuration. @@ -243,7 +240,7 @@ class NginxConfigurator(object): return all_names # TODO: make "sites-available" a configurable directory - def get_virtual_hosts(self): + def _get_vhosts(self): """Returns list of virtual hosts found in the Nginx configuration. :returns: List of @@ -261,7 +258,7 @@ class NginxConfigurator(object): return vhs - def make_vhost_ssl(self, nonssl_vhost): # pylint: disable=too-many-locals + def _make_vhost_ssl(self, nonssl_vhost): # pylint: disable=too-many-locals """Makes an ssl_vhost version of a nonssl_vhost. Duplicates vhost and adds default ssl options @@ -296,7 +293,7 @@ class NginxConfigurator(object): new_file.write(line) new_file.write("\n") except IOError: - logging.fatal("Error writing/reading to file in make_vhost_ssl") + logging.fatal("Error writing/reading to file in _make_vhost_ssl") sys.exit(49) self.aug.load() @@ -356,12 +353,13 @@ class NginxConfigurator(object): for vhost in self.vhosts: if vhost.ssl: # TODO: get the cert, key, and conf file paths + pass return c_k - ##################### - # enhancement methods - ##################### + ################################## + # enhancement methods (IInstaller) + ################################## def supported_enhancements(self): # pylint: disable=no-self-use """Returns currently supported enhancements.""" return [] @@ -386,10 +384,10 @@ class NginxConfigurator(object): except errors.LetsEncryptConfiguratorError: logging.warn("Failed %s for %s", enhancement, domain) - ######################### - # Nginx server management - ######################### - def is_site_enabled(self, avail_fp): + ###################################### + # Nginx server management (IInstaller) + ###################################### + def _is_site_enabled(self, avail_fp): """Checks to see if the given site is enabled. .. todo:: fix hardcoded sites-enabled, check os.path.samefile @@ -407,7 +405,7 @@ class NginxConfigurator(object): return False - def enable_site(self, vhost): + def _enable_site(self, vhost): """Enables an available site, Nginx restart required. .. todo:: This function should number subdomains before the domain vhost @@ -421,7 +419,7 @@ class NginxConfigurator(object): :rtype: bool """ - if self.is_site_enabled(vhost.filep): + if self._is_site_enabled(vhost.filep): return True if "/sites-available/" in vhost.filep: @@ -470,7 +468,7 @@ class NginxConfigurator(object): return True - def verify_setup(self): + def _verify_setup(self): """Verify the setup to ensure safe operating environment. Make sure that files/directories are setup with appropriate permissions @@ -483,7 +481,7 @@ class NginxConfigurator(object): le_util.make_or_verify_dir(self.config.work_dir, 0o755, uid) le_util.make_or_verify_dir(self.config.backup_dir, 0o755, uid) - def get_version(self): + def _get_version(self): """Return version of Nginx Server. Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7)) @@ -539,9 +537,9 @@ class NginxConfigurator(object): version=".".join(str(i) for i in self.version)) ) - ###################################### - # Wrapper functions for Reverter class - ###################################### + ################################################### + # Wrapper functions for Reverter class (IInstaller) + ################################################### def save(self, title=None, temporary=False): """Saves all changes to the configuration files. @@ -571,6 +569,7 @@ class NginxConfigurator(object): os.rename(f + '.le', f) else: logging.warn("Expected file %s to exist", tmpfile) + self.save_files.remove(f) if title and not temporary: self.reverter.finalize_checkpoint(title) @@ -602,12 +601,13 @@ class NginxConfigurator(object): self.reverter.view_config_changes() ########################################################################### - # Challenges Section + # Challenges Section for IAuthenticator ########################################################################### def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use """Return list of challenge preferences.""" return [challenges.DVSNI] + # Entry point in main.py for performing challenges def perform(self, achalls): """Perform the configuration related challenge. @@ -640,6 +640,7 @@ class NginxConfigurator(object): return responses + # called after challenges are performed def cleanup(self, achalls): """Revert all challenges.""" self._chall_out -= len(achalls) diff --git a/letsencrypt/client/plugins/nginx/parser.py b/letsencrypt/client/plugins/nginx/parser.py index 0f95c056c..dfb091881 100644 --- a/letsencrypt/client/plugins/nginx/parser.py +++ b/letsencrypt/client/plugins/nginx/parser.py @@ -1,8 +1,12 @@ """NginxParser is a member object of the NginxConfigurator class.""" +import glob +import logging import os import re +import pyparsing from letsencrypt.client import errors +from letsencrypt.client.plugins.nginx.nginxparser import dump, load class NginxParser(object): @@ -10,22 +14,19 @@ class NginxParser(object): :ivar str root: Normalized abosulte path to the server root directory. Without trailing slash. + :ivar dict parsed: Mapping of file paths to parsed trees """ - def __init__(self, aug, root, ssl_options): - # Find configuration root and make sure augeas can parse it. - self.aug = aug + def __init__(self, root, ssl_options): + self.parsed = {} self.root = os.path.abspath(root) self.loc = self._set_locations(ssl_options) self._parse_file(self.loc["root"]) # Must also attempt to parse sites-available or equivalent # Sites-available is not included naturally in configuration - self._parse_file(os.path.join(self.root, "sites-available") + "/*") - - # This problem has been fixed in Augeas 1.0 - self.standardize_excl() + self._parse_file(os.path.join(self.root, "sites-available") + "/*.conf") def add_dir_to_ifmodssl(self, aug_conf_path, directive, val): """Adds directive and value to IfMod ssl block. @@ -246,24 +247,19 @@ class NginxParser(object): return regex def _parse_file(self, filepath): - """Parse file with Augeas - - Checks to see if file_path is parsed by Augeas - If filepath isn't parsed, the file is added and Augeas is reloaded + """Parse file :param str filepath: Nginx config file path """ - # Test if augeas included file for Httpd.lens - # Note: This works for augeas globs, ie. *.conf - inc_test = self.aug.match( - "/augeas/load/Httpd/incl [. ='%s']" % filepath) - if not inc_test: - # Load up files - # This doesn't seem to work on TravisCI - # self.aug.add_transform("Httpd.lns", [filepath]) - self._add_httpd_transform(filepath) - self.aug.load() + files = glob.glob(filepath) + for f in files: + try: + self.parsed[f] = load(open(f)) + except IOError: + logging.warn("Could not parse file: %s" % f) + except pyparsing.ParseException: + logging.warn("Could not parse file: %s" % f) def _add_httpd_transform(self, incl): """Add a transform to Augeas. @@ -286,38 +282,6 @@ class NginxParser(object): self.aug.set("/augeas/load/Httpd/lens", "Httpd.lns") self.aug.set("/augeas/load/Httpd/incl", incl) - def standardize_excl(self): - """Standardize the excl arguments for the Httpd lens in Augeas. - - Note: Hack! - Standardize the excl arguments for the Httpd lens in Augeas - Servers sometimes give incorrect defaults - Note: This problem should be fixed in Augeas 1.0. Unfortunately, - Augeas 0.10 appears to be the most popular version currently. - - """ - # attempt to protect against augeas error in 0.10.0 - ubuntu - # *.augsave -> /*.augsave upon augeas.load() - # Try to avoid bad httpd files - # There has to be a better way... but after a day and a half of testing - # I had no luck - # This is a hack... work around... submit to augeas if still not fixed - - excl = ["*.augnew", "*.augsave", "*.dpkg-dist", "*.dpkg-bak", - "*.dpkg-new", "*.dpkg-old", "*.rpmsave", "*.rpmnew", - "*~", - self.root + "/*.augsave", - self.root + "/*~", - self.root + "/*/*augsave", - self.root + "/*/*~", - self.root + "/*/*/*.augsave", - self.root + "/*/*/*~"] - - for i, excluded in enumerate(excl, 1): - self.aug.set("/augeas/load/Httpd/excl[%d]" % i, excluded) - - self.aug.load() - def _set_locations(self, ssl_options): """Set default location for directives. @@ -326,7 +290,7 @@ class NginxParser(object): """ root = self._find_config_root() - default = self._set_user_config_file(root) + default = os.path.join(self.root, 'nginx.conf') temp = os.path.join(self.root, "ports.conf") if os.path.isfile(temp): @@ -341,7 +305,7 @@ class NginxParser(object): def _find_config_root(self): """Find the Nginx Configuration Root file.""" - location = ["nginx2.conf", "httpd.conf"] + location = ['nginx.conf'] for name in location: if os.path.isfile(os.path.join(self.root, name)): @@ -350,24 +314,6 @@ class NginxParser(object): raise errors.LetsEncryptNoInstallationError( "Could not find configuration root") - def _set_user_config_file(self, root): - """Set the appropriate user configuration file - - .. todo:: This will have to be updated for other distros versions - - :param str root: pathname which contains the user config - - """ - # Basic check to see if httpd.conf exists and - # in hierarchy via direct include - # httpd.conf was very common as a user file in Nginx 2.2 - if (os.path.isfile(os.path.join(self.root, 'httpd.conf')) and - self.find_dir( - case_i("Include"), case_i("httpd.conf"), root)): - return os.path.join(self.root, 'httpd.conf') - else: - return os.path.join(self.root, 'nginx2.conf') - def case_i(string): """Returns case insensitive regex.