From 5dce1e15abb99aa5b08260b9f1b1dc1a63cd7a57 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Sun, 22 Nov 2015 01:39:57 -0800 Subject: [PATCH 01/39] If we're going to have --webroot-map, make integrate it fully [tests broken] * -d is implied for things included in it * if --webroot-path and -w are both used, the later does not override explicit entries in the former --- letsencrypt/cli.py | 28 ++++++++++++++++------------ letsencrypt/tests/cli_test.py | 3 +++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 9c4c4a5f5..19cfc76f9 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -101,7 +101,13 @@ def usage_strings(plugins): def _find_domains(args, installer): - if args.domains is None: + # we get domains from -d, but also from the webroot map... + if args.webroot_map: + for domain in args.webroot_map.keys(): + if domain not in args.domains: + args.domains.append(domain) + + if not args.domains: domains = display_ops.choose_names(installer) else: domains = args.domains @@ -474,7 +480,7 @@ def run(args, config, plugins): # pylint: disable=too-many-branches,too-many-lo def obtain_cert(args, config, plugins): - """Authenticate & obtain cert, but do not install it.""" + """Implements "certonly": authenticate & obtain cert, but do not install it.""" if args.domains is not None and args.csr is not None: # TODO: --csr could have a priority, when --domains is @@ -839,7 +845,7 @@ def prepare_and_parse_args(plugins, args): # --domains is useful, because it can be stored in config #for subparser in parser_run, parser_auth, parser_install: # subparser.add_argument("domains", nargs="*", metavar="domain") - helpful.add(None, "-d", "--domains", dest="domains", + helpful.add(None, "-d", "--domains", dest="domains", default=[], metavar="DOMAIN", action=DomainFlagProcessor, help="Domain names to apply. For multiple domains you can use " "multiple -d flags or enter a comma separated list of domains " @@ -1038,7 +1044,7 @@ def _plugins_parsing(helpful, plugins): help="public_html / webroot path") parse_dict = lambda s: dict(json.loads(s)) helpful.add("webroot", "--webroot-map", default={}, type=parse_dict, - help="Mapping from domains to webroot paths") + help="JSON dictionary mapping domains to webroot paths; this implies -d for each entry.") class WebrootPathProcessor(argparse.Action): # pylint: disable=missing-docstring @@ -1047,15 +1053,15 @@ class WebrootPathProcessor(argparse.Action): # pylint: disable=missing-docstring Keep a record of --webroot-path / -w flags during processing, so that we know which apply to which -d flags """ - if not config.webroot_path: + if config.webroot_path is None: # first -w flag encountered config.webroot_path = [] # if any --domain flags preceded the first --webroot-path flag, # apply that webroot path to those; subsequent entries in # config.webroot_map are filled in by cli.DomainFlagProcessor if config.domains: - config.webroot_map = dict([(d, webroot) for d in config.domains]) - else: - config.webroot_map = {} + for d in config.domains: + config.webroot_map.setdefault(d, webroot) + config.webroot_path.append(webroot) @@ -1065,15 +1071,13 @@ class DomainFlagProcessor(argparse.Action): # pylint: disable=missing-docstring Process a new -d flag, helping the webroot plugin construct a map of {domain : webrootpath} if -w / --webroot-path is in use """ - if not config.domains: - config.domains = [] - for d in map(string.strip, domain_arg.split(",")): # pylint: disable=bad-builtin if d not in config.domains: config.domains.append(d) # Each domain has a webroot_path of the most recent -w flag + # unless it was explicitly included in webroot_map if config.webroot_path: - config.webroot_map[d] = config.webroot_path[-1] + config.webroot_map.setdefault(d, config.webroot_path[-1]) def setup_log_file_handler(args, logfile, fmt): diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index 853109636..60f3c245a 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -341,6 +341,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods namespace = cli.prepare_and_parse_args(plugins, long_args) self.assertEqual(namespace.domains, ['example.com', 'another.net']) + def test_parse_webroot(self): plugins = disco.PluginsRegistry.find_all() webroot_args = ['--webroot', '-d', 'stray.example.com', '-w', @@ -356,7 +357,9 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods webroot_map_args = ['--webroot-map', '{"eg.com" : "/tmp"}'] namespace = cli.prepare_and_parse_args(plugins, webroot_map_args) + domains = cli._find_domains(namespace, mock.MagicMock()) self.assertEqual(namespace.webroot_map, {u"eg.com": u"/tmp"}) + self.assertEqual(domains, ["eg.com"]) @mock.patch('letsencrypt.crypto_util.notAfter') @mock.patch('letsencrypt.cli.zope.component.getUtility') From c816910c0d811377bd5a3823609d720ba7b30554 Mon Sep 17 00:00:00 2001 From: Filip Ochnik Date: Wed, 20 Jan 2016 14:25:22 +0700 Subject: [PATCH 02/39] Support trailing period in domain names --- letsencrypt/cli.py | 1 + letsencrypt/tests/cli_test.py | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 89606089f..58d7609fd 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -1236,6 +1236,7 @@ class DomainFlagProcessor(argparse.Action): # pylint: disable=missing-docstring {domain : webrootpath} if -w / --webroot-path is in use """ for domain in (d.strip() for d in domain_arg.split(",")): + domain = domain[:-1] if domain.endswith('.') else domain if domain not in config.domains: config.domains.append(domain) # Each domain has a webroot_path of the most recent -w flag diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index 16ef5c093..8424c7a51 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -330,6 +330,10 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods namespace = cli.prepare_and_parse_args(plugins, short_args) self.assertEqual(namespace.domains, ['example.com']) + short_args = ['-d', 'trailing.period.com.'] + namespace = cli.prepare_and_parse_args(plugins, short_args) + self.assertEqual(namespace.domains, ['trailing.period.com']) + short_args = ['-d', 'example.com,another.net,third.org,example.com'] namespace = cli.prepare_and_parse_args(plugins, short_args) self.assertEqual(namespace.domains, ['example.com', 'another.net', @@ -339,6 +343,10 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods namespace = cli.prepare_and_parse_args(plugins, long_args) self.assertEqual(namespace.domains, ['example.com']) + long_args = ['--domains', 'trailing.period.com.'] + namespace = cli.prepare_and_parse_args(plugins, long_args) + self.assertEqual(namespace.domains, ['trailing.period.com']) + long_args = ['--domains', 'example.com,another.net,example.com'] namespace = cli.prepare_and_parse_args(plugins, long_args) self.assertEqual(namespace.domains, ['example.com', 'another.net']) @@ -360,7 +368,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods plugins = disco.PluginsRegistry.find_all() webroot_args = ['--webroot', '-w', '/var/www/example', '-d', 'example.com,www.example.com', '-w', '/var/www/superfluous', - '-d', 'superfluo.us', '-d', 'www.superfluo.us'] + '-d', 'superfluo.us', '-d', 'www.superfluo.us.'] namespace = cli.prepare_and_parse_args(plugins, webroot_args) self.assertEqual(namespace.webroot_map, { 'example.com': '/var/www/example', From 94f24a5982990fa111b46ba69e13633f4ae798ee Mon Sep 17 00:00:00 2001 From: Filip Ochnik Date: Thu, 21 Jan 2016 13:01:10 +0700 Subject: [PATCH 03/39] Support trailing periods in webroot-map --- letsencrypt/cli.py | 13 ++++++++++--- letsencrypt/tests/cli_test.py | 6 ++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 58d7609fd..63c7604aa 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -1196,10 +1196,9 @@ def _plugins_parsing(helpful, plugins): "handle different domains; each domain will have the webroot path that" " preceded it. For instance: `-w /var/www/example -d example.com -d " "www.example.com -w /var/www/thing -d thing.net -d m.thing.net`") - parse_dict = lambda s: dict(json.loads(s)) # --webroot-map still has some awkward properties, so it is undocumented - helpful.add("webroot", "--webroot-map", default={}, type=parse_dict, - help=argparse.SUPPRESS) + helpful.add("webroot", "--webroot-map", default={}, + action=WebrootMapProcessor, help=argparse.SUPPRESS) class WebrootPathProcessor(argparse.Action): # pylint: disable=missing-docstring @@ -1229,6 +1228,14 @@ class WebrootPathProcessor(argparse.Action): # pylint: disable=missing-docstring config.webroot_path.append(webroot) +class WebrootMapProcessor(argparse.Action): # pylint: disable=missing-docstring + def __call__(self, parser, config, webroot_map_arg, option_string=None): + webroot_map = json.loads(webroot_map_arg) + for domain, webroot in webroot_map.iteritems(): + domain = domain[:-1] if domain.endswith('.') else domain + config.webroot_map[domain] = webroot + + class DomainFlagProcessor(argparse.Action): # pylint: disable=missing-docstring def __call__(self, parser, config, domain_arg, option_string=None): """ diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index 8424c7a51..19bdb058f 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -379,9 +379,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods webroot_args = ['-d', 'stray.example.com'] + webroot_args self.assertRaises(errors.Error, cli.prepare_and_parse_args, plugins, webroot_args) - webroot_map_args = ['--webroot-map', '{"eg.com" : "/tmp"}'] + webroot_map_args = ['--webroot-map', + '{"eg.com": "/tmp", "www.eg.com.": "/tmp"}'] namespace = cli.prepare_and_parse_args(plugins, webroot_map_args) - self.assertEqual(namespace.webroot_map, {u"eg.com": u"/tmp"}) + self.assertEqual(namespace.webroot_map, + {u"eg.com": u"/tmp", u"www.eg.com": u"/tmp"}) @mock.patch('letsencrypt.cli._suggest_donate') @mock.patch('letsencrypt.crypto_util.notAfter') From 1a12aa01b4bdade8cd4751474fcaaf5891083eef Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Tue, 26 Jan 2016 10:17:34 -0800 Subject: [PATCH 04/39] make different options ssl conf for centos --- .../centos-options-ssl-apache.conf | 21 +++++++++++++++++++ .../letsencrypt_apache/constants.py | 17 ++++++++------- 2 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 letsencrypt-apache/letsencrypt_apache/centos-options-ssl-apache.conf diff --git a/letsencrypt-apache/letsencrypt_apache/centos-options-ssl-apache.conf b/letsencrypt-apache/letsencrypt_apache/centos-options-ssl-apache.conf new file mode 100644 index 000000000..fbe8da0f2 --- /dev/null +++ b/letsencrypt-apache/letsencrypt_apache/centos-options-ssl-apache.conf @@ -0,0 +1,21 @@ +# Baseline setting to Include for SSL sites + +SSLEngine on + +# Intermediate configuration, tweak to your needs +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA +SSLHonorCipherOrder on + +SSLOptions +StrictRequire + +# Add vhost name to log entries: +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined +LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common + +#CustomLog /var/log/apache2/access.log vhost_combined +#LogLevel warn +#ErrorLog /var/log/apache2/error.log + +# Always ensure Cookies have "Secure" set (JAH 2012/1) +#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/letsencrypt-apache/letsencrypt_apache/constants.py b/letsencrypt-apache/letsencrypt_apache/constants.py index fe5ef3335..72ff384f7 100644 --- a/letsencrypt-apache/letsencrypt_apache/constants.py +++ b/letsencrypt-apache/letsencrypt_apache/constants.py @@ -16,7 +16,9 @@ CLI_DEFAULTS_DEBIAN = dict( le_vhost_ext="-le-ssl.conf", handle_mods=True, handle_sites=True, - challenge_location="/etc/apache2" + challenge_location="/etc/apache2", + MOD_SSL_CONF_SRC = pkg_resources.resource_filename( + "letsencrypt_apache", "options-ssl-apache.conf") ) CLI_DEFAULTS_CENTOS = dict( server_root="/etc/httpd", @@ -31,7 +33,9 @@ CLI_DEFAULTS_CENTOS = dict( le_vhost_ext="-le-ssl.conf", handle_mods=False, handle_sites=False, - challenge_location="/etc/httpd/conf.d" + challenge_location="/etc/httpd/conf.d", + MOD_SSL_CONF_SRC = pkg_resources.resource_filename( + "letsencrypt_apache", "centos-options-ssl-apache.conf") ) CLI_DEFAULTS_GENTOO = dict( server_root="/etc/apache2", @@ -46,7 +50,9 @@ CLI_DEFAULTS_GENTOO = dict( le_vhost_ext="-le-ssl.conf", handle_mods=False, handle_sites=False, - challenge_location="/etc/apache2/vhosts.d" + challenge_location="/etc/apache2/vhosts.d", + MOD_SSL_CONF_SRC = pkg_resources.resource_filename( + "letsencrypt_apache", "options-ssl-apache.conf") ) CLI_DEFAULTS = { "debian": CLI_DEFAULTS_DEBIAN, @@ -62,11 +68,6 @@ CLI_DEFAULTS = { MOD_SSL_CONF_DEST = "options-ssl-apache.conf" """Name of the mod_ssl config file as saved in `IConfig.config_dir`.""" -MOD_SSL_CONF_SRC = pkg_resources.resource_filename( - "letsencrypt_apache", "options-ssl-apache.conf") -"""Path to the Apache mod_ssl config file found in the Let's Encrypt -distribution.""" - AUGEAS_LENS_DIR = pkg_resources.resource_filename( "letsencrypt_apache", "augeas_lens") """Path to the Augeas lens directory""" From 1a14b4c8d5ec67115f41deec71fd15c4cd350be5 Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Tue, 26 Jan 2016 10:39:54 -0800 Subject: [PATCH 05/39] fix mapping issue --- letsencrypt-apache/letsencrypt_apache/configurator.py | 2 +- letsencrypt-apache/letsencrypt_apache/constants.py | 6 +++--- letsencrypt-apache/letsencrypt_apache/tests/util.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 8818923f4..8cd26e32b 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -1587,4 +1587,4 @@ def install_ssl_options_conf(options_ssl): # Check to make sure options-ssl.conf is installed if not os.path.isfile(options_ssl): - shutil.copyfile(constants.MOD_SSL_CONF_SRC, options_ssl) + shutil.copyfile(constants.os_constant("MOD_SSL_CONF_SRC"), options_ssl) diff --git a/letsencrypt-apache/letsencrypt_apache/constants.py b/letsencrypt-apache/letsencrypt_apache/constants.py index 72ff384f7..50156444b 100644 --- a/letsencrypt-apache/letsencrypt_apache/constants.py +++ b/letsencrypt-apache/letsencrypt_apache/constants.py @@ -17,7 +17,7 @@ CLI_DEFAULTS_DEBIAN = dict( handle_mods=True, handle_sites=True, challenge_location="/etc/apache2", - MOD_SSL_CONF_SRC = pkg_resources.resource_filename( + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( "letsencrypt_apache", "options-ssl-apache.conf") ) CLI_DEFAULTS_CENTOS = dict( @@ -34,7 +34,7 @@ CLI_DEFAULTS_CENTOS = dict( handle_mods=False, handle_sites=False, challenge_location="/etc/httpd/conf.d", - MOD_SSL_CONF_SRC = pkg_resources.resource_filename( + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( "letsencrypt_apache", "centos-options-ssl-apache.conf") ) CLI_DEFAULTS_GENTOO = dict( @@ -51,7 +51,7 @@ CLI_DEFAULTS_GENTOO = dict( handle_mods=False, handle_sites=False, challenge_location="/etc/apache2/vhosts.d", - MOD_SSL_CONF_SRC = pkg_resources.resource_filename( + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( "letsencrypt_apache", "options-ssl-apache.conf") ) CLI_DEFAULTS = { diff --git a/letsencrypt-apache/letsencrypt_apache/tests/util.py b/letsencrypt-apache/letsencrypt_apache/tests/util.py index fb86d2320..ff60c05e3 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/util.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/util.py @@ -33,7 +33,7 @@ class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods pkg="letsencrypt_apache.tests") self.ssl_options = common.setup_ssl_options( - self.config_dir, constants.MOD_SSL_CONF_SRC, + self.config_dir, constants.os_constant("MOD_SSL_CONF_SRC"), constants.MOD_SSL_CONF_DEST) self.config_path = os.path.join(self.temp_dir, config_root) From 8ba59406adfa48b49437b34120820613784e2a82 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 26 Jan 2016 11:40:44 -0800 Subject: [PATCH 06/39] When in doubt, use the config root --- .../letsencrypt_apache/parser.py | 19 +------------------ .../letsencrypt_apache/tests/parser_test.py | 12 +----------- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/parser.py b/letsencrypt-apache/letsencrypt_apache/parser.py index cc7f2ec42..3c13aae5f 100644 --- a/letsencrypt-apache/letsencrypt_apache/parser.py +++ b/letsencrypt-apache/letsencrypt_apache/parser.py @@ -597,7 +597,7 @@ class ApacheParser(object): .. todo:: Make sure that files are included """ - default = self._set_user_config_file() + default = self.loc["root"] temp = os.path.join(self.root, "ports.conf") if os.path.isfile(temp): @@ -618,23 +618,6 @@ class ApacheParser(object): raise errors.NoInstallationError("Could not find configuration root") - def _set_user_config_file(self): - """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 Apache 2.2 - if (os.path.isfile(os.path.join(self.root, "httpd.conf")) and - self.find_dir("Include", "httpd.conf", self.loc["root"])): - 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. diff --git a/letsencrypt-apache/letsencrypt_apache/tests/parser_test.py b/letsencrypt-apache/letsencrypt_apache/tests/parser_test.py index e976bc9f6..2e6481aba 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/parser_test.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/parser_test.py @@ -106,7 +106,7 @@ class BasicParserTest(util.ParserTest): def test_set_locations(self): with mock.patch("letsencrypt_apache.parser.os.path") as mock_path: - mock_path.isfile.side_effect = [True, False, False] + mock_path.isfile.side_effect = [False, False] # pylint: disable=protected-access results = self.parser._set_locations() @@ -114,16 +114,6 @@ class BasicParserTest(util.ParserTest): self.assertEqual(results["default"], results["listen"]) self.assertEqual(results["default"], results["name"]) - def test_set_user_config_file(self): - # pylint: disable=protected-access - path = os.path.join(self.parser.root, "httpd.conf") - open(path, 'w').close() - self.parser.add_dir(self.parser.loc["default"], "Include", - "httpd.conf") - - self.assertEqual( - path, self.parser._set_user_config_file()) - @mock.patch("letsencrypt_apache.parser.ApacheParser._get_runtime_cfg") def test_update_runtime_variables(self, mock_cfg): mock_cfg.return_value = ( From 80ce6e29426d56f1ccbcd53ebd383a9b77614a7c Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Tue, 26 Jan 2016 13:42:56 -0800 Subject: [PATCH 07/39] initial servername write test --- .../letsencrypt_apache/configurator.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 8818923f4..9869c9029 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -298,12 +298,12 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): return self.assoc[target_name] # Try to find a reasonable vhost - vhost = self._find_best_vhost(target_name) + vhost, is_generic_host = self._find_best_vhost(target_name) if vhost is not None: if temp: return vhost if not vhost.ssl: - vhost = self.make_vhost_ssl(vhost) + vhost = self.make_vhost_ssl(vhost, is_generic_host, target_name) self.assoc[target_name] = vhost return vhost @@ -353,6 +353,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Points 1 - Address name with no SSL best_candidate = None best_points = 0 + is_generic_host = False for vhost in self.vhosts: if vhost.modmacro is True: @@ -364,6 +365,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): else: # No points given if names can't be found. # This gets hit but doesn't register + is_generic_host = True continue # pragma: no cover if vhost.ssl: @@ -383,7 +385,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if len(reasonable_vhosts) == 1: best_candidate = reasonable_vhosts[0] - return best_candidate + return (best_candidate, is_generic_host) def _non_default_vhosts(self): """Return all non _default_ only vhosts.""" @@ -666,7 +668,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): "based virtual host", addr) self.add_name_vhost(addr) - def make_vhost_ssl(self, nonssl_vhost): # pylint: disable=too-many-locals + def make_vhost_ssl(self, nonssl_vhost, is_generic_host=False, target_name=None): # pylint: disable=too-many-locals """Makes an ssl_vhost version of a nonssl_vhost. Duplicates vhost and adds default ssl options @@ -692,7 +694,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Reload augeas to take into account the new vhost self.aug.load() - + #TODO: add line to write vhost name # Get Vhost augeas path for new vhost vh_p = self.aug.match("/files%s//* [label()=~regexp('%s')]" % (ssl_fp, parser.case_i("VirtualHost"))) @@ -709,6 +711,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Add directives self._add_dummy_ssl_directives(vh_p) + if is_generic_host: + self._add_servername(target_name, vh_p) # Log actions and create save notes logger.info("Created an SSL vhost at %s", ssl_fp) @@ -859,6 +863,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): "insert_key_file_path") self.parser.add_dir(vh_path, "Include", self.mod_ssl_conf) + def _add_servername(self, servername, vh_path): + self.parser.add_dir(vh_path, "ServerName", servername) + self.parser.add_dir(vh_path, "ServerAlias", servername) + def _add_name_vhost_if_necessary(self, vhost): """Add NameVirtualHost Directives if necessary for new vhost. From 4bae5b432ce82eb5f2859a0b04cb2e9a50f97fcd Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Tue, 26 Jan 2016 13:53:43 -0800 Subject: [PATCH 08/39] fix tests --- .../letsencrypt_apache/tests/configurator_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py index 00a98e33a..002520a05 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py @@ -188,12 +188,12 @@ class TwoVhost80Test(util.ApacheTest): def test_find_best_vhost(self): # pylint: disable=protected-access self.assertEqual( - self.vh_truth[3], self.config._find_best_vhost("letsencrypt.demo")) + (self.vh_truth[3], True), self.config._find_best_vhost("letsencrypt.demo")) self.assertEqual( - self.vh_truth[0], + (self.vh_truth[0], True), self.config._find_best_vhost("encryption-example.demo")) - self.assertTrue( - self.config._find_best_vhost("does-not-exist.com") is None) + self.assertEqual( + self.config._find_best_vhost("does-not-exist.com"), (None, True)) def test_find_best_vhost_variety(self): # pylint: disable=protected-access @@ -202,7 +202,7 @@ class TwoVhost80Test(util.ApacheTest): obj.Addr(("zombo.com",))]), True, False) self.config.vhosts.append(ssl_vh) - self.assertEqual(self.config._find_best_vhost("zombo.com"), ssl_vh) + self.assertEqual(self.config._find_best_vhost("zombo.com"), (ssl_vh, True)) def test_find_best_vhost_default(self): # pylint: disable=protected-access @@ -213,7 +213,7 @@ class TwoVhost80Test(util.ApacheTest): ] self.assertEqual( - self.config._find_best_vhost("example.demo"), self.vh_truth[2]) + self.config._find_best_vhost("example.demo"), (self.vh_truth[2], True)) def test_non_default_vhosts(self): # pylint: disable=protected-access From 870a743ce1f2b797157bae770bd5b89df89006d8 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 26 Jan 2016 18:09:55 -0800 Subject: [PATCH 09/39] Detect * and _default_ conflict --- letsencrypt-apache/letsencrypt_apache/configurator.py | 11 ++++++++++- .../letsencrypt_apache/tests/configurator_test.py | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 8818923f4..745a00719 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -874,9 +874,18 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # See if the exact address appears in any other vhost # Remember 1.1.1.1:* == 1.1.1.1 -> hence any() for addr in vhost.addrs: + + # In Apache 2.2, when a NameVirtualHost directive is not + # set, "*" and "_default_" will conflict when sharing a port + addrs = [addr] + if addr.get_addr() == "*": + addrs.append(obj.Addr(("_default_", addr.get_port(),))) + elif addr.get_addr() == "_default_": + addrs.append(obj.Addr(("*", addr.get_port(),))) + for test_vh in self.vhosts: if (vhost.filep != test_vh.filep and - any(test_addr == addr for + any(test_addr in addrs for test_addr in test_vh.addrs) and not self.is_name_vhost(addr)): self.add_name_vhost(addr) diff --git a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py index 00a98e33a..cae5869dd 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py @@ -606,6 +606,14 @@ class TwoVhost80Test(util.ApacheTest): self.config._add_name_vhost_if_necessary(self.vh_truth[0]) self.assertTrue(self.config.save.called) + new_addrs = set() + for addr in self.vh_truth[0].addrs: + new_addrs.add(obj.Addr(("_default_", addr.get_port(),))) + + self.vh_truth[0].addrs = new_addrs + self.config._add_name_vhost_if_necessary(self.vh_truth[0]) + self.assertEqual(self.config.save.call_count, 2) + @mock.patch("letsencrypt_apache.configurator.tls_sni_01.ApacheTlsSni01.perform") @mock.patch("letsencrypt_apache.configurator.ApacheConfigurator.restart") def test_perform(self, mock_restart, mock_perform): From ff98ae3f2270dcca3426f6f14892ebad146f5c75 Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Thu, 28 Jan 2016 13:25:10 -0800 Subject: [PATCH 10/39] remove is_generic always write target_name --- .../letsencrypt_apache/configurator.py | 25 +++++++++---------- .../tests/configurator_test.py | 10 ++++---- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 9869c9029..7d603cfb5 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -298,12 +298,12 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): return self.assoc[target_name] # Try to find a reasonable vhost - vhost, is_generic_host = self._find_best_vhost(target_name) + vhost = self._find_best_vhost(target_name) if vhost is not None: if temp: return vhost if not vhost.ssl: - vhost = self.make_vhost_ssl(vhost, is_generic_host, target_name) + vhost = self.make_vhost_ssl(vhost, target_name) self.assoc[target_name] = vhost return vhost @@ -326,7 +326,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # TODO: Conflicts is too conservative if not any(vhost.enabled and vhost.conflicts(addrs) for vhost in self.vhosts): - vhost = self.make_vhost_ssl(vhost) + vhost = self.make_vhost_ssl(vhost, target_name) else: logger.error( "The selected vhost would conflict with other HTTPS " @@ -353,8 +353,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Points 1 - Address name with no SSL best_candidate = None best_points = 0 - is_generic_host = False - for vhost in self.vhosts: if vhost.modmacro is True: continue @@ -365,7 +363,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): else: # No points given if names can't be found. # This gets hit but doesn't register - is_generic_host = True continue # pragma: no cover if vhost.ssl: @@ -385,7 +382,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if len(reasonable_vhosts) == 1: best_candidate = reasonable_vhosts[0] - return (best_candidate, is_generic_host) + return (best_candidate) def _non_default_vhosts(self): """Return all non _default_ only vhosts.""" @@ -668,7 +665,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): "based virtual host", addr) self.add_name_vhost(addr) - def make_vhost_ssl(self, nonssl_vhost, is_generic_host=False, target_name=None): # pylint: disable=too-many-locals + def make_vhost_ssl(self, nonssl_vhost, target_name=None): # pylint: disable=too-many-locals """Makes an ssl_vhost version of a nonssl_vhost. Duplicates vhost and adds default ssl options @@ -711,8 +708,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Add directives self._add_dummy_ssl_directives(vh_p) - if is_generic_host: - self._add_servername(target_name, vh_p) + if target_name: + self._add_servername_alias(target_name, vh_p) # Log actions and create save notes logger.info("Created an SSL vhost at %s", ssl_fp) @@ -863,9 +860,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): "insert_key_file_path") self.parser.add_dir(vh_path, "Include", self.mod_ssl_conf) - def _add_servername(self, servername, vh_path): - self.parser.add_dir(vh_path, "ServerName", servername) - self.parser.add_dir(vh_path, "ServerAlias", servername) + def _add_servername_alias(self, target_name, vh_path): + if not self.parser.find_dir("ServerName", None, start=vh_path, exclude=False): + self.parser.add_dir(vh_path, "ServerName", target_name) + else: + self.parser.add_dir(vh_path, "ServerAlias", target_name) def _add_name_vhost_if_necessary(self, vhost): """Add NameVirtualHost Directives if necessary for new vhost. diff --git a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py index 002520a05..887dcfe02 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py @@ -188,12 +188,12 @@ class TwoVhost80Test(util.ApacheTest): def test_find_best_vhost(self): # pylint: disable=protected-access self.assertEqual( - (self.vh_truth[3], True), self.config._find_best_vhost("letsencrypt.demo")) + self.vh_truth[3], self.config._find_best_vhost("letsencrypt.demo")) self.assertEqual( - (self.vh_truth[0], True), + self.vh_truth[0], self.config._find_best_vhost("encryption-example.demo")) self.assertEqual( - self.config._find_best_vhost("does-not-exist.com"), (None, True)) + self.config._find_best_vhost("does-not-exist.com"), None) def test_find_best_vhost_variety(self): # pylint: disable=protected-access @@ -202,7 +202,7 @@ class TwoVhost80Test(util.ApacheTest): obj.Addr(("zombo.com",))]), True, False) self.config.vhosts.append(ssl_vh) - self.assertEqual(self.config._find_best_vhost("zombo.com"), (ssl_vh, True)) + self.assertEqual(self.config._find_best_vhost("zombo.com"), ssl_vh) def test_find_best_vhost_default(self): # pylint: disable=protected-access @@ -213,7 +213,7 @@ class TwoVhost80Test(util.ApacheTest): ] self.assertEqual( - self.config._find_best_vhost("example.demo"), (self.vh_truth[2], True)) + self.config._find_best_vhost("example.demo"), self.vh_truth[2]) def test_non_default_vhosts(self): # pylint: disable=protected-access From e7507e535472021f38d76dd21f63048c8e8d1dd9 Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Thu, 28 Jan 2016 14:53:47 -0800 Subject: [PATCH 11/39] don't double write servers --- letsencrypt-apache/letsencrypt_apache/configurator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 7d603cfb5..1abcf5b29 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -382,7 +382,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if len(reasonable_vhosts) == 1: best_candidate = reasonable_vhosts[0] - return (best_candidate) + return best_candidate def _non_default_vhosts(self): """Return all non _default_ only vhosts.""" @@ -861,6 +861,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): self.parser.add_dir(vh_path, "Include", self.mod_ssl_conf) def _add_servername_alias(self, target_name, vh_path): + if (self.parser.find_dir("ServerName", target_name, start=vh_path, exclude=False) + or self.parser.find_dir("ServerAlias", target_name, start=vh_path, exclude=False)): + return if not self.parser.find_dir("ServerName", None, start=vh_path, exclude=False): self.parser.add_dir(vh_path, "ServerName", target_name) else: From 63851bfa52ce76a7a23b2fb379dd87a74e2d9bd0 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 28 Jan 2016 14:54:11 -0800 Subject: [PATCH 12/39] Treat webroot_map -> domain importation as a general property of configs --- letsencrypt/cli.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index c15c9e6a6..e703c83f9 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -113,12 +113,6 @@ def usage_strings(plugins): def _find_domains(args, installer): - # we get domains from -d, but also from the webroot map... - if args.webroot_map: - for domain in args.webroot_map.keys(): - if domain not in args.domains: - args.domains.append(domain) - if not args.domains: domains = display_ops.choose_names(installer) else: @@ -835,6 +829,12 @@ class HelpfulArgumentParser(object): # Do any post-parsing homework here + # we get domains from -d, but also from the webroot map... + if parsed_args.webroot_map: + for domain in parsed_args.webroot_map.keys(): + if domain not in parsed_args.domains: + parsed_args.domains.append(domain) + # argparse seemingly isn't flexible enough to give us this behaviour easily... if parsed_args.staging: if parsed_args.server not in (flag_default("server"), constants.STAGING_URI): From 1417dc43cd8d39bb5773c123a27dfd42341946bd Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Thu, 28 Jan 2016 15:38:14 -0800 Subject: [PATCH 13/39] save ssl directives --- letsencrypt-apache/letsencrypt_apache/configurator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 1abcf5b29..cd41a02cf 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -708,6 +708,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Add directives self._add_dummy_ssl_directives(vh_p) + self.save("don't lose ssl directives", True) if target_name: self._add_servername_alias(target_name, vh_p) @@ -863,7 +864,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): def _add_servername_alias(self, target_name, vh_path): if (self.parser.find_dir("ServerName", target_name, start=vh_path, exclude=False) or self.parser.find_dir("ServerAlias", target_name, start=vh_path, exclude=False)): - return + return if not self.parser.find_dir("ServerName", None, start=vh_path, exclude=False): self.parser.add_dir(vh_path, "ServerName", target_name) else: From 3e5b89daa5a7cfe928eb48ce1a2d08030dab6ee7 Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Thu, 28 Jan 2016 15:54:02 -0800 Subject: [PATCH 14/39] remove save params --- letsencrypt-apache/letsencrypt_apache/configurator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index cd41a02cf..31281681c 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -708,7 +708,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Add directives self._add_dummy_ssl_directives(vh_p) - self.save("don't lose ssl directives", True) + self.save() if target_name: self._add_servername_alias(target_name, vh_p) From aac52e755ae6f7a952d7d05c3c8f06a2c6a9e013 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 28 Jan 2016 15:54:28 -0800 Subject: [PATCH 15/39] Whatever domains we picked should make it to the renewal conf --- letsencrypt/cli.py | 15 ++++++++------- letsencrypt/tests/cli_test.py | 12 +++++++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index e703c83f9..fb0f6a17d 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -112,11 +112,12 @@ def usage_strings(plugins): return USAGE % (apache_doc, nginx_doc), SHORT_USAGE -def _find_domains(args, installer): - if not args.domains: - domains = display_ops.choose_names(installer) +def _find_domains(config, installer): + if not config.domains: + # set args.domains so that it's written to the renewal conf file + domains = config.domains = display_ops.choose_names(installer) else: - domains = args.domains + domains = config.domains if not domains: raise errors.Error("Please specify --domains, or --installer that " @@ -590,7 +591,7 @@ def run(args, config, plugins): # pylint: disable=too-many-branches,too-many-lo except errors.PluginSelectionError, e: return e.message - domains = _find_domains(args, installer) + domains = _find_domains(config, installer) # TODO: Handle errors from _init_le_client? le_client = _init_le_client(args, config, authenticator, installer) @@ -636,7 +637,7 @@ def obtain_cert(args, config, plugins): certr, chain, args.cert_path, args.chain_path, args.fullchain_path) _report_new_cert(cert_path, cert_fullchain) else: - domains = _find_domains(args, installer) + domains = _find_domains(config, installer) _auth_from_domains(le_client, config, domains) _suggest_donate() @@ -654,7 +655,7 @@ def install(args, config, plugins): except errors.PluginSelectionError, e: return e.message - domains = _find_domains(args, installer) + domains = _find_domains(config, installer) le_client = _init_le_client( args, config, authenticator=None, installer=installer) assert args.cert_path is not None # required=True in the subparser diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index ad74c5c0a..2a9532f00 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -400,9 +400,19 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods webroot_map_args = ['--webroot-map', '{"eg.com" : "/tmp"}'] namespace = cli.prepare_and_parse_args(plugins, webroot_map_args) domains = cli._find_domains(namespace, mock.MagicMock()) - self.assertEqual(namespace.webroot_map, {u"eg.com": u"/tmp"}) + expected_map = {u"eg.com": u"/tmp"} + self.assertEqual(namespace.webroot_map, expected_map) self.assertEqual(domains, ["eg.com"]) + # test merging webroot maps from the cli and a webroot map + webroot_map_args.extend(["-w", "/tmp2", "-d", "eg2.com,eg.com"]) + namespace = cli.prepare_and_parse_args(plugins, webroot_map_args) + domains = cli._find_domains(namespace, mock.MagicMock()) + # for eg.com, --webroot-map should take precedence over -w / -d + expected_map[u"eg2.com"] = u"/tmp2" + self.assertEqual(namespace.webroot_map, expected_map) + self.assertEqual(set(domains), set(["eg.com", "eg2.com"])) + @mock.patch('letsencrypt.cli._suggest_donate') @mock.patch('letsencrypt.crypto_util.notAfter') @mock.patch('letsencrypt.cli.zope.component.getUtility') From 69ac8c6cfa84a68eece92c0da235f0f68253478b Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Thu, 28 Jan 2016 16:24:56 -0800 Subject: [PATCH 16/39] add servername/alias for all vhosts --- .../letsencrypt_apache/configurator.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 31281681c..67260a235 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -303,8 +303,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if temp: return vhost if not vhost.ssl: - vhost = self.make_vhost_ssl(vhost, target_name) + vhost = self.make_vhost_ssl(vhost) + self._add_servername_alias(target_name, vhost) self.assoc[target_name] = vhost return vhost @@ -326,7 +327,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # TODO: Conflicts is too conservative if not any(vhost.enabled and vhost.conflicts(addrs) for vhost in self.vhosts): - vhost = self.make_vhost_ssl(vhost, target_name) + vhost = self.make_vhost_ssl(vhost) else: logger.error( "The selected vhost would conflict with other HTTPS " @@ -335,6 +336,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): raise errors.PluginError( "VirtualHost not able to be selected.") + self._add_servername_alias(target_name, vhost) self.assoc[target_name] = vhost return vhost @@ -665,7 +667,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): "based virtual host", addr) self.add_name_vhost(addr) - def make_vhost_ssl(self, nonssl_vhost, target_name=None): # 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 @@ -709,8 +711,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Add directives self._add_dummy_ssl_directives(vh_p) self.save() - if target_name: - self._add_servername_alias(target_name, vh_p) # Log actions and create save notes logger.info("Created an SSL vhost at %s", ssl_fp) @@ -861,7 +861,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): "insert_key_file_path") self.parser.add_dir(vh_path, "Include", self.mod_ssl_conf) - def _add_servername_alias(self, target_name, vh_path): + def _add_servername_alias(self, target_name, vhost): + fp = vhost.filep + vh_p = self.aug.match("/files%s//* [label()=~regexp('%s')]" % + (ssl_fp, parser.case_i("VirtualHost"))) + vh_path = vh_p[0] if (self.parser.find_dir("ServerName", target_name, start=vh_path, exclude=False) or self.parser.find_dir("ServerAlias", target_name, start=vh_path, exclude=False)): return From 5f50d698eee1a29142938a357683b19bc7f883b1 Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Thu, 28 Jan 2016 16:26:44 -0800 Subject: [PATCH 17/39] fix var name --- letsencrypt-apache/letsencrypt_apache/configurator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 67260a235..7575e0cbb 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -864,7 +864,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): def _add_servername_alias(self, target_name, vhost): fp = vhost.filep vh_p = self.aug.match("/files%s//* [label()=~regexp('%s')]" % - (ssl_fp, parser.case_i("VirtualHost"))) + (fp, parser.case_i("VirtualHost"))) vh_path = vh_p[0] if (self.parser.find_dir("ServerName", target_name, start=vh_path, exclude=False) or self.parser.find_dir("ServerAlias", target_name, start=vh_path, exclude=False)): From 10c8c1f533e49affd0dd79417bfddf3108b671bb Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 28 Jan 2016 16:46:06 -0800 Subject: [PATCH 18/39] Include interactively specified domains in webroot_map --- letsencrypt/cli.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index fb0f6a17d..669bed7c7 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -115,7 +115,10 @@ def usage_strings(plugins): def _find_domains(config, installer): if not config.domains: # set args.domains so that it's written to the renewal conf file - domains = config.domains = display_ops.choose_names(installer) + domains = display_ops.choose_names(installer) + # record in config.domains, and set webroot_map entries if applicable + for d in domains: + _process_domain(config, d) else: domains = config.domains @@ -1291,19 +1294,24 @@ class WebrootPathProcessor(argparse.Action): # pylint: disable=missing-docstring config.webroot_path.append(webroot) +def _process_domain(config, domain_arg): + """ + Process a new -d flag, helping the webroot plugin construct a map of + {domain : webrootpath} if -w / --webroot-path is in use + """ + for domain in (d.strip() for d in domain_arg.split(",")): + if domain not in config.domains: + config.domains.append(domain) + # Each domain has a webroot_path of the most recent -w flag + # unless it was explicitly included in webroot_map + if config.webroot_path: + config.webroot_map.setdefault(domain, config.webroot_path[-1]) + + class DomainFlagProcessor(argparse.Action): # pylint: disable=missing-docstring def __call__(self, parser, config, domain_arg, option_string=None): - """ - Process a new -d flag, helping the webroot plugin construct a map of - {domain : webrootpath} if -w / --webroot-path is in use - """ - for domain in (d.strip() for d in domain_arg.split(",")): - if domain not in config.domains: - config.domains.append(domain) - # Each domain has a webroot_path of the most recent -w flag - # unless it was explicitly included in webroot_map - if config.webroot_path: - config.webroot_map.setdefault(domain, config.webroot_path[-1]) + """Just wrap _process_domain in argparseese.""" + _process_domain(config, domain_arg) def setup_log_file_handler(args, logfile, fmt): From c9c81ef01575af8e186fc1f638d4d35bc6d06d99 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 28 Jan 2016 16:46:52 -0800 Subject: [PATCH 19/39] Test interactive domain -> webroot-map inclusion Also factorise test cases --- letsencrypt/tests/cli_test.py | 40 ++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index 2a9532f00..acb6e97c2 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -1,5 +1,6 @@ """Tests for letsencrypt.cli.""" import argparse +import copy import itertools import os import shutil @@ -382,6 +383,21 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods short_args = ['--staging', '--server', 'example.com'] self.assertRaises(errors.Error, cli.prepare_and_parse_args, plugins, short_args) + def _webroot_map_test(self, map_arg, path_arg, domains_arg, + expected_map, expectect_domains): + plugins = disco.PluginsRegistry.find_all() + webroot_map_args = [] + if map_arg: + webroot_map_args.extend(["--webroot-map", map_arg]) + if path_arg: + webroot_map_args.extend(["-w", path_arg]) + if domains_arg: + webroot_map_args.extend(["-d", domains_arg]) + namespace = cli.prepare_and_parse_args(plugins, webroot_map_args) + domains = cli._find_domains(namespace, mock.MagicMock()) + self.assertEqual(namespace.webroot_map, expected_map) + self.assertEqual(set(domains), set(expectect_domains)) + def test_parse_webroot(self): plugins = disco.PluginsRegistry.find_all() webroot_args = ['--webroot', '-w', '/var/www/example', @@ -397,21 +413,21 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods webroot_args = ['-d', 'stray.example.com'] + webroot_args self.assertRaises(errors.Error, cli.prepare_and_parse_args, plugins, webroot_args) - webroot_map_args = ['--webroot-map', '{"eg.com" : "/tmp"}'] - namespace = cli.prepare_and_parse_args(plugins, webroot_map_args) - domains = cli._find_domains(namespace, mock.MagicMock()) - expected_map = {u"eg.com": u"/tmp"} - self.assertEqual(namespace.webroot_map, expected_map) - self.assertEqual(domains, ["eg.com"]) + simple_map = '{"eg.com" : "/tmp"}' + expected_map = {u"eg.com" : u"/tmp"} + self._webroot_map_test(simple_map, None, None, expected_map, ["eg.com"]) # test merging webroot maps from the cli and a webroot map - webroot_map_args.extend(["-w", "/tmp2", "-d", "eg2.com,eg.com"]) - namespace = cli.prepare_and_parse_args(plugins, webroot_map_args) - domains = cli._find_domains(namespace, mock.MagicMock()) - # for eg.com, --webroot-map should take precedence over -w / -d expected_map[u"eg2.com"] = u"/tmp2" - self.assertEqual(namespace.webroot_map, expected_map) - self.assertEqual(set(domains), set(["eg.com", "eg2.com"])) + domains = ["eg.com", "eg2.com"] + self._webroot_map_test(simple_map, "/tmp2", "eg2.com,eg.com", expected_map, domains) + + # test inclusion of interactively specified domains in the webroot map + with mock.patch('letsencrypt.cli.display_ops.choose_names') as mock_choose: + mock_choose.return_value = domains + expected_map[u"eg2.com"] = u"/tmp" + self._webroot_map_test(None, "/tmp", None, expected_map, domains) + @mock.patch('letsencrypt.cli._suggest_donate') @mock.patch('letsencrypt.crypto_util.notAfter') From b7c94bb297dd47e11122fda94b75724bd3bd95ed Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Thu, 28 Jan 2016 16:48:12 -0800 Subject: [PATCH 20/39] don't continue if there's no vhost --- letsencrypt-apache/letsencrypt_apache/configurator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 7575e0cbb..cf465e32f 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -865,6 +865,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): fp = vhost.filep vh_p = self.aug.match("/files%s//* [label()=~regexp('%s')]" % (fp, parser.case_i("VirtualHost"))) + if not vh_p: + return vh_path = vh_p[0] if (self.parser.find_dir("ServerName", target_name, start=vh_path, exclude=False) or self.parser.find_dir("ServerAlias", target_name, start=vh_path, exclude=False)): From bf7f9d2cc15019485c5b2bedda761b62538d1b8f Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 28 Jan 2016 16:57:31 -0800 Subject: [PATCH 21/39] Debugging, but also helpful errors... --- letsencrypt/le_util.py | 2 +- letsencrypt/tests/cli_test.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/letsencrypt/le_util.py b/letsencrypt/le_util.py index 64295a80f..3eb4be0a7 100644 --- a/letsencrypt/le_util.py +++ b/letsencrypt/le_util.py @@ -317,4 +317,4 @@ def check_domain_sanity(domain): # first and last char is not "-" fqdn = re.compile("^((?!-)[A-Za-z0-9-]{1,63}(? Date: Thu, 28 Jan 2016 17:14:55 -0800 Subject: [PATCH 22/39] Test setting webroot-path in a config file --- letsencrypt/tests/cli_test.py | 7 +++++-- letsencrypt/tests/testdata/webrootconftest.ini | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 letsencrypt/tests/testdata/webrootconftest.ini diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index ee135825d..ac600a357 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -383,9 +383,9 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods self.assertRaises(errors.Error, cli.prepare_and_parse_args, plugins, short_args) def _webroot_map_test(self, map_arg, path_arg, domains_arg, - expected_map, expectect_domains): + expected_map, expectect_domains, extra_args=[]): plugins = disco.PluginsRegistry.find_all() - webroot_map_args = [] + webroot_map_args = [] + extra_args if map_arg: webroot_map_args.extend(["--webroot-map", map_arg]) if path_arg: @@ -427,6 +427,9 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods expected_map[u"eg2.com"] = u"/tmp" self._webroot_map_test(None, "/tmp", None, expected_map, domains) + extra_args = ['-c', test_util.vector_path('webrootconftest.ini')] + self._webroot_map_test(None, None, None, expected_map, domains, extra_args) + @mock.patch('letsencrypt.cli._suggest_donate') @mock.patch('letsencrypt.crypto_util.notAfter') diff --git a/letsencrypt/tests/testdata/webrootconftest.ini b/letsencrypt/tests/testdata/webrootconftest.ini new file mode 100644 index 000000000..de3bd98a6 --- /dev/null +++ b/letsencrypt/tests/testdata/webrootconftest.ini @@ -0,0 +1,3 @@ +webroot +webroot-path = /tmp +domains = eg.com, eg2.com From c552bce609090c0d942d3f4bd07a3b2dfafb0f14 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 28 Jan 2016 17:22:43 -0800 Subject: [PATCH 23/39] lintmonster --- letsencrypt/tests/cli_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index ac600a357..6c8facd81 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -382,10 +382,10 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods short_args = ['--staging', '--server', 'example.com'] self.assertRaises(errors.Error, cli.prepare_and_parse_args, plugins, short_args) - def _webroot_map_test(self, map_arg, path_arg, domains_arg, - expected_map, expectect_domains, extra_args=[]): + def _webroot_map_test(self, map_arg, path_arg, domains_arg, # pylint: disable=too-many-arguments + expected_map, expectect_domains, extra_args=None): plugins = disco.PluginsRegistry.find_all() - webroot_map_args = [] + extra_args + webroot_map_args = extra_args if extra_args else [] if map_arg: webroot_map_args.extend(["--webroot-map", map_arg]) if path_arg: @@ -393,7 +393,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods if domains_arg: webroot_map_args.extend(["-d", domains_arg]) namespace = cli.prepare_and_parse_args(plugins, webroot_map_args) - domains = cli._find_domains(namespace, mock.MagicMock()) + domains = cli._find_domains(namespace, mock.MagicMock()) # pylint: disable=protected-access self.assertEqual(namespace.webroot_map, expected_map) self.assertEqual(set(domains), set(expectect_domains)) @@ -413,7 +413,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods self.assertRaises(errors.Error, cli.prepare_and_parse_args, plugins, webroot_args) simple_map = '{"eg.com" : "/tmp"}' - expected_map = {u"eg.com" : u"/tmp"} + expected_map = {u"eg.com": u"/tmp"} self._webroot_map_test(simple_map, None, None, expected_map, ["eg.com"]) # test merging webroot maps from the cli and a webroot map From e056b35f28338f9b098ad6f9a820d8d7d422d0cf Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 28 Jan 2016 17:51:42 -0800 Subject: [PATCH 24/39] Fix some merge bugs --- letsencrypt/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 09a9646e0..5df6ffb6d 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -1308,14 +1308,14 @@ def _process_domain(config, domain_arg, webroot_path=None): # Each domain has a webroot_path of the most recent -w flag # unless it was explicitly included in webroot_map if webroot_path: - config.webroot_map.setdefault(domain, config.webroot_path[-1]) + config.webroot_map.setdefault(domain, webroot_path[-1]) class WebrootMapProcessor(argparse.Action): # pylint: disable=missing-docstring def __call__(self, parser, config, webroot_map_arg, option_string=None): webroot_map = json.loads(webroot_map_arg) for domains, webroot_path in webroot_map.iteritems(): - _process_domain(config, domains, webroot) + _process_domain(config, domains, [webroot_path]) class DomainFlagProcessor(argparse.Action): # pylint: disable=missing-docstring From 4114ca1c23dd33fe25924578583b96c043d83e8a Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 28 Jan 2016 17:54:35 -0800 Subject: [PATCH 25/39] Since we have a hook now, let's be done with all this unicode nonsense --- letsencrypt/cli.py | 3 +-- letsencrypt/tests/cli_test.py | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 5df6ffb6d..6537ee8b8 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -1315,7 +1315,7 @@ class WebrootMapProcessor(argparse.Action): # pylint: disable=missing-docstring def __call__(self, parser, config, webroot_map_arg, option_string=None): webroot_map = json.loads(webroot_map_arg) for domains, webroot_path in webroot_map.iteritems(): - _process_domain(config, domains, [webroot_path]) + _process_domain(config, str(domains), [str(webroot_path)]) class DomainFlagProcessor(argparse.Action): # pylint: disable=missing-docstring @@ -1324,7 +1324,6 @@ class DomainFlagProcessor(argparse.Action): # pylint: disable=missing-docstring _process_domain(config, domain_arg) - def setup_log_file_handler(args, logfile, fmt): """Setup file debug logging.""" log_file_path = os.path.join(args.logs_dir, logfile) diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index ac8932112..f316fe2e8 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -421,18 +421,18 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods self.assertRaises(errors.Error, cli.prepare_and_parse_args, plugins, webroot_args) simple_map = '{"eg.com" : "/tmp"}' - expected_map = {u"eg.com": u"/tmp"} + expected_map = {"eg.com": "/tmp"} self._webroot_map_test(simple_map, None, None, expected_map, ["eg.com"]) # test merging webroot maps from the cli and a webroot map - expected_map[u"eg2.com"] = u"/tmp2" + expected_map["eg2.com"] = "/tmp2" domains = ["eg.com", "eg2.com"] self._webroot_map_test(simple_map, "/tmp2", "eg2.com,eg.com", expected_map, domains) # test inclusion of interactively specified domains in the webroot map with mock.patch('letsencrypt.cli.display_ops.choose_names') as mock_choose: mock_choose.return_value = domains - expected_map[u"eg2.com"] = u"/tmp" + expected_map["eg2.com"] = "/tmp" self._webroot_map_test(None, "/tmp", None, expected_map, domains) extra_args = ['-c', test_util.vector_path('webrootconftest.ini')] @@ -442,7 +442,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods '{"eg.com.,www.eg.com": "/tmp", "eg.is.": "/tmp2"}'] namespace = cli.prepare_and_parse_args(plugins, webroot_map_args) self.assertEqual(namespace.webroot_map, - {u"eg.com": u"/tmp", u"www.eg.com": u"/tmp", u"eg.is": "/tmp2"}) + {"eg.com": "/tmp", "www.eg.com": "/tmp", "eg.is": "/tmp2"}) @mock.patch('letsencrypt.cli._suggest_donate') @mock.patch('letsencrypt.crypto_util.notAfter') From 35ce4236e09dd3b31a9f374677be73727f955744 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 28 Jan 2016 18:01:41 -0800 Subject: [PATCH 26/39] Better docs for --webroot-map --- letsencrypt/cli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 6537ee8b8..0b9a1e5d3 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -1264,7 +1264,9 @@ def _plugins_parsing(helpful, plugins): # --webroot-map still has some awkward properties, so it is undocumented helpful.add("webroot", "--webroot-map", default={}, action=WebrootMapProcessor, help="JSON dictionary mapping domains to webroot paths; this implies -d " - "for each entry.") + "for each entry. You may need to escape this from your shell. " + """Eg: --webroot-map '{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}' """ + "This option is merged with, but takes precedence over, -w / -d entries") class WebrootPathProcessor(argparse.Action): # pylint: disable=missing-docstring def __init__(self, *args, **kwargs): From 9bc4efe50c81b66308d2a85809887a578be156f7 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 28 Jan 2016 18:07:47 -0800 Subject: [PATCH 27/39] Better comment --- letsencrypt/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 0b9a1e5d3..a6e9eb3ed 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -114,9 +114,9 @@ def usage_strings(plugins): def _find_domains(config, installer): if not config.domains: - # set args.domains so that it's written to the renewal conf file domains = display_ops.choose_names(installer) - # record in config.domains, and set webroot_map entries if applicable + # record in config.domains (so that it can be serialised in renewal config files), + # and set webroot_map entries if applicable for d in domains: _process_domain(config, d) else: From 9cce97ee6614add4ee7725f5abe5db2b2631abd6 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 28 Jan 2016 18:19:28 -0800 Subject: [PATCH 28/39] delint --- letsencrypt/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index a6e9eb3ed..84723da3c 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -1294,7 +1294,7 @@ class WebrootPathProcessor(argparse.Action): # pylint: disable=missing-docstring "them must precede all domain flags") config.webroot_path.append(webroot) -_undot = lambda domain : domain[:-1] if domain.endswith('.') else domain +_undot = lambda domain: domain[:-1] if domain.endswith('.') else domain def _process_domain(config, domain_arg, webroot_path=None): """ From 90f9254fc3eb906fda27404b71523bcc37796d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Kr=C3=BCger?= Date: Fri, 29 Jan 2016 18:25:50 +0100 Subject: [PATCH 29/39] Fix multiple snakeoil certs for nginx --- letsencrypt-nginx/letsencrypt_nginx/configurator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/letsencrypt-nginx/letsencrypt_nginx/configurator.py b/letsencrypt-nginx/letsencrypt_nginx/configurator.py index 691688969..0da12d143 100644 --- a/letsencrypt-nginx/letsencrypt_nginx/configurator.py +++ b/letsencrypt-nginx/letsencrypt_nginx/configurator.py @@ -310,11 +310,11 @@ class NginxConfigurator(common.Plugin): key = OpenSSL.crypto.load_privatekey( OpenSSL.crypto.FILETYPE_PEM, le_key.pem) cert = acme_crypto_util.gen_ss_cert(key, domains=[socket.gethostname()]) - cert_path = os.path.join(tmp_dir, "cert.pem") cert_pem = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, cert) - with open(cert_path, 'w') as cert_file: - cert_file.write(cert_pem) + cert_file, cert_path = le_util.unique_file(os.path.join(tmp_dir, "cert.pem")) + cert_file.write(cert_pem) + cert_file.close() return cert_path, le_key.file def _make_server_ssl(self, vhost): From 52894206927477fbc054f18b655c8c4dc81d6e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Kr=C3=BCger?= Date: Fri, 29 Jan 2016 20:40:28 +0100 Subject: [PATCH 30/39] Protect opened files against IO-exceptions --- letsencrypt-nginx/letsencrypt_nginx/configurator.py | 4 ++-- letsencrypt/crypto_util.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/letsencrypt-nginx/letsencrypt_nginx/configurator.py b/letsencrypt-nginx/letsencrypt_nginx/configurator.py index 0da12d143..3d48d100f 100644 --- a/letsencrypt-nginx/letsencrypt_nginx/configurator.py +++ b/letsencrypt-nginx/letsencrypt_nginx/configurator.py @@ -313,8 +313,8 @@ class NginxConfigurator(common.Plugin): cert_pem = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, cert) cert_file, cert_path = le_util.unique_file(os.path.join(tmp_dir, "cert.pem")) - cert_file.write(cert_pem) - cert_file.close() + with cert_file: + cert_file.write(cert_pem) return cert_path, le_key.file def _make_server_ssl(self, vhost): diff --git a/letsencrypt/crypto_util.py b/letsencrypt/crypto_util.py index 730c32398..76265a739 100644 --- a/letsencrypt/crypto_util.py +++ b/letsencrypt/crypto_util.py @@ -53,8 +53,8 @@ def init_save_key(key_size, key_dir, keyname="key-letsencrypt.pem"): config.strict_permissions) key_f, key_path = le_util.unique_file( os.path.join(key_dir, keyname), 0o600) - key_f.write(key_pem) - key_f.close() + with key_f: + key_f.write(key_pem) logger.info("Generating key (%d bits): %s", key_size, key_path) From 50e2f769c0d1a506df9db153b664583d0322ae07 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 29 Jan 2016 12:54:34 -0800 Subject: [PATCH 31/39] loc--, readability++ --- letsencrypt-apache/letsencrypt_apache/configurator.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 745a00719..9e3f34990 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -874,14 +874,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # See if the exact address appears in any other vhost # Remember 1.1.1.1:* == 1.1.1.1 -> hence any() for addr in vhost.addrs: - # In Apache 2.2, when a NameVirtualHost directive is not # set, "*" and "_default_" will conflict when sharing a port - addrs = [addr] - if addr.get_addr() == "*": - addrs.append(obj.Addr(("_default_", addr.get_port(),))) - elif addr.get_addr() == "_default_": - addrs.append(obj.Addr(("*", addr.get_port(),))) + if addr.get_addr() in ("*", "_default_"): + addrs = [obj.Addr((a, addr.get_port(),)) + for a in ("*", "_default_")] for test_vh in self.vhosts: if (vhost.filep != test_vh.filep and From d281162f170f746bd72b97747c073a37444b7a50 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Fri, 29 Jan 2016 13:41:24 -0800 Subject: [PATCH 32/39] Domain errors should include the domain in question --- letsencrypt/le_util.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/letsencrypt/le_util.py b/letsencrypt/le_util.py index 3eb4be0a7..d97d43dc6 100644 --- a/letsencrypt/le_util.py +++ b/letsencrypt/le_util.py @@ -298,18 +298,19 @@ def check_domain_sanity(domain): # Check if there's a wildcard domain if domain.startswith("*."): raise errors.ConfigurationError( - "Wildcard domains are not supported") + "Wildcard domains are not supported: {0}".format(domain)) # Punycode if "xn--" in domain: raise errors.ConfigurationError( - "Punycode domains are not presently supported") + "Punycode domains are not presently supported: {0}".format(domain)) # Unicode try: domain.encode('ascii') except UnicodeDecodeError: raise errors.ConfigurationError( - "Internationalized domain names are not presently supported") + "Internationalized domain names are not presently supported: {0}" + .format(domain)) # FQDN checks from # http://www.mkyong.com/regular-expressions/domain-name-regular-expression-example/ From 60e72188e440282d79702e09461fe3c4649bb993 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Fri, 29 Jan 2016 14:01:59 -0800 Subject: [PATCH 33/39] Don't stringify unicode after all - Paths can be unicode; that's fine. - For now, unicode domains are caught and errored appropriately in le_util.check_domain_sanity(); one day they may be allowed --- letsencrypt/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 84723da3c..dc095a42e 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -1294,6 +1294,7 @@ class WebrootPathProcessor(argparse.Action): # pylint: disable=missing-docstring "them must precede all domain flags") config.webroot_path.append(webroot) + _undot = lambda domain: domain[:-1] if domain.endswith('.') else domain def _process_domain(config, domain_arg, webroot_path=None): @@ -1317,7 +1318,7 @@ class WebrootMapProcessor(argparse.Action): # pylint: disable=missing-docstring def __call__(self, parser, config, webroot_map_arg, option_string=None): webroot_map = json.loads(webroot_map_arg) for domains, webroot_path in webroot_map.iteritems(): - _process_domain(config, str(domains), [str(webroot_path)]) + _process_domain(config, domains, [webroot_path]) class DomainFlagProcessor(argparse.Action): # pylint: disable=missing-docstring From 95061b84874a3a5291b545a4a8ada083afebfe5e Mon Sep 17 00:00:00 2001 From: Aaron Zirbes Date: Fri, 29 Jan 2016 17:03:31 -0500 Subject: [PATCH 34/39] Stop spewing "grep: /etc/os-release: No such file or directory" when running le-auto. Fix #2255. Also bump embedded version number. --- letsencrypt-auto-source/letsencrypt-auto | 4 ++-- letsencrypt-auto-source/letsencrypt-auto.template | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index e0812501c..7800a5eb6 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -19,7 +19,7 @@ XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share} VENV_NAME="letsencrypt" VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"} VENV_BIN=${VENV_PATH}/bin -LE_AUTO_VERSION="0.3.0" +LE_AUTO_VERSION="0.4.0.dev0" # This script takes the same arguments as the main letsencrypt program, but it # additionally responds to --verbose (more output) and --debug (allow support @@ -374,7 +374,7 @@ Bootstrap() { elif [ -f /etc/redhat-release ]; then echo "Bootstrapping dependencies for RedHat-based OSes..." BootstrapRpmCommon - elif `grep -q openSUSE /etc/os-release` ; then + elif [ -f /etc/os-release] && `grep -q openSUSE /etc/os-release` ; then echo "Bootstrapping dependencies for openSUSE-based OSes..." BootstrapSuseCommon elif [ -f /etc/arch-release ]; then diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index 8118a5f69..ba17d7a99 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -130,7 +130,7 @@ Bootstrap() { elif [ -f /etc/redhat-release ]; then echo "Bootstrapping dependencies for RedHat-based OSes..." BootstrapRpmCommon - elif `grep -q openSUSE /etc/os-release` ; then + elif [ -f /etc/os-release] && `grep -q openSUSE /etc/os-release` ; then echo "Bootstrapping dependencies for openSUSE-based OSes..." BootstrapSuseCommon elif [ -f /etc/arch-release ]; then From a6d0410f4e0a7d689338ad4fcc23b77942400aef Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Fri, 29 Jan 2016 15:30:23 -0800 Subject: [PATCH 35/39] remove old TODO --- letsencrypt-apache/letsencrypt_apache/configurator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index cf465e32f..e3b8546a9 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -693,7 +693,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Reload augeas to take into account the new vhost self.aug.load() - #TODO: add line to write vhost name # Get Vhost augeas path for new vhost vh_p = self.aug.match("/files%s//* [label()=~regexp('%s')]" % (ssl_fp, parser.case_i("VirtualHost"))) From 7215376ff92907e6e879bd9d2f56070d93fa07b5 Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Fri, 29 Jan 2016 16:09:19 -0800 Subject: [PATCH 36/39] update server list with added names --- letsencrypt-apache/letsencrypt_apache/configurator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index e3b8546a9..bae3da232 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -874,6 +874,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): self.parser.add_dir(vh_path, "ServerName", target_name) else: self.parser.add_dir(vh_path, "ServerAlias", target_name) + self._add_servernames(vhost) def _add_name_vhost_if_necessary(self, vhost): """Add NameVirtualHost Directives if necessary for new vhost. From 7d63a0c8df5b6ef43cf2d50402af4d9f98f4abfd Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Fri, 29 Jan 2016 16:44:22 -0800 Subject: [PATCH 37/39] fix stupid broken tests --- .../letsencrypt_apache/tests/configurator_test.py | 2 +- letsencrypt-apache/letsencrypt_apache/tests/util.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py index 887dcfe02..d4f770248 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py @@ -162,7 +162,7 @@ class TwoVhost80Test(util.ApacheTest): mock_select.return_value = self.vh_truth[0] chosen_vhost = self.config.choose_vhost("none.com") self.assertEqual( - self.vh_truth[0].get_names(), chosen_vhost.get_names()) + self.vh_truth[6].get_names(), chosen_vhost.get_names()) # Make sure we go from HTTP -> HTTPS self.assertFalse(self.vh_truth[0].ssl) diff --git a/letsencrypt-apache/letsencrypt_apache/tests/util.py b/letsencrypt-apache/letsencrypt_apache/tests/util.py index fb86d2320..169f1b379 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/util.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/util.py @@ -151,7 +151,13 @@ def get_vh_truth(temp_dir, config_name): os.path.join(aug_pre, ("default-ssl-port-only.conf/" "IfModule/VirtualHost")), set([obj.Addr.fromstring("_default_:443")]), True, False), + obj.VirtualHost( + os.path.join(prefix, "encryption-example.conf"), + os.path.join(aug_pre, "encryption-example.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), + False, True, "encryption-example.demo") ] + vh_truth[6].aliases.add("none.com") return vh_truth return None # pragma: no cover From 36e2c9a9927c2047f7ce95115474433ca93ff3ca Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Fri, 29 Jan 2016 17:11:05 -0800 Subject: [PATCH 38/39] remove test hackery --- .../letsencrypt_apache/tests/configurator_test.py | 3 ++- letsencrypt-apache/letsencrypt_apache/tests/util.py | 8 +------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py index d4f770248..8aa933d6a 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py @@ -161,8 +161,9 @@ class TwoVhost80Test(util.ApacheTest): def test_choose_vhost_select_vhost_non_ssl(self, mock_select): mock_select.return_value = self.vh_truth[0] chosen_vhost = self.config.choose_vhost("none.com") + self.vh_truth[0].aliases.add("none.com") self.assertEqual( - self.vh_truth[6].get_names(), chosen_vhost.get_names()) + self.vh_truth[0].get_names(), chosen_vhost.get_names()) # Make sure we go from HTTP -> HTTPS self.assertFalse(self.vh_truth[0].ssl) diff --git a/letsencrypt-apache/letsencrypt_apache/tests/util.py b/letsencrypt-apache/letsencrypt_apache/tests/util.py index 169f1b379..47d4278f8 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/util.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/util.py @@ -150,14 +150,8 @@ def get_vh_truth(temp_dir, config_name): os.path.join(prefix, "default-ssl-port-only.conf"), os.path.join(aug_pre, ("default-ssl-port-only.conf/" "IfModule/VirtualHost")), - set([obj.Addr.fromstring("_default_:443")]), True, False), - obj.VirtualHost( - os.path.join(prefix, "encryption-example.conf"), - os.path.join(aug_pre, "encryption-example.conf/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), - False, True, "encryption-example.demo") + set([obj.Addr.fromstring("_default_:443")]), True, False) ] - vh_truth[6].aliases.add("none.com") return vh_truth return None # pragma: no cover From 609776a709f449b6ecc681d1d765dce4ffd0dae6 Mon Sep 17 00:00:00 2001 From: bmw Date: Fri, 29 Jan 2016 17:30:27 -0800 Subject: [PATCH 39/39] Revert "Revert "Revert "Temporarily disable Apache 2.2 support""" --- letsencrypt-apache/letsencrypt_apache/configurator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 11bba59e2..5ca2ddcb6 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -155,7 +155,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Set Version if self.version is None: self.version = self.get_version() - if self.version < (2, 4): + if self.version < (2, 2): raise errors.NotSupportedError( "Apache Version %s not supported.", str(self.version))