Remove Apache parsing from configurator

This commit is contained in:
James Kasten
2014-12-16 21:00:14 -08:00
parent 6cd67e652b
commit 323aa350dc
6 changed files with 526 additions and 570 deletions
@@ -42,9 +42,11 @@ class Addr(object):
return self.tup[1]
def get_addr_obj(self, port):
"""Return new address object with same addr and new port."""
return self.__class__((self.tup[0], port))
# pylint: disable=too-few-public-methods
class VH(object):
"""Represents an Apache Virtualhost.
+402
View File
@@ -0,0 +1,402 @@
"""ApacheParser is a member object of the ApacheConfigurator class."""
import os
import re
class ApacheParser(object):
"""Class handles the fine details of parsing the Apache Configuration."""
def __init__(self, aug, root, ssl_options):
# Find configuration root and make sure augeas can parse it.
self.aug = aug
self.root = 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()
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 <IfMod mod> 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 type(arg) is not list:
self.aug.set(aug_conf_path + "/directive[last()]/arg", arg)
else:
for i in range(len(arg)):
self.aug.set("%s/directive[last()]/arg[%d]" %
(aug_conf_path, (i+1)),
arg[i])
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 Apache 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 direcitve 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 Apache Include directive into Augeas path.
Converts an Apache 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 Apache 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 apache 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
# Apache 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 Apache
elif arg.startswith("conf/"):
arg = self.root + arg[5:]
# TODO: Test if Apache 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:
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 Apache's basic fnmatch to regular expression.
:param str clean_fn_match: Apache style filename match, similar to globs
:returns: regex suitable for augeas
:rtype: str
"""
regex = ""
for letter in clean_fn_match:
if letter == '.':
regex = regex + r"\."
elif letter == '*':
regex = regex + ".*"
# According to apache.org ? shouldn't appear
# but in case it is valid...
elif letter == '?':
regex = regex + "."
else:
regex = regex + letter
return regex
def _parse_file(self, file_path):
"""Parse file with Augeas
Checks to see if file_path is parsed by Augeas
If file_path isn't parsed, the file is added and Augeas is reloaded
:param str file_path: Apache 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']" % file_path)
if not inc_test:
# 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()
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 in range(len(excl)):
self.aug.set("/augeas/load/Httpd/excl[%d]" % (i+1), excl[i])
self.aug.load()
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 is broken.
:param str incl: TODO
"""
last_include = self.aug.match("/augeas/load/Httpd/incl [last()]")
self.aug.insert(last_include[0], "incl", False)
self.aug.set("/augeas/load/Httpd/incl[last()]", incl)
def _set_locations(self, ssl_options):
"""Set default location for directives.
Locations are given as file_paths
.. todo:: Make sure that files are included
"""
root = self._find_config_root()
default = self._set_user_config_file()
temp = os.path.join(self.root, "ports.conf")
if os.path.isfile(temp):
listen = temp
name = temp
else:
listen = default
name = default
return {"root": root, "default": default, "listen": listen,
"name": name, "ssl_options": ssl_options}
def _find_config_root(self):
"""Find the Apache Configuration Root file."""
location = ["apache2.conf", "httpd.conf"]
for name in location:
if os.path.isfile(os.path.join(self.root, name)):
return os.path.join(self.root, name)
raise errors.LetsEncryptConfiguratorError(
"Could not find configuration root")
def _set_user_config_file(self, filename=''):
"""Set the appropriate user configuration file
.. todo:: This will have to be updated for other distros versions
:param str filename: optional filename that will be used as the
user config
"""
if filename:
return filename
else:
# Basic check to see if httpd.conf exists and
# in heirarchy via direct include
# httpd.conf was very common as a user file in Apache 2.2
if (os.path.isfile(self.root + 'httpd.conf') and
self.find_dir(
case_i("Include"), case_i("httpd.conf"))):
return os.path.join(self.root, 'httpd.conf')
else:
return os.path.join(self.root + 'apache2.conf')
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 ""
+83 -532
View File
@@ -8,13 +8,14 @@ import socket
import subprocess
import sys
from letsencrypt.client import apache_obj
from letsencrypt.client import augeas_configurator
from letsencrypt.client import challenge_util
from letsencrypt.client import CONFIG
from letsencrypt.client import errors
from letsencrypt.client import le_util
from letsencrypt.client.apache import obj
from letsencrypt.client.apache import parser
# Configurator should be turned into a Singleton
@@ -28,7 +29,7 @@ from letsencrypt.client import le_util
# Augeas views <VirtualHost> </Virtualhost> as an error. This will just
# require another check_parsing_errors() after all files are included...
# (after a find_directive search is executed currently). It can be a one
# time check however because all of Trustifies transactions will ensure
# time check however because all of LE's transactions will ensure
# only properly formed sections are added.
# Note: This protocol works for filenames with spaces in it, the sites are
@@ -59,6 +60,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
parser automatically.
.. todo:: Add support for config file variables Define rootDir /var/www/
.. todo:: Add proper support for module configuration
The API of this class will change in the coming weeks as the exact
needs of client's are clarified with the new and developing protocol.
@@ -68,7 +70,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
with the configuration
:ivar float version: version of Apache
:ivar list vhosts: All vhosts found in the configuration
(:class:`list` of :class:`letsencrypt.client.apache_obj.VH`)
(:class:`list` of :class:`letsencrypt.client.apache.obj.VH`)
:ivar dict assoc: Mapping between domains and vhosts
@@ -95,8 +97,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
super(ApacheConfigurator, self).__init__(direc)
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
@@ -107,22 +107,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if os.geteuid() == 0:
self.verify_setup()
# Find configuration root and make sure augeas can parse it.
self.location = self._set_locations(ssl_options)
self._parse_file(self.location["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.server_root, "sites-available/*"))
self.parser = parser.ApacheParser(self.aug, server_root, ssl_options)
# Check for errors in parsing files with Augeas
self.check_parsing_errors("httpd.aug")
# Set Version
self.version = self.get_version() if version is None else version
# Check for errors in parsing files with Augeas
self.check_parsing_errors("httpd.aug")
# This problem has been fixed in Augeas 1.0
self.standardize_excl()
# Get all of the available vhosts
self.vhosts = self.get_virtual_hosts()
# Add name_server association dict
@@ -158,7 +149,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
This shouldn't happen within letsencrypt though
:param vhost: ssl vhost to deploy certificate
:type vhost: :class:`letsencrypt.client.apache_obj.VH`
:type vhost: :class:`letsencrypt.client.apache.obj.VH`
:param str cert: certificate filename
:param str key: private key filename
@@ -170,15 +161,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
path = {}
path["cert_file"] = self.find_directive(case_i(
path["cert_file"] = self.parser.find_dir(parser.case_i(
"SSLCertificateFile"), None, vhost.path)
path["cert_key"] = self.find_directive(case_i(
path["cert_key"] = self.parser.find_dir(parser.case_i(
"SSLCertificateKeyFile"), None, vhost.path)
# Only include if a certificate chain is specified
if cert_chain is not None:
path["cert_chain"] = self.find_directive(
case_i("SSLCertificateChainFile"), None, vhost.path)
path["cert_chain"] = self.parser.find_dir(
parser.case_i("SSLCertificateChainFile"), None, vhost.path)
if len(path["cert_file"]) == 0 or len(path["cert_key"]) == 0:
# Throw some "can't find all of the directives error"
@@ -194,7 +185,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.aug.set(path["cert_key"][0], key)
if cert_chain is not None:
if len(path["cert_chain"]) == 0:
self.add_dir(vhost.path, "SSLCertificateChainFile", cert_chain)
self.parser.add_dir(
vhost.path, "SSLCertificateChainFile", cert_chain)
else:
self.aug.set(path["cert_chain"][0], cert_chain)
@@ -216,7 +208,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:param str name: domain name
:returns: ssl vhost associated with name
:rtype: :class:`letsencrypt.client.apache_obj.VH`
:rtype: :class:`letsencrypt.client.apache.obj.VH`
"""
# Allows for domain names to be associated with a virtual host
@@ -230,7 +222,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
return vhost
# Checking for domain name in vhost address
# This technique is not recommended by Apache but is technically valid
target_addr = apache_obj.Addr((target_name, "443"))
target_addr = obj.Addr((target_name, "443"))
for vhost in self.vhosts:
if target_addr in vhost.addrs:
return vhost
@@ -254,7 +246,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:param str domain: domain name to associate
:param vhost: virtual host to associate with domain
:type vhost: :class:`letsencrypt.client.apache_obj.VH`
:type vhost: :class:`letsencrypt.client.apache.obj.VH`
"""
self.assoc[domain] = vhost
@@ -287,73 +279,19 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
return all_names
def _set_locations(self, ssl_options):
"""Set default location for directives.
Locations are given as file_paths
.. todo:: Make sure that files are included
"""
root = self._find_config_root()
default = self._set_user_config_file()
temp = os.path.join(self.server_root, "ports.conf")
if os.path.isfile(temp):
listen = temp
name = temp
else:
listen = default
name = default
return {"root": root, "default": default, "listen": listen,
"name": name, "ssl_options": ssl_options}
def _find_config_root(self):
"""Find the Apache Configuration Root file."""
location = ["apache2.conf", "httpd.conf"]
for name in location:
if os.path.isfile(os.path.join(self.server_root, name)):
return os.path.join(self.server_root, name)
raise errors.LetsEncryptConfiguratorError(
"Could not find configuration root")
def _set_user_config_file(self, filename=''):
"""Set the appropriate user configuration file
.. todo:: This will have to be updated for other distros versions
:param str filename: optional filename that will be used as the
user config
"""
if filename:
return filename
else:
# Basic check to see if httpd.conf exists and
# in heirarchy via direct include
# httpd.conf was very common as a user file in Apache 2.2
if (os.path.isfile(self.server_root + 'httpd.conf') and
self.find_directive(
case_i("Include"), case_i("httpd.conf"))):
return os.path.join(self.server_root, 'httpd.conf')
else:
return os.path.join(self.server_root + 'apache2.conf')
def _add_servernames(self, host):
"""Helper function for get_virtual_hosts().
:param host: In progress vhost whose names will be added
:type host: :class:`letsencrypt.client.apache_obj.VH`
:type host: :class:`letsencrypt.client.apache.obj.VH`
"""
name_match = self.aug.match(("%s//*[self::directive=~regexp('%s')] | "
"%s//*[self::directive=~regexp('%s')]" %
(host.path,
case_i('ServerName'),
parser.case_i('ServerName'),
host.path,
case_i('ServerAlias'))))
parser.case_i('ServerAlias'))))
for name in name_match:
args = self.aug.match(name + "/*")
@@ -366,22 +304,22 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:param str path: Augeas path to virtual host
:returns: newly created vhost
:rtype: :class:`letsencrypt.client.apache_obj.VH`
:rtype: :class:`letsencrypt.client.apache.obj.VH`
"""
addrs = set()
args = self.aug.match(path + "/arg")
for arg in args:
addrs.add(apache_obj.Addr.fromstring(self.aug.get(arg)))
addrs.add(obj.Addr.fromstring(self.aug.get(arg)))
is_ssl = False
if self.find_directive(
case_i("SSLEngine"), case_i("on"), path):
if self.parser.find_dir(
parser.case_i("SSLEngine"), parser.case_i("on"), path):
is_ssl = True
filename = get_file_path(path)
is_enabled = self.is_site_enabled(filename)
vhost = apache_obj.VH(filename, path, addrs, is_ssl, is_enabled)
vhost = obj.VH(filename, path, addrs, is_ssl, is_enabled)
self._add_servernames(vhost)
return vhost
@@ -389,7 +327,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
def get_virtual_hosts(self):
"""Returns list of virtual hosts found in the Apache configuration.
:returns: List of :class:`letsencrypt.client.apache_obj.VH` objects
:returns: List of :class:`letsencrypt.client.apache.obj.VH` objects
found in configuration
:rtype: list
@@ -397,7 +335,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Search sites-available, httpd.conf for possible virtual hosts
paths = self.aug.match(
("/files%ssites-available//*[label()=~regexp('%s')]" %
(self.server_root, case_i('VirtualHost'))))
(self.parser.root, parser.case_i('VirtualHost'))))
vhs = []
for path in paths:
@@ -425,8 +363,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# search for NameVirtualHost directive for ip_addr
# note ip_addr can be FQDN although Apache does not recommend it
return (self.version >= (2, 4) or
self.find_directive(
case_i("NameVirtualHost"), case_i(str(target_addr))))
self.parser.find_dir(
parser.case_i("NameVirtualHost"),
parser.case_i(str(target_addr))))
def add_name_vhost(self, addr):
"""Adds NameVirtualHost directive for given address.
@@ -434,33 +373,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:param str addr: Address that will be added as NameVirtualHost directive
"""
path = self._add_dir_to_ifmodssl(
get_aug_path(self.location["name"]), "NameVirtualHost", str(addr))
path = self.parser.add_dir_to_ifmodssl(
parser.get_aug_path(
self.parser.loc["name"]), "NameVirtualHost", str(addr))
self.save_notes += "Setting %s to be NameBasedVirtualHost\n" % addr
self.save_notes += "\tDirective added to %s\n" % path
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 _prepare_server_https(self):
"""Prepare the server for HTTPS.
@@ -475,18 +394,18 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Check for Listen 443
# Note: This could be made to also look for ip:443 combo
# TODO: Need to search only open directives and IfMod mod_ssl.c
if len(self.find_directive(case_i("Listen"), "443")) == 0:
if len(self.parser.find_dir(parser.case_i("Listen"), "443")) == 0:
logging.debug("No Listen 443 directive found")
logging.debug("Setting the Apache Server to Listen on port 443")
path = self._add_dir_to_ifmodssl(
get_aug_path(self.location["listen"]), "Listen", "443")
path = self.parser.add_dir_to_ifmodssl(
parser.get_aug_path(self.parser.loc["listen"]), "Listen", "443")
self.save_notes += "Added Listen 443 directive to %s\n" % path
def make_server_sni_ready(self, vhost, default_addr="*:443"):
"""Checks to see if the server is ready for SNI challenges.
:param vhost: VHost to check SNI compatibility
:type vhost: :class:`letsencrypt.client.apache_obj.VH`
:type vhost: :class:`letsencrypt.client.apache.obj.VH`
:param str default_addr: TODO - investigate function further
@@ -509,178 +428,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"based virtual host", addr)
self.add_name_vhost(addr)
def _get_ifmod(self, aug_conf_path, mod):
"""Returns the path to <IfMod mod> 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 type(arg) is not list:
self.aug.set(aug_conf_path + "/directive[last()]/arg", arg)
else:
for i in range(len(arg)):
self.aug.set("%s/directive[last()]/arg[%d]" %
(aug_conf_path, (i+1)),
arg[i])
def find_directive(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:: arg should probably be a list
Note: Augeas is inherently case sensitive while Apache 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 direcitve 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.location["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_directive(
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 Apache Include directive into Augeas path.
Converts an Apache 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 Apache 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 apache 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
# Apache 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 Apache
elif arg.startswith("conf/"):
arg = self.server_root + arg[5:]
# TODO: Test if Apache 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:
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 make_vhost_ssl(self, nonssl_vhost):
"""Makes an ssl_vhost version of a nonssl_vhost.
@@ -688,10 +435,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
New vhost will reside as (nonssl_vhost.path) + CONFIG.LE_VHOST_EXT
:param nonssl_vhost: Valid VH that doesn't have SSLEngine on
:type nonssl_vhost: :class:`letsencrypt.client.apache_obj.VH`
:type nonssl_vhost: :class:`letsencrypt.client.apache.obj.VH`
:returns: SSL vhost
:rtype: :class:`letsencrypt.client.apache_obj.VH`
:rtype: :class:`letsencrypt.client.apache.obj.VH`
"""
avail_fp = nonssl_vhost.filep
@@ -726,10 +473,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# change address to address:443
addr_match = "/files%s//* [label()=~regexp('%s')]/arg"
ssl_addr_p = self.aug.match(
addr_match % (ssl_fp, case_i('VirtualHost')))
addr_match % (ssl_fp, parser.case_i('VirtualHost')))
for i in range(len(ssl_addr_p)):
ssl_addr_arg = apache_obj.Addr.fromstring(
ssl_addr_arg = obj.Addr.fromstring(
str(self.aug.get(ssl_addr_p[i])))
ssl_addr_arg.set_port("443")
self.aug.set(ssl_addr_p[i], str(ssl_addr_arg))
@@ -737,16 +484,16 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Add directives
vh_p = self.aug.match(("/files%s//* [label()=~regexp('%s')]" %
(ssl_fp, case_i('VirtualHost'))))
(ssl_fp, parser.case_i('VirtualHost'))))
if len(vh_p) != 1:
logging.error("Error: should only be one vhost in %s", avail_fp)
sys.exit(1)
self.add_dir(vh_p[0], "SSLCertificateFile",
"/etc/ssl/certs/ssl-cert-snakeoil.pem")
self.add_dir(vh_p[0], "SSLCertificateKeyFile",
"/etc/ssl/private/ssl-cert-snakeoil.key")
self.add_dir(vh_p[0], "Include", self.location["ssl_options"])
self.parser.add_dir(vh_p[0], "SSLCertificateFile",
"/etc/ssl/certs/ssl-cert-snakeoil.pem")
self.parser.add_dir(vh_p[0], "SSLCertificateKeyFile",
"/etc/ssl/private/ssl-cert-snakeoil.key")
self.parser.add_dir(vh_p[0], "Include", self.parser.loc["ssl_options"])
# Log actions and create save notes
logging.info("Created an SSL vhost at %s", ssl_fp)
@@ -785,10 +532,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
The function then adds the directive
:param ssl_vhost: Destination of traffic, an ssl enabled vhost
:type ssl_vhost: :class:`letsencrypt.client.apache_obj.VH`
:type ssl_vhost: :class:`letsencrypt.client.apache.obj.VH`
:returns: Success, general_vhost (HTTP vhost)
:rtype: (bool, :class:`letsencrypt.client.apache_obj.VH`)
:rtype: (bool, :class:`letsencrypt.client.apache.obj.VH`)
"""
# TODO: Enable check to see if it is already there
@@ -812,9 +559,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
logging.debug("Unknown redirect exists for this vhost")
return False, general_v
# Add directives to server
self.add_dir(general_v.path, "RewriteEngine", "On")
self.add_dir(general_v.path,
"RewriteRule", CONFIG.REWRITE_HTTPS_ARGS)
self.parser.add_dir(general_v.path, "RewriteEngine", "On")
self.parser.add_dir(
general_v.path, "RewriteRule", CONFIG.REWRITE_HTTPS_ARGS)
self.save_notes += ('Redirecting host in %s to ssl vhost in %s\n' %
(general_v.filep, ssl_vhost.filep))
self.save()
@@ -834,16 +581,16 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
-1 is also returned in case of no redirection/rewrite directives
:param vhost: vhost to check
:type vhost: :class:`letsencrypt.client.apache_obj.VH`
:type vhost: :class:`letsencrypt.client.apache.obj.VH`
:returns: Success, code value... see documentation
:rtype: bool, int
"""
rewrite_path = self.find_directive(
case_i("RewriteRule"), None, vhost.path)
redirect_path = self.find_directive(
case_i("Redirect"), None, vhost.path)
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"
@@ -865,10 +612,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""Creates an http_vhost specifically to redirect for the ssl_vhost.
:param ssl_vhost: ssl vhost
:type ssl_vhost: :class:`letsencrypt.client.apache_obj.VH`
:type ssl_vhost: :class:`letsencrypt.client.apache.obj.VH`
:returns: Success, vhost
:rtype: (bool, :class:`letsencrypt.client.apache_obj.VH`)
:rtype: (bool, :class:`letsencrypt.client.apache.obj.VH`)
"""
# Consider changing this to a dictionary check
@@ -914,7 +661,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
redirect_filename = "le-redirect-%s.conf" % ssl_vhost.names[0]
redirect_filepath = ("%ssites-available/%s" %
(self.server_root, redirect_filename))
(self.parser.root, redirect_filename))
# Register the new file that will be created
# Note: always register the creation before writing to ensure file will
@@ -928,8 +675,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.aug.load()
# Make a new vhost data structure and add it to the lists
new_fp = self.server_root + "sites-available/" + redirect_filename
new_vhost = self._create_vhost(get_aug_path(new_fp))
new_fp = self.parser.root + "sites-available/" + redirect_filename
new_vhost = self._create_vhost(parser.get_aug_path(new_fp))
self.vhosts.append(new_vhost)
# Finally create documentation for the change
@@ -951,7 +698,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
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.apache_obj.VH`
:type ssl_vhost: :class:`letsencrypt.client.apache.obj.VH`
:returns: TODO
:rtype: TODO
@@ -984,18 +731,18 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
Consider changing this into a dict check
:param ssl_vhost: ssl vhost to check
:type ssl_vhost: :class:`letsencrypt.client.apache_obj.VH`
:type ssl_vhost: :class:`letsencrypt.client.apache.obj.VH`
:returns: HTTP vhost or None if unsuccessful
:rtype: :class:`letsencrypt.client.apache_obj.VH` or None
:rtype: :class:`letsencrypt.client.apache.obj.VH` 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 == apache_obj.Addr.fromstring("_default_:443"):
ssl_addrs = [apache_obj.Addr.fromstring("*:443")]
if ssl_addrs == obj.Addr.fromstring("_default_:443"):
ssl_addrs = [obj.Addr.fromstring("*:443")]
for vhost in self.vhosts:
found = 0
@@ -1038,10 +785,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
for vhost in self.vhosts:
if vhost.ssl:
cert_path = self.find_directive(
case_i("SSLCertificateFile"), None, vhost.path)
key_path = self.find_directive(
case_i("SSLCertificateKeyFile"), None, vhost.path)
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:
@@ -1066,7 +813,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:rtype: bool
"""
enabled_dir = os.path.join(self.server_root, "sites-enabled/")
enabled_dir = os.path.join(self.parser.root, "sites-enabled/")
for entry in os.listdir(enabled_dir):
if os.path.realpath(enabled_dir + entry) == avail_fp:
return True
@@ -1081,7 +828,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
.. todo:: Make sure link is not broken...
:param vhost: vhost to enable
:type vhost: :class:`letsencrypt.client.apache_obj.VH`
:type vhost: :class:`letsencrypt.client.apache.obj.VH`
:returns: Success
:rtype: bool
@@ -1092,7 +839,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if "/sites-available/" in vhost.filep:
enabled_path = ("%ssites-enabled/%s" %
(self.server_root, os.path.basename(vhost.filep)))
(self.parser.root, os.path.basename(vhost.filep)))
self.register_file_creation(False, enabled_path)
os.symlink(vhost.filep, enabled_path)
vhost.enabled = True
@@ -1101,82 +848,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
return True
return False
def fnmatch_to_re(self, clean_fn_match): # pylint: disable=no-self-use
"""Method converts Apache's basic fnmatch to regular expression.
:param str clean_fn_match: Apache style filename match, similar to globs
:returns: regex suitable for augeas
:rtype: str
"""
regex = ""
for letter in clean_fn_match:
if letter == '.':
regex = regex + r"\."
elif letter == '*':
regex = regex + ".*"
# According to apache.org ? shouldn't appear
# but in case it is valid...
elif letter == '?':
regex = regex + "."
else:
regex = regex + letter
return regex
def _parse_file(self, file_path):
"""Parse file with Augeas
Checks to see if file_path is parsed by Augeas
If file_path isn't parsed, the file is added and Augeas is reloaded
:param str file_path: Apache 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']" % file_path)
if not inc_test:
# 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()
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.server_root + "*.augsave",
self.server_root + "*~",
self.server_root + "*/*augsave",
self.server_root + "*/*~",
self.server_root + "*/*/*.augsave",
self.server_root + "*/*/*~"]
for i in range(len(excl)):
self.aug.set("/augeas/load/Httpd/excl[%d]" % (i+1), excl[i])
self.aug.load()
def restart(self, quiet=False): # pylint: disable=no-self-use
"""Restarts apache server.
@@ -1186,19 +857,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
return apache_restart()
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 is broken.
:param str incl: TODO
"""
last_include = self.aug.match("/augeas/load/Httpd/incl [last()]")
self.aug.insert(last_include[0], "incl", False)
self.aug.set("/augeas/load/Httpd/incl[last()]", incl)
def config_test(self):
"""Check the configuration of Apache for errors.
@@ -1376,7 +1034,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:type dvsni_key: :class:`letsencrypt.client.client.Client.Key`
:param list ll_addrs: list of list of
:class:`letsencrypt.client.apache_obj.Addr` to apply
:class:`letsencrypt.client.apache.obj.Addr` to apply
"""
# WARNING: THIS IS A POTENTIAL SECURITY VULNERABILITY
@@ -1398,7 +1056,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
list_sni_tuple[idx][2], lis, dvsni_key.file)
config_text += "</IfModule> \n"
self.dvsni_conf_include_check(self.location["default"])
self.dvsni_conf_include_check(self.parser.loc["default"])
self.register_file_creation(True, CONFIG.APACHE_CHALLENGE_CONF)
with open(CONFIG.APACHE_CHALLENGE_CONF, 'w') as new_conf:
@@ -1413,18 +1071,18 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:param str main_config: file path to main user apache config file
"""
if len(self.find_directive(
case_i("Include"), CONFIG.APACHE_CHALLENGE_CONF)) == 0:
if len(self.parser.find_dir(
parser.case_i("Include"), CONFIG.APACHE_CHALLENGE_CONF)) == 0:
# print "Including challenge virtual host(s)"
self.add_dir(get_aug_path(main_config),
"Include", CONFIG.APACHE_CHALLENGE_CONF)
self.parser.add_dir(parser.get_aug_path(main_config),
"Include", CONFIG.APACHE_CHALLENGE_CONF)
def get_config_text(self, nonce, ip_addrs, dvsni_key_file):
"""Chocolate virtual server configuration text
:param str nonce: hex form of nonce
:param list ip_addrs: addresses of challenged domain
:class:`list` of type :class:`letsencrypt.client.apache_obj.Addr`
:class:`list` of type :class:`letsencrypt.client.apache.obj.Addr`
:param str dvsni_key_file: Path to key file
:returns: virtual host configuration text
@@ -1439,7 +1097,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"\n"
"LimitRequestBody 1048576\n"
"\n"
"Include " + self.location["ssl_options"] + "\n"
"Include " + self.parser.loc["ssl_options"] + "\n"
"SSLCertificateFile " + self.dvsni_get_cert_file(nonce) + "\n"
"SSLCertificateKeyFile " + dvsni_key_file + "\n"
"\n"
@@ -1538,22 +1196,6 @@ def apache_restart():
return True
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_file_path(vhost_path):
"""Get file path from augeas_vhost_path.
@@ -1580,94 +1222,3 @@ def get_file_path(vhost_path):
continue
break
return avail_fp
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 main():
"""Main function used for quick testing purposes"""
config = ApacheConfigurator()
# for v in config.vhosts:
# print v.filep
# print v.addrs
# for name in v.names:
# print name
print config.find_directive(
case_i("NameVirtualHost"), case_i("holla:443"))
# for m in config.find_directive("Listen", "443"):
# print "Directive Path:", m, "Value:", config.aug.get(m)
# for v in config.vhosts:
# for a in v.addrs:
# print "Address:",a, "- Is name vhost?", config.is_name_vhost(a)
# print config.get_all_names()
# test_file = "/home/james/Desktop/ports_test.conf"
# config._parse_file(test_file)
# config.aug.insert("/files"+test_file+"/IfModule[1]/arg","directive",False)
# config.aug.set("/files"+test_file+"/IfModule[1]/directive[1]", "Listen")
# config.aug.set(
# "/files" +test_file+ "/IfModule[1]/directive[1]/arg", "556")
# #config.save_notes = "Added listen 431 for test"
# #config.register_file_creation("/home/james/Desktop/new_file.txt")
# #config.save("Testing Saves", False)
# #config.recover_checkpoint(1)
# # config.display_checkpoints()
config.config_test()
# # Testing redirection and make_vhost_ssl
# ssl_vh = None
# for vh in config.vhosts:
# if not vh.addrs:
# print vh.names
# print vh.filep
# if vh.addrs[0] == "23.20.47.131:80":
# print "Here we go"
# ssl_vh = config.make_vhost_ssl(vh)
# config.enable_redirect(ssl_vh)
# for vh in config.vhosts:
# if len(vh.names) > 0:
# config.deploy_cert(
# vh,
# "/home/james/Documents/apache_choc/req.pem",
# "/home/james/Documents/apache_choc/key.pem",
# "/home/james/Downloads/sub.class1.server.ca.pem")
if __name__ == "__main__":
main()
@@ -15,8 +15,6 @@ from letsencrypt.client import le_util
class AugeasConfigurator(configurator.Configurator):
"""Base Augeas Configurator class.
.. todo:: Fix generic exception handling.
:ivar aug: Augeas object
:type aug: :class:`augeas.Augeas`
@@ -8,12 +8,12 @@ import unittest
import mock
from letsencrypt.client import apache_obj
from letsencrypt.client import apache_configurator
from letsencrypt.client import CONFIG
from letsencrypt.client import display
from letsencrypt.client import errors
from letsencrypt.client.apache import obj
from letsencrypt.client.apache import parser
UBUNTU_CONFIGS = pkg_resources.resource_filename(
__name__, "testdata/debian_apache_2_4")
@@ -62,24 +62,24 @@ class TwoVhost80Test(unittest.TestCase):
self.temp_dir, "two_vhost_80/apache2/sites-available")
aug_pre = "/files" + prefix
self.vh_truth = [
apache_obj.VH(
obj.VH(
os.path.join(prefix, "encryption-example.conf"),
os.path.join(aug_pre, "encryption-example.conf/VirtualHost"),
set([apache_obj.Addr.fromstring("*:80")]),
set([obj.Addr.fromstring("*:80")]),
False, True, set(["encryption-example.demo"])),
apache_obj.VH(
obj.VH(
os.path.join(prefix, "default-ssl.conf"),
os.path.join(aug_pre, "default-ssl.conf/IfModule/VirtualHost"),
set([apache_obj.Addr.fromstring("_default_:443")]), True, False),
apache_obj.VH(
set([obj.Addr.fromstring("_default_:443")]), True, False),
obj.VH(
os.path.join(prefix, "000-default.conf"),
os.path.join(aug_pre, "000-default.conf/VirtualHost"),
set([apache_obj.Addr.fromstring("*:80")]), False, True,
set([obj.Addr.fromstring("*:80")]), False, True,
set(["ip-172-30-0-17"])),
apache_obj.VH(
obj.VH(
os.path.join(prefix, "letsencrypt.conf"),
os.path.join(aug_pre, "letsencrypt.conf/VirtualHost"),
set([apache_obj.Addr.fromstring("*:80")]), False, True,
set([obj.Addr.fromstring("*:80")]), False, True,
set(["letsencrypt.demo"])),
]
@@ -97,7 +97,9 @@ class TwoVhost80Test(unittest.TestCase):
"""
file_path = os.path.join(
self.config_path, "sites-available", "letsencrypt.conf")
self.config._parse_file(file_path) # pylint: disable=protected-access
# pylint: disable=protected-access
self.config.parser._parse_file(file_path)
# search for the httpd incl
matches = self.config.aug.match(
@@ -110,12 +112,12 @@ class TwoVhost80Test(unittest.TestCase):
self.assertEqual(names, set(
['letsencrypt.demo', 'encryption-example.demo', 'ip-172-30-0-17']))
def test_find_directive(self):
test = self.config.find_directive(
apache_configurator.case_i("Listen"), "443")
def test_find_dir(self):
test = self.config.parser.find_dir(
parser.case_i("Listen"), "443")
# This will only look in enabled hosts
test2 = self.config.find_directive(
apache_configurator.case_i("documentroot"))
test2 = self.config.parser.find_dir(
parser.case_i("documentroot"))
self.assertEqual(len(test), 2)
self.assertEqual(len(test2), 3)
@@ -139,26 +141,26 @@ class TwoVhost80Test(unittest.TestCase):
self.assertTrue(self.config.is_site_enabled(self.vh_truth[3].filep))
def test_add_dir(self):
aug_default = "/files" + self.config.location["default"]
self.config.add_dir(
aug_default = "/files" + self.config.parser.loc["default"]
self.config.parser.add_dir(
aug_default, "AddDirective", "test")
self.assertTrue(
self.config.find_directive("AddDirective", "test", aug_default))
self.config.parser.find_dir("AddDirective", "test", aug_default))
def test_deploy_cert(self):
self.config.deploy_cert(
self.vh_truth[1],
"example/cert.pem", "example/key.pem", "example/cert_chain.pem")
loc_cert = self.config.find_directive(
apache_configurator.case_i("sslcertificatefile"),
loc_cert = self.config.parser.find_dir(
parser.case_i("sslcertificatefile"),
re.escape("example/cert.pem"), self.vh_truth[1].path)
loc_key = self.config.find_directive(
apache_configurator.case_i("sslcertificateKeyfile"),
loc_key = self.config.parser.find_dir(
parser.case_i("sslcertificateKeyfile"),
re.escape("example/key.pem"), self.vh_truth[1].path)
loc_chain = self.config.find_directive(
apache_configurator.case_i("SSLCertificateChainFile"),
loc_chain = self.config.parser.find_dir(
parser.case_i("SSLCertificateChainFile"),
re.escape("example/cert_chain.pem"), self.vh_truth[1].path)
# Verify one directive was found in the correct file
@@ -175,27 +177,27 @@ class TwoVhost80Test(unittest.TestCase):
self.vh_truth[1].filep)
def test_is_name_vhost(self):
addr = apache_obj.Addr.fromstring("*:80")
addr = obj.Addr.fromstring("*:80")
self.assertTrue(self.config.is_name_vhost(addr))
self.config.version = (2, 2)
self.assertFalse(self.config.is_name_vhost(addr))
def test_add_name_vhost(self):
self.config.add_name_vhost("*:443")
# self.config.save(temporary=True)
self.assertTrue(self.config.find_directive(
self.assertTrue(self.config.parser.find_dir(
"NameVirtualHost", re.escape("*:443")))
def test_add_dir_to_ifmodssl(self):
"""test _add_dir_to_ifmodssl.
"""test add_dir_to_ifmodssl.
Path must be valid before attempting to add to augeas
"""
self.config._add_dir_to_ifmodssl( # pylint: disable=protected-access
"/files" + self.config.location["default"], "FakeDirective", "123")
self.config.parser.add_dir_to_ifmodssl(
parser.get_aug_path(self.config.parser.loc["default"]),
"FakeDirective", "123")
matches = self.config.find_directive("FakeDirective", "123")
matches = self.config.parser.find_dir("FakeDirective", "123")
self.assertEqual(len(matches), 1)
self.assertTrue("IfModule" in matches[0])
@@ -210,16 +212,16 @@ class TwoVhost80Test(unittest.TestCase):
self.assertEqual(ssl_vhost.path,
"/files" + ssl_vhost.filep + "/IfModule/VirtualHost")
self.assertEqual(ssl_vhost.addrs, set([apache_obj.Addr.fromstring("*:443")]))
self.assertEqual(ssl_vhost.addrs, set([obj.Addr.fromstring("*:443")]))
self.assertEqual(ssl_vhost.names, set(["encryption-example.demo"]))
self.assertTrue(ssl_vhost.ssl)
self.assertFalse(ssl_vhost.enabled)
self.assertTrue(self.config.find_directive(
self.assertTrue(self.config.parser.find_dir(
"SSLCertificateFile", None, ssl_vhost.path))
self.assertTrue(self.config.find_directive(
self.assertTrue(self.config.parser.find_dir(
"SSLCertificateKeyFile", None, ssl_vhost.path))
self.assertTrue(self.config.find_directive(
self.assertTrue(self.config.parser.find_dir(
"Include", self.ssl_options, ssl_vhost.path))
self.assertEqual(self.config.is_name_vhost(self.vh_truth[0]),
+1
View File
@@ -35,6 +35,7 @@ setup(
packages=[
'letsencrypt',
'letsencrypt.client',
'letsencrypt.client.apache',
'letsencrypt.scripts',
],
install_requires=install_requires,