diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt/client/plugins/nginx/configurator.py index 1e5e819f8..0e88c4446 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt/client/plugins/nginx/configurator.py @@ -162,10 +162,6 @@ class NginxConfigurator(object): if cert_chain: self.save_notes += "\tSSLCertificateChainFile %s\n" % cert_chain - # Make sure vhost is enabled - if not vhost.enabled: - self._enable_site(vhost) - ####################### # Vhost parsing methods ####################### @@ -175,6 +171,9 @@ class NginxConfigurator(object): .. todo:: This should maybe return list if no obvious answer is presented. + .. todo:: The special name "$hostname" corresponds to the machine's + hostname. Currently we just ignore this. + :param str target_name: domain name :returns: ssl vhost associated with name @@ -367,52 +366,6 @@ class NginxConfigurator(object): ###################################### # 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 - - :param str avail_fp: Complete file path of available site - - :returns: Success - :rtype: bool - - """ - enabled_dir = os.path.join(self.parser.root, "sites-enabled") - for entry in os.listdir(enabled_dir): - if os.path.realpath(os.path.join(enabled_dir, entry)) == avail_fp: - return True - - return False - - def _enable_site(self, vhost): - """Enables an available site, Nginx restart required. - - .. todo:: This function should number subdomains before the domain vhost - - .. todo:: Make sure link is not broken... - - :param vhost: vhost to enable - :type vhost: :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost` - - :returns: Success - :rtype: bool - - """ - if self._is_site_enabled(vhost.filep): - return True - - if "/sites-available/" in vhost.filep: - enabled_path = ("%s/sites-enabled/%s" % - (self.parser.root, os.path.basename(vhost.filep))) - self.reverter.register_file_creation(False, enabled_path) - os.symlink(vhost.filep, enabled_path) - vhost.enabled = True - logging.info("Enabling available site: %s", vhost.filep) - self.save_notes += "Enabled site %s\n" % vhost.filep - return True - return False - def restart(self): """Restarts nginx server. diff --git a/letsencrypt/client/plugins/nginx/nginx_configurator.py b/letsencrypt/client/plugins/nginx/nginx_configurator.py deleted file mode 100644 index 86aa7e371..000000000 --- a/letsencrypt/client/plugins/nginx/nginx_configurator.py +++ /dev/null @@ -1,208 +0,0 @@ -import zope.interface - -from letsencrypt.client import augeas_configurator -from letsencrypt.client import CONFIG -from letsencrypt.client import interfaces - - -# This might be helpful... but feel free to use whatever you want -# class VH(object): -# def __init__(self, filename_path, vh_path, vh_addrs, is_ssl, is_enabled): -# self.file = filename_path -# self.path = vh_path -# self.addrs = vh_addrs -# self.names = [] -# self.ssl = is_ssl -# self.enabled = is_enabled - -# def set_names(self, listOfNames): -# self.names = listOfNames - -# def add_name(self, name): -# self.names.append(name) - -class NginxConfigurator(augeas_configurator.AugeasConfigurator): - """Nginx Configurator class.""" - zope.interface.implements(interfaces.IAuthenticator, interfaces.IInstaller) - - def __init__(self, server_root=CONFIG.SERVER_ROOT): - super(NginxConfigurator, self).__init__() - self.server_root = server_root - - # See if any temporary changes need to be recovered - # This needs to occur before VH objects are setup... - # because this will change the underlying configuration and potential - # vhosts - self.recovery_routine() - # Check for errors in parsing files with Augeas - # TODO - insert nginx lens info here??? - #self.check_parsing_errors("httpd.aug") - - def deploy_cert(self, vhost, cert, key, cert_chain=None): - """Deploy cert in nginx""" - - def choose_virtual_host(self, name): - """Chooses a virtual host based on the given domain name""" - - def get_all_names(self): - """Returns all names found in the nginx configuration""" - return set() - - # Might be helpful... I know nothing about nginx lens - # def get_include_path(self, cur_dir, arg): - # """ - # Converts an Nginx Include directive argument into an Augeas - # searchable path - # Returns path string - # """ - # # Sanity check argument - maybe - # # Question: what can the attacker do with control over this string - # # Effect parse file... maybe exploit unknown errors in Augeas - # # If the attacker can Include anything though... and this function - # # only operates on Nginx real config data... then the attacker has - # # already won. - # # Perhaps it is better to simply check the permissions on all - # # included files? - # # check_config to validate nginx config doesn't work because it - # # would create a race condition between the check and this input - - # # TODO: Fix this - # # Check to make sure only expected characters are used, maybe remove - # # validChars = re.compile("[a-zA-Z0-9.*?_-/]*") - # # matchObj = validChars.match(arg) - # # if matchObj.group() != arg: - # # logging.error("Error: Invalid regexp characters in %s", arg) - # # return [] - - # # Standardize the include argument based on server root - # if not arg.startswith("/"): - # arg = cur_dir + arg - # # conf/ is a special variable for ServerRoot in Nginx - # elif arg.startswith("conf/"): - # arg = self.server_root + arg[5:] - # # TODO: Test if Nginx allows ../ or ~/ for Includes - - # # Attempts to add a transform to the file if one does not already - # # exist - # self.parse_file(arg) - - # # Argument represents an fnmatch regular expression, convert it - # # Split up the path and convert each into an Augeas accepted regex - # # then reassemble - # if "*" in arg or "?" in arg: - # postfix = "" - # splitArg = arg.split("/") - # for idx, split in enumerate(splitArg): - # # * and ? are the two special fnmatch characters - # if "*" in split or "?" in split: - # # Turn it into a augeas regex - # # TODO: Can this be an augeas glob instead of regex - # splitArg[idx] = ("* [label()=~regexp('%s')]" % - # self.fnmatch_to_re(split) - # # Reassemble the argument - # arg = "/".join(splitArg) - - # # If the include is a directory, just return the directory as a file - # if arg.endswith("/"): - # return "/files" + arg[:len(arg)-1] - # return "/files"+arg - - def enable_redirect(self, ssl_vhost): - """ - 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 - """ - return - - def enable_ocsp_stapling(self, ssl_vhost): - return False - - def enable_hsts(self, ssl_vhost): - return False - - def get_all_certs_keys(self): - """ - Retrieve all certs and keys set in VirtualHosts on the Nginx server - returns: list of tuples with form [(cert, key, path)] - """ - return None - - # Probably helpful reference - # def get_file_path(self, vhost_path): - # """ - # Takes in Augeas path and returns the file name - # """ - # # Strip off /files - # avail_fp = vhost_path[6:] - # # This can be optimized... - # while True: - # # Cast both to lowercase to be case insensitive - # find_if = avail_fp.lower().find("/ifmodule") - # if find_if != -1: - # avail_fp = avail_fp[:find_if] - # continue - # find_vh = avail_fp.lower().find("/virtualhost") - # if find_vh != -1: - # avail_fp = avail_fp[:find_vh] - # continue - # break - # return avail_fp - - def enable_site(self, vhost): - """Enables an available site, Nginx restart required""" - return False - - # Might be a usefule reference - # def parse_file(self, file_path): - # """ - # Checks to see if file_path is parsed by Augeas - # If file_path isn't parsed, the file is added and Augeas is reloaded - # """ - # # Test if augeas included file for Httpd.lens - # # Note: This works for augeas globs, ie. *.conf - # incTest = self.aug.match( - # "/augeas/load/Httpd/incl [. ='" + file_path + "']") - # if not incTest: - # # Load up files - # #self.httpd_incl.append(file_path) - # #self.aug.add_transform( - # # "Httpd.lns", self.httpd_incl, None, self.httpd_excl) - # self.__add_httpd_transform(file_path) - # self.aug.load() - - # Helpful reference? - # def verify_setup(self): - # """ - # Make sure that files/directories are setup with appropriate - # permissions. Aim for defensive coding... make sure all input files - # have permissions of root - # """ - # le_util.make_or_verify_dir(CONFIG.CONFIG_DIR, 0o755) - # le_util.make_or_verify_dir(CONFIG.WORK_DIR, 0o755) - # le_util.make_or_verify_dir(CONFIG.BACKUP_DIR, 0o755) - - def restart(self, quiet=False): - """Restarts nginx server""" - - # May be of use? - # def __add_httpd_transform(self, incl): - # """ - # This function will correctly add a transform to augeas - # The existing augeas.add_transform in python is broken - # """ - # lastInclude = self.aug.match("/augeas/load/Httpd/incl [last()]") - # self.aug.insert(lastInclude[0], "incl", False) - # self.aug.set("/augeas/load/Httpd/incl[last()]", incl) - - def config_test(self): - """Check Configuration""" - return False - - -def main(): - return - -if __name__ == "__main__": - main() diff --git a/letsencrypt/client/plugins/nginx/parser.py b/letsencrypt/client/plugins/nginx/parser.py index 361f0c7e2..d05bcf13d 100644 --- a/letsencrypt/client/plugins/nginx/parser.py +++ b/letsencrypt/client/plugins/nginx/parser.py @@ -2,7 +2,6 @@ import glob import logging import os -import re import pyparsing from letsencrypt.client import errors @@ -155,224 +154,6 @@ class NginxParser(object): return parsed_server - def add_dir_to_ifmodssl(self, aug_conf_path, directive, val): - """Adds directive and value to IfMod ssl block. - - Adds given directive and value along configuration path within - an IfMod mod_ssl.c block. If the IfMod block does not exist in - the file, it is created. - - :param str aug_conf_path: Desired Augeas config path to add directive - :param str directive: Directive you would like to add - :param str val: Value of directive ie. Listen 443, 443 is the value - - """ - # TODO: Add error checking code... does the path given even exist? - # Does it throw exceptions? - if_mod_path = self._get_ifmod(aug_conf_path, "mod_ssl.c") - # IfModule can have only one valid argument, so append after - self.aug.insert(if_mod_path + "arg", "directive", False) - nvh_path = if_mod_path + "directive[1]" - self.aug.set(nvh_path, directive) - self.aug.set(nvh_path + "/arg", val) - - def _get_ifmod(self, aug_conf_path, mod): - """Returns the path to and creates one if it doesn't exist. - - :param str aug_conf_path: Augeas configuration path - :param str mod: module ie. mod_ssl.c - - """ - if_mods = self.aug.match(("%s/IfModule/*[self::arg='%s']" % - (aug_conf_path, mod))) - if len(if_mods) == 0: - self.aug.set("%s/IfModule[last() + 1]" % aug_conf_path, "") - self.aug.set("%s/IfModule[last()]/arg" % aug_conf_path, mod) - if_mods = self.aug.match(("%s/IfModule/*[self::arg='%s']" % - (aug_conf_path, mod))) - # Strip off "arg" at end of first ifmod path - return if_mods[0][:len(if_mods[0]) - 3] - - def add_dir(self, aug_conf_path, directive, arg): - """Appends directive to the end fo the file given by aug_conf_path. - - .. note:: Not added to AugeasConfigurator because it may depend - on the lens - - :param str aug_conf_path: Augeas configuration path to add directive - :param str directive: Directive to add - :param str arg: Value of the directive. ie. Listen 443, 443 is arg - - """ - self.aug.set(aug_conf_path + "/directive[last() + 1]", directive) - if isinstance(arg, list): - for i, value in enumerate(arg, 1): - self.aug.set( - "%s/directive[last()]/arg[%d]" % (aug_conf_path, i), value) - else: - self.aug.set(aug_conf_path + "/directive[last()]/arg", arg) - - def find_dir(self, directive, arg=None, start=None): - """Finds directive in the configuration. - - Recursively searches through config files to find directives - Directives should be in the form of a case insensitive regex currently - - .. todo:: Add order to directives returned. Last directive comes last.. - .. todo:: arg should probably be a list - - Note: Augeas is inherently case sensitive while Nginx is case - insensitive. Augeas 1.0 allows case insensitive regexes like - regexp(/Listen/, "i"), however the version currently supported - by Ubuntu 0.10 does not. Thus I have included my own case insensitive - transformation by calling case_i() on everything to maintain - compatibility. - - :param str directive: Directive to look for - - :param arg: Specific value directive must have, None if all should - be considered - :type arg: str or None - - :param str start: Beginning Augeas path to begin looking - - """ - # Cannot place member variable in the definition of the function so... - if not start: - start = get_aug_path(self.loc["root"]) - - # Debug code - # print "find_dir:", directive, "arg:", arg, " | Looking in:", start - # No regexp code - # if arg is None: - # matches = self.aug.match(start + - # "//*[self::directive='" + directive + "']/arg") - # else: - # matches = self.aug.match(start + - # "//*[self::directive='" + directive + - # "']/* [self::arg='" + arg + "']") - - # includes = self.aug.match(start + - # "//* [self::directive='Include']/* [label()='arg']") - - if arg is None: - matches = self.aug.match(("%s//*[self::directive=~regexp('%s')]/arg" - % (start, directive))) - else: - matches = self.aug.match(("%s//*[self::directive=~regexp('%s')]/*" - "[self::arg=~regexp('%s')]" % - (start, directive, arg))) - - incl_regex = "(%s)|(%s)" % (case_i('Include'), - case_i('IncludeOptional')) - - includes = self.aug.match(("%s//* [self::directive=~regexp('%s')]/* " - "[label()='arg']" % (start, incl_regex))) - - # for inc in includes: - # print inc, self.aug.get(inc) - - for include in includes: - # start[6:] to strip off /files - matches.extend(self.find_dir( - directive, arg, self._get_include_path( - strip_dir(start[6:]), self.aug.get(include)))) - - return matches - - def _get_include_path(self, cur_dir, arg): - """Converts an Nginx Include directive into Augeas path. - - Converts an Nginx Include directive argument into an Augeas - searchable path - - .. todo:: convert to use os.path.join() - - :param str cur_dir: current working directory - - :param str arg: Argument of Include directive - - :returns: Augeas path string - :rtype: str - - """ - # Sanity check argument - maybe - # Question: what can the attacker do with control over this string - # Effect parse file... maybe exploit unknown errors in Augeas - # If the attacker can Include anything though... and this function - # only operates on Nginx real config data... then the attacker has - # already won. - # Perhaps it is better to simply check the permissions on all - # included files? - # check_config to validate nginx config doesn't work because it - # would create a race condition between the check and this input - - # TODO: Maybe... although I am convinced we have lost if - # Nginx files can't be trusted. The augeas include path - # should be made to be exact. - - # Check to make sure only expected characters are used <- maybe remove - # validChars = re.compile("[a-zA-Z0-9.*?_-/]*") - # matchObj = validChars.match(arg) - # if matchObj.group() != arg: - # logging.error("Error: Invalid regexp characters in %s", arg) - # return [] - - # Standardize the include argument based on server root - if not arg.startswith("/"): - arg = cur_dir + arg - # conf/ is a special variable for ServerRoot in Nginx - elif arg.startswith("conf/"): - arg = self.root + arg[4:] - # TODO: Test if Nginx allows ../ or ~/ for Includes - - # Attempts to add a transform to the file if one does not already exist - self._parse_files(arg) - - # Argument represents an fnmatch regular expression, convert it - # Split up the path and convert each into an Augeas accepted regex - # then reassemble - if "*" in arg or "?" in arg: - split_arg = arg.split("/") - for idx, split in enumerate(split_arg): - # * and ? are the two special fnmatch characters - if "*" in split or "?" in split: - # Turn it into a augeas regex - # TODO: Can this instead be an augeas glob instead of regex - split_arg[idx] = ("* [label()=~regexp('%s')]" % - self.fnmatch_to_re(split)) - # Reassemble the argument - arg = "/".join(split_arg) - - # If the include is a directory, just return the directory as a file - if arg.endswith("/"): - return get_aug_path(arg[:len(arg)-1]) - return get_aug_path(arg) - - def fnmatch_to_re(self, clean_fn_match): # pylint: disable=no-self-use - """Method converts Nginx's basic fnmatch to regular expression. - - :param str clean_fn_match: Nginx style filename match, similar to globs - - :returns: regex suitable for augeas - :rtype: str - - """ - # Checkout fnmatch.py in venv/local/lib/python2.7/fnmatch.py - regex = "" - for letter in clean_fn_match: - if letter == '.': - regex = regex + r"\." - elif letter == '*': - regex = regex + ".*" - # According to nginx.org ? shouldn't appear - # but in case it is valid... - elif letter == '?': - regex = regex + "." - else: - regex = regex + letter - return regex - def _parse_files(self, filepath): """Parse files from a glob @@ -397,27 +178,6 @@ class NginxParser(object): logging.warn("Could not parse file: %s" % f) return trees - def _add_httpd_transform(self, incl): - """Add a transform to Augeas. - - This function will correctly add a transform to augeas - The existing augeas.add_transform in python doesn't seem to work for - Travis CI as it loads in libaugeas.so.0.10.0 - - :param str incl: filepath to include for transform - - """ - last_include = self.aug.match("/augeas/load/Httpd/incl [last()]") - if last_include: - # Insert a new node immediately after the last incl - self.aug.insert(last_include[0], "incl", False) - self.aug.set("/augeas/load/Httpd/incl[last()]", incl) - # On first use... must load lens and add file to incl - else: - # Augeas uses base 1 indexing... insert at beginning... - self.aug.set("/augeas/load/Httpd/lens", "Httpd.lns") - self.aug.set("/augeas/load/Httpd/incl", incl) - def _set_locations(self, ssl_options): """Set default location for directives. @@ -450,6 +210,39 @@ class NginxParser(object): raise errors.LetsEncryptNoInstallationError( "Could not find configuration root") + def add_dir(self, aug_conf_path, directive, arg): + """Appends directive to the end fo the file given by aug_conf_path. + + .. note:: Not added to AugeasConfigurator because it may depend + on the lens + + :param str aug_conf_path: Augeas configuration path to add directive + :param str directive: Directive to add + :param str arg: Value of the directive. ie. Listen 443, 443 is arg + + """ + pass + + def find_dir(self, directive, arg=None, start=None): + """Finds directive in the configuration. + + Recursively searches through config files to find directives + + .. todo:: Add order to directives returned. Last directive comes last.. + .. todo:: arg should probably be a list + + :param str directive: Directive to look for + + :param arg: Specific value directive must have, None if all should + be considered + :type arg: str or None + + :param str start: Beginning Augeas path to begin looking + :rtype: list + + """ + return [] + def filedump(self, ext='tmp'): """Dumps parsed configurations into files. @@ -468,50 +261,6 @@ class NginxParser(object): logging.error("Could not open file for writing: %s" % filename) -def case_i(string): - """Returns case insensitive regex. - - Returns a sloppy, but necessary version of a case insensitive regex. - Any string should be able to be submitted and the string is - escaped and then made case insensitive. - May be replaced by a more proper /i once augeas 1.0 is widely - supported. - - :param str string: string to make case i regex - - """ - return "".join(["["+c.upper()+c.lower()+"]" - if c.isalpha() else c for c in re.escape(string)]) - - -def get_aug_path(file_path): - """Return augeas path for full filepath. - - :param str file_path: Full filepath - - """ - return "/files%s" % file_path - - -def strip_dir(path): - """Returns directory of file path. - - .. todo:: Replace this with Python standard function - - :param str path: path is a file path. not an augeas section or - directive path - - :returns: directory - :rtype: str - - """ - index = path.rfind("/") - if index > 0: - return path[:index+1] - # No directory - return "" - - def do_for_subarray(entry, condition, func): """Executes a function for a subarray of a nested array if it matches the given condition.