From 6f4af62f61bfacb63d5b141a4f9cbc17729fdf17 Mon Sep 17 00:00:00 2001 From: yan Date: Tue, 28 Apr 2015 18:38:28 -0700 Subject: [PATCH] Add dvsni tests --- letsencrypt/client/plugins/nginx/dvsni.py | 99 ++++++++++-- letsencrypt/client/plugins/nginx/obj.py | 14 +- letsencrypt/client/plugins/nginx/parser.py | 6 +- .../client/plugins/nginx/tests/dvsni_test.py | 148 ++++++++++++++---- .../client/plugins/nginx/tests/obj_test.py | 6 +- 5 files changed, 224 insertions(+), 49 deletions(-) diff --git a/letsencrypt/client/plugins/nginx/dvsni.py b/letsencrypt/client/plugins/nginx/dvsni.py index 7233d7c62..0eab4d402 100644 --- a/letsencrypt/client/plugins/nginx/dvsni.py +++ b/letsencrypt/client/plugins/nginx/dvsni.py @@ -1,7 +1,11 @@ """NginxDVSNI""" import logging +import os +from letsencrypt.client import errors from letsencrypt.client.plugins.apache.dvsni import ApacheDvsni +from letsencrypt.client.plugins.nginx import obj +from letsencrypt.client.plugins.nginx.nginxparser import dump class NginxDvsni(ApacheDvsni): @@ -29,35 +33,110 @@ class NginxDvsni(ApacheDvsni): """ def perform(self): - """Perform a DVSNI challenge on Nginx.""" + """Perform a DVSNI challenge on Nginx. + + :returns: list of :class:`letsencrypt.acme.challenges.DVSNIResponse` + :rtype: list + + """ if not self.achalls: return [] self.configurator.save() addresses = [] + default_addr = "443 default_server ssl" + for achall in self.achalls: vhost = self.configurator.choose_vhost(achall.domain) if vhost is None: logging.error( - "No nginx vhost exists with servername or alias of: %s", + "No nginx vhost exists with server_name or alias of: %s", achall.domain) logging.error("No default 443 nginx vhost exists") - logging.error("Please specify servernames in the Nginx config") + logging.error("Please specify server_names in the Nginx config") return None + + for addr in vhost.addrs: + if addr.default: + addresses.append([obj.Addr.fromstring(default_addr)]) + break else: addresses.append(list(vhost.addrs)) - responses = [] + # Create challenge certs + responses = [self._setup_challenge_cert(x) for x in self.achalls] - # Create all of the challenge certs - # for achall in self.achalls: - # responses.append(self._setup_challenge_cert(achall)) - - # Setup the configuration - # self._mod_config(addresses) + # Set up the configuration + self._mod_config(addresses) # Save reversible changes self.configurator.save("SNI Challenge", True) return responses + + def _mod_config(self, ll_addrs): + """Modifies Nginx config to include challenge server blocks. + + :param list ll_addrs: list of lists of + :class:`letsencrypt.client.plugins.apache.obj.Addr` to apply + + :raises errors.LetsEncryptMisconfigurationError: + Unable to find a suitable HTTP block to include DVSNI hosts. + + """ + # Add the 'include' statement for the challenges if it doesn't exist + # already in the main config + included = False + directive = ['include', self.challenge_conf] + root = self.configurator.parser.loc["root"] + main = self.configurator.parser.parsed[root] + for entry in main: + if entry[0] == ['http']: + body = entry[1] + if directive not in body: + body.append(directive) + included = True + break + if not included: + raise errors.LetsEncryptMisconfigurationError( + 'LetsEncrypt could not find an HTTP block to include DVSNI ' + 'challenges in %s.' % root) + + config = [] + for idx, addrs in enumerate(ll_addrs): + config.append(self._make_server_block(self.achalls[idx], addrs)) + + self.configurator.reverter.register_file_creation( + True, self.challenge_conf) + + with open(self.challenge_conf, "w") as new_conf: + dump(config, new_conf) + + def _make_server_block(self, achall, addrs): + """Creates a server block for a DVSNI challenge. + + :param achall: Annotated DVSNI challenge. + :type achall: :class:`letsencrypt.client.achallenges.DVSNI` + + :param list addrs: addresses of challenged domain + :class:`list` of type :class:`~nginx.obj.Addr` + + :returns: server block for the challenge host + :rtype: list + + """ + block = [] + for addr in addrs: + block.append(['listen', str(addr)]) + + block.append(['server_name', achall.nonce_domain]) + block.append(['include', self.configurator.parser.loc["ssl_options"]]) + block.append(['ssl_certificate', self.get_cert_file(achall)]) + block.append(['ssl_certificate_key', achall.key.file]) + + document_root = os.path.join( + self.configurator.config.config_dir, "dvsni_page") + block.append([['location', '/'], [['root', document_root]]]) + + return [['server'], block] diff --git a/letsencrypt/client/plugins/nginx/obj.py b/letsencrypt/client/plugins/nginx/obj.py index acaacb3b0..f4b2f0f57 100644 --- a/letsencrypt/client/plugins/nginx/obj.py +++ b/letsencrypt/client/plugins/nginx/obj.py @@ -67,12 +67,20 @@ class Addr(ApacheAddr): return cls(host, port, ssl, default) def __str__(self): + parts = '' if self.tup[0] and self.tup[1]: - return "%s:%s" % self.tup + parts = "%s:%s" % self.tup elif self.tup[0]: - return self.tup[0] + parts = self.tup[0] else: - return self.tup[1] + parts = self.tup[1] + + if self.default: + parts += ' default_server' + if self.ssl: + parts += ' ssl' + + return parts def __eq__(self, other): if isinstance(other, self.__class__): diff --git a/letsencrypt/client/plugins/nginx/parser.py b/letsencrypt/client/plugins/nginx/parser.py index 55a0b01e8..8de705dde 100644 --- a/letsencrypt/client/plugins/nginx/parser.py +++ b/letsencrypt/client/plugins/nginx/parser.py @@ -33,6 +33,7 @@ class NginxParser(object): """Loads Nginx files into a parsed tree. """ + self.parsed = {} self._parse_recursively(self.loc["root"]) def _parse_recursively(self, filepath): @@ -252,8 +253,9 @@ class NginxParser(object): def add_server_directives(self, filename, names, directives, replace=False): - """Add or replace directives in server blocks whose server_name set - is 'names'. If replace is True, this raises a misconfiguration error + """Add or replace directives in server blocks identified by server_name. + + ..note :: If replace is True, this raises a misconfiguration error if the directive does not already exist. ..todo :: Doesn't match server blocks whose server_name directives are diff --git a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py b/letsencrypt/client/plugins/nginx/tests/dvsni_test.py index bf66367e6..a44e0fcd6 100644 --- a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py +++ b/letsencrypt/client/plugins/nginx/tests/dvsni_test.py @@ -6,13 +6,16 @@ import shutil import mock from letsencrypt.acme import challenges -from letsencrypt.acme import messages2 from letsencrypt.client import achallenges +from letsencrypt.client import errors from letsencrypt.client import le_util +from letsencrypt.client.plugins.nginx.obj import Addr from letsencrypt.client.plugins.nginx.tests import util +from letsencrypt.client.tests import acme_util + class DvsniPerformTest(util.NginxTest): """Test the NginxDVSNI challenge.""" @@ -36,25 +39,29 @@ class DvsniPerformTest(util.NginxTest): self.achalls = [ achallenges.DVSNI( - challb=messages2.ChallengeBody( - chall=challenges.DVSNI( + challb=acme_util.chall_to_challb( + challenges.DVSNI( r="foo", - nonce="bar", - ), - uri="https://letsencrypt-ca.org/chall0_uri", - status=messages2.Status("pending"), - ), domain="www.example.com", key=auth_key), + nonce="bar" + ), "pending"), + domain="www.example.com", key=auth_key), achallenges.DVSNI( - challb=messages2.ChallengeBody( - chall=challenges.DVSNI( + challb=acme_util.chall_to_challb( + challenges.DVSNI( r="\xba\xa9\xda?