mirror of
https://github.com/certbot/certbot.git
synced 2026-04-26 16:47:40 -04:00
Merge remote-tracking branch 'origin/master' into useragent
This commit is contained in:
commit
a45de558de
39 changed files with 654 additions and 185 deletions
|
|
@ -21,7 +21,8 @@ WORKDIR /opt/letsencrypt
|
|||
# If <dest> doesn't exist, it is created along with all missing
|
||||
# directories in its path.
|
||||
|
||||
COPY bootstrap/ubuntu.sh /opt/letsencrypt/src/
|
||||
|
||||
COPY bootstrap/ubuntu.sh /opt/letsencrypt/src/ubuntu.sh
|
||||
RUN /opt/letsencrypt/src/ubuntu.sh && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* \
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ WORKDIR /opt/letsencrypt
|
|||
|
||||
# TODO: Install non-default Python versions for tox.
|
||||
# TODO: Install Apache/Nginx for plugin development.
|
||||
COPY bootstrap/ubuntu.sh /opt/letsencrypt/src/
|
||||
COPY bootstrap/ubuntu.sh /opt/letsencrypt/src/ubuntu.sh
|
||||
RUN /opt/letsencrypt/src/ubuntu.sh && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* \
|
||||
|
|
|
|||
|
|
@ -35,11 +35,11 @@ It's all automated:
|
|||
|
||||
All you need to do to sign a single domain is::
|
||||
|
||||
user@www:~$ sudo letsencrypt -d www.example.org auth
|
||||
user@www:~$ sudo letsencrypt -d www.example.org certonly
|
||||
|
||||
For multiple domains (SAN) use::
|
||||
|
||||
user@www:~$ sudo letsencrypt -d www.example.org -d example.org auth
|
||||
user@www:~$ sudo letsencrypt -d www.example.org -d example.org certonly
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -176,5 +176,5 @@ PS384 = JWASignature.register(_JWAPS('PS384', hashes.SHA384))
|
|||
PS512 = JWASignature.register(_JWAPS('PS512', hashes.SHA512))
|
||||
|
||||
ES256 = JWASignature.register(_JWAES('ES256'))
|
||||
ES256 = JWASignature.register(_JWAES('ES384'))
|
||||
ES256 = JWASignature.register(_JWAES('ES512'))
|
||||
ES384 = JWASignature.register(_JWAES('ES384'))
|
||||
ES512 = JWASignature.register(_JWAES('ES512'))
|
||||
|
|
|
|||
|
|
@ -227,25 +227,25 @@ htmlhelp_basename = 'acme-pythondoc'
|
|||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'acme-python.tex', u'acme-python Documentation',
|
||||
u'Let\'s Encrypt Project', 'manual'),
|
||||
(master_doc, 'acme-python.tex', u'acme-python Documentation',
|
||||
u'Let\'s Encrypt Project', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
|
|
@ -289,9 +289,9 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'acme-python', u'acme-python Documentation',
|
||||
author, 'acme-python', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(master_doc, 'acme-python', u'acme-python Documentation',
|
||||
author, 'acme-python', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
|
|
|||
14
bootstrap/_suse_common.sh
Executable file
14
bootstrap/_suse_common.sh
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
|
||||
# SLE12 dont have python-virtualenv
|
||||
|
||||
zypper -nq in -l git-core \
|
||||
python \
|
||||
python-devel \
|
||||
python-virtualenv \
|
||||
gcc \
|
||||
dialog \
|
||||
augeas-lenses \
|
||||
libopenssl-devel \
|
||||
libffi-devel \
|
||||
ca-certificates \
|
||||
|
|
@ -29,6 +29,9 @@ elif [ -f /etc/gentoo-release ] ; then
|
|||
elif uname | grep -iq FreeBSD ; then
|
||||
echo "Bootstrapping dependencies for FreeBSD..."
|
||||
$SUDO $BOOTSTRAP/freebsd.sh
|
||||
elif `grep -qs openSUSE /etc/os-release` ; then
|
||||
echo "Bootstrapping dependencies for openSUSE.."
|
||||
$SUDO $BOOTSTRAP/suse.sh
|
||||
elif uname | grep -iq Darwin ; then
|
||||
echo "Bootstrapping dependencies for Mac OS X..."
|
||||
echo "WARNING: Mac support is very experimental at present..."
|
||||
|
|
|
|||
1
bootstrap/suse.sh
Symbolic link
1
bootstrap/suse.sh
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
_suse_common.sh
|
||||
26
docs/conf.py
26
docs/conf.py
|
|
@ -230,25 +230,25 @@ htmlhelp_basename = 'LetsEncryptdoc'
|
|||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'LetsEncrypt.tex', u'Let\'s Encrypt Documentation',
|
||||
u'Let\'s Encrypt Project', 'manual'),
|
||||
('index', 'LetsEncrypt.tex', u'Let\'s Encrypt Documentation',
|
||||
u'Let\'s Encrypt Project', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
|
|
@ -295,9 +295,9 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'LetsEncrypt', u'Let\'s Encrypt Documentation',
|
||||
u'Let\'s Encrypt Project', 'LetsEncrypt', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
('index', 'LetsEncrypt', u'Let\'s Encrypt Documentation',
|
||||
u'Let\'s Encrypt Project', 'LetsEncrypt', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
|
|
|||
|
|
@ -232,25 +232,25 @@ htmlhelp_basename = 'letsencrypt-apachedoc'
|
|||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'letsencrypt-apache.tex', u'letsencrypt-apache Documentation',
|
||||
u'Let\'s Encrypt Project', 'manual'),
|
||||
(master_doc, 'letsencrypt-apache.tex', u'letsencrypt-apache Documentation',
|
||||
u'Let\'s Encrypt Project', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
|
|
@ -293,9 +293,9 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'letsencrypt-apache', u'letsencrypt-apache Documentation',
|
||||
author, 'letsencrypt-apache', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(master_doc, 'letsencrypt-apache', u'letsencrypt-apache Documentation',
|
||||
author, 'letsencrypt-apache', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
|
|
|||
|
|
@ -73,7 +73,8 @@ class AugeasConfigurator(common.Plugin):
|
|||
|
||||
This function first checks for save errors, if none are found,
|
||||
all configuration changes made will be saved. According to the
|
||||
function parameters.
|
||||
function parameters. If an exception is raised, a new checkpoint
|
||||
was not created.
|
||||
|
||||
:param str title: The title of the save. If a title is given, the
|
||||
configuration will be saved as a new checkpoint and put in a
|
||||
|
|
@ -82,8 +83,9 @@ class AugeasConfigurator(common.Plugin):
|
|||
:param bool temporary: Indicates whether the changes made will
|
||||
be quickly reversed in the future (ie. challenges)
|
||||
|
||||
:raises .errors.PluginError: If there was an error in Augeas, in an
|
||||
attempt to save the configuration, or an error creating a checkpoint
|
||||
:raises .errors.PluginError: If there was an error in Augeas, in
|
||||
an attempt to save the configuration, or an error creating a
|
||||
checkpoint
|
||||
|
||||
"""
|
||||
save_state = self.aug.get("/augeas/save")
|
||||
|
|
@ -122,16 +124,16 @@ class AugeasConfigurator(common.Plugin):
|
|||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
|
||||
self.aug.set("/augeas/save", save_state)
|
||||
self.save_notes = ""
|
||||
self.aug.save()
|
||||
|
||||
if title and not temporary:
|
||||
try:
|
||||
self.reverter.finalize_checkpoint(title)
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
|
||||
self.aug.set("/augeas/save", save_state)
|
||||
self.save_notes = ""
|
||||
self.aug.save()
|
||||
|
||||
def _log_save_errors(self, ex_errs):
|
||||
"""Log errors due to bad Augeas save.
|
||||
|
||||
|
|
|
|||
|
|
@ -306,6 +306,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
best_points = 0
|
||||
|
||||
for vhost in self.vhosts:
|
||||
if vhost.modmacro is True:
|
||||
continue
|
||||
if target_name in vhost.get_names():
|
||||
points = 2
|
||||
elif any(addr.get_addr() == target_name for addr in vhost.addrs):
|
||||
|
|
@ -325,7 +327,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
# No winners here... is there only one reasonable vhost?
|
||||
if best_candidate is None:
|
||||
# reasonable == Not all _default_ addrs
|
||||
reasonable_vhosts = self._non_default_vhosts()
|
||||
vhosts = self._non_default_vhosts()
|
||||
# remove mod_macro hosts from reasonable vhosts
|
||||
reasonable_vhosts = [vh for vh
|
||||
in vhosts if vh.modmacro is False]
|
||||
if len(reasonable_vhosts) == 1:
|
||||
best_candidate = reasonable_vhosts[0]
|
||||
|
||||
|
|
@ -347,8 +352,12 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
"""
|
||||
all_names = set()
|
||||
|
||||
vhost_macro = []
|
||||
|
||||
for vhost in self.vhosts:
|
||||
all_names.update(vhost.get_names())
|
||||
if vhost.modmacro:
|
||||
vhost_macro.append(vhost.filep)
|
||||
|
||||
for addr in vhost.addrs:
|
||||
if common.hostname_regex.match(addr.get_addr()):
|
||||
|
|
@ -358,6 +367,12 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
if name:
|
||||
all_names.add(name)
|
||||
|
||||
if len(vhost_macro) > 0:
|
||||
zope.component.getUtility(interfaces.IDisplay).notification(
|
||||
"Apache mod_macro seems to be in use in file(s):\n{0}"
|
||||
"\n\nUnfortunately mod_macro is not yet supported".format(
|
||||
"\n ".join(vhost_macro)))
|
||||
|
||||
return all_names
|
||||
|
||||
def get_name_from_ip(self, addr): # pylint: disable=no-self-use
|
||||
|
|
@ -394,11 +409,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
"ServerAlias", None, start=host.path, exclude=False)
|
||||
|
||||
for alias in serveralias_match:
|
||||
host.aliases.add(self.parser.get_arg(alias))
|
||||
serveralias = self.parser.get_arg(alias)
|
||||
if not host.modmacro:
|
||||
host.aliases.add(serveralias)
|
||||
|
||||
if servername_match:
|
||||
# Get last ServerName as each overwrites the previous
|
||||
host.name = self.parser.get_arg(servername_match[-1])
|
||||
servername = self.parser.get_arg(servername_match[-1])
|
||||
if not host.modmacro:
|
||||
host.name = servername
|
||||
|
||||
def _create_vhost(self, path):
|
||||
"""Used by get_virtual_hosts to create vhost objects
|
||||
|
|
@ -421,7 +440,12 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
filename = get_file_path(path)
|
||||
is_enabled = self.is_site_enabled(filename)
|
||||
|
||||
vhost = obj.VirtualHost(filename, path, addrs, is_ssl, is_enabled)
|
||||
macro = False
|
||||
if "/macro/" in path.lower():
|
||||
macro = True
|
||||
|
||||
vhost = obj.VirtualHost(filename, path, addrs, is_ssl,
|
||||
is_enabled, modmacro=macro)
|
||||
self._add_servernames(vhost)
|
||||
return vhost
|
||||
|
||||
|
|
@ -1234,7 +1258,7 @@ def get_file_path(vhost_path):
|
|||
avail_fp = vhost_path[6:]
|
||||
# This can be optimized...
|
||||
while True:
|
||||
# Cast both to lowercase to be case insensitive
|
||||
# Cast all to lowercase to be case insensitive
|
||||
find_if = avail_fp.lower().find("/ifmodule")
|
||||
if find_if != -1:
|
||||
avail_fp = avail_fp[:find_if]
|
||||
|
|
@ -1243,6 +1267,10 @@ def get_file_path(vhost_path):
|
|||
if find_vh != -1:
|
||||
avail_fp = avail_fp[:find_vh]
|
||||
continue
|
||||
find_macro = avail_fp.lower().find("/macro")
|
||||
if find_macro != -1:
|
||||
avail_fp = avail_fp[:find_macro]
|
||||
continue
|
||||
break
|
||||
return avail_fp
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
|
|||
|
||||
:ivar bool ssl: SSLEngine on in vhost
|
||||
:ivar bool enabled: Virtual host is enabled
|
||||
:ivar bool modmacro: VirtualHost is using mod_macro
|
||||
|
||||
https://httpd.apache.org/docs/2.4/vhosts/details.html
|
||||
|
||||
|
|
@ -112,7 +113,9 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
|
|||
# ?: is used for not returning enclosed characters
|
||||
strip_name = re.compile(r"^(?:.+://)?([^ :$]*)")
|
||||
|
||||
def __init__(self, filep, path, addrs, ssl, enabled, name=None, aliases=None):
|
||||
def __init__(self, filep, path, addrs, ssl, enabled, name=None,
|
||||
aliases=None, modmacro=False):
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
"""Initialize a VH."""
|
||||
self.filep = filep
|
||||
|
|
@ -122,6 +125,7 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
|
|||
self.aliases = aliases if aliases is not None else set()
|
||||
self.ssl = ssl
|
||||
self.enabled = enabled
|
||||
self.modmacro = modmacro
|
||||
|
||||
def get_names(self):
|
||||
"""Return a set of all names."""
|
||||
|
|
@ -141,21 +145,25 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
|
|||
"Name: {name}\n"
|
||||
"Aliases: {aliases}\n"
|
||||
"TLS Enabled: {tls}\n"
|
||||
"Site Enabled: {active}".format(
|
||||
"Site Enabled: {active}\n"
|
||||
"mod_macro Vhost: {modmacro}".format(
|
||||
filename=self.filep,
|
||||
vhpath=self.path,
|
||||
addrs=", ".join(str(addr) for addr in self.addrs),
|
||||
name=self.name if self.name is not None else "",
|
||||
aliases=", ".join(name for name in self.aliases),
|
||||
tls="Yes" if self.ssl else "No",
|
||||
active="Yes" if self.enabled else "No"))
|
||||
active="Yes" if self.enabled else "No",
|
||||
modmacro="Yes" if self.modmacro else "No"))
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return (self.filep == other.filep and self.path == other.path and
|
||||
self.addrs == other.addrs and
|
||||
self.get_names() == other.get_names() and
|
||||
self.ssl == other.ssl and self.enabled == other.enabled)
|
||||
self.ssl == other.ssl and
|
||||
self.enabled == other.enabled and
|
||||
self.modmacro == other.modmacro)
|
||||
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -59,14 +59,20 @@ class TwoVhost80Test(util.ApacheTest):
|
|||
# Weak test..
|
||||
ApacheConfigurator.add_parser_arguments(mock.MagicMock())
|
||||
|
||||
def test_get_all_names(self):
|
||||
@mock.patch("zope.component.getUtility")
|
||||
def test_get_all_names(self, mock_getutility):
|
||||
mock_getutility.notification = mock.MagicMock(return_value=True)
|
||||
names = self.config.get_all_names()
|
||||
self.assertEqual(names, set(
|
||||
["letsencrypt.demo", "encryption-example.demo", "ip-172-30-0-17"]))
|
||||
|
||||
@mock.patch("zope.component.getUtility")
|
||||
@mock.patch("letsencrypt_apache.configurator.socket.gethostbyaddr")
|
||||
def test_get_all_names_addrs(self, mock_gethost):
|
||||
def test_get_all_names_addrs(self, mock_gethost, mock_getutility):
|
||||
mock_gethost.side_effect = [("google.com", "", ""), socket.error]
|
||||
notification = mock.Mock()
|
||||
notification.notification = mock.Mock(return_value=True)
|
||||
mock_getutility.return_value = notification
|
||||
vhost = obj.VirtualHost(
|
||||
"fp", "ap",
|
||||
set([obj.Addr(("8.8.8.8", "443")),
|
||||
|
|
@ -97,7 +103,7 @@ class TwoVhost80Test(util.ApacheTest):
|
|||
|
||||
"""
|
||||
vhs = self.config.get_virtual_hosts()
|
||||
self.assertEqual(len(vhs), 4)
|
||||
self.assertEqual(len(vhs), 5)
|
||||
found = 0
|
||||
|
||||
for vhost in vhs:
|
||||
|
|
@ -108,7 +114,7 @@ class TwoVhost80Test(util.ApacheTest):
|
|||
else:
|
||||
raise Exception("Missed: %s" % vhost) # pragma: no cover
|
||||
|
||||
self.assertEqual(found, 4)
|
||||
self.assertEqual(found, 5)
|
||||
|
||||
@mock.patch("letsencrypt_apache.display_ops.select_vhost")
|
||||
def test_choose_vhost_none_avail(self, mock_select):
|
||||
|
|
@ -174,7 +180,7 @@ class TwoVhost80Test(util.ApacheTest):
|
|||
|
||||
def test_non_default_vhosts(self):
|
||||
# pylint: disable=protected-access
|
||||
self.assertEqual(len(self.config._non_default_vhosts()), 3)
|
||||
self.assertEqual(len(self.config._non_default_vhosts()), 4)
|
||||
|
||||
def test_is_site_enabled(self):
|
||||
"""Test if site is enabled.
|
||||
|
|
@ -345,7 +351,7 @@ class TwoVhost80Test(util.ApacheTest):
|
|||
self.assertEqual(self.config.is_name_vhost(self.vh_truth[0]),
|
||||
self.config.is_name_vhost(ssl_vhost))
|
||||
|
||||
self.assertEqual(len(self.config.vhosts), 5)
|
||||
self.assertEqual(len(self.config.vhosts), 6)
|
||||
|
||||
def test_make_vhost_ssl_extra_vhs(self):
|
||||
self.config.aug.match = mock.Mock(return_value=["p1", "p2"])
|
||||
|
|
@ -587,7 +593,7 @@ class TwoVhost80Test(util.ApacheTest):
|
|||
self.vh_truth[1].aliases = set(["yes.default.com"])
|
||||
|
||||
self.config._enable_redirect(self.vh_truth[1], "") # pylint: disable=protected-access
|
||||
self.assertEqual(len(self.config.vhosts), 5)
|
||||
self.assertEqual(len(self.config.vhosts), 6)
|
||||
|
||||
def get_achalls(self):
|
||||
"""Return testing achallenges."""
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class SelectVhostTest(unittest.TestCase):
|
|||
|
||||
@mock.patch("letsencrypt_apache.display_ops.zope.component.getUtility")
|
||||
def test_multiple_names(self, mock_util):
|
||||
mock_util().menu.return_value = (display_util.OK, 4)
|
||||
mock_util().menu.return_value = (display_util.OK, 5)
|
||||
|
||||
self.vhosts.append(
|
||||
obj.VirtualHost(
|
||||
|
|
@ -65,7 +65,7 @@ class SelectVhostTest(unittest.TestCase):
|
|||
False, False,
|
||||
"wildcard.com", set(["*.wildcard.com"])))
|
||||
|
||||
self.assertEqual(self.vhosts[4], self._call(self.vhosts))
|
||||
self.assertEqual(self.vhosts[5], self._call(self.vhosts))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class BasicParserTest(util.ParserTest):
|
|||
test2 = self.parser.find_dir("documentroot")
|
||||
|
||||
self.assertEqual(len(test), 1)
|
||||
self.assertEqual(len(test2), 3)
|
||||
self.assertEqual(len(test2), 4)
|
||||
|
||||
def test_add_dir(self):
|
||||
aug_default = "/files" + self.parser.loc["default"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
<Macro VHost $name $domain>
|
||||
<VirtualHost *:80>
|
||||
ServerName $domain
|
||||
ServerAlias www.$domain
|
||||
DocumentRoot /var/www/html
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
</VirtualHost>
|
||||
</Macro>
|
||||
Use VHost macro1 test.com
|
||||
Use VHost macro2 hostname.org
|
||||
Use VHost macro3 apache.org
|
||||
|
||||
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
|
||||
|
|
@ -0,0 +1 @@
|
|||
../sites-available/mod_macro-example.conf
|
||||
|
|
@ -124,6 +124,11 @@ def get_vh_truth(temp_dir, config_name):
|
|||
os.path.join(aug_pre, "letsencrypt.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]), False, True,
|
||||
"letsencrypt.demo"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "mod_macro-example.conf"),
|
||||
os.path.join(aug_pre,
|
||||
"mod_macro-example.conf/Macro/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]), False, True, modmacro=True)
|
||||
]
|
||||
return vh_truth
|
||||
|
||||
|
|
|
|||
|
|
@ -8,17 +8,16 @@
|
|||
# without requiring specific versions of its dependencies from the operating
|
||||
# system.
|
||||
|
||||
# Note: you can set XDG_DATA_HOME or VENV_PATH before running this script,
|
||||
# if you want to change where the virtual environment will be installed
|
||||
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
|
||||
|
||||
if test "`id -u`" -ne "0" ; then
|
||||
SUDO=sudo
|
||||
else
|
||||
SUDO=
|
||||
fi
|
||||
|
||||
# This script takes the same arguments as the main letsencrypt program, but it
|
||||
# additionally responds to --verbose (more output) and --debug (allow support
|
||||
# for experimental platforms)
|
||||
for arg in "$@" ; do
|
||||
# This first clause is redundant with the third, but hedging on portability
|
||||
if [ "$arg" = "-v" ] || [ "$arg" = "--verbose" ] || echo "$arg" | grep -E -- "-v+$" ; then
|
||||
|
|
@ -28,6 +27,42 @@ for arg in "$@" ; do
|
|||
fi
|
||||
done
|
||||
|
||||
# letsencrypt-auto needs root access to bootstrap OS dependencies, and
|
||||
# letsencrypt itself needs root access for almost all modes of operation
|
||||
# The "normal" case is that sudo is used for the steps that need root, but
|
||||
# this script *can* be run as root (not recommended), or fall back to using
|
||||
# `su`
|
||||
if test "`id -u`" -ne "0" ; then
|
||||
if command -v sudo 1>/dev/null 2>&1; then
|
||||
SUDO=sudo
|
||||
else
|
||||
echo \"sudo\" is not available, will use \"su\" for installation steps...
|
||||
# Because the parameters in `su -c` has to be a string,
|
||||
# we need properly escape it
|
||||
su_sudo() {
|
||||
args=""
|
||||
# This `while` loop iterates over all parameters given to this function.
|
||||
# For each parameter, all `'` will be replace by `'"'"'`, and the escaped string
|
||||
# will be wrap in a pair of `'`, then append to `$args` string
|
||||
# For example, `echo "It's only 1\$\!"` will be escaped to:
|
||||
# 'echo' 'It'"'"'s only 1$!'
|
||||
# │ │└┼┘│
|
||||
# │ │ │ └── `'s only 1$!'` the literal string
|
||||
# │ │ └── `\"'\"` is a single quote (as a string)
|
||||
# │ └── `'It'`, to be concatenated with the strings followed it
|
||||
# └── `echo` wrapped in a pair of `'`, it's totally fine for the shell command itself
|
||||
while [ $# -ne 0 ]; do
|
||||
args="$args'$(printf "%s" "$1" | sed -e "s/'/'\"'\"'/g")' "
|
||||
shift
|
||||
done
|
||||
su root -c "$args"
|
||||
}
|
||||
SUDO=su_sudo
|
||||
fi
|
||||
else
|
||||
SUDO=
|
||||
fi
|
||||
|
||||
ExperimentalBootstrap() {
|
||||
# Arguments: Platform name, boostrap script name, SUDO command (iff needed)
|
||||
if [ "$DEBUG" = 1 ] ; then
|
||||
|
|
@ -85,6 +120,9 @@ then
|
|||
elif [ -f /etc/redhat-release ] ; then
|
||||
echo "Bootstrapping dependencies for RedHat-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_rpm_common.sh
|
||||
elif `grep -q openSUSE /etc/os-release` ; then
|
||||
echo "Bootstrapping dependencies for openSUSE-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_suse_common.sh
|
||||
elif [ -f /etc/arch-release ] ; then
|
||||
echo "Bootstrapping dependencies for Archlinux..."
|
||||
$SUDO $BOOTSTRAP/archlinux.sh
|
||||
|
|
@ -133,7 +171,7 @@ else
|
|||
$VENV_BIN/pip install -U pip > /dev/null
|
||||
printf .
|
||||
# nginx is buggy / disabled for now...
|
||||
$VENV_BIN/pip install -r py26reqs.txt
|
||||
$VENV_BIN/pip install -r py26reqs.txt > /dev/null
|
||||
printf .
|
||||
$VENV_BIN/pip install -U letsencrypt > /dev/null
|
||||
printf .
|
||||
|
|
|
|||
|
|
@ -226,25 +226,26 @@ htmlhelp_basename = 'letsencrypt-compatibility-testdoc'
|
|||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'letsencrypt-compatibility-test.tex', u'letsencrypt-compatibility-test Documentation',
|
||||
u'Let\'s Encrypt Project', 'manual'),
|
||||
(master_doc, 'letsencrypt-compatibility-test.tex',
|
||||
u'letsencrypt-compatibility-test Documentation',
|
||||
u'Let\'s Encrypt Project', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
|
|
@ -273,7 +274,8 @@ latex_documents = [
|
|||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'letsencrypt-compatibility-test', u'letsencrypt-compatibility-test Documentation',
|
||||
(master_doc, 'letsencrypt-compatibility-test',
|
||||
u'letsencrypt-compatibility-test Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
|
@ -287,9 +289,10 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'letsencrypt-compatibility-test', u'letsencrypt-compatibility-test Documentation',
|
||||
author, 'letsencrypt-compatibility-test', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(master_doc, 'letsencrypt-compatibility-test',
|
||||
u'letsencrypt-compatibility-test Documentation',
|
||||
author, 'letsencrypt-compatibility-test',
|
||||
'One line description of project.', 'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
|
@ -309,6 +312,8 @@ intersphinx_mapping = {
|
|||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'letsencrypt': ('https://letsencrypt.readthedocs.org/en/latest/', None),
|
||||
'letsencrypt-apache': ('https://letsencrypt-apache.readthedocs.org/en/latest/', None),
|
||||
'letsencrypt-nginx': ('https://letsencrypt-nginx.readthedocs.org/en/latest/', None),
|
||||
'letsencrypt-apache': (
|
||||
'https://letsencrypt-apache.readthedocs.org/en/latest/', None),
|
||||
'letsencrypt-nginx': (
|
||||
'https://letsencrypt-nginx.readthedocs.org/en/latest/', None),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,25 +225,25 @@ htmlhelp_basename = 'letsencrypt-nginxdoc'
|
|||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'letsencrypt-nginx.tex', u'letsencrypt-nginx Documentation',
|
||||
u'Let\'s Encrypt Project', 'manual'),
|
||||
(master_doc, 'letsencrypt-nginx.tex', u'letsencrypt-nginx Documentation',
|
||||
u'Let\'s Encrypt Project', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
|
|
@ -286,9 +286,9 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'letsencrypt-nginx', u'letsencrypt-nginx Documentation',
|
||||
author, 'letsencrypt-nginx', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(master_doc, 'letsencrypt-nginx', u'letsencrypt-nginx Documentation',
|
||||
author, 'letsencrypt-nginx', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ class NginxConfigurator(common.Plugin):
|
|||
# This is called in determine_authenticator and determine_installer
|
||||
def prepare(self):
|
||||
"""Prepare the authenticator/installer."""
|
||||
# Verify Nginx is installed
|
||||
if not le_util.exe_exists(self.conf('ctl')):
|
||||
raise errors.NoInstallationError
|
||||
|
||||
self.parser = parser.NginxParser(
|
||||
self.conf('server-root'), self.mod_ssl_conf)
|
||||
|
||||
|
|
|
|||
|
|
@ -99,8 +99,8 @@ class NginxDvsni(common.TLSSNI01):
|
|||
for key, body in main:
|
||||
if key == ['http']:
|
||||
found_bucket = False
|
||||
for key, _ in body:
|
||||
if key == bucket_directive[0]:
|
||||
for k, _ in body:
|
||||
if k == bucket_directive[0]:
|
||||
found_bucket = True
|
||||
if not found_bucket:
|
||||
body.insert(0, bucket_directive)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# pylint: disable=too-many-public-methods
|
||||
"""Test for letsencrypt_nginx.configurator."""
|
||||
import os
|
||||
import shutil
|
||||
|
|
@ -29,6 +30,12 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
shutil.rmtree(self.config_dir)
|
||||
shutil.rmtree(self.work_dir)
|
||||
|
||||
@mock.patch("letsencrypt_nginx.configurator.le_util.exe_exists")
|
||||
def test_prepare_no_install(self, mock_exe_exists):
|
||||
mock_exe_exists.return_value = False
|
||||
self.assertRaises(
|
||||
errors.NoInstallationError, self.config.prepare)
|
||||
|
||||
def test_prepare(self):
|
||||
self.assertEquals((1, 6, 2), self.config.version)
|
||||
self.assertEquals(5, len(self.config.parser.parsed))
|
||||
|
|
|
|||
|
|
@ -49,21 +49,25 @@ def get_nginx_configurator(
|
|||
|
||||
backups = os.path.join(work_dir, "backups")
|
||||
|
||||
config = configurator.NginxConfigurator(
|
||||
config=mock.MagicMock(
|
||||
nginx_server_root=config_path,
|
||||
le_vhost_ext="-le-ssl.conf",
|
||||
config_dir=config_dir,
|
||||
work_dir=work_dir,
|
||||
backup_dir=backups,
|
||||
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
|
||||
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
|
||||
server="https://acme-server.org:443/new",
|
||||
tls_sni_01_port=5001,
|
||||
),
|
||||
name="nginx",
|
||||
version=version)
|
||||
config.prepare()
|
||||
with mock.patch("letsencrypt_nginx.configurator.le_util."
|
||||
"exe_exists") as mock_exe_exists:
|
||||
mock_exe_exists.return_value = True
|
||||
|
||||
config = configurator.NginxConfigurator(
|
||||
config=mock.MagicMock(
|
||||
nginx_server_root=config_path,
|
||||
le_vhost_ext="-le-ssl.conf",
|
||||
config_dir=config_dir,
|
||||
work_dir=work_dir,
|
||||
backup_dir=backups,
|
||||
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
|
||||
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
|
||||
server="https://acme-server.org:443/new",
|
||||
tls_sni_01_port=5001,
|
||||
),
|
||||
name="nginx",
|
||||
version=version)
|
||||
config.prepare()
|
||||
|
||||
# Provide general config utility.
|
||||
nsconfig = configuration.NamespaceConfig(config.config)
|
||||
|
|
|
|||
|
|
@ -557,6 +557,7 @@ def plugins_cmd(args, config, plugins): # TODO: Use IDisplay rather than print
|
|||
logger.debug("Filtered plugins: %r", filtered)
|
||||
|
||||
if not args.init and not args.prepare:
|
||||
print str(filtered)
|
||||
return
|
||||
|
||||
filtered.init(config)
|
||||
|
|
@ -564,17 +565,19 @@ def plugins_cmd(args, config, plugins): # TODO: Use IDisplay rather than print
|
|||
logger.debug("Verified plugins: %r", verified)
|
||||
|
||||
if not args.prepare:
|
||||
print str(verified)
|
||||
return
|
||||
|
||||
verified.prepare()
|
||||
available = verified.available()
|
||||
logger.debug("Prepared plugins: %s", available)
|
||||
print str(available)
|
||||
|
||||
|
||||
def read_file(filename, mode="rb"):
|
||||
"""Returns the given file's contents.
|
||||
|
||||
:param str filename: Filename
|
||||
:param str filename: filename as an absolute path
|
||||
:param str mode: open mode (see `open`)
|
||||
|
||||
:returns: A tuple of filename and its contents
|
||||
|
|
@ -584,6 +587,7 @@ def read_file(filename, mode="rb"):
|
|||
|
||||
"""
|
||||
try:
|
||||
filename = os.path.abspath(filename)
|
||||
return filename, open(filename, mode).read()
|
||||
except IOError as exc:
|
||||
raise argparse.ArgumentTypeError(exc.strerror)
|
||||
|
|
@ -676,8 +680,32 @@ class HelpfulArgumentParser(object):
|
|||
parsed_args = self.parser.parse_args(self.args)
|
||||
parsed_args.func = self.VERBS[self.verb]
|
||||
|
||||
parsed_args.domains = self._parse_domains(parsed_args.domains)
|
||||
return parsed_args
|
||||
|
||||
def _parse_domains(self, domains):
|
||||
"""Helper function for parse_args() that parses domains from a
|
||||
(possibly) comma separated list and returns list of unique domains.
|
||||
|
||||
:param domains: List of domain flags
|
||||
:type domains: `list` of `string`
|
||||
|
||||
:returns: List of unique domains
|
||||
:rtype: `list` of `string`
|
||||
|
||||
"""
|
||||
|
||||
uniqd = None
|
||||
|
||||
if domains:
|
||||
dlist = []
|
||||
for domain in domains:
|
||||
dlist.extend([d.strip() for d in domain.split(",")])
|
||||
# Make sure we don't have duplicates
|
||||
uniqd = [d for i, d in enumerate(dlist) if d not in dlist[:i]]
|
||||
|
||||
return uniqd
|
||||
|
||||
def determine_verb(self):
|
||||
"""Determines the verb/subcommand provided by the user.
|
||||
|
||||
|
|
@ -819,7 +847,11 @@ 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", metavar="DOMAIN", action="append")
|
||||
helpful.add(None, "-d", "--domains", dest="domains",
|
||||
metavar="DOMAIN", action="append",
|
||||
help="Domain names to apply. For multiple domains you can use "
|
||||
"multiple -d flags or enter a comma separated list of domains"
|
||||
"as a parameter.")
|
||||
helpful.add(
|
||||
None, "--duplicate", dest="duplicate", action="store_true",
|
||||
help="Allow getting a certificate that duplicates an existing one")
|
||||
|
|
@ -888,7 +920,6 @@ def prepare_and_parse_args(plugins, args):
|
|||
# parser (--help should display plugin-specific options last)
|
||||
_plugins_parsing(helpful, plugins)
|
||||
|
||||
|
||||
return helpful.parse_args()
|
||||
|
||||
|
||||
|
|
@ -939,26 +970,28 @@ def _paths_parser(helpful):
|
|||
if verb in ("install", "revoke", "certonly"):
|
||||
section = verb
|
||||
if verb == "certonly":
|
||||
add(section, "--cert-path", default=flag_default("auth_cert_path"), help=cph)
|
||||
add(section, "--cert-path", type=os.path.abspath,
|
||||
default=flag_default("auth_cert_path"), help=cph)
|
||||
elif verb == "revoke":
|
||||
add(section, "--cert-path", type=read_file, required=True, help=cph)
|
||||
else:
|
||||
add(section, "--cert-path", help=cph, required=(verb == "install"))
|
||||
add(section, "--cert-path", type=os.path.abspath,
|
||||
help=cph, required=(verb == "install"))
|
||||
|
||||
section = "paths"
|
||||
if verb in ("install", "revoke"):
|
||||
section = verb
|
||||
# revoke --key-path reads a file, install --key-path takes a string
|
||||
add(section, "--key-path", type=((verb == "revoke" and read_file) or str),
|
||||
required=(verb == "install"),
|
||||
add(section, "--key-path", required=(verb == "install"),
|
||||
type=((verb == "revoke" and read_file) or os.path.abspath),
|
||||
help="Path to private key for cert creation or revocation (if account key is missing)")
|
||||
|
||||
default_cp = None
|
||||
if verb == "certonly":
|
||||
default_cp = flag_default("auth_chain_path")
|
||||
add("paths", "--fullchain-path", default=default_cp,
|
||||
add("paths", "--fullchain-path", default=default_cp, type=os.path.abspath,
|
||||
help="Accompanying path to a full certificate chain (cert plus chain).")
|
||||
add("paths", "--chain-path", default=default_cp,
|
||||
add("paths", "--chain-path", default=default_cp, type=os.path.abspath,
|
||||
help="Accompanying path to a certificate chain.")
|
||||
add("paths", "--config-dir", default=flag_default("config_dir"),
|
||||
help=config_help("config_dir"))
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import os
|
|||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
import OpenSSL
|
||||
import zope.component
|
||||
|
||||
from acme import client as acme_client
|
||||
from acme import jose
|
||||
|
|
@ -20,6 +21,7 @@ from letsencrypt import continuity_auth
|
|||
from letsencrypt import crypto_util
|
||||
from letsencrypt import errors
|
||||
from letsencrypt import error_handler
|
||||
from letsencrypt import interfaces
|
||||
from letsencrypt import le_util
|
||||
from letsencrypt import reverter
|
||||
from letsencrypt import storage
|
||||
|
|
@ -345,26 +347,13 @@ class Client(object):
|
|||
|
||||
self.installer.save("Deployed Let's Encrypt Certificate")
|
||||
|
||||
# sites may have been enabled / final cleanup
|
||||
with error_handler.ErrorHandler(self._rollback_and_restart):
|
||||
msg = ("We were unable to install your certificate, "
|
||||
"however, we successfully restored your "
|
||||
"server to its prior configuration.")
|
||||
with error_handler.ErrorHandler(self._rollback_and_restart, msg):
|
||||
# sites may have been enabled / final cleanup
|
||||
self.installer.restart()
|
||||
|
||||
def _rollback_and_restart(self):
|
||||
"""Rollback the most recent checkpoint and restart the webserver"""
|
||||
logger.critical("Rolling back to previous server configuration...")
|
||||
try:
|
||||
self.installer.rollback_checkpoints()
|
||||
self.installer.restart()
|
||||
except:
|
||||
# TODO: suggest letshelp-letsencypt here
|
||||
logger.critical("Failure to rollback config "
|
||||
"changes and restart your server")
|
||||
logger.critical("Please submit a bug report to "
|
||||
"https://github.com/letsencrypt/letsencrypt")
|
||||
raise
|
||||
logger.critical("Rollback successful; your server has "
|
||||
"been restarted with your old configuration")
|
||||
|
||||
def enhance_config(self, domains, redirect=None):
|
||||
"""Enhance the configuration.
|
||||
|
||||
|
|
@ -401,7 +390,9 @@ class Client(object):
|
|||
:type vhost: :class:`letsencrypt.interfaces.IInstaller`
|
||||
|
||||
"""
|
||||
with error_handler.ErrorHandler(self.installer.recovery_routine):
|
||||
msg = ("We were unable to set up a redirect for your server, "
|
||||
"however, we successfully installed your certificate.")
|
||||
with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg):
|
||||
for dom in domains:
|
||||
try:
|
||||
self.installer.enhance(dom, "redirect")
|
||||
|
|
@ -410,8 +401,41 @@ class Client(object):
|
|||
raise
|
||||
|
||||
self.installer.save("Add Redirects")
|
||||
|
||||
with error_handler.ErrorHandler(self._rollback_and_restart, msg):
|
||||
self.installer.restart()
|
||||
|
||||
def _recovery_routine_with_msg(self, success_msg):
|
||||
"""Calls the installer's recovery routine and prints success_msg
|
||||
|
||||
:param str success_msg: message to show on successful recovery
|
||||
|
||||
"""
|
||||
self.installer.recovery_routine()
|
||||
reporter = zope.component.getUtility(interfaces.IReporter)
|
||||
reporter.add_message(success_msg, reporter.HIGH_PRIORITY)
|
||||
|
||||
def _rollback_and_restart(self, success_msg):
|
||||
"""Rollback the most recent checkpoint and restart the webserver
|
||||
|
||||
:param str success_msg: message to show on successful rollback
|
||||
|
||||
"""
|
||||
logger.critical("Rolling back to previous server configuration...")
|
||||
reporter = zope.component.getUtility(interfaces.IReporter)
|
||||
try:
|
||||
self.installer.rollback_checkpoints()
|
||||
self.installer.restart()
|
||||
except:
|
||||
# TODO: suggest letshelp-letsencypt here
|
||||
reporter.add_message(
|
||||
"An error occured and we failed to restore your config and "
|
||||
"restart your server. Please submit a bug report to "
|
||||
"https://github.com/letsencrypt/letsencrypt",
|
||||
reporter.HIGH_PRIORITY)
|
||||
raise
|
||||
reporter.add_message(success_msg, reporter.HIGH_PRIORITY)
|
||||
|
||||
|
||||
def validate_key_csr(privkey, csr=None):
|
||||
"""Validate Key and CSR files.
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ class NamespaceConfig(object):
|
|||
|
||||
def __init__(self, namespace):
|
||||
self.namespace = namespace
|
||||
|
||||
self.namespace.config_dir = os.path.abspath(self.namespace.config_dir)
|
||||
self.namespace.work_dir = os.path.abspath(self.namespace.work_dir)
|
||||
self.namespace.logs_dir = os.path.abspath(self.namespace.logs_dir)
|
||||
|
||||
# Check command line parameters sanity, and error out in case of problem.
|
||||
check_config_sanity(self)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Registers functions to be called if an exception or signal occurs."""
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
|
|
@ -40,11 +41,11 @@ class ErrorHandler(object):
|
|||
to be called again by the next signal handler.
|
||||
|
||||
"""
|
||||
def __init__(self, func=None):
|
||||
def __init__(self, func=None, *args, **kwargs):
|
||||
self.funcs = []
|
||||
self.prev_handlers = {}
|
||||
if func is not None:
|
||||
self.register(func)
|
||||
self.register(func, *args, **kwargs)
|
||||
|
||||
def __enter__(self):
|
||||
self.set_signal_handlers()
|
||||
|
|
@ -57,9 +58,13 @@ class ErrorHandler(object):
|
|||
self.call_registered()
|
||||
self.reset_signal_handlers()
|
||||
|
||||
def register(self, func):
|
||||
"""Registers func to be called if an error occurs."""
|
||||
self.funcs.append(func)
|
||||
def register(self, func, *args, **kwargs):
|
||||
"""Sets func to be called with *args and **kwargs during cleanup
|
||||
|
||||
:param function func: function to be called in case of an error
|
||||
|
||||
"""
|
||||
self.funcs.append(functools.partial(func, *args, **kwargs))
|
||||
|
||||
def call_registered(self):
|
||||
"""Calls all registered functions"""
|
||||
|
|
|
|||
|
|
@ -298,7 +298,8 @@ class IInstaller(IPlugin):
|
|||
|
||||
Both title and temporary are needed because a save may be
|
||||
intended to be permanent, but the save is not ready to be a full
|
||||
checkpoint
|
||||
checkpoint. If an exception is raised, it is assumed a new
|
||||
checkpoint was not created.
|
||||
|
||||
:param str title: The title of the save. If a title is given, the
|
||||
configuration will be saved as a new checkpoint and put in a
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ class PluginEntryPointTest(unittest.TestCase):
|
|||
|
||||
def test_description(self):
|
||||
self.assertEqual(
|
||||
"Automatically use a temporary webserver",
|
||||
self.plugin_ep.description)
|
||||
"Automatically use a temporary webserver",
|
||||
self.plugin_ep.description)
|
||||
|
||||
def test_description_with_name(self):
|
||||
self.plugin_ep.plugin_cls = mock.MagicMock(description="Desc")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Tests for letsencrypt.cli."""
|
||||
import argparse
|
||||
import itertools
|
||||
import os
|
||||
import shutil
|
||||
|
|
@ -24,7 +25,7 @@ from letsencrypt.tests import test_util
|
|||
CSR = test_util.vector_path('csr.der')
|
||||
|
||||
|
||||
class CLITest(unittest.TestCase):
|
||||
class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
"""Tests for different commands."""
|
||||
|
||||
def setUp(self):
|
||||
|
|
@ -113,7 +114,6 @@ class CLITest(unittest.TestCase):
|
|||
out = self._help_output(['-h'])
|
||||
self.assertTrue(cli.usage_strings(plugins)[0] in out)
|
||||
|
||||
|
||||
@mock.patch('letsencrypt.cli.sys.stdout')
|
||||
@mock.patch('letsencrypt.cli.sys.stderr')
|
||||
@mock.patch('letsencrypt.cli.client.acme_client.Client')
|
||||
|
|
@ -142,14 +142,35 @@ class CLITest(unittest.TestCase):
|
|||
cli.main(args)
|
||||
acme_net.assert_called_once_with(mock.ANY, verify_ssl=True, user_agent=ua)
|
||||
|
||||
self._call(['install', '--domain', 'foo.bar', '--cert-path', 'cert',
|
||||
|
||||
def test_install_abspath(self):
|
||||
cert = 'cert'
|
||||
key = 'key'
|
||||
chain = 'chain'
|
||||
fullchain = 'fullchain'
|
||||
|
||||
with MockedVerb('install') as mock_install:
|
||||
self._call(['install', '--cert-path', cert, '--key-path', 'key',
|
||||
'--chain-path', 'chain',
|
||||
'--fullchain-path', 'fullchain'])
|
||||
|
||||
args = mock_install.call_args[0][0]
|
||||
self.assertEqual(args.cert_path, os.path.abspath(cert))
|
||||
self.assertEqual(args.key_path, os.path.abspath(key))
|
||||
self.assertEqual(args.chain_path, os.path.abspath(chain))
|
||||
self.assertEqual(args.fullchain_path, os.path.abspath(fullchain))
|
||||
|
||||
@mock.patch('letsencrypt.cli.record_chosen_plugins')
|
||||
@mock.patch('letsencrypt.cli.display_ops')
|
||||
def test_installer_selection(self, mock_display_ops, _rec):
|
||||
self._call(['install', '--domain', 'foo.bar', '--cert-path', 'cert',
|
||||
self._call(['install', '--domains', 'foo.bar', '--cert-path', 'cert',
|
||||
'--key-path', 'key', '--chain-path', 'chain'])
|
||||
self.assertEqual(mock_display_ops.pick_installer.call_count, 1)
|
||||
|
||||
def test_configurator_selection(self):
|
||||
@mock.patch('letsencrypt.le_util.exe_exists')
|
||||
def test_configurator_selection(self, mock_exe_exists):
|
||||
mock_exe_exists.return_value = True
|
||||
real_plugins = disco.PluginsRegistry.find_all()
|
||||
args = ['--agree-dev-preview', '--apache',
|
||||
'--authenticator', 'standalone']
|
||||
|
|
@ -196,6 +217,65 @@ class CLITest(unittest.TestCase):
|
|||
for r in xrange(len(flags)))):
|
||||
self._call(['plugins'] + list(args))
|
||||
|
||||
@mock.patch('letsencrypt.cli.plugins_disco')
|
||||
def test_plugins_no_args(self, mock_disco):
|
||||
ifaces = []
|
||||
plugins = mock_disco.PluginsRegistry.find_all()
|
||||
|
||||
_, stdout, _, _ = self._call(['plugins'])
|
||||
plugins.visible.assert_called_once_with()
|
||||
plugins.visible().ifaces.assert_called_once_with(ifaces)
|
||||
filtered = plugins.visible().ifaces()
|
||||
stdout.write.called_once_with(str(filtered))
|
||||
|
||||
@mock.patch('letsencrypt.cli.plugins_disco')
|
||||
def test_plugins_init(self, mock_disco):
|
||||
ifaces = []
|
||||
plugins = mock_disco.PluginsRegistry.find_all()
|
||||
|
||||
_, stdout, _, _ = self._call(['plugins', '--init'])
|
||||
plugins.visible.assert_called_once_with()
|
||||
plugins.visible().ifaces.assert_called_once_with(ifaces)
|
||||
filtered = plugins.visible().ifaces()
|
||||
self.assertEqual(filtered.init.call_count, 1)
|
||||
filtered.verify.assert_called_once_with(ifaces)
|
||||
verified = filtered.verify()
|
||||
stdout.write.called_once_with(str(verified))
|
||||
|
||||
@mock.patch('letsencrypt.cli.plugins_disco')
|
||||
def test_plugins_prepare(self, mock_disco):
|
||||
ifaces = []
|
||||
plugins = mock_disco.PluginsRegistry.find_all()
|
||||
|
||||
_, stdout, _, _ = self._call(['plugins', '--init', '--prepare'])
|
||||
plugins.visible.assert_called_once_with()
|
||||
plugins.visible().ifaces.assert_called_once_with(ifaces)
|
||||
filtered = plugins.visible().ifaces()
|
||||
self.assertEqual(filtered.init.call_count, 1)
|
||||
filtered.verify.assert_called_once_with(ifaces)
|
||||
verified = filtered.verify()
|
||||
verified.prepare.assert_called_once_with()
|
||||
verified.available.assert_called_once_with()
|
||||
available = verified.available()
|
||||
stdout.write.called_once_with(str(available))
|
||||
|
||||
def test_certonly_abspath(self):
|
||||
cert = 'cert'
|
||||
key = 'key'
|
||||
chain = 'chain'
|
||||
fullchain = 'fullchain'
|
||||
|
||||
with MockedVerb('certonly') as mock_obtaincert:
|
||||
self._call(['certonly', '--cert-path', cert, '--key-path', 'key',
|
||||
'--chain-path', 'chain',
|
||||
'--fullchain-path', 'fullchain'])
|
||||
|
||||
args = mock_obtaincert.call_args[0][0]
|
||||
self.assertEqual(args.cert_path, os.path.abspath(cert))
|
||||
self.assertEqual(args.key_path, os.path.abspath(key))
|
||||
self.assertEqual(args.chain_path, os.path.abspath(chain))
|
||||
self.assertEqual(args.fullchain_path, os.path.abspath(fullchain))
|
||||
|
||||
def test_certonly_bad_args(self):
|
||||
ret, _, _, _ = self._call(['-d', 'foo.bar', 'certonly', '--csr', CSR])
|
||||
self.assertEqual(ret, '--domains and --csr are mutually exclusive')
|
||||
|
|
@ -221,6 +301,27 @@ class CLITest(unittest.TestCase):
|
|||
self._call,
|
||||
['-d', '*.wildcard.tld'])
|
||||
|
||||
def test_parse_domains(self):
|
||||
from letsencrypt import cli
|
||||
plugins = disco.PluginsRegistry.find_all()
|
||||
|
||||
short_args = ['-d', 'example.com']
|
||||
namespace = cli.prepare_and_parse_args(plugins, short_args)
|
||||
self.assertEqual(namespace.domains, ['example.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',
|
||||
'third.org'])
|
||||
|
||||
long_args = ['--domains', 'example.com']
|
||||
namespace = cli.prepare_and_parse_args(plugins, long_args)
|
||||
self.assertEqual(namespace.domains, ['example.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'])
|
||||
|
||||
@mock.patch('letsencrypt.crypto_util.notAfter')
|
||||
@mock.patch('letsencrypt.cli.zope.component.getUtility')
|
||||
def test_certonly_new_request_success(self, mock_get_utility, mock_notAfter):
|
||||
|
|
@ -341,6 +442,20 @@ class CLITest(unittest.TestCase):
|
|||
mock_sys.exit.assert_called_with(''.join(
|
||||
traceback.format_exception_only(KeyboardInterrupt, interrupt)))
|
||||
|
||||
def test_read_file(self):
|
||||
from letsencrypt import cli
|
||||
rel_test_path = os.path.relpath(os.path.join(self.tmp_dir, 'foo'))
|
||||
self.assertRaises(
|
||||
argparse.ArgumentTypeError, cli.read_file, rel_test_path)
|
||||
|
||||
test_contents = 'bar\n'
|
||||
with open(rel_test_path, 'w') as f:
|
||||
f.write(test_contents)
|
||||
|
||||
path, contents = cli.read_file(rel_test_path)
|
||||
self.assertEqual(path, os.path.abspath(path))
|
||||
self.assertEqual(contents, test_contents)
|
||||
|
||||
|
||||
class DetermineAccountTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.cli._determine_account."""
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ class ClientTest(unittest.TestCase):
|
|||
|
||||
shutil.rmtree(tmp_path)
|
||||
|
||||
def test_deploy_certificate(self):
|
||||
def test_deploy_certificate_success(self):
|
||||
self.assertRaises(errors.Error, self.client.deploy_certificate,
|
||||
["foo.bar"], "key", "cert", "chain", "fullchain")
|
||||
|
||||
|
|
@ -166,17 +166,38 @@ class ClientTest(unittest.TestCase):
|
|||
self.assertEqual(installer.save.call_count, 2)
|
||||
installer.restart.assert_called_once_with()
|
||||
|
||||
def test_deploy_certificate_restart_failure_with_recovery(self):
|
||||
def test_deploy_certificate_failure(self):
|
||||
installer = mock.MagicMock()
|
||||
self.client.installer = installer
|
||||
|
||||
installer.deploy_cert.side_effect = errors.PluginError
|
||||
self.assertRaises(errors.PluginError, self.client.deploy_certificate,
|
||||
["foo.bar"], "key", "cert", "chain", "fullchain")
|
||||
installer.recovery_routine.assert_called_once_with()
|
||||
|
||||
def test_deploy_certificate_save_failure(self):
|
||||
installer = mock.MagicMock()
|
||||
self.client.installer = installer
|
||||
|
||||
installer.save.side_effect = errors.PluginError
|
||||
self.assertRaises(errors.PluginError, self.client.deploy_certificate,
|
||||
["foo.bar"], "key", "cert", "chain", "fullchain")
|
||||
installer.recovery_routine.assert_called_once_with()
|
||||
|
||||
@mock.patch("letsencrypt.client.zope.component.getUtility")
|
||||
def test_deploy_certificate_restart_failure(self, mock_get_utility):
|
||||
installer = mock.MagicMock()
|
||||
installer.restart.side_effect = [errors.PluginError, None]
|
||||
self.client.installer = installer
|
||||
|
||||
self.assertRaises(errors.PluginError, self.client.deploy_certificate,
|
||||
["foo.bar"], "key", "cert", "chain", "fullchain")
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 1)
|
||||
installer.rollback_checkpoints.assert_called_once_with()
|
||||
self.assertEqual(installer.restart.call_count, 2)
|
||||
|
||||
def test_deploy_certificate_restart_failure_without_recovery(self):
|
||||
@mock.patch("letsencrypt.client.zope.component.getUtility")
|
||||
def test_deploy_certificate_restart_failure2(self, mock_get_utility):
|
||||
installer = mock.MagicMock()
|
||||
installer.restart.side_effect = errors.PluginError
|
||||
installer.rollback_checkpoints.side_effect = errors.ReverterError
|
||||
|
|
@ -184,6 +205,7 @@ class ClientTest(unittest.TestCase):
|
|||
|
||||
self.assertRaises(errors.PluginError, self.client.deploy_certificate,
|
||||
["foo.bar"], "key", "cert", "chain", "fullchain")
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 1)
|
||||
installer.rollback_checkpoints.assert_called_once_with()
|
||||
self.assertEqual(installer.restart.call_count, 1)
|
||||
|
||||
|
|
@ -201,10 +223,68 @@ class ClientTest(unittest.TestCase):
|
|||
self.assertEqual(installer.save.call_count, 1)
|
||||
installer.restart.assert_called_once_with()
|
||||
|
||||
def test_enhance_config_no_installer(self):
|
||||
self.assertRaises(errors.Error,
|
||||
self.client.enhance_config, ["foo.bar"])
|
||||
|
||||
@mock.patch("letsencrypt.client.zope.component.getUtility")
|
||||
@mock.patch("letsencrypt.client.enhancements")
|
||||
def test_enhance_config_enhance_failure(self, mock_enhancements,
|
||||
mock_get_utility):
|
||||
mock_enhancements.ask.return_value = True
|
||||
installer = mock.MagicMock()
|
||||
self.client.installer = installer
|
||||
installer.enhance.side_effect = errors.PluginError
|
||||
|
||||
self.assertRaises(errors.PluginError,
|
||||
self.client.enhance_config, ["foo.bar"], True)
|
||||
installer.recovery_routine.assert_called_once_with()
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 1)
|
||||
|
||||
@mock.patch("letsencrypt.client.zope.component.getUtility")
|
||||
@mock.patch("letsencrypt.client.enhancements")
|
||||
def test_enhance_config_save_failure(self, mock_enhancements,
|
||||
mock_get_utility):
|
||||
mock_enhancements.ask.return_value = True
|
||||
installer = mock.MagicMock()
|
||||
self.client.installer = installer
|
||||
installer.save.side_effect = errors.PluginError
|
||||
|
||||
self.assertRaises(errors.PluginError,
|
||||
self.client.enhance_config, ["foo.bar"], True)
|
||||
installer.recovery_routine.assert_called_once_with()
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 1)
|
||||
|
||||
@mock.patch("letsencrypt.client.zope.component.getUtility")
|
||||
@mock.patch("letsencrypt.client.enhancements")
|
||||
def test_enhance_config_restart_failure(self, mock_enhancements,
|
||||
mock_get_utility):
|
||||
mock_enhancements.ask.return_value = True
|
||||
installer = mock.MagicMock()
|
||||
self.client.installer = installer
|
||||
installer.restart.side_effect = [errors.PluginError, None]
|
||||
|
||||
self.assertRaises(errors.PluginError,
|
||||
self.client.enhance_config, ["foo.bar"], True)
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 1)
|
||||
installer.rollback_checkpoints.assert_called_once_with()
|
||||
self.assertEqual(installer.restart.call_count, 2)
|
||||
|
||||
@mock.patch("letsencrypt.client.zope.component.getUtility")
|
||||
@mock.patch("letsencrypt.client.enhancements")
|
||||
def test_enhance_config_restart_failure2(self, mock_enhancements,
|
||||
mock_get_utility):
|
||||
mock_enhancements.ask.return_value = True
|
||||
installer = mock.MagicMock()
|
||||
self.client.installer = installer
|
||||
installer.restart.side_effect = errors.PluginError
|
||||
installer.rollback_checkpoints.side_effect = errors.ReverterError
|
||||
|
||||
self.assertRaises(errors.PluginError,
|
||||
self.client.enhance_config, ["foo.bar"], True)
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 1)
|
||||
installer.rollback_checkpoints.assert_called_once_with()
|
||||
self.assertEqual(installer.restart.call_count, 1)
|
||||
|
||||
|
||||
class RollbackTest(unittest.TestCase):
|
||||
|
|
|
|||
|
|
@ -59,6 +59,38 @@ class NamespaceConfigTest(unittest.TestCase):
|
|||
self.namespace.http01_port = None
|
||||
self.assertEqual(80, self.config.http01_port)
|
||||
|
||||
def test_absolute_paths(self):
|
||||
from letsencrypt.configuration import NamespaceConfig
|
||||
|
||||
config_base = "foo"
|
||||
work_base = "bar"
|
||||
logs_base = "baz"
|
||||
|
||||
mock_namespace = mock.MagicMock(spec=['config_dir', 'work_dir',
|
||||
'logs_dir', 'http01_port',
|
||||
'tls_sni_01_port',
|
||||
'domains', 'server'])
|
||||
mock_namespace.config_dir = config_base
|
||||
mock_namespace.work_dir = work_base
|
||||
mock_namespace.logs_dir = logs_base
|
||||
config = NamespaceConfig(mock_namespace)
|
||||
|
||||
self.assertTrue(os.path.isabs(config.config_dir))
|
||||
self.assertEqual(config.config_dir,
|
||||
os.path.join(os.getcwd(), config_base))
|
||||
self.assertTrue(os.path.isabs(config.work_dir))
|
||||
self.assertEqual(config.work_dir,
|
||||
os.path.join(os.getcwd(), work_base))
|
||||
self.assertTrue(os.path.isabs(config.logs_dir))
|
||||
self.assertEqual(config.logs_dir,
|
||||
os.path.join(os.getcwd(), logs_base))
|
||||
self.assertTrue(os.path.isabs(config.accounts_dir))
|
||||
self.assertTrue(os.path.isabs(config.backup_dir))
|
||||
self.assertTrue(os.path.isabs(config.csr_dir))
|
||||
self.assertTrue(os.path.isabs(config.in_progress_dir))
|
||||
self.assertTrue(os.path.isabs(config.key_dir))
|
||||
self.assertTrue(os.path.isabs(config.temp_checkpoint_dir))
|
||||
|
||||
|
||||
class RenewerConfigurationTest(unittest.TestCase):
|
||||
"""Test for letsencrypt.configuration.RenewerConfiguration."""
|
||||
|
|
@ -81,6 +113,28 @@ class RenewerConfigurationTest(unittest.TestCase):
|
|||
self.config.renewal_configs_dir, '/tmp/config/renewal_configs')
|
||||
self.assertEqual(self.config.renewer_config_file, '/tmp/config/r.conf')
|
||||
|
||||
def test_absolute_paths(self):
|
||||
from letsencrypt.configuration import NamespaceConfig
|
||||
from letsencrypt.configuration import RenewerConfiguration
|
||||
|
||||
config_base = "foo"
|
||||
work_base = "bar"
|
||||
logs_base = "baz"
|
||||
|
||||
mock_namespace = mock.MagicMock(spec=['config_dir', 'work_dir',
|
||||
'logs_dir', 'http01_port',
|
||||
'tls_sni_01_port',
|
||||
'domains', 'server'])
|
||||
mock_namespace.config_dir = config_base
|
||||
mock_namespace.work_dir = work_base
|
||||
mock_namespace.logs_dir = logs_base
|
||||
config = RenewerConfiguration(NamespaceConfig(mock_namespace))
|
||||
|
||||
self.assertTrue(os.path.isabs(config.archive_dir))
|
||||
self.assertTrue(os.path.isabs(config.live_dir))
|
||||
self.assertTrue(os.path.isabs(config.renewal_configs_dir))
|
||||
self.assertTrue(os.path.isabs(config.renewer_config_file))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -13,7 +13,11 @@ class ErrorHandlerTest(unittest.TestCase):
|
|||
from letsencrypt import error_handler
|
||||
|
||||
self.init_func = mock.MagicMock()
|
||||
self.handler = error_handler.ErrorHandler(self.init_func)
|
||||
self.init_args = set((42,))
|
||||
self.init_kwargs = {'foo': 'bar'}
|
||||
self.handler = error_handler.ErrorHandler(self.init_func,
|
||||
*self.init_args,
|
||||
**self.init_kwargs)
|
||||
# pylint: disable=protected-access
|
||||
self.signals = error_handler._SIGNALS
|
||||
|
||||
|
|
@ -23,7 +27,8 @@ class ErrorHandlerTest(unittest.TestCase):
|
|||
raise ValueError
|
||||
except ValueError:
|
||||
pass
|
||||
self.init_func.assert_called_once_with()
|
||||
self.init_func.assert_called_once_with(*self.init_args,
|
||||
**self.init_kwargs)
|
||||
|
||||
@mock.patch('letsencrypt.error_handler.os')
|
||||
@mock.patch('letsencrypt.error_handler.signal')
|
||||
|
|
@ -37,7 +42,8 @@ class ErrorHandlerTest(unittest.TestCase):
|
|||
|
||||
signum = self.signals[0]
|
||||
signal_handler(signum, None)
|
||||
self.init_func.assert_called_once_with()
|
||||
self.init_func.assert_called_once_with(*self.init_args,
|
||||
**self.init_kwargs)
|
||||
mock_os.kill.assert_called_once_with(mock_os.getpid(), signum)
|
||||
|
||||
self.handler.reset_signal_handlers()
|
||||
|
|
@ -48,7 +54,8 @@ class ErrorHandlerTest(unittest.TestCase):
|
|||
bad_func = mock.MagicMock(side_effect=[ValueError])
|
||||
self.handler.register(bad_func)
|
||||
self.handler.call_registered()
|
||||
self.init_func.assert_called_once_with()
|
||||
self.init_func.assert_called_once_with(*self.init_args,
|
||||
**self.init_kwargs)
|
||||
bad_func.assert_called_once_with()
|
||||
|
||||
def test_sysexit_ignored(self):
|
||||
|
|
|
|||
|
|
@ -692,6 +692,9 @@ class RenewableCertTests(BaseRenewableCertTest):
|
|||
self.test_rc.configfile["renewalparams"]["http01_port"] = "1234"
|
||||
self.test_rc.configfile["renewalparams"]["account"] = "abcde"
|
||||
self.test_rc.configfile["renewalparams"]["domains"] = ["example.com"]
|
||||
self.test_rc.configfile["renewalparams"]["config_dir"] = "config"
|
||||
self.test_rc.configfile["renewalparams"]["work_dir"] = "work"
|
||||
self.test_rc.configfile["renewalparams"]["logs_dir"] = "logs"
|
||||
mock_auth = mock.MagicMock()
|
||||
mock_pd.PluginsRegistry.find_all.return_value = {"apache": mock_auth}
|
||||
# Fails because "fake" != "apache"
|
||||
|
|
|
|||
|
|
@ -225,25 +225,25 @@ htmlhelp_basename = 'letshelp-letsencryptdoc'
|
|||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'letshelp-letsencrypt.tex', u'letshelp-letsencrypt Documentation',
|
||||
u'Let\'s Encrypt Project', 'manual'),
|
||||
(master_doc, 'letshelp-letsencrypt.tex', u'letshelp-letsencrypt Documentation',
|
||||
u'Let\'s Encrypt Project', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
|
|
@ -286,9 +286,9 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'letshelp-letsencrypt', u'letshelp-letsencrypt Documentation',
|
||||
author, 'letshelp-letsencrypt', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(master_doc, 'letshelp-letsencrypt', u'letshelp-letsencrypt Documentation',
|
||||
author, 'letshelp-letsencrypt', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ common auth --csr "$CSR_PATH" \
|
|||
openssl x509 -in "${root}/csr/0000_cert.pem" -text
|
||||
openssl x509 -in "${root}/csr/0000_chain.pem" -text
|
||||
|
||||
common --domain le3.wtf install \
|
||||
common --domains le3.wtf install \
|
||||
--cert-path "${root}/csr/cert.pem" \
|
||||
--key-path "${root}/csr/key.pem"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue