Remove redirect enhancement, fix reload

This commit is contained in:
yan 2015-04-03 14:56:04 -07:00
parent 37649966c2
commit 33ff366171

View file

@ -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 = ("<VirtualHost" + redirect_addrs + ">\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"
"</VirtualHost>\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()