Merge branch 'master' into revert-2492-unbreak-le-auto

This commit is contained in:
Brad Warren 2016-02-26 08:49:37 -08:00
commit 0c345cb8d3
51 changed files with 2551 additions and 259 deletions

View file

@ -30,10 +30,9 @@ matrix:
env: TOXENV=py26 BOULDER_INTEGRATION=1
- python: "2.6"
env: TOXENV=py26-oldest BOULDER_INTEGRATION=1
# Disabled for now due to requiring sudo -> causing more boulder integration
# DNS timeouts :(
# - python: "2.7"
# env: TOXENV=apacheconftest
- python: "2.7"
env: TOXENV=apacheconftest
sudo: required
- python: "2.7"
env: TOXENV=py27 BOULDER_INTEGRATION=1
- python: "2.7"

View file

@ -1,6 +1,7 @@
"""ACME client API."""
import collections
import datetime
from email.utils import parsedate_tz
import heapq
import logging
import time
@ -11,7 +12,6 @@ from six.moves import http_client # pylint: disable=import-error
import OpenSSL
import requests
import sys
import werkzeug
from acme import errors
from acme import jose
@ -248,6 +248,9 @@ class Client(object): # pylint: disable=too-many-instance-attributes
def retry_after(cls, response, default):
"""Compute next `poll` time based on response ``Retry-After`` header.
Handles integers and various datestring formats per
https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.37
:param requests.Response response: Response from `poll`.
:param int default: Default value (in seconds), used when
``Retry-After`` header is not present or invalid.
@ -260,12 +263,16 @@ class Client(object): # pylint: disable=too-many-instance-attributes
try:
seconds = int(retry_after)
except ValueError:
# pylint: disable=no-member
decoded = werkzeug.parse_date(retry_after) # RFC1123
if decoded is None:
seconds = default
else:
return decoded
# The RFC 2822 parser handles all of RFC 2616's cases in modern
# environments (primarily HTTP 1.1+ but also py27+)
when = parsedate_tz(retry_after)
if when is not None:
try:
tz_secs = datetime.timedelta(when[-1] if when[-1] else 0)
return datetime.datetime(*when[:7]) - tz_secs
except (ValueError, OverflowError):
pass
seconds = default
return datetime.datetime.now() + datetime.timedelta(seconds=seconds)
@ -357,7 +364,7 @@ class Client(object): # pylint: disable=too-many-instance-attributes
attempts = collections.defaultdict(int)
exhausted = set()
# priority queue with datetime (based on Retry-After) as key,
# priority queue with datetime.datetime (based on Retry-After) as key,
# and original Authorization Resource as value
waiting = [(datetime.datetime.now(), authzr) for authzr in authzrs]
# mapping between original Authorization Resource and the most

View file

@ -222,6 +222,17 @@ class ClientTest(unittest.TestCase):
datetime.datetime(2015, 3, 27, 0, 0, 10),
self.client.retry_after(response=self.response, default=10))
@mock.patch('acme.client.datetime')
def test_retry_after_overflow(self, dt_mock):
dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27)
dt_mock.timedelta = datetime.timedelta
dt_mock.datetime.side_effect = datetime.datetime
self.response.headers['Retry-After'] = "Tue, 116 Feb 2016 11:50:00 MST"
self.assertEqual(
datetime.datetime(2015, 3, 27, 0, 0, 10),
self.client.retry_after(response=self.response, default=10))
@mock.patch('acme.client.datetime')
def test_retry_after_seconds(self, dt_mock):
dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27)

View file

@ -20,7 +20,6 @@ install_requires = [
'requests',
'setuptools', # pkg_resources
'six',
'werkzeug',
]
# env markers in extras_require cause problems with older pip: #517

View file

@ -429,6 +429,12 @@ If you don't want to use the Apache plugin, you can omit the
Packages for Debian Jessie are coming in the next few weeks.
**Fedora**
.. code-block:: shell
sudo dnf install letsencrypt
**Gentoo**
The official Let's Encrypt client is available in Gentoo Portage. If you

View file

@ -9,10 +9,10 @@ from letsencrypt import interfaces
from letsencrypt.plugins import common
@zope.interface.implementer(interfaces.IAuthenticator)
@zope.interface.provider(interfaces.IPluginFactory)
class Authenticator(common.Plugin):
"""Example Authenticator."""
zope.interface.implements(interfaces.IAuthenticator)
zope.interface.classProvides(interfaces.IPluginFactory)
description = "Example Authenticator plugin"
@ -20,10 +20,10 @@ class Authenticator(common.Plugin):
# "self" as first argument, e.g. def prepare(self)...
@zope.interface.implementer(interfaces.IInstaller)
@zope.interface.provider(interfaces.IPluginFactory)
class Installer(common.Plugin):
"""Example Installer."""
zope.interface.implements(interfaces.IInstaller)
zope.interface.classProvides(interfaces.IPluginFactory)
description = "Example Installer plugin"

View file

@ -45,9 +45,8 @@ autoload xfm
let dels (s:string) = del s s
(* deal with continuation lines *)
let sep_spc = del /([ \t]+|[ \t]*\\\\\r?\n[ \t]*)/ " "
let sep_osp = Sep.opt_space
let sep_spc = del /([ \t]+|[ \t]*\\\\\r?\n[ \t]*)/ " "
let sep_osp = del /([ \t]*|[ \t]*\\\\\r?\n[ \t]*)/ ""
let sep_eq = del /[ \t]*=[ \t]*/ "="
let nmtoken = /[a-zA-Z:_][a-zA-Z0-9:_.-]*/
@ -60,7 +59,7 @@ let indent = Util.indent
(* borrowed from shellvars.aug *)
let char_arg_dir = /([^\\ '"{\t\r\n]|[^ '"{\t\r\n]+[^\\ \t\r\n])|\\\\"|\\\\'/
let char_arg_sec = /[^ '"\t\r\n>]|\\\\"|\\\\'/
let char_arg_sec = /([^\\ '"\t\r\n>]|[^ '"\t\r\n>]+[^\\ \t\r\n>])|\\\\"|\\\\'/
let char_arg_wl = /([^\\ '"},\t\r\n]|[^ '"},\t\r\n]+[^\\ '"},\t\r\n])/
let cdot = /\\\\./

View file

@ -60,6 +60,8 @@ logger = logging.getLogger(__name__)
# sites-available doesn't allow immediate find_dir search even with save()
# and load()
@zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller)
@zope.interface.provider(interfaces.IPluginFactory)
class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# pylint: disable=too-many-instance-attributes,too-many-public-methods
"""Apache configurator.
@ -80,8 +82,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:ivar dict assoc: Mapping between domains and vhosts
"""
zope.interface.implements(interfaces.IAuthenticator, interfaces.IInstaller)
zope.interface.classProvides(interfaces.IPluginFactory)
description = "Apache Web Server - Alpha"
@ -1075,7 +1075,12 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# even with save() and load()
if not self._is_rewrite_engine_on(general_vh):
self.parser.add_dir(general_vh.path, "RewriteEngine", "on")
names = ssl_vhost.get_names()
for idx, name in enumerate(names):
args = ["%{SERVER_NAME}", "={0}".format(name), "[OR]"]
if idx == len(names) - 1:
args.pop()
self.parser.add_dir(general_vh.path, "RewriteCond", args)
if self.get_version() >= (2, 3, 9):
self.parser.add_dir(general_vh.path, "RewriteRule",
constants.REWRITE_HTTPS_ARGS_WITH_END)
@ -1245,6 +1250,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
for http_vh in candidate_http_vhs:
if http_vh.same_server(ssl_vhost):
return http_vh
# Third filter - if none with same names, return generic
for http_vh in candidate_http_vhs:
if http_vh.same_server(ssl_vhost, generic=True):
return http_vh
return None
@ -1431,7 +1440,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
self.config_test()
logger.debug(self.reverter.view_config_changes(for_logging=True))
self._reload()
def _reload(self):

View file

@ -189,22 +189,28 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
return True
return False
def same_server(self, vhost):
def same_server(self, vhost, generic=False):
"""Determines if the vhost is the same 'server'.
Used in redirection - indicates whether or not the two virtual hosts
serve on the exact same IP combinations, but different ports.
The generic flag indicates that that we're trying to match to a
default or generic vhost
.. todo:: Handle _default_
"""
if vhost.get_names() != self.get_names():
return False
if not generic:
if vhost.get_names() != self.get_names():
return False
# If equal and set is not empty... assume same server
if self.name is not None or self.aliases:
return True
# If equal and set is not empty... assume same server
if self.name is not None or self.aliases:
return True
# If we're looking for a generic vhost, don't return one with a ServerName
elif self.name:
return False
# Both sets of names are empty.

View file

@ -477,7 +477,7 @@ class ApacheParser(object):
# Note: This works for augeas globs, ie. *.conf
if use_new:
inc_test = self.aug.match(
"/augeas/load/Httpd/incl [. ='%s']" % filepath)
"/augeas/load/Httpd['%s' =~ glob(incl)]" % filepath)
if not inc_test:
# Load up files
# This doesn't seem to work on TravisCI

View file

@ -0,0 +1,284 @@
NameVirtualHost 0.0.0.0:7080
NameVirtualHost [00000:000:000:000::0]:7080
NameVirtualHost 0.0.0.0:7080
NameVirtualHost 127.0.0.1:7080
NameVirtualHost 0.0.0.0:7081
NameVirtualHost [0000:000:000:000::2]:7081
NameVirtualHost 0.0.0.0:7081
NameVirtualHost 127.0.0.1:7081
ServerName "example.com"
ServerAdmin "srv@example.com"
DocumentRoot /tmp
<IfModule mod_logio.c>
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" plesklog
</IfModule>
<IfModule !mod_logio.c>
LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" plesklog
</IfModule>
TraceEnable off
ServerTokens ProductOnly
<Directory "/var/www/vhosts">
AllowOverride "All"
Options SymLinksIfOwnerMatch
Order allow,deny
Allow from all
<IfModule sapi_apache2.c>
php_admin_flag engine off
</IfModule>
<IfModule mod_php5.c>
php_admin_flag engine off
</IfModule>
</Directory>
<Directory "/usr/lib/mailman">
AllowOverride "All"
Options SymLinksIfOwnerMatch
Order allow,deny
Allow from all
<IfModule sapi_apache2.c>
php_admin_flag engine off
</IfModule>
<IfModule mod_php5.c>
php_admin_flag engine off
</IfModule>
</Directory>
<IfModule mod_headers.c>
Header add X-Powered-By PleskLin
</IfModule>
<IfModule mod_security2.c>
SecRuleEngine DetectionOnly
SecRequestBodyAccess On
SecRequestBodyLimit 134217728
SecResponseBodyAccess Off
SecResponseBodyLimit 524288
SecAuditEngine On
SecAuditLog "/var/log/modsec_audit.log"
SecAuditLogType serial
</IfModule>
#Include "/etc/httpd/conf/plesk.conf.d/ip_default/*.conf"
<VirtualHost \
0.0.0.0:7080 \
[00000:000:000:0000::2]:7080 \
0.0.0.0:7080 \
127.0.0.1:7080 \
>
ServerName "default"
UseCanonicalName Off
DocumentRoot /tmp
ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin"
<IfModule mod_ssl.c>
SSLEngine off
</IfModule>
<Directory "/var/www/vhosts/default/cgi-bin">
AllowOverride None
Options None
Order allow,deny
Allow from all
</Directory>
<Directory "/var/www/vhosts/default/htdocs">
<IfModule sapi_apache2.c>
php_admin_flag engine on
</IfModule>
<IfModule mod_php5.c>
php_admin_flag engine on
</IfModule>
</Directory>
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost \
0.0.0.0:7081 \
127.0.0.1:7081 \
>
ServerName "default-0_0_0_0"
UseCanonicalName Off
DocumentRoot /tmp
ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin"
SSLEngine on
SSLVerifyClient none
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
<Directory "/var/www/vhosts/default/cgi-bin">
AllowOverride None
Options None
Order allow,deny
Allow from all
</Directory>
<Directory "/var/www/vhosts/default/htdocs">
<IfModule sapi_apache2.c>
php_admin_flag engine on
</IfModule>
<IfModule mod_php5.c>
php_admin_flag engine on
</IfModule>
</Directory>
</VirtualHost>
<VirtualHost \
[00000:000:000:000::2]:7081 \
127.0.0.1:7081 \
>
ServerName "default-0000_000_000_00000__2"
UseCanonicalName Off
DocumentRoot /tmp
ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin"
SSLEngine on
SSLVerifyClient none
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
<Directory "/var/www/vhosts/default/cgi-bin">
AllowOverride None
Options None
Order allow,deny
Allow from all
</Directory>
<Directory "/var/www/vhosts/default/htdocs">
<IfModule sapi_apache2.c>
php_admin_flag engine on
</IfModule>
<IfModule mod_php5.c>
php_admin_flag engine on
</IfModule>
</Directory>
</VirtualHost>
<VirtualHost \
0.0.0.0:7081 \
127.0.0.1:7081 \
>
ServerName "default-0_0_0_0"
UseCanonicalName Off
DocumentRoot /tmp
ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin"
SSLEngine on
SSLVerifyClient none
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
#SSLCACertificateFile "/usr/local/psa/var/certificates/cert-nLy6Z1"
<Directory "/var/www/vhosts/default/cgi-bin">
AllowOverride None
Options None
Order allow,deny
Allow from all
</Directory>
<Directory "/var/www/vhosts/default/htdocs">
<IfModule sapi_apache2.c>
php_admin_flag engine on
</IfModule>
<IfModule mod_php5.c>
php_admin_flag engine on
</IfModule>
</Directory>
</VirtualHost>
</IfModule>
<VirtualHost \
0.0.0.0:7080 \
[0000:000:000:000::2]:7080 \
0.0.0.0:7080 \
127.0.0.1:7080 \
>
DocumentRoot /tmp
ServerName lists
ServerAlias lists.*
UseCanonicalName Off
ScriptAlias "/mailman/" "/usr/lib/mailman/cgi-bin/"
Alias "/icons/" "/var/www/icons/"
Alias "/pipermail/" "/var/lib/mailman/archives/public/"
<IfModule mod_ssl.c>
SSLEngine off
</IfModule>
<Directory "/var/lib/mailman/archives/">
Options FollowSymLinks
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost \
0.0.0.0:7081 \
[00000:000:000:0000::2]:7081 \
0.0.0.0:7081 \
127.0.0.1:7081 \
>
DocumentRoot /tmp
ServerName lists
ServerAlias lists.*
UseCanonicalName Off
ScriptAlias "/mailman/" "/usr/lib/mailman/cgi-bin/"
Alias "/icons/" "/var/www/icons/"
Alias "/pipermail/" "/var/lib/mailman/archives/public/"
SSLEngine on
SSLVerifyClient none
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
<Directory "/var/lib/mailman/archives/">
Options FollowSymLinks
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
</IfModule>
<IfModule mod_rpaf.c>
RPAFproxy_ips 0.0.0.0 [00000:000:000:00000::2] 0.0.0.0
</IfModule>
<IfModule mod_rpaf-2.0.c>
RPAFproxy_ips 0.0.0.0 [0000:000:000:0000::2] 0.0.0.0
</IfModule>
<IfModule mod_remoteip.c>
RemoteIPInternalProxy 0.0.0.0 [0000:000:000:0000::2] 0.0.0.0
RemoteIPHeader X-Forwarded-For
</IfModule>

View file

@ -746,9 +746,9 @@ class TwoVhost80Test(util.ApacheTest):
self.config.parser.modules.add("rewrite_module")
mock_exe.return_value = True
ssl_vh = obj.VirtualHost(
"fp", "ap", set([obj.Addr(("*", "443")),
obj.Addr(("satoshi.com",))]),
"fp", "ap", set([obj.Addr(("*", "443"))]),
True, False)
ssl_vh.name = "satoshi.com"
self.config.vhosts.append(ssl_vh)
self.assertRaises(
errors.PluginError,

View file

@ -0,0 +1,5 @@
# Depends: dav_svn
<IfModule !mod_dav_svn.c>
Include mods-enabled/dav_svn.load
</IfModule>
LoadModule authz_svn_module /usr/lib/apache2/modules/mod_authz_svn.so

View file

@ -0,0 +1,3 @@
<IfModule !mod_dav.c>
LoadModule dav_module /usr/lib/apache2/modules/mod_dav.so
</IfModule>

View file

@ -0,0 +1,56 @@
# dav_svn.conf - Example Subversion/Apache configuration
#
# For details and further options see the Apache user manual and
# the Subversion book.
#
# NOTE: for a setup with multiple vhosts, you will want to do this
# configuration in /etc/apache2/sites-available/*, not here.
# <Location URL> ... </Location>
# URL controls how the repository appears to the outside world.
# In this example clients access the repository as http://hostname/svn/
# Note, a literal /svn should NOT exist in your document root.
#<Location /svn>
# Uncomment this to enable the repository
#DAV svn
# Set this to the path to your repository
#SVNPath /var/lib/svn
# Alternatively, use SVNParentPath if you have multiple repositories under
# under a single directory (/var/lib/svn/repo1, /var/lib/svn/repo2, ...).
# You need either SVNPath and SVNParentPath, but not both.
#SVNParentPath /var/lib/svn
# Access control is done at 3 levels: (1) Apache authentication, via
# any of several methods. A "Basic Auth" section is commented out
# below. (2) Apache <Limit> and <LimitExcept>, also commented out
# below. (3) mod_authz_svn is a svn-specific authorization module
# which offers fine-grained read/write access control for paths
# within a repository. (The first two layers are coarse-grained; you
# can only enable/disable access to an entire repository.) Note that
# mod_authz_svn is noticeably slower than the other two layers, so if
# you don't need the fine-grained control, don't configure it.
# Basic Authentication is repository-wide. It is not secure unless
# you are using https. See the 'htpasswd' command to create and
# manage the password file - and the documentation for the
# 'auth_basic' and 'authn_file' modules, which you will need for this
# (enable them with 'a2enmod').
#AuthType Basic
#AuthName "Subversion Repository"
#AuthUserFile /etc/apache2/dav_svn.passwd
# To enable authorization via mod_authz_svn (enable that module separately):
#<IfModule mod_authz_svn.c>
#AuthzSVNAccessFile /etc/apache2/dav_svn.authz
#</IfModule>
# The following three lines allow anonymous read, but make
# committers authenticate themselves. It requires the 'authz_user'
# module (enable it with 'a2enmod').
#<LimitExcept GET PROPFIND OPTIONS REPORT>
#Require valid-user
#</LimitExcept>
#</Location>

View file

@ -0,0 +1,7 @@
# Depends: dav
<IfModule !mod_dav_svn.c>
<IfModule !mod_dav.c>
Include mods-enabled/dav.load
</IfModule>
LoadModule dav_svn_module /usr/lib/apache2/modules/mod_dav_svn.so
</IfModule>

View file

@ -0,0 +1 @@
../mods-available/authz_svn.load

View file

@ -0,0 +1 @@
../mods-available/dav.load

View file

@ -0,0 +1 @@
../mods-available/dav_svn.conf

View file

@ -0,0 +1 @@
../mods-available/dav_svn.load

View file

@ -62,7 +62,7 @@ class ApacheTlsSni01(common.TLSSNI01):
return []
# Save any changes to the configuration as a precaution
# About to make temporary changes to the config
self.configurator.save()
self.configurator.save("Changes before challenge setup", True)
# Prepare the server for HTTPS
self.configurator.prepare_server_https(
@ -108,7 +108,7 @@ class ApacheTlsSni01(common.TLSSNI01):
self.configurator.reverter.register_file_creation(
True, self.challenge_conf)
logger.debug("writing a config file with text: %s", config_text)
logger.debug("writing a config file with text:\n %s", config_text)
with open(self.challenge_conf, "w") as new_conf:
new_conf.write(config_text)
@ -144,6 +144,8 @@ class ApacheTlsSni01(common.TLSSNI01):
if len(self.configurator.parser.find_dir(
parser.case_i("Include"), self.challenge_conf)) == 0:
# print "Including challenge virtual host(s)"
logger.debug("Adding Include %s to %s",
self.challenge_conf, parser.get_aug_path(main_config))
self.configurator.parser.add_dir(
parser.get_aug_path(main_config),
"Include", self.challenge_conf)

View file

@ -1 +0,0 @@
letsencrypt-auto-source/letsencrypt-auto

1809
letsencrypt-auto Executable file

File diff suppressed because it is too large Load diff

View file

@ -168,7 +168,7 @@ BootstrapDebCommon() {
/bin/echo '(Backports are only installed if explicitly requested via "apt-get install -t wheezy-backports")'
fi
sudo sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO apt-get update
fi
fi
@ -307,10 +307,11 @@ BootstrapArchCommon() {
pkg-config
"
missing=$("$SUDO" pacman -T $deps)
# pacman -T exits with 127 if there are missing dependencies
missing=$($SUDO pacman -T $deps) || true
if [ "$missing" ]; then
"$SUDO" pacman -S --needed $missing
$SUDO pacman -S --needed $missing
fi
}
@ -327,19 +328,19 @@ BootstrapGentooCommon() {
case "$PACKAGE_MANAGER" in
(paludis)
"$SUDO" cave resolve --preserve-world --keep-targets if-possible $PACKAGES -x
$SUDO cave resolve --preserve-world --keep-targets if-possible $PACKAGES -x
;;
(pkgcore)
"$SUDO" pmerge --noreplace --oneshot $PACKAGES
$SUDO pmerge --noreplace --oneshot $PACKAGES
;;
(portage|*)
"$SUDO" emerge --noreplace --oneshot $PACKAGES
$SUDO emerge --noreplace --oneshot $PACKAGES
;;
esac
}
BootstrapFreeBsd() {
"$SUDO" pkg install -Ay \
$SUDO pkg install -Ay \
python \
py27-virtualenv \
augeas \
@ -362,8 +363,8 @@ BootstrapMac() {
brew install dialog
fi
if ! hash pip 2>/dev/null; then
echo "pip not installed.\nInstalling python from Homebrew..."
if [ -z "$(brew list --versions python)" ]; then
echo "python not installed.\nInstalling python from Homebrew..."
brew install python
fi
@ -620,10 +621,6 @@ traceback2==1.4.0
# sha256: IogqDkGMKE4fcYqCKzsCKUTVPS2QjhaQsxmp0-ssBXk
unittest2==1.1.0
# sha256: aUkbUwUVfDxuDwSnAZhNaud_1yn8HJrNJQd_HfOFMms
# sha256: 619wCpv8lkILBVY1r5AC02YuQ9gMP_0x8iTCW8DV9GI
Werkzeug==0.11.3
# sha256: KCwRK1XdjjyGmjVx-GdnwVCrEoSprOK97CJsWSrK-Bo
zope.component==4.2.2
@ -756,6 +753,7 @@ except ImportError:
from pip.util import url_to_path # 0.7.0
except ImportError:
from pip.util import url_to_filename as url_to_path # 0.6.2
from pip.exceptions import InstallationError
from pip.index import PackageFinder, Link
try:
from pip.log import logger
@ -774,7 +772,7 @@ except ImportError:
DownloadProgressBar = DownloadProgressSpinner = NullProgressBar
__version__ = 3, 0, 0
__version__ = 3, 1, 1
try:
from pip.index import FormatControl # noqa
@ -792,6 +790,7 @@ ITS_FINE_ITS_FINE = 0
SOMETHING_WENT_WRONG = 1
# "Traditional" for command-line errors according to optparse docs:
COMMAND_LINE_ERROR = 2
UNHANDLED_EXCEPTION = 3
ARCHIVE_EXTENSIONS = ('.tar.bz2', '.tar.gz', '.tgz', '.tar', '.zip')
@ -1554,7 +1553,7 @@ def peep_install(argv):
first_every_last(buckets[SatisfiedReq], *printers)
return ITS_FINE_ITS_FINE
except (UnsupportedRequirementError, DownloadError) as exc:
except (UnsupportedRequirementError, InstallationError, DownloadError) as exc:
out(str(exc))
return SOMETHING_WENT_WRONG
finally:
@ -1574,16 +1573,23 @@ def peep_port(paths):
print('Please specify one or more requirements files so I have '
'something to port.\n')
return COMMAND_LINE_ERROR
comes_from = None
for req in chain.from_iterable(
_parse_requirements(path, package_finder(argv)) for path in paths):
req_path, req_line = path_and_line(req)
hashes = [hexlify(urlsafe_b64decode((hash + '=').encode('ascii'))).decode('ascii')
for hash in hashes_above(*path_and_line(req))]
for hash in hashes_above(req_path, req_line)]
if req_path != comes_from:
print()
print('# from %s' % req_path)
print()
comes_from = req_path
if not hashes:
print(req.req)
elif len(hashes) == 1:
print('%s --hash=sha256:%s' % (req.req, hashes[0]))
else:
print('%s' % req.req, end='')
print('%s' % (req.link if getattr(req, 'link', None) else req.req), end='')
for hash in hashes:
print(' \\')
print(' --hash=sha256:%s' % hash, end='')
@ -1628,7 +1634,7 @@ if __name__ == '__main__':
exit(main())
except Exception:
exception_handler(*sys.exc_info())
exit(SOMETHING_WENT_WRONG)
exit(UNHANDLED_EXCEPTION)
UNLIKELY_EOF
# -------------------------------------------------------------------------
@ -1810,12 +1816,21 @@ UNLIKELY_EOF
# future Windows compatibility.
"$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION"
# Install new copy of letsencrypt-auto. This preserves permissions and
# ownership from the old copy.
# Install new copy of letsencrypt-auto.
# TODO: Deal with quotes in pathnames.
echo "Replacing letsencrypt-auto..."
echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$0"
$SUDO cp "$TEMP_DIR/letsencrypt-auto" "$0"
# Clone permissions with cp. chmod and chown don't have a --reference
# option on OS X or BSD, and stat -c on Linux is stat -f on OS X and BSD:
echo " " $SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
$SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
$SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
# Using mv rather than cp leaves the old file descriptor pointing to the
# original copy so the shell can continue to read it unmolested. mv across
# filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the
# cp is unlikely to fail (esp. under sudo) if the rm doesn't.
echo " " $SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
$SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
# TODO: Clean up temp dir safely, even if it has quotes in its path.
rm -rf "$TEMP_DIR"
fi # A newer version is available.

View file

@ -254,12 +254,21 @@ UNLIKELY_EOF
# future Windows compatibility.
"$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION"
# Install new copy of letsencrypt-auto. This preserves permissions and
# ownership from the old copy.
# Install new copy of letsencrypt-auto.
# TODO: Deal with quotes in pathnames.
echo "Replacing letsencrypt-auto..."
echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$0"
$SUDO cp "$TEMP_DIR/letsencrypt-auto" "$0"
# Clone permissions with cp. chmod and chown don't have a --reference
# option on OS X or BSD, and stat -c on Linux is stat -f on OS X and BSD:
echo " " $SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
$SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
$SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
# Using mv rather than cp leaves the old file descriptor pointing to the
# original copy so the shell can continue to read it unmolested. mv across
# filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the
# cp is unlikely to fail (esp. under sudo) if the rm doesn't.
echo " " $SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
$SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
# TODO: Clean up temp dir safely, even if it has quotes in its path.
rm -rf "$TEMP_DIR"
fi # A newer version is available.

View file

@ -18,9 +18,10 @@ BootstrapArchCommon() {
pkg-config
"
missing=$("$SUDO" pacman -T $deps)
# pacman -T exits with 127 if there are missing dependencies
missing=$($SUDO pacman -T $deps) || true
if [ "$missing" ]; then
"$SUDO" pacman -S --needed $missing
$SUDO pacman -S --needed $missing
fi
}

View file

@ -51,7 +51,7 @@ BootstrapDebCommon() {
/bin/echo '(Backports are only installed if explicitly requested via "apt-get install -t wheezy-backports")'
fi
sudo sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO apt-get update
fi
fi

View file

@ -1,5 +1,5 @@
BootstrapFreeBsd() {
"$SUDO" pkg install -Ay \
$SUDO pkg install -Ay \
python \
py27-virtualenv \
augeas \

View file

@ -11,13 +11,13 @@ BootstrapGentooCommon() {
case "$PACKAGE_MANAGER" in
(paludis)
"$SUDO" cave resolve --preserve-world --keep-targets if-possible $PACKAGES -x
$SUDO cave resolve --preserve-world --keep-targets if-possible $PACKAGES -x
;;
(pkgcore)
"$SUDO" pmerge --noreplace --oneshot $PACKAGES
$SUDO pmerge --noreplace --oneshot $PACKAGES
;;
(portage|*)
"$SUDO" emerge --noreplace --oneshot $PACKAGES
$SUDO emerge --noreplace --oneshot $PACKAGES
;;
esac
}

View file

@ -14,8 +14,8 @@ BootstrapMac() {
brew install dialog
fi
if ! hash pip 2>/dev/null; then
echo "pip not installed.\nInstalling python from Homebrew..."
if [ -z "$(brew list --versions python)" ]; then
echo "python not installed.\nInstalling python from Homebrew..."
brew install python
fi

View file

@ -172,10 +172,6 @@ traceback2==1.4.0
# sha256: IogqDkGMKE4fcYqCKzsCKUTVPS2QjhaQsxmp0-ssBXk
unittest2==1.1.0
# sha256: aUkbUwUVfDxuDwSnAZhNaud_1yn8HJrNJQd_HfOFMms
# sha256: 619wCpv8lkILBVY1r5AC02YuQ9gMP_0x8iTCW8DV9GI
Werkzeug==0.11.3
# sha256: KCwRK1XdjjyGmjVx-GdnwVCrEoSprOK97CJsWSrK-Bo
zope.component==4.2.2

View file

@ -86,6 +86,7 @@ except ImportError:
from pip.util import url_to_path # 0.7.0
except ImportError:
from pip.util import url_to_filename as url_to_path # 0.6.2
from pip.exceptions import InstallationError
from pip.index import PackageFinder, Link
try:
from pip.log import logger
@ -104,7 +105,7 @@ except ImportError:
DownloadProgressBar = DownloadProgressSpinner = NullProgressBar
__version__ = 3, 0, 0
__version__ = 3, 1, 1
try:
from pip.index import FormatControl # noqa
@ -122,6 +123,7 @@ ITS_FINE_ITS_FINE = 0
SOMETHING_WENT_WRONG = 1
# "Traditional" for command-line errors according to optparse docs:
COMMAND_LINE_ERROR = 2
UNHANDLED_EXCEPTION = 3
ARCHIVE_EXTENSIONS = ('.tar.bz2', '.tar.gz', '.tgz', '.tar', '.zip')
@ -884,7 +886,7 @@ def peep_install(argv):
first_every_last(buckets[SatisfiedReq], *printers)
return ITS_FINE_ITS_FINE
except (UnsupportedRequirementError, DownloadError) as exc:
except (UnsupportedRequirementError, InstallationError, DownloadError) as exc:
out(str(exc))
return SOMETHING_WENT_WRONG
finally:
@ -904,16 +906,23 @@ def peep_port(paths):
print('Please specify one or more requirements files so I have '
'something to port.\n')
return COMMAND_LINE_ERROR
comes_from = None
for req in chain.from_iterable(
_parse_requirements(path, package_finder(argv)) for path in paths):
req_path, req_line = path_and_line(req)
hashes = [hexlify(urlsafe_b64decode((hash + '=').encode('ascii'))).decode('ascii')
for hash in hashes_above(*path_and_line(req))]
for hash in hashes_above(req_path, req_line)]
if req_path != comes_from:
print()
print('# from %s' % req_path)
print()
comes_from = req_path
if not hashes:
print(req.req)
elif len(hashes) == 1:
print('%s --hash=sha256:%s' % (req.req, hashes[0]))
else:
print('%s' % req.req, end='')
print('%s' % (req.link if getattr(req, 'link', None) else req.req), end='')
for hash in hashes:
print(' \\')
print(' --hash=sha256:%s' % hash, end='')
@ -958,4 +967,4 @@ if __name__ == '__main__':
exit(main())
except Exception:
exception_handler(*sys.exc_info())
exit(SOMETHING_WENT_WRONG)
exit(UNHANDLED_EXCEPTION)

View file

@ -34,11 +34,10 @@ SHARED_MODULES = {
"vhost_alias"}
@zope.interface.implementer(interfaces.IConfiguratorProxy)
class Proxy(apache_common.Proxy):
"""Wraps the ApacheConfigurator for Apache 2.4 tests"""
zope.interface.implements(interfaces.IConfiguratorProxy)
def __init__(self, args):
"""Initializes the plugin with the given command line args"""
super(Proxy, self).__init__(args)

View file

@ -19,12 +19,11 @@ APACHE_VERSION_REGEX = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
APACHE_COMMANDS = ["apachectl", "a2enmod", "a2dismod"]
@zope.interface.implementer(interfaces.IConfiguratorProxy)
class Proxy(configurators_common.Proxy):
# pylint: disable=too-many-instance-attributes
"""A common base for Apache test configurators"""
zope.interface.implements(interfaces.IConfiguratorProxy)
def __init__(self, args):
"""Initializes the plugin with the given command line args"""
super(Proxy, self).__init__(args)

View file

@ -12,10 +12,10 @@ from letsencrypt import interfaces
logger = logging.getLogger(__name__)
@zope.interface.implementer(interfaces.IValidator)
class Validator(object):
# pylint: disable=no-self-use
"""Collection of functions to test a live webserver's configuration"""
zope.interface.implements(interfaces.IValidator)
def certificate(self, cert, name, alt_host=None, port=443):
"""Verifies the certificate presented at name is cert"""

View file

@ -31,6 +31,8 @@ from letsencrypt_nginx import parser
logger = logging.getLogger(__name__)
@zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller)
@zope.interface.provider(interfaces.IPluginFactory)
class NginxConfigurator(common.Plugin):
# pylint: disable=too-many-instance-attributes,too-many-public-methods
"""Nginx configurator.
@ -52,8 +54,6 @@ class NginxConfigurator(common.Plugin):
:ivar tup version: version of Nginx
"""
zope.interface.implements(interfaces.IAuthenticator, interfaces.IInstaller)
zope.interface.classProvides(interfaces.IPluginFactory)
description = "Nginx Web Server - currently doesn't work"

View file

@ -230,23 +230,23 @@ class NginxParserTest(util.NginxTest):
def test_parse_server_ssl(self):
server = parser.parse_server([
['listen', '443']
])
['listen', '443']
])
self.assertFalse(server['ssl'])
server = parser.parse_server([
['listen', '443 ssl']
])
['listen', '443 ssl']
])
self.assertTrue(server['ssl'])
server = parser.parse_server([
['listen', '443'], ['ssl', 'off']
])
['listen', '443'], ['ssl', 'off']
])
self.assertFalse(server['ssl'])
server = parser.parse_server([
['listen', '443'], ['ssl', 'on']
])
['listen', '443'], ['ssl', 'on']
])
self.assertTrue(server['ssl'])
if __name__ == "__main__":

View file

@ -983,10 +983,6 @@ def renew(config, unused_plugins):
"renew specific certificates, use the certonly "
"command. The renew verb may provide other options "
"for selecting certificates to renew in the future.")
if config.csr is not None:
raise errors.Error("Currently, the renew verb cannot be used when "
"specifying a CSR file. Please try the certonly "
"command instead.")
renewer_config = configuration.RenewerConfiguration(config)
renew_successes = []
renew_failures = []
@ -1030,6 +1026,12 @@ def renew(config, unused_plugins):
_renew_describe_results(config, renew_successes, renew_failures,
renew_skipped, parse_failures)
if renew_failures or parse_failures:
raise errors.Error("{0} renew failure(s), {1} parse failure(s)".format(
len(renew_failures), len(parse_failures)))
else:
logger.debug("no renewal failures")
def revoke(config, unused_plugins): # TODO: coop with renewal config
"""Revoke a previously obtained certificate."""
@ -1244,6 +1246,12 @@ class HelpfulArgumentParser(object):
Process a --csr flag. This needs to happen early enough that the
webroot plugin can know about the calls to _process_domain
"""
if parsed_args.verb != "certonly":
raise errors.Error("Currently, a CSR file may only be specified "
"when obtaining a new or replacement "
"via the certonly command. Please try the "
"certonly command instead.")
try:
csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="der")
typ = OpenSSL.crypto.FILETYPE_ASN1

View file

@ -11,6 +11,7 @@ from letsencrypt import interfaces
from letsencrypt import le_util
@zope.interface.implementer(interfaces.IConfig)
class NamespaceConfig(object):
"""Configuration wrapper around :class:`argparse.Namespace`.
@ -32,7 +33,6 @@ class NamespaceConfig(object):
:type namespace: :class:`argparse.Namespace`
"""
zope.interface.implements(interfaces.IConfig)
def __init__(self, namespace):
self.namespace = namespace

View file

@ -9,6 +9,7 @@ from letsencrypt import interfaces
from letsencrypt import proof_of_possession
@zope.interface.implementer(interfaces.IAuthenticator)
class ContinuityAuthenticator(object):
"""IAuthenticator for
:const:`~acme.challenges.ContinuityChallenge` class challenges.
@ -18,7 +19,6 @@ class ContinuityAuthenticator(object):
:class:`letsencrypt.proof_of_possession.Proof_of_Possession`
"""
zope.interface.implements(interfaces.IAuthenticator)
# This will have an installer soon for get_key/cert purposes
def __init__(self, config, installer): # pylint: disable=unused-argument

View file

@ -118,6 +118,7 @@ def make_csr(key_str, domains):
value=", ".join("DNS:%s" % d for d in domains)
),
])
req.set_version(2)
req.set_pubkey(pkey)
req.sign(pkey, "sha256")
return tuple(OpenSSL.crypto.dump_certificate_request(method, req)

View file

@ -36,11 +36,10 @@ def _wrap_lines(msg):
fixed_l.append(textwrap.fill(line, 80))
return os.linesep.join(fixed_l)
@zope.interface.implementer(interfaces.IDisplay)
class NcursesDisplay(object):
"""Ncurses-based display."""
zope.interface.implements(interfaces.IDisplay)
def __init__(self, width=WIDTH, height=HEIGHT):
super(NcursesDisplay, self).__init__()
self.dialog = dialog.Dialog()
@ -176,11 +175,10 @@ class NcursesDisplay(object):
message, width=self.width, height=self.height, choices=choices)
@zope.interface.implementer(interfaces.IDisplay)
class FileDisplay(object):
"""File-based display."""
zope.interface.implements(interfaces.IDisplay)
def __init__(self, outfile):
super(FileDisplay, self).__init__()
self.outfile = outfile
@ -411,11 +409,10 @@ class FileDisplay(object):
return OK, selection
@zope.interface.implementer(interfaces.IDisplay)
class NoninteractiveDisplay(object):
"""An iDisplay implementation that never asks for interactive user input"""
zope.interface.implements(interfaces.IDisplay)
def __init__(self, outfile):
super(NoninteractiveDisplay, self).__init__()
self.outfile = outfile

View file

@ -31,11 +31,11 @@ hostname_regex = re.compile(
r"^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*[a-z]+$", re.IGNORECASE)
@zope.interface.implementer(interfaces.IPlugin)
class Plugin(object):
"""Generic plugin."""
zope.interface.implements(interfaces.IPlugin)
# classProvides is not inherited, subclasses must define it on their own
#zope.interface.classProvides(interfaces.IPluginFactory)
# provider is not inherited, subclasses must define it on their own
# @zope.interface.provider(interfaces.IPluginFactory)
def __init__(self, config, name):
self.config = config

View file

@ -23,6 +23,8 @@ from letsencrypt.plugins import common
logger = logging.getLogger(__name__)
@zope.interface.implementer(interfaces.IAuthenticator)
@zope.interface.provider(interfaces.IPluginFactory)
class Authenticator(common.Plugin):
"""Manual Authenticator.
@ -34,8 +36,6 @@ class Authenticator(common.Plugin):
.. todo:: Support for `~.challenges.TLSSNI01`.
"""
zope.interface.implements(interfaces.IAuthenticator)
zope.interface.classProvides(interfaces.IPluginFactory)
hidden = True
description = "Manually configure an HTTP server"

View file

@ -11,10 +11,10 @@ from letsencrypt.plugins import common
logger = logging.getLogger(__name__)
@zope.interface.implementer(interfaces.IInstaller)
@zope.interface.provider(interfaces.IPluginFactory)
class Installer(common.Plugin):
"""Null installer."""
zope.interface.implements(interfaces.IInstaller)
zope.interface.classProvides(interfaces.IPluginFactory)
description = "Null Installer"
hidden = True

View file

@ -135,6 +135,8 @@ def supported_challenges_validator(data):
return data
@zope.interface.implementer(interfaces.IAuthenticator)
@zope.interface.provider(interfaces.IPluginFactory)
class Authenticator(common.Plugin):
"""Standalone Authenticator.
@ -143,8 +145,6 @@ class Authenticator(common.Plugin):
challenges from the certificate authority. Therefore, it does not
rely on any existing server program.
"""
zope.interface.implements(interfaces.IAuthenticator)
zope.interface.classProvides(interfaces.IPluginFactory)
description = "Automatically use a temporary webserver"

View file

@ -17,10 +17,10 @@ from letsencrypt.plugins import common
logger = logging.getLogger(__name__)
@zope.interface.implementer(interfaces.IAuthenticator)
@zope.interface.provider(interfaces.IPluginFactory)
class Authenticator(common.Plugin):
"""Webroot Authenticator."""
zope.interface.implements(interfaces.IAuthenticator)
zope.interface.classProvides(interfaces.IPluginFactory)
description = "Webroot Authenticator"

View file

@ -17,6 +17,7 @@ from letsencrypt import le_util
logger = logging.getLogger(__name__)
@zope.interface.implementer(interfaces.IReporter)
class Reporter(object):
"""Collects and displays information to the user.
@ -24,7 +25,6 @@ class Reporter(object):
the user.
"""
zope.interface.implements(interfaces.IReporter)
HIGH_PRIORITY = 0
"""High priority constant. See `add_message`."""

View file

@ -356,6 +356,15 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._call,
['-d', '204.11.231.35'])
def test_run_with_csr(self):
# This is an error because you can only use --csr with certonly
try:
self._call(['--csr', CSR])
except errors.Error as e:
assert "Please try the certonly" in e.message
return
assert False, "Expected supplying --csr to fail with default verb"
def _get_argument_parser(self):
plugins = disco.PluginsRegistry.find_all()
return functools.partial(cli.prepare_and_parse_args, plugins)
@ -540,8 +549,8 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._certonly_new_request_common, mock_client)
def _test_renewal_common(self, due_for_renewal, extra_args, log_out=None,
args=None, renew=True):
# pylint: disable=too-many-locals
args=None, renew=True, error_expected=False):
# pylint: disable=too-many-locals,too-many-arguments
cert_path = 'letsencrypt/tests/testdata/cert.pem'
chain_path = '/etc/letsencrypt/live/foo.bar/fullchain.pem'
mock_lineage = mock.MagicMock(cert=cert_path, fullchain=chain_path)
@ -567,11 +576,17 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
args = ['-d', 'isnot.org', '-a', 'standalone', 'certonly']
if extra_args:
args += extra_args
self._call(args)
if log_out:
with open(os.path.join(self.logs_dir, "letsencrypt.log")) as lf:
self.assertTrue(log_out in lf.read())
try:
ret, _, _, _ = self._call(args)
if ret:
print "Returned", ret
raise AssertionError(ret)
assert not error_expected, "renewal should have errored"
except: # pylint: disable=bare-except
if not error_expected:
raise AssertionError(
"Unexpected renewal error:\n" +
traceback.format_exc())
if renew:
mock_client.obtain_certificate.assert_called_once_with(['isnot.org'])
@ -580,6 +595,10 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
except:
self._dump_log()
raise
finally:
if log_out:
with open(os.path.join(self.logs_dir, "letsencrypt.log")) as lf:
self.assertTrue(log_out in lf.read())
return mock_lineage, mock_get_utility
@ -625,11 +644,13 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._test_renewal_common(True, [], args=args, renew=True)
def test_renew_verb_empty_config(self):
renewer_configs_dir = os.path.join(self.config_dir, 'renewal')
os.makedirs(renewer_configs_dir)
with open(os.path.join(renewer_configs_dir, 'empty.conf'), 'w'):
rd = os.path.join(self.config_dir, 'renewal')
if not os.path.exists(rd):
os.makedirs(rd)
with open(os.path.join(rd, 'empty.conf'), 'w'):
pass # leave the file empty
self.test_renew_verb()
args = ["renew", "--dry-run", "-tvv"]
self._test_renewal_common(False, [], args=args, renew=False, error_expected=True)
def _make_dummy_renewal_config(self):
renewer_configs_dir = os.path.join(self.config_dir, 'renewal')
@ -637,7 +658,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
with open(os.path.join(renewer_configs_dir, 'test.conf'), 'w') as f:
f.write("My contents don't matter")
def _test_renew_common(self, renewalparams=None,
def _test_renew_common(self, renewalparams=None, error_expected=False,
names=None, assert_oc_called=None):
self._make_dummy_renewal_config()
with mock.patch('letsencrypt.storage.RenewableCert') as mock_rc:
@ -649,7 +670,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
mock_lineage.names.return_value = names
mock_rc.return_value = mock_lineage
with mock.patch('letsencrypt.cli.obtain_cert') as mock_obtain_cert:
self._test_renewal_common(True, None,
self._test_renewal_common(True, None, error_expected=error_expected,
args=['renew'], renew=False)
if assert_oc_called is not None:
if assert_oc_called:
@ -658,21 +679,22 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertFalse(mock_obtain_cert.called)
def test_renew_no_renewalparams(self):
self._test_renew_common(assert_oc_called=False)
self._test_renew_common(assert_oc_called=False, error_expected=True)
def test_renew_no_authenticator(self):
self._test_renew_common(renewalparams={}, assert_oc_called=False)
self._test_renew_common(renewalparams={}, assert_oc_called=False,
error_expected=True)
def test_renew_with_bad_int(self):
renewalparams = {'authenticator': 'webroot',
'rsa_key_size': 'over 9000'}
self._test_renew_common(renewalparams=renewalparams,
self._test_renew_common(renewalparams=renewalparams, error_expected=True,
assert_oc_called=False)
def test_renew_with_bad_domain(self):
renewalparams = {'authenticator': 'webroot'}
names = ['*.example.com']
self._test_renew_common(renewalparams=renewalparams,
self._test_renew_common(renewalparams=renewalparams, error_expected=True,
names=names, assert_oc_called=False)
def test_renew_plugin_config_restoration(self):
@ -686,7 +708,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
# pylint: disable=protected-access
with mock.patch('letsencrypt.cli._reconstitute') as mock_reconstitute:
mock_reconstitute.side_effect = Exception
self._test_renew_common(assert_oc_called=False)
self._test_renew_common(assert_oc_called=False, error_expected=True)
def test_renew_obtain_cert_error(self):
self._make_dummy_renewal_config()
@ -698,15 +720,14 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
'renewalparams': {'authenticator': 'webroot'}}
with mock.patch('letsencrypt.cli.obtain_cert') as mock_obtain_cert:
mock_obtain_cert.side_effect = Exception
self._test_renewal_common(True, None,
self._test_renewal_common(True, None, error_expected=True,
args=['renew'], renew=False)
def test_renew_with_bad_cli_args(self):
self.assertRaises(errors.Error, self._test_renewal_common, True, None,
args='renew -d example.com'.split(), renew=False)
self.assertRaises(errors.Error, self._test_renewal_common, True, None,
args='renew --csr {0}'.format(CSR).split(),
renew=False)
self._test_renewal_common(True, None, args='renew -d example.com'.split(),
renew=False, error_expected=True)
self._test_renewal_common(True, None, args='renew --csr {0}'.format(CSR).split(),
renew=False, error_expected=True)
@mock.patch('letsencrypt.cli.zope.component.getUtility')
@mock.patch('letsencrypt.cli._treat_as_renewal')

View file

@ -83,6 +83,14 @@ class RegisterTest(unittest.TestCase):
self._call()
mock_logger.warn.assert_called_once_with(mock.ANY)
def test_unsupported_error(self):
from acme import messages
msg = "Test"
mx_err = messages.Error(detail=msg, typ="malformed", title="title")
with mock.patch("letsencrypt.client.acme_client.Client") as mock_client:
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
self.assertRaises(messages.Error, self._call)
class ClientTest(unittest.TestCase):
"""Tests for letsencrypt.client.Client."""
@ -119,12 +127,16 @@ class ClientTest(unittest.TestCase):
self.acme.fetch_chain.assert_called_once_with(mock.sentinel.certr)
# FIXME move parts of this to test_cli.py...
@mock.patch("letsencrypt.client.logger")
@mock.patch("letsencrypt.cli._process_domain")
def test_obtain_certificate_from_csr(self, mock_process_domain):
def test_obtain_certificate_from_csr(self, mock_process_domain, mock_logger):
self._mock_obtain_certificate()
from letsencrypt import cli
test_csr = le_util.CSR(form="der", file=None, data=CSR_SAN)
mock_parsed_args = mock.MagicMock()
# The CLI should believe that this is a certonly request, because
# a CSR would not be allowed with other kinds of requests!
mock_parsed_args.verb = "certonly"
with mock.patch("letsencrypt.client.le_util.CSR") as mock_CSR:
mock_CSR.return_value = test_csr
mock_parsed_args.domains = self.eg_domains[:]
@ -145,6 +157,14 @@ class ClientTest(unittest.TestCase):
# and that the cert was obtained correctly
self._check_obtain_certificate()
# Test for no auth_handler
self.client.auth_handler = None
self.assertRaises(
errors.Error,
self.client.obtain_certificate_from_csr,
self.eg_domains,
test_csr)
mock_logger.warning.assert_called_once_with(mock.ANY)
@mock.patch("letsencrypt.client.crypto_util")
def test_obtain_certificate(self, mock_crypto_util):

View file

@ -333,6 +333,55 @@ def create_client_instances(targetlist):
print()
return instances
def test_client_process(inqueue, outqueue):
cur_proc = mp.current_process()
for inreq in iter(inqueue.get, SENTINEL):
ii, target = inreq
#save all stdout to log file
sys.stdout = open(LOGDIR+'/'+'%d_%s.log'%(ii,target['name']), 'w')
print("[%s : client %d %s %s]" % (cur_proc.name, ii, target['ami'], target['name']))
instances[ii] = block_until_instance_ready(instances[ii])
print("server %s at %s"%(instances[ii], instances[ii].public_ip_address))
env.host_string = "%s@%s"%(target['user'], instances[ii].public_ip_address)
print(env.host_string)
try:
install_and_launch_letsencrypt(instances[ii], boulder_url, target)
outqueue.put((ii, target, 'pass'))
print("%s - %s SUCCESS"%(target['ami'], target['name']))
except:
outqueue.put((ii, target, 'fail'))
print("%s - %s FAIL"%(target['ami'], target['name']))
pass
# append server letsencrypt.log to each per-machine output log
print("\n\nletsencrypt.log\n" + "-"*80 + "\n")
try:
execute(grab_letsencrypt_log)
except:
print("log fail\n")
pass
def cleanup(cl_args, instances, targetlist):
print('Logs in ', LOGDIR)
if not cl_args.saveinstances:
print('Terminating EC2 Instances and Cleaning Dangling EBS Volumes')
if cl_args.killboulder:
boulder_server.terminate()
terminate_and_clean(instances)
else:
# print login information for the boxes for debugging
for ii, target in enumerate(targetlist):
print(target['name'],
target['ami'],
"%s@%s"%(target['user'], instances[ii].public_ip_address))
#-------------------------------------------------------------------------------
# SCRIPT BEGINS
#-------------------------------------------------------------------------------
@ -413,124 +462,85 @@ else:
#machine_type='t2.medium',
security_groups=['letsencrypt_test'])
if not cl_args.boulderonly:
instances = create_client_instances(targetlist)
try:
if not cl_args.boulderonly:
instances = create_client_instances(targetlist)
# Configure and launch boulder server
#-------------------------------------------------------------------------------
print("Waiting on Boulder Server")
boulder_server = block_until_instance_ready(boulder_server)
print(" server %s"%boulder_server)
# Configure and launch boulder server
#-------------------------------------------------------------------------------
print("Waiting on Boulder Server")
boulder_server = block_until_instance_ready(boulder_server)
print(" server %s"%boulder_server)
# env.host_string defines the ssh user and host for connection
env.host_string = "ubuntu@%s"%boulder_server.public_ip_address
print("Boulder Server at (SSH):", env.host_string)
if not boulder_preexists:
print("Configuring and Launching Boulder")
config_and_launch_boulder(boulder_server)
# blocking often unnecessary, but cheap EC2 VMs can get very slow
block_until_http_ready('http://%s:4000'%boulder_server.public_ip_address,
wait_time=10, timeout=500)
# env.host_string defines the ssh user and host for connection
env.host_string = "ubuntu@%s"%boulder_server.public_ip_address
print("Boulder Server at (SSH):", env.host_string)
if not boulder_preexists:
print("Configuring and Launching Boulder")
config_and_launch_boulder(boulder_server)
# blocking often unnecessary, but cheap EC2 VMs can get very slow
block_until_http_ready('http://%s:4000'%boulder_server.public_ip_address,
wait_time=10, timeout=500)
boulder_url = "http://%s:4000/directory"%boulder_server.private_ip_address
print("Boulder Server at (public ip): http://%s:4000/directory"%boulder_server.public_ip_address)
print("Boulder Server at (EC2 private ip): %s"%boulder_url)
boulder_url = "http://%s:4000/directory"%boulder_server.private_ip_address
print("Boulder Server at (public ip): http://%s:4000/directory"%boulder_server.public_ip_address)
print("Boulder Server at (EC2 private ip): %s"%boulder_url)
if cl_args.boulderonly:
sys.exit(0)
if cl_args.boulderonly:
sys.exit(0)
# Install and launch client scripts in parallel
#-------------------------------------------------------------------------------
print("Uploading and running test script in parallel: %s"%cl_args.test_script)
print("Output routed to log files in %s"%LOGDIR)
# (Advice: always use Manager.Queue, never regular multiprocessing.Queue
# the latter has implementation flaws that deadlock it in some circumstances)
manager = Manager()
outqueue = manager.Queue()
inqueue = manager.Queue()
SENTINEL = None #queue kill signal
# Install and launch client scripts in parallel
#-------------------------------------------------------------------------------
print("Uploading and running test script in parallel: %s"%cl_args.test_script)
print("Output routed to log files in %s"%LOGDIR)
# (Advice: always use Manager.Queue, never regular multiprocessing.Queue
# the latter has implementation flaws that deadlock it in some circumstances)
manager = Manager()
outqueue = manager.Queue()
inqueue = manager.Queue()
SENTINEL = None #queue kill signal
# launch as many processes as clients to test
num_processes = len(targetlist)
jobs = [] #keep a reference to current procs
# launch as many processes as clients to test
num_processes = len(targetlist)
jobs = [] #keep a reference to current procs
def test_client_process(inqueue, outqueue):
cur_proc = mp.current_process()
for inreq in iter(inqueue.get, SENTINEL):
ii, target = inreq
#save all stdout to log file
sys.stdout = open(LOGDIR+'/'+'%d_%s.log'%(ii,target['name']), 'w')
# initiate process execution
for i in range(num_processes):
p = mp.Process(target=test_client_process, args=(inqueue, outqueue))
jobs.append(p)
p.daemon = True # kills subprocesses if parent is killed
p.start()
print("[%s : client %d %s %s]" % (cur_proc.name, ii, target['ami'], target['name']))
instances[ii] = block_until_instance_ready(instances[ii])
print("server %s at %s"%(instances[ii], instances[ii].public_ip_address))
env.host_string = "%s@%s"%(target['user'], instances[ii].public_ip_address)
print(env.host_string)
try:
install_and_launch_letsencrypt(instances[ii], boulder_url, target)
outqueue.put((ii, target, 'pass'))
print("%s - %s SUCCESS"%(target['ami'], target['name']))
except:
outqueue.put((ii, target, 'fail'))
print("%s - %s FAIL"%(target['ami'], target['name']))
pass
# append server letsencrypt.log to each per-machine output log
print("\n\nletsencrypt.log\n" + "-"*80 + "\n")
try:
execute(grab_letsencrypt_log)
except:
print("log fail\n")
pass
# initiate process execution
for i in range(num_processes):
p = mp.Process(target=test_client_process, args=(inqueue, outqueue))
jobs.append(p)
p.daemon = True # kills subprocesses if parent is killed
p.start()
# fill up work queue
for ii, target in enumerate(targetlist):
inqueue.put((ii, target))
# add SENTINELs to end client processes
for i in range(num_processes):
inqueue.put(SENTINEL)
# wait on termination of client processes
for p in jobs:
p.join()
# add SENTINEL to output queue
outqueue.put(SENTINEL)
# clean up
execute(local_repo_clean)
# print and save summary results
results_file = open(LOGDIR+'/results', 'w')
outputs = [outq for outq in iter(outqueue.get, SENTINEL)]
outputs.sort(key=lambda x: x[0])
for outq in outputs:
ii, target, status = outq
print('%d %s %s'%(ii, target['name'], status))
results_file.write('%d %s %s\n'%(ii, target['name'], status))
results_file.close()
if not cl_args.saveinstances:
print('Logs in ', LOGDIR)
print('Terminating EC2 Instances and Cleaning Dangling EBS Volumes')
if cl_args.killboulder:
boulder_server.terminate()
terminate_and_clean(instances)
else:
# print login information for the boxes for debugging
# fill up work queue
for ii, target in enumerate(targetlist):
print(target['name'],
target['ami'],
"%s@%s"%(target['user'], instances[ii].public_ip_address))
inqueue.put((ii, target))
# kill any connections
fabric.network.disconnect_all()
# add SENTINELs to end client processes
for i in range(num_processes):
inqueue.put(SENTINEL)
# wait on termination of client processes
for p in jobs:
p.join()
# add SENTINEL to output queue
outqueue.put(SENTINEL)
# clean up
execute(local_repo_clean)
# print and save summary results
results_file = open(LOGDIR+'/results', 'w')
outputs = [outq for outq in iter(outqueue.get, SENTINEL)]
outputs.sort(key=lambda x: x[0])
for outq in outputs:
ii, target, status = outq
print('%d %s %s'%(ii, target['name'], status))
results_file.write('%d %s %s\n'%(ii, target['name'], status))
results_file.close()
finally:
cleanup(cl_args, instances, targetlist)
# kill any connections
fabric.network.disconnect_all()

View file

@ -171,7 +171,10 @@ while ! openssl dgst -sha256 -verify $RELEASE_OPENSSL_PUBKEY -signature \
read -p "Please correctly sign letsencrypt-auto with offline-signrequest.sh"
done
git add letsencrypt-auto-source
# copy leauto to the root, overwriting the previous release version
cp -p letsencrypt-auto-source/letsencrypt-auto letsencrypt-auto
git add letsencrypt-auto letsencrypt-auto-source
git diff --cached
git commit --gpg-sign="$RELEASE_GPG_KEY" -m "Release $version"
git tag --local-user "$RELEASE_GPG_KEY" --sign --message "Release $version" "$tag"