mirror of
https://github.com/certbot/certbot.git
synced 2026-06-08 16:22:18 -04:00
Merge remote-tracking branch 'github/letsencrypt/master' into test-mode
Conflicts: letsencrypt/network2.py
This commit is contained in:
commit
7495145563
57 changed files with 401 additions and 147 deletions
11
.dockerignore
Normal file
11
.dockerignore
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# this file uses slightly different syntax than .gitignore,
|
||||
# e.g. ".tox/" will not ignore .tox directory
|
||||
|
||||
# well, official docker build should be done on clean git checkout
|
||||
# anyway, so .tox should be empty... But I'm sure people will try to
|
||||
# test docker on their git working directories.
|
||||
|
||||
.git
|
||||
.tox
|
||||
venv
|
||||
docs
|
||||
61
Dockerfile
Normal file
61
Dockerfile
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# https://github.com/letsencrypt/lets-encrypt-preview/pull/431#issuecomment-103659297
|
||||
# it is more likely developers will already have ubuntu:trusty rather
|
||||
# than e.g. debian:jessie and image size differences are negligible
|
||||
FROM ubuntu:trusty
|
||||
MAINTAINER Jakub Warmuz <jakub@warmuz.org>
|
||||
MAINTAINER William Budington <bill@eff.org>
|
||||
|
||||
# Note: this only exposes the port to other docker containers. You
|
||||
# still have to bind to 443@host at runtime, as per the ACME spec.
|
||||
EXPOSE 443
|
||||
|
||||
# TODO: make sure --config-dir and --work-dir cannot be changed
|
||||
# through the CLI (letsencrypt-docker wrapper that uses standalone
|
||||
# authenticator and text mode only?)
|
||||
VOLUME /etc/letsencrypt /var/lib/letsencrypt
|
||||
|
||||
WORKDIR /opt/letsencrypt
|
||||
|
||||
# no need to mkdir anything:
|
||||
# https://docs.docker.com/reference/builder/#copy
|
||||
# If <dest> doesn't exist, it is created along with all missing
|
||||
# directories in its path.
|
||||
|
||||
COPY bootstrap/ubuntu.sh /opt/letsencrypt/src/
|
||||
RUN /opt/letsencrypt/src/ubuntu.sh && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* \
|
||||
/tmp/* \
|
||||
/var/tmp/*
|
||||
|
||||
# the above is not likely to change, so by putting it further up the
|
||||
# Dockerfile we make sure we cache as much as possible
|
||||
|
||||
|
||||
COPY setup.py README.rst CHANGES.rst MANIFEST.in /opt/letsencrypt/src/
|
||||
|
||||
# all above files are necessary for setup.py, however, package source
|
||||
# code directory has to be copied separately to a subdirectory...
|
||||
# https://docs.docker.com/reference/builder/#copy: "If <src> is a
|
||||
# directory, the entire contents of the directory are copied,
|
||||
# including filesystem metadata. Note: The directory itself is not
|
||||
# copied, just its contents." Order again matters, three files are far
|
||||
# more likely to be cached than the whole project directory
|
||||
|
||||
COPY letsencrypt /opt/letsencrypt/src/letsencrypt/
|
||||
COPY acme /opt/letsencrypt/src/acme/
|
||||
COPY letsencrypt_apache /opt/letsencrypt/src/letsencrypt_apache/
|
||||
COPY letsencrypt_nginx /opt/letsencrypt/src/letsencrypt_nginx/
|
||||
|
||||
|
||||
RUN virtualenv --no-site-packages -p python2 /opt/letsencrypt/venv && \
|
||||
/opt/letsencrypt/venv/bin/pip install -e /opt/letsencrypt/src
|
||||
|
||||
# install in editable mode (-e) to save space: it's not possible to
|
||||
# "rm -rf /opt/letsencrypt/src" (it's stays in the underlaying image);
|
||||
# this might also help in debugging: you can "docker run --entrypoint
|
||||
# bash" and investigate, apply patches, etc.
|
||||
|
||||
ENV PATH /opt/letsencrypt/venv/bin:$PATH
|
||||
# TODO: is --text really necessary?
|
||||
ENTRYPOINT [ "letsencrypt", "--text" ]
|
||||
|
|
@ -3,8 +3,7 @@ include CHANGES.rst
|
|||
include CONTRIBUTING.md
|
||||
include linter_plugin.py
|
||||
include letsencrypt/EULA
|
||||
|
||||
recursive-include letsencrypt/client/tests/testdata *
|
||||
recursive-include letsencrypt/tests/testdata *
|
||||
|
||||
recursive-include acme/schemata *.json
|
||||
recursive-include acme/jose/testdata *
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ All you need to do is::
|
|||
|
||||
user@www:~$ sudo letsencrypt -d www.example.org auth
|
||||
|
||||
and if you have a compatbile web server (Apache or Nginx), Let's Encrypt can
|
||||
and if you have a compatible web server (Apache or Nginx), Let's Encrypt can
|
||||
not only get a new certificate, but also deploy it and configure your
|
||||
server automatically!::
|
||||
|
||||
|
|
|
|||
|
|
@ -18,11 +18,9 @@ class Error(jose.JSONObjectWithFields, Exception):
|
|||
'badCSR': 'The CSR is unacceptable (e.g., due to a short key)',
|
||||
}
|
||||
|
||||
# TODO: Boulder omits 'type' and 'instance', spec requires, boulder#128
|
||||
typ = jose.Field('type', omitempty=True)
|
||||
typ = jose.Field('type')
|
||||
title = jose.Field('title', omitempty=True)
|
||||
detail = jose.Field('detail')
|
||||
instance = jose.Field('instance', omitempty=True)
|
||||
|
||||
@typ.encoder
|
||||
def typ(value): # pylint: disable=missing-docstring,no-self-argument
|
||||
|
|
@ -227,10 +225,6 @@ class Authorization(ResourceBody):
|
|||
challenges = jose.Field('challenges', omitempty=True)
|
||||
combinations = jose.Field('combinations', omitempty=True)
|
||||
|
||||
# TODO: acme-spec #92, #98
|
||||
key = Registration._fields['key']
|
||||
contact = Registration._fields['contact']
|
||||
|
||||
status = jose.Field('status', omitempty=True, decoder=Status.from_json)
|
||||
# TODO: 'expires' is allowed for Authorization Resources in
|
||||
# general, but for Key Authorization '[t]he "expires" field MUST
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ class ErrorTest(unittest.TestCase):
|
|||
|
||||
def setUp(self):
|
||||
from acme.messages2 import Error
|
||||
self.error = Error(detail='foo', typ='malformed')
|
||||
self.error = Error(detail='foo', typ='malformed', title='title')
|
||||
self.jobj = {'detail': 'foo', 'title': 'some title'}
|
||||
|
||||
def test_typ_prefix(self):
|
||||
self.assertEqual('malformed', self.error.typ)
|
||||
|
|
@ -32,15 +33,15 @@ class ErrorTest(unittest.TestCase):
|
|||
|
||||
def test_typ_decoder_missing_prefix(self):
|
||||
from acme.messages2 import Error
|
||||
self.assertRaises(jose.DeserializationError, Error.from_json,
|
||||
{'detail': 'foo', 'type': 'malformed'})
|
||||
self.assertRaises(jose.DeserializationError, Error.from_json,
|
||||
{'detail': 'foo', 'type': 'not valid bare type'})
|
||||
self.jobj['type'] = 'malformed'
|
||||
self.assertRaises(jose.DeserializationError, Error.from_json, self.jobj)
|
||||
self.jobj['type'] = 'not valid bare type'
|
||||
self.assertRaises(jose.DeserializationError, Error.from_json, self.jobj)
|
||||
|
||||
def test_typ_decoder_not_recognized(self):
|
||||
from acme.messages2 import Error
|
||||
self.assertRaises(jose.DeserializationError, Error.from_json,
|
||||
{'detail': 'foo', 'type': 'urn:acme:error:baz'})
|
||||
self.jobj['type'] = 'urn:acme:error:baz'
|
||||
self.assertRaises(jose.DeserializationError, Error.from_json, self.jobj)
|
||||
|
||||
def test_description(self):
|
||||
self.assertEqual(
|
||||
|
|
|
|||
|
|
@ -10,21 +10,35 @@
|
|||
# - 7.8 "wheezy" (x64)
|
||||
# - 8.0 "jessie" (x64)
|
||||
|
||||
|
||||
# virtualenv binary can be found in different packages depending on
|
||||
# distro version (#346)
|
||||
distro=$(lsb_release -si)
|
||||
# 6.0.10 => 60, 14.04 => 1404
|
||||
version=$(lsb_release -sr | awk -F '.' '{print $1 $2}')
|
||||
if [ "$distro" = "Ubuntu" -a "$version" -ge 1410 ]
|
||||
then
|
||||
virtualenv="virtualenv"
|
||||
elif [ "$distro" = "Debian" -a "$version" -ge 80 ]
|
||||
newer () {
|
||||
distro=$(lsb_release -si)
|
||||
# 6.0.10 => 60, 14.04 => 1404
|
||||
# TODO: in sid version==unstable
|
||||
version=$(lsb_release -sr | awk -F '.' '{print $1 $2}')
|
||||
if [ "$distro" = "Ubuntu" -a "$version" -ge 1410 ]
|
||||
then
|
||||
return 0;
|
||||
elif [ "$distro" = "Debian" -a "$version" -ge 80 ]
|
||||
then
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
fi
|
||||
}
|
||||
|
||||
# you can force newer if lsb_release is not available (e.g. Docker
|
||||
# debian:jessie base image)
|
||||
if [ "$1" = "newer" ] || newer
|
||||
then
|
||||
virtualenv="virtualenv"
|
||||
else
|
||||
virtualenv="python-virtualenv"
|
||||
fi
|
||||
|
||||
|
||||
# dpkg-dev: dpkg-architecture binary necessary to compile M2Crypto, c.f.
|
||||
# #276, https://github.com/martinpaljak/M2Crypto/issues/62,
|
||||
# M2Crypto setup.py:add_multiarch_paths
|
||||
|
|
|
|||
14
docker-compose.yml
Normal file
14
docker-compose.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
production:
|
||||
build: .
|
||||
ports:
|
||||
- "443:443"
|
||||
|
||||
# For development, mount git root to /opt/letsencrypt/src in order to
|
||||
# make the dev workflow more vagrant-like.
|
||||
development:
|
||||
build: .
|
||||
ports:
|
||||
- "443:443"
|
||||
volumes:
|
||||
- .:/opt/letsencrypt/src
|
||||
- /opt/letsencrypt/venv
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
:mod:`letsencrypt.client.proof_of_possession`
|
||||
--------------------------------------------------
|
||||
|
||||
.. automodule:: letsencrypt.client.proof_of_possession
|
||||
:members:
|
||||
5
docs/api/proof_of_possession.rst
Normal file
5
docs/api/proof_of_possession.rst
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
:mod:`letsencrypt.proof_of_possession`
|
||||
--------------------------------------
|
||||
|
||||
.. automodule:: letsencrypt.proof_of_possession
|
||||
:members:
|
||||
|
|
@ -2,6 +2,24 @@
|
|||
Using the Let's Encrypt client
|
||||
==============================
|
||||
|
||||
Quick start
|
||||
===========
|
||||
|
||||
Using docker you can quickly get yourself a testing cert. From the
|
||||
server that the domain your requesting a cert for resolves to,
|
||||
download docker, and issue the following command
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo docker run -it --rm -p 443:443 --name letsencrypt \
|
||||
-v "/etc/letsencrypt:/etc/letsencrypt" \
|
||||
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
|
||||
quay.io/letsencrypt/lets-encrypt-preview:latest
|
||||
|
||||
And follow the instructions. Your new cert will be available in
|
||||
``/etc/letsencrypt/certs``.
|
||||
|
||||
|
||||
Prerequisites
|
||||
=============
|
||||
|
||||
|
|
@ -31,7 +49,7 @@ Debian
|
|||
|
||||
sudo ./bootstrap/debian.sh
|
||||
|
||||
For squezze you will need to:
|
||||
For squeeze you will need to:
|
||||
|
||||
- Use ``virtualenv --no-site-packages -p python`` instead of ``-p python2``.
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class Authenticator(common.Plugin):
|
|||
# "self" as first argument, e.g. def prepare(self)...
|
||||
|
||||
|
||||
class Installer(common.Plugins):
|
||||
class Installer(common.Plugin):
|
||||
"""Example Installer."""
|
||||
zope.interface.implements(interfaces.IInstaller)
|
||||
zope.interface.classProvides(interfaces.IPluginFactory)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class ContinuityAuthenticator(object):
|
|||
|
||||
:ivar proof_of_pos: Performs "proofOfPossession" challenges.
|
||||
:type proof_of_pos:
|
||||
:class:`letsencrypt.client.proof_of_possession.Proof_of_Possession`
|
||||
:class:`letsencrypt.proof_of_possession.Proof_of_Possession`
|
||||
|
||||
"""
|
||||
zope.interface.implements(interfaces.IAuthenticator)
|
||||
|
|
@ -32,7 +32,7 @@ class ContinuityAuthenticator(object):
|
|||
:type config: :class:`letsencrypt.interfaces.IConfig`
|
||||
|
||||
:param installer: Let's Encrypt Installer.
|
||||
:type installer: :class:`letsencrypt.client.interfaces.IInstaller`
|
||||
:type installer: :class:`letsencrypt.interfaces.IInstaller`
|
||||
|
||||
"""
|
||||
self.rec_token = recovery_token.RecoveryToken(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Let's Encrypt client crypto utility functions
|
||||
"""Let's Encrypt client crypto utility functions.
|
||||
|
||||
.. todo:: Make the transition to use PSS rather than PKCS1_v1_5 when the server
|
||||
is capable of handling the signatures.
|
||||
|
|
@ -13,6 +13,7 @@ import Crypto.PublicKey.RSA
|
|||
import Crypto.Signature.PKCS1_v1_5
|
||||
|
||||
import M2Crypto
|
||||
import OpenSSL
|
||||
|
||||
from letsencrypt import le_util
|
||||
|
||||
|
|
@ -231,3 +232,44 @@ def make_ss_cert(key_str, domains, not_before=None,
|
|||
assert cert.verify()
|
||||
# print check_purpose(,0
|
||||
return cert.as_pem()
|
||||
|
||||
|
||||
def _request_san(req): # TODO: implement directly in PyOpenSSL!
|
||||
# constants based on implementation of
|
||||
# OpenSSL.crypto.X509Error._subjectAltNameString
|
||||
parts_separator = ", "
|
||||
part_separator = ":"
|
||||
extension_short_name = "subjectAltName"
|
||||
|
||||
# pylint: disable=protected-access,no-member
|
||||
label = OpenSSL.crypto.X509Extension._prefixes[OpenSSL.crypto._lib.GEN_DNS]
|
||||
assert parts_separator not in label
|
||||
prefix = label + part_separator
|
||||
|
||||
extensions = [ext._subjectAltNameString().split(parts_separator)
|
||||
for ext in req.get_extensions()
|
||||
if ext.get_short_name() == extension_short_name]
|
||||
# WARNING: this function assumes that no SAN can include
|
||||
# parts_separator, hence the split!
|
||||
|
||||
return [part.split(part_separator)[1] for parts in extensions
|
||||
for part in parts if part.startswith(prefix)]
|
||||
|
||||
|
||||
def get_sans_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM):
|
||||
"""Get list of Subject Alternative Names from signing request.
|
||||
|
||||
:param str csr: Certificate Signing Request in PEM format (must contain
|
||||
one or more subjectAlternativeNames, or the function will fail,
|
||||
raising ValueError)
|
||||
|
||||
:returns: List of referenced subject alternative names
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
try:
|
||||
request = OpenSSL.crypto.load_certificate_request(typ, csr)
|
||||
except OpenSSL.crypto.Error as error:
|
||||
logging.exception(error)
|
||||
raise
|
||||
return _request_san(request)
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ class Network(object):
|
|||
:rtype: `requests.Response`
|
||||
|
||||
"""
|
||||
logging.debug('Sending GET request to %s', uri)
|
||||
kwargs.setdefault('verify', self.verify_ssl)
|
||||
try:
|
||||
response = requests.get(uri, **kwargs)
|
||||
|
|
@ -136,13 +137,13 @@ class Network(object):
|
|||
:rtype: `requests.Response`
|
||||
|
||||
"""
|
||||
logging.debug('Sending POST data: %s', data)
|
||||
logging.debug('Sending POST data to %s: %s', uri, data)
|
||||
kwargs.setdefault('verify', self.verify_ssl)
|
||||
try:
|
||||
response = requests.post(uri, data=data, **kwargs)
|
||||
except requests.exceptions.RequestException as error:
|
||||
raise errors.NetworkError(error)
|
||||
logging.debug('Received response %s: %s', response, response.text)
|
||||
logging.debug('Received response %s: %r', response, response.text)
|
||||
|
||||
self._check_response(response, content_type=content_type)
|
||||
return response
|
||||
|
|
@ -251,6 +252,7 @@ class Network(object):
|
|||
|
||||
def _authzr_from_response(self, response, identifier,
|
||||
uri=None, new_cert_uri=None):
|
||||
# pylint: disable=no-self-use
|
||||
if new_cert_uri is None:
|
||||
try:
|
||||
new_cert_uri = response.links['next']['url']
|
||||
|
|
@ -261,8 +263,7 @@ class Network(object):
|
|||
body=messages2.Authorization.from_json(response.json()),
|
||||
uri=response.headers.get('Location', uri),
|
||||
new_cert_uri=new_cert_uri)
|
||||
if (authzr.body.key != self.key.public()
|
||||
or authzr.body.identifier != identifier):
|
||||
if authzr.body.identifier != identifier:
|
||||
raise errors.UnexpectedUpdate(authzr)
|
||||
return authzr
|
||||
|
||||
|
|
|
|||
|
|
@ -215,8 +215,9 @@ class PluginsRegistry(collections.Mapping):
|
|||
return None
|
||||
|
||||
def __repr__(self):
|
||||
return "{0}({1!r})".format(
|
||||
self.__class__.__name__, set(self._plugins.itervalues()))
|
||||
return "{0}({1})".format(
|
||||
self.__class__.__name__, ','.join(
|
||||
repr(p_ep) for p_ep in self._plugins.itervalues()))
|
||||
|
||||
def __str__(self):
|
||||
if not self._plugins:
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ class PluginsRegistryTest(unittest.TestCase):
|
|||
def setUp(self):
|
||||
from letsencrypt.plugins.disco import PluginsRegistry
|
||||
self.plugin_ep = mock.MagicMock(name="mock")
|
||||
self.plugin_ep.__hash__.side_effect = TypeError
|
||||
self.plugins = {"mock": self.plugin_ep}
|
||||
self.reg = PluginsRegistry(self.plugins)
|
||||
|
||||
|
|
@ -227,7 +228,7 @@ class PluginsRegistryTest(unittest.TestCase):
|
|||
|
||||
def test_repr(self):
|
||||
self.plugin_ep.__repr__ = lambda _: "PluginEntryPoint#mock"
|
||||
self.assertEqual("PluginsRegistry(set([PluginEntryPoint#mock]))",
|
||||
self.assertEqual("PluginsRegistry(PluginEntryPoint#mock)",
|
||||
repr(self.reg))
|
||||
|
||||
def test_str(self):
|
||||
|
|
|
|||
|
|
@ -152,9 +152,6 @@ class StandaloneAuthenticator(common.Plugin):
|
|||
:rtype: bool
|
||||
|
||||
"""
|
||||
signal.signal(signal.SIGIO, self.client_signal_handler)
|
||||
signal.signal(signal.SIGUSR1, self.client_signal_handler)
|
||||
signal.signal(signal.SIGUSR2, self.client_signal_handler)
|
||||
|
||||
display = zope.component.getUtility(interfaces.IDisplay)
|
||||
|
||||
|
|
@ -259,6 +256,16 @@ class StandaloneAuthenticator(common.Plugin):
|
|||
:rtype: bool
|
||||
|
||||
"""
|
||||
# In order to avoid a race condition, we set the signal handler
|
||||
# that will be needed by the parent process now, and undo this
|
||||
# action if we turn out to be the child process. (This needs
|
||||
# to happen before the fork because the child will send one of
|
||||
# these signals to the parent almost immediately after the
|
||||
# fork, and the parent must already be ready to receive it.)
|
||||
signal.signal(signal.SIGIO, self.client_signal_handler)
|
||||
signal.signal(signal.SIGUSR1, self.client_signal_handler)
|
||||
signal.signal(signal.SIGUSR2, self.client_signal_handler)
|
||||
|
||||
fork_result = os.fork()
|
||||
Crypto.Random.atfork()
|
||||
if fork_result:
|
||||
|
|
@ -269,6 +276,12 @@ class StandaloneAuthenticator(common.Plugin):
|
|||
return self.do_parent_process(port)
|
||||
else:
|
||||
# CHILD process (the TCP listener subprocess)
|
||||
# Undo the parent's signal handler settings, which aren't
|
||||
# applicable to us.
|
||||
signal.signal(signal.SIGIO, signal.SIG_DFL)
|
||||
signal.signal(signal.SIGUSR1, signal.SIG_DFL)
|
||||
signal.signal(signal.SIGUSR2, signal.SIG_DFL)
|
||||
|
||||
self.child_pid = os.getpid()
|
||||
# do_child_process() is normally not expected to return but
|
||||
# should terminate via sys.exit().
|
||||
|
|
|
|||
|
|
@ -404,47 +404,39 @@ class DoParentProcessTest(unittest.TestCase):
|
|||
StandaloneAuthenticator
|
||||
self.authenticator = StandaloneAuthenticator(config=None, name=None)
|
||||
|
||||
@mock.patch("letsencrypt.plugins.standalone.authenticator.signal.signal")
|
||||
@mock.patch("letsencrypt.plugins.standalone.authenticator."
|
||||
"zope.component.getUtility")
|
||||
def test_do_parent_process_ok(self, mock_get_utility, mock_signal):
|
||||
def test_do_parent_process_ok(self, mock_get_utility):
|
||||
self.authenticator.subproc_state = "ready"
|
||||
result = self.authenticator.do_parent_process(1717)
|
||||
self.assertTrue(result)
|
||||
self.assertEqual(mock_get_utility.call_count, 1)
|
||||
self.assertEqual(mock_signal.call_count, 3)
|
||||
|
||||
@mock.patch("letsencrypt.plugins.standalone.authenticator.signal.signal")
|
||||
@mock.patch("letsencrypt.plugins.standalone.authenticator."
|
||||
"zope.component.getUtility")
|
||||
def test_do_parent_process_inuse(self, mock_get_utility, mock_signal):
|
||||
def test_do_parent_process_inuse(self, mock_get_utility):
|
||||
self.authenticator.subproc_state = "inuse"
|
||||
result = self.authenticator.do_parent_process(1717)
|
||||
self.assertFalse(result)
|
||||
self.assertEqual(mock_get_utility.call_count, 1)
|
||||
self.assertEqual(mock_signal.call_count, 3)
|
||||
|
||||
@mock.patch("letsencrypt.plugins.standalone.authenticator.signal.signal")
|
||||
@mock.patch("letsencrypt.plugins.standalone.authenticator."
|
||||
"zope.component.getUtility")
|
||||
def test_do_parent_process_cantbind(self, mock_get_utility, mock_signal):
|
||||
def test_do_parent_process_cantbind(self, mock_get_utility):
|
||||
self.authenticator.subproc_state = "cantbind"
|
||||
result = self.authenticator.do_parent_process(1717)
|
||||
self.assertFalse(result)
|
||||
self.assertEqual(mock_get_utility.call_count, 1)
|
||||
self.assertEqual(mock_signal.call_count, 3)
|
||||
|
||||
@mock.patch("letsencrypt.plugins.standalone.authenticator.signal.signal")
|
||||
@mock.patch("letsencrypt.plugins.standalone.authenticator."
|
||||
"zope.component.getUtility")
|
||||
def test_do_parent_process_timeout(self, mock_get_utility, mock_signal):
|
||||
def test_do_parent_process_timeout(self, mock_get_utility):
|
||||
# Normally times out in 5 seconds and returns False. We can
|
||||
# now set delay_amount to a lower value so that it times out
|
||||
# faster than it would under normal use.
|
||||
result = self.authenticator.do_parent_process(1717, delay_amount=1)
|
||||
self.assertFalse(result)
|
||||
self.assertEqual(mock_get_utility.call_count, 1)
|
||||
self.assertEqual(mock_signal.call_count, 3)
|
||||
|
||||
|
||||
class DoChildProcessTest(unittest.TestCase):
|
||||
|
|
|
|||
|
|
@ -156,14 +156,6 @@ class GetAuthorizationsTest(unittest.TestCase):
|
|||
self.assertRaises(errors.AuthorizationError,
|
||||
self.handler.get_authorizations, ["0"])
|
||||
|
||||
def _get_exp_response(self, domain, path, challs):
|
||||
# pylint: disable=no-self-use
|
||||
exp_resp = [None] * len(challs)
|
||||
for i in path:
|
||||
exp_resp[i] = TRANSLATE[challs[i].typ] + str(domain)
|
||||
|
||||
return exp_resp
|
||||
|
||||
def _validate_all(self, unused_1, unused_2):
|
||||
for dom in self.handler.authzr.keys():
|
||||
azr = self.handler.authzr[dom]
|
||||
|
|
@ -284,8 +276,6 @@ class PollChallengesTest(unittest.TestCase):
|
|||
identifier=authzr.body.identifier,
|
||||
challenges=new_challbs,
|
||||
combinations=authzr.body.combinations,
|
||||
key=authzr.body.key,
|
||||
contact=authzr.body.contact,
|
||||
status=status_,
|
||||
),
|
||||
)
|
||||
|
|
@ -443,19 +433,5 @@ def gen_dom_authzr(domain, unused_new_authzr_uri, challs):
|
|||
[messages2.STATUS_PENDING]*len(challs))
|
||||
|
||||
|
||||
def gen_path(required, challs):
|
||||
"""Generate a combination by picking ``required`` from ``challs``.
|
||||
|
||||
:param required: Required types of challenges (subclasses of
|
||||
:class:`~acme.challenges.Challenge`).
|
||||
:param challs: Sequence of ACME challenge messages, corresponding to
|
||||
:attr:`acme.messages.Challenge.challenges`.
|
||||
|
||||
:return: :class:`list` of :class:`int`
|
||||
|
||||
"""
|
||||
return [challs.index(chall) for chall in required]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@ class CLITest(unittest.TestCase):
|
|||
def _call(cls, args):
|
||||
from letsencrypt import cli
|
||||
args = ['--text'] + args
|
||||
with mock.patch("letsencrypt.cli.sys.stdout") as stdout:
|
||||
with mock.patch("letsencrypt.cli.sys.stderr") as stderr:
|
||||
ret = cli.main(args)
|
||||
return ret, stdout, stderr
|
||||
with mock.patch('letsencrypt.cli.sys.stdout') as stdout:
|
||||
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
|
||||
with mock.patch('letsencrypt.cli.client') as client:
|
||||
ret = cli.main(args)
|
||||
return ret, stdout, stderr, client
|
||||
|
||||
def test_no_flags(self):
|
||||
self.assertRaises(SystemExit, self._call, [])
|
||||
|
|
@ -23,6 +24,18 @@ class CLITest(unittest.TestCase):
|
|||
def test_help(self):
|
||||
self.assertRaises(SystemExit, self._call, ['--help'])
|
||||
|
||||
def test_rollback(self):
|
||||
_, _, _, client = self._call(['rollback'])
|
||||
client.rollback.assert_called_once()
|
||||
|
||||
_, _, _, client = self._call(['rollback', '--checkpoints', '123'])
|
||||
client.rollback.assert_called_once_with(
|
||||
mock.ANY, 123, mock.ANY, mock.ANY)
|
||||
|
||||
def test_config_changes(self):
|
||||
_, _, _, client = self._call(['config_changes'])
|
||||
client.view_config_changes.assert_called_once()
|
||||
|
||||
def test_plugins(self):
|
||||
flags = ['--init', '--prepare', '--authenticators', '--installers']
|
||||
for args in itertools.chain(
|
||||
|
|
|
|||
|
|
@ -56,8 +56,7 @@ class DetermineAccountTest(unittest.TestCase):
|
|||
class RollbackTest(unittest.TestCase):
|
||||
"""Test the rollback function."""
|
||||
def setUp(self):
|
||||
from letsencrypt_apache.configurator import ApacheConfigurator
|
||||
self.m_install = mock.MagicMock(spec=ApacheConfigurator)
|
||||
self.m_install = mock.MagicMock()
|
||||
|
||||
@classmethod
|
||||
def _call(cls, checkpoints, side_effect):
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import tempfile
|
|||
import unittest
|
||||
|
||||
import M2Crypto
|
||||
import OpenSSL
|
||||
import mock
|
||||
|
||||
|
||||
|
|
@ -150,5 +151,41 @@ class MakeSSCertTest(unittest.TestCase):
|
|||
make_ss_cert(RSA512_KEY, ['example.com', 'www.example.com'])
|
||||
|
||||
|
||||
class GetSansFromCsrTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.crypto_util.get_sans_from_csr."""
|
||||
def test_extract_one_san(self):
|
||||
from letsencrypt.crypto_util import get_sans_from_csr
|
||||
csr = pkg_resources.resource_string(
|
||||
__name__, os.path.join('testdata', 'csr.pem'))
|
||||
self.assertEqual(get_sans_from_csr(csr), ['example.com'])
|
||||
|
||||
def test_extract_two_sans(self):
|
||||
from letsencrypt.crypto_util import get_sans_from_csr
|
||||
csr = pkg_resources.resource_string(
|
||||
__name__, os.path.join('testdata', 'csr-san.pem'))
|
||||
self.assertEqual(get_sans_from_csr(csr), ['example.com',
|
||||
'www.example.com'])
|
||||
|
||||
def test_extract_six_sans(self):
|
||||
from letsencrypt.crypto_util import get_sans_from_csr
|
||||
csr = pkg_resources.resource_string(
|
||||
__name__, os.path.join('testdata', 'csr-6sans.pem'))
|
||||
self.assertEqual(get_sans_from_csr(csr),
|
||||
["example.com", "example.org", "example.net",
|
||||
"example.info", "subdomain.example.com",
|
||||
"other.subdomain.example.com"])
|
||||
|
||||
def test_parse_non_csr(self):
|
||||
from letsencrypt.crypto_util import get_sans_from_csr
|
||||
self.assertRaises(OpenSSL.crypto.Error, get_sans_from_csr,
|
||||
"hello there")
|
||||
|
||||
def test_parse_no_sans(self):
|
||||
from letsencrypt.crypto_util import get_sans_from_csr
|
||||
csr = pkg_resources.resource_string(
|
||||
__name__, os.path.join('testdata', 'csr-nosans.pem'))
|
||||
self.assertEqual([], get_sans_from_csr(csr))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import datetime
|
|||
import httplib
|
||||
import os
|
||||
import pkg_resources
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import M2Crypto
|
||||
|
|
@ -50,6 +52,8 @@ class NetworkTest(unittest.TestCase):
|
|||
self.identifier = messages2.Identifier(
|
||||
typ=messages2.IDENTIFIER_FQDN, value='example.com')
|
||||
|
||||
self.config = mock.Mock(accounts_dir=tempfile.mkdtemp())
|
||||
|
||||
# Registration
|
||||
self.contact = ('mailto:cert-admin@example.com', 'tel:+12025551212')
|
||||
reg = messages2.Registration(
|
||||
|
|
@ -69,7 +73,7 @@ class NetworkTest(unittest.TestCase):
|
|||
self.authz = messages2.Authorization(
|
||||
identifier=messages2.Identifier(
|
||||
typ=messages2.IDENTIFIER_FQDN, value='example.com'),
|
||||
challenges=(challb,), combinations=None, key=KEY.public())
|
||||
challenges=(challb,), combinations=None)
|
||||
self.authzr = messages2.AuthorizationResource(
|
||||
body=self.authz, uri=authzr_uri,
|
||||
new_cert_uri='https://www.letsencrypt-demo.org/acme/new-cert')
|
||||
|
|
@ -80,6 +84,9 @@ class NetworkTest(unittest.TestCase):
|
|||
uri='https://www.letsencrypt-demo.org/acme/cert/1',
|
||||
cert_chain_uri='https://www.letsencrypt-demo.org/ca')
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.config.accounts_dir)
|
||||
|
||||
def _mock_post_get(self):
|
||||
# pylint: disable=protected-access
|
||||
self.net._post = mock.MagicMock(return_value=self.response)
|
||||
|
|
@ -97,7 +104,7 @@ class NetworkTest(unittest.TestCase):
|
|||
return self.value
|
||||
@classmethod
|
||||
def from_json(cls, value):
|
||||
return cls(value)
|
||||
pass # pragma: no cover
|
||||
# pylint: disable=protected-access
|
||||
jws = self.net._wrap_in_jws(MockJSONDeSerializable('foo'))
|
||||
self.assertEqual(jose.JWS.json_loads(jws).payload, '"foo"')
|
||||
|
|
@ -111,7 +118,8 @@ class NetworkTest(unittest.TestCase):
|
|||
|
||||
def test_check_response_not_ok_jobj_error(self):
|
||||
self.response.ok = False
|
||||
self.response.json.return_value = messages2.Error(detail='foo')
|
||||
self.response.json.return_value = messages2.Error(
|
||||
detail='foo', typ='serverInternal', title='some title').to_json()
|
||||
# pylint: disable=protected-access
|
||||
self.assertRaises(
|
||||
messages2.Error, self.net._check_response, self.response)
|
||||
|
|
@ -218,8 +226,8 @@ class NetworkTest(unittest.TestCase):
|
|||
def test_register_from_account(self):
|
||||
self.net.register = mock.Mock()
|
||||
acc = account.Account(
|
||||
mock.Mock(accounts_dir='mock_dir'), 'key',
|
||||
email='cert-admin@example.com', phone='+12025551212')
|
||||
self.config, 'key', email='cert-admin@example.com',
|
||||
phone='+12025551212')
|
||||
|
||||
self.net.register_from_account(acc)
|
||||
|
||||
|
|
@ -228,9 +236,8 @@ class NetworkTest(unittest.TestCase):
|
|||
def test_register_from_account_partial_info(self):
|
||||
self.net.register = mock.Mock()
|
||||
acc = account.Account(
|
||||
mock.Mock(accounts_dir='mock_dir'), 'key',
|
||||
email='cert-admin@example.com')
|
||||
acc2 = account.Account(mock.Mock(accounts_dir='mock_dir'), 'key')
|
||||
self.config, 'key', email='cert-admin@example.com')
|
||||
acc2 = account.Account(self.config, 'key')
|
||||
|
||||
self.net.register_from_account(acc)
|
||||
self.net.register.assert_called_with(
|
||||
|
|
@ -270,11 +277,10 @@ class NetworkTest(unittest.TestCase):
|
|||
# TODO: test POST call arguments
|
||||
|
||||
# TODO: split here and separate test
|
||||
authz_wrong_key = self.authz.update(key=KEY2.public())
|
||||
self.response.json.return_value = authz_wrong_key.to_json()
|
||||
self.assertRaises(
|
||||
errors.UnexpectedUpdate, self.net.request_challenges,
|
||||
self.identifier, self.regr)
|
||||
self.response.json.return_value = self.authz.update(
|
||||
identifier=self.identifier.update(value='foo')).to_json()
|
||||
self.assertRaises(errors.UnexpectedUpdate, self.net.request_challenges,
|
||||
self.identifier, self.authzr.uri)
|
||||
|
||||
def test_request_challenges_missing_next(self):
|
||||
self.response.status_code = httplib.CREATED
|
||||
|
|
@ -348,6 +354,11 @@ class NetworkTest(unittest.TestCase):
|
|||
self.assertEqual((self.authzr, self.response),
|
||||
self.net.poll(self.authzr))
|
||||
|
||||
# TODO: split here and separate test
|
||||
self.response.json.return_value = self.authz.update(
|
||||
identifier=self.identifier.update(value='foo')).to_json()
|
||||
self.assertRaises(errors.UnexpectedUpdate, self.net.poll, self.authzr)
|
||||
|
||||
def test_request_issuance(self):
|
||||
self.response.content = CERT.as_der()
|
||||
self.response.headers['Location'] = self.certr.uri
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ from letsencrypt import errors
|
|||
from letsencrypt import le_util
|
||||
from letsencrypt.display import util as display_util
|
||||
|
||||
from letsencrypt_apache import configurator
|
||||
|
||||
|
||||
class RevokerBase(unittest.TestCase): # pylint: disable=too-few-public-methods
|
||||
"""Base Class for Revoker Tests."""
|
||||
|
|
@ -60,8 +58,7 @@ class RevokerTest(RevokerBase):
|
|||
self._store_certs()
|
||||
|
||||
self.revoker = Revoker(
|
||||
mock.MagicMock(spec=configurator.ApacheConfigurator),
|
||||
self.mock_config)
|
||||
installer=mock.MagicMock(), config=self.mock_config)
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.backup_dir)
|
||||
|
|
|
|||
12
letsencrypt/tests/testdata/csr-6sans.pem
vendored
Normal file
12
letsencrypt/tests/testdata/csr-6sans.pem
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBuzCCAWUCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE1pY2hpZ2FuMRIw
|
||||
EAYDVQQHEwlBbm4gQXJib3IxDDAKBgNVBAoTA0VGRjEfMB0GA1UECxMWVW5pdmVy
|
||||
c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wXDANBgkqhkiG
|
||||
9w0BAQEFAANLADBIAkEA9LYRcVE3Nr+qleecEcX8JwVDnjeG1X7ucsCasuuZM0e0
|
||||
9cmYuUzxIkMjO/9x4AVcvXXRXPEV+LzWWkfkTlzRMwIDAQABoIGGMIGDBgkqhkiG
|
||||
9w0BCQ4xdjB0MHIGA1UdEQRrMGmCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZ4IL
|
||||
ZXhhbXBsZS5uZXSCDGV4YW1wbGUuaW5mb4IVc3ViZG9tYWluLmV4YW1wbGUuY29t
|
||||
ghtvdGhlci5zdWJkb21haW4uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADQQBd
|
||||
k4BE5qvEvkYoZM/2++Xd9RrQ6wsdj0QiJQCozfsI4lQx6ZJnbtNc7HpDrX4W6XIv
|
||||
IvzVBz/nD11drfz/RNuX
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
8
letsencrypt/tests/testdata/csr-nosans.pem
vendored
Normal file
8
letsencrypt/tests/testdata/csr-nosans.pem
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBFTCBwAIBADBbMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh
|
||||
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRQwEgYDVQQDDAtleGFt
|
||||
cGxlLm9yZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQD0thFxUTc2v6qV55wRxfwn
|
||||
BUOeN4bVfu5ywJqy65kzR7T1yZi5TPEiQyM7/3HgBVy9ddFc8RX4vNZaR+ROXNEz
|
||||
AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAMikGL8Ch7hQCStXH7chhDp6+pt2+VSo
|
||||
wgsrPQ2Bw4veDMlSemUrH+4e0TwbbntHfvXTDHWs9P3BiIDJLxFrjuA=
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
|
|
@ -7,6 +7,8 @@ import unittest
|
|||
|
||||
import mock
|
||||
|
||||
from letsencrypt import constants as core_constants
|
||||
|
||||
from letsencrypt_apache import configurator
|
||||
from letsencrypt_apache import constants
|
||||
from letsencrypt_apache import obj
|
||||
|
|
@ -20,7 +22,7 @@ class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods
|
|||
self.temp_dir, self.config_dir, self.work_dir = dir_setup(
|
||||
"debian_apache_2_4/two_vhost_80")
|
||||
|
||||
self.ssl_options = setup_apache_ssl_options(self.config_dir)
|
||||
self.ssl_options = setup_ssl_options(self.config_dir)
|
||||
|
||||
self.config_path = os.path.join(
|
||||
self.temp_dir, "debian_apache_2_4/two_vhost_80/apache2")
|
||||
|
|
@ -31,14 +33,19 @@ class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods
|
|||
"acme.jose", "testdata/rsa256_key.pem")
|
||||
|
||||
|
||||
def dir_setup(test_dir="debian_apache_2_4/two_vhost_80"):
|
||||
def dir_setup(test_dir="debian_apache_2_4/two_vhost_80",
|
||||
pkg="letsencrypt_apache.tests"):
|
||||
"""Setup the directories necessary for the configurator."""
|
||||
temp_dir = tempfile.mkdtemp("temp")
|
||||
config_dir = tempfile.mkdtemp("config")
|
||||
work_dir = tempfile.mkdtemp("work")
|
||||
|
||||
os.chmod(temp_dir, core_constants.CONFIG_DIRS_MODE)
|
||||
os.chmod(config_dir, core_constants.CONFIG_DIRS_MODE)
|
||||
os.chmod(work_dir, core_constants.CONFIG_DIRS_MODE)
|
||||
|
||||
test_configs = pkg_resources.resource_filename(
|
||||
"letsencrypt_apache.tests", "testdata/%s" % test_dir)
|
||||
pkg, os.path.join("testdata", test_dir))
|
||||
|
||||
shutil.copytree(
|
||||
test_configs, os.path.join(temp_dir, test_dir), symlinks=True)
|
||||
|
|
@ -46,10 +53,11 @@ def dir_setup(test_dir="debian_apache_2_4/two_vhost_80"):
|
|||
return temp_dir, config_dir, work_dir
|
||||
|
||||
|
||||
def setup_apache_ssl_options(config_dir):
|
||||
def setup_ssl_options(
|
||||
config_dir, mod_ssl_conf=constants.MOD_SSL_CONF):
|
||||
"""Move the ssl_options into position and return the path."""
|
||||
option_path = os.path.join(config_dir, "options-ssl.conf")
|
||||
shutil.copyfile(constants.MOD_SSL_CONF, option_path)
|
||||
shutil.copyfile(mod_ssl_conf, option_path)
|
||||
return option_path
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ class NginxConfigurator(common.Plugin):
|
|||
the existing one?
|
||||
|
||||
:param vhost: The vhost to add SSL to.
|
||||
:type vhost: :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost`
|
||||
:type vhost: :class:`~letsencrypt_nginx.obj.VirtualHost`
|
||||
|
||||
"""
|
||||
ssl_block = [['listen', '443 ssl'],
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class NginxDvsni(ApacheDvsni):
|
|||
"""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
|
||||
:class:`letsencrypt_nginx.obj.Addr` to apply
|
||||
|
||||
:raises errors.LetsEncryptMisconfigurationError:
|
||||
Unable to find a suitable HTTP block to include DVSNI hosts.
|
||||
|
|
@ -115,7 +115,7 @@ class NginxDvsni(ApacheDvsni):
|
|||
"""Creates a server block for a DVSNI challenge.
|
||||
|
||||
:param achall: Annotated DVSNI challenge.
|
||||
:type achall: :class:`letsencrypt.client.achallenges.DVSNI`
|
||||
:type achall: :class:`letsencrypt.achallenges.DVSNI`
|
||||
|
||||
:param list addrs: addresses of challenged domain
|
||||
:class:`list` of type :class:`~nginx.obj.Addr`
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import string
|
|||
|
||||
from pyparsing import (
|
||||
Literal, White, Word, alphanums, CharsNotIn, Forward, Group,
|
||||
Optional, OneOrMore, ZeroOrMore, pythonStyleComment)
|
||||
Optional, OneOrMore, Regex, ZeroOrMore, pythonStyleComment)
|
||||
|
||||
|
||||
class RawNginxParser(object):
|
||||
|
|
@ -16,17 +16,21 @@ class RawNginxParser(object):
|
|||
semicolon = Literal(";").suppress()
|
||||
space = White().suppress()
|
||||
key = Word(alphanums + "_/")
|
||||
value = CharsNotIn("{};,")
|
||||
# Matches anything that is not a special character AND any chars in single
|
||||
# or double quotes
|
||||
value = Regex(r"((\".*\")?(\'.*\')?[^\{\};,]?)+")
|
||||
location = CharsNotIn("{};," + string.whitespace)
|
||||
# modifier for location uri [ = | ~ | ~* | ^~ ]
|
||||
modifier = Literal("=") | Literal("~*") | Literal("~") | Literal("^~")
|
||||
|
||||
# rules
|
||||
assignment = (key + Optional(space + value) + semicolon)
|
||||
location_statement = Optional(space + modifier) + Optional(space + location)
|
||||
if_statement = Literal("if") + space + Regex(r"\(.+\)") + space
|
||||
block = Forward()
|
||||
|
||||
block << Group(
|
||||
Group(key + Optional(space + modifier) + Optional(space + location))
|
||||
(Group(key + location_statement) ^ Group(if_statement))
|
||||
+ left_bracket
|
||||
+ Group(ZeroOrMore(Group(assignment) | block))
|
||||
+ right_bracket)
|
||||
|
|
|
|||
|
|
@ -84,6 +84,26 @@ class TestRawNginxParser(unittest.TestCase):
|
|||
]]]]]
|
||||
)
|
||||
|
||||
def test_parse_from_file2(self):
|
||||
parsed = load(open(util.get_data_filename('edge_cases.conf')))
|
||||
self.assertEqual(
|
||||
parsed,
|
||||
[[['server'], [['server_name', 'simple']]],
|
||||
[['server'],
|
||||
[['server_name', 'with.if'],
|
||||
[['location', '~', '^/services/.+$'],
|
||||
[[['if', '($request_filename ~* \\.(ttf|woff)$)'],
|
||||
[['add_header', 'Access-Control-Allow-Origin "*"']]]]]]],
|
||||
[['server'],
|
||||
[['server_name', 'with.complicated.headers'],
|
||||
[['location', '~*', '\\.(?:gif|jpe?g|png)$'],
|
||||
[['add_header', 'Pragma public'],
|
||||
['add_header',
|
||||
'Cache-Control \'public, must-revalidate, proxy-revalidate\''
|
||||
' "test,;{}" foo'],
|
||||
['blah', '"hello;world"'],
|
||||
['try_files', '$uri @rewrites']]]]]])
|
||||
|
||||
def test_dump_as_file(self):
|
||||
parsed = load(open(util.get_data_filename('nginx.conf')))
|
||||
parsed[-1][-1].append([['server'],
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ class NginxParserTest(util.NginxTest):
|
|||
shutil.rmtree(self.work_dir)
|
||||
|
||||
def test_root_normalized(self):
|
||||
path = os.path.join(self.temp_dir, "foo/////"
|
||||
"bar/../../testdata")
|
||||
path = os.path.join(self.temp_dir, "etc_nginx/////"
|
||||
"ubuntu_nginx/../../etc_nginx")
|
||||
nparser = parser.NginxParser(path, None)
|
||||
self.assertEqual(nparser.root, self.config_path)
|
||||
|
||||
|
|
|
|||
27
letsencrypt_nginx/tests/testdata/etc_nginx/edge_cases.conf
vendored
Normal file
27
letsencrypt_nginx/tests/testdata/etc_nginx/edge_cases.conf
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# This is not a valid nginx config file but it tests edge cases in valid nginx syntax
|
||||
|
||||
server {
|
||||
server_name simple;
|
||||
}
|
||||
|
||||
server {
|
||||
server_name with.if;
|
||||
location ~ ^/services/.+$ {
|
||||
if ($request_filename ~* \.(ttf|woff)$) {
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
server_name with.complicated.headers;
|
||||
|
||||
location ~* \.(?:gif|jpe?g|png)$ {
|
||||
|
||||
add_header Pragma public;
|
||||
add_header Cache-Control 'public, must-revalidate, proxy-revalidate' "test,;{}" foo;
|
||||
blah "hello;world";
|
||||
|
||||
try_files $uri @rewrites;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
"""Common utilities for letsencrypt_nginx."""
|
||||
import os
|
||||
import pkg_resources
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from letsencrypt_apache.tests import util as apache_util
|
||||
|
||||
from letsencrypt_nginx import constants
|
||||
from letsencrypt_nginx import configurator
|
||||
|
||||
|
|
@ -16,13 +16,13 @@ class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods
|
|||
def setUp(self):
|
||||
super(NginxTest, self).setUp()
|
||||
|
||||
self.temp_dir, self.config_dir, self.work_dir = dir_setup(
|
||||
"testdata")
|
||||
self.temp_dir, self.config_dir, self.work_dir = apache_util.dir_setup(
|
||||
"etc_nginx", "letsencrypt_nginx.tests")
|
||||
|
||||
self.ssl_options = setup_nginx_ssl_options(self.config_dir)
|
||||
self.ssl_options = apache_util.setup_ssl_options(
|
||||
self.config_dir, constants.MOD_SSL_CONF)
|
||||
|
||||
self.config_path = os.path.join(
|
||||
self.temp_dir, "testdata")
|
||||
self.config_path = os.path.join(self.temp_dir, "etc_nginx")
|
||||
|
||||
self.rsa256_file = pkg_resources.resource_filename(
|
||||
"acme.jose", "testdata/rsa256_key.pem")
|
||||
|
|
@ -33,29 +33,8 @@ class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods
|
|||
def get_data_filename(filename):
|
||||
"""Gets the filename of a test data file."""
|
||||
return pkg_resources.resource_filename(
|
||||
"letsencrypt_nginx.tests", "testdata/%s" % filename)
|
||||
|
||||
|
||||
def dir_setup(test_dir="debian_nginx/two_vhost_80"):
|
||||
"""Setup the directories necessary for the configurator."""
|
||||
temp_dir = tempfile.mkdtemp("temp")
|
||||
config_dir = tempfile.mkdtemp("config")
|
||||
work_dir = tempfile.mkdtemp("work")
|
||||
|
||||
test_configs = pkg_resources.resource_filename(
|
||||
"letsencrypt_nginx.tests", test_dir)
|
||||
|
||||
shutil.copytree(
|
||||
test_configs, os.path.join(temp_dir, test_dir), symlinks=True)
|
||||
|
||||
return temp_dir, config_dir, work_dir
|
||||
|
||||
|
||||
def setup_nginx_ssl_options(config_dir):
|
||||
"""Move the ssl_options into position and return the path."""
|
||||
option_path = os.path.join(config_dir, "options-ssl.conf")
|
||||
shutil.copyfile(constants.MOD_SSL_CONF, option_path)
|
||||
return option_path
|
||||
"letsencrypt_nginx.tests", os.path.join(
|
||||
"testdata", "etc_nginx", filename))
|
||||
|
||||
|
||||
def get_nginx_configurator(
|
||||
|
|
|
|||
3
setup.py
3
setup.py
|
|
@ -38,7 +38,8 @@ install_requires = [
|
|||
'psutil>=2.1.0', # net_connections introduced in 2.1.0
|
||||
'pyasn1', # urllib3 InsecurePlatformWarning (#304)
|
||||
'pycrypto',
|
||||
'PyOpenSSL',
|
||||
# https://pyopenssl.readthedocs.org/en/latest/api/crypto.html#OpenSSL.crypto.X509Req.get_extensions
|
||||
'PyOpenSSL>=0.15',
|
||||
'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary?
|
||||
'pyrfc3339',
|
||||
'python-augeas',
|
||||
|
|
|
|||
Loading…
Reference in a new issue