mirror of
https://github.com/certbot/certbot.git
synced 2026-07-28 00:35:50 +02:00
Merge remote-tracking branch 'upstream/master' into hsts
This commit is contained in:
+1
-2
@@ -60,8 +60,7 @@ addons:
|
||||
- rsyslog
|
||||
|
||||
install: "travis_retry pip install tox coveralls"
|
||||
before_script: '[ "xxx$BOULDER_INTEGRATION" = "xxx" ] || ./tests/boulder-start.sh'
|
||||
script: 'travis_retry tox && ([ "xxx$BOULDER_INTEGRATION" = "xxx" ] || (source .tox/$TOXENV/bin/activate && ./tests/boulder-integration.sh))'
|
||||
script: 'travis_retry tox && ([ "xxx$BOULDER_INTEGRATION" = "xxx" ] || ./tests/travis-integration.sh)'
|
||||
|
||||
after_success: '[ "$TOXENV" == "cover" ] && coveralls'
|
||||
|
||||
|
||||
@@ -228,6 +228,9 @@ class HTTP01Response(KeyAuthorizationChallengeResponse):
|
||||
|
||||
"""
|
||||
|
||||
WHITESPACE_CUTSET = "\n\r\t "
|
||||
"""Whitespace characters which should be ignored at the end of the body."""
|
||||
|
||||
def simple_verify(self, chall, domain, account_public_key, port=None):
|
||||
"""Simple verify.
|
||||
|
||||
@@ -273,10 +276,11 @@ class HTTP01Response(KeyAuthorizationChallengeResponse):
|
||||
found_ct, chall.CONTENT_TYPE)
|
||||
return False
|
||||
|
||||
if self.key_authorization != http_response.text:
|
||||
challenge_response = http_response.text.rstrip(self.WHITESPACE_CUTSET)
|
||||
if self.key_authorization != challenge_response:
|
||||
logger.debug("Key authorization from response (%r) doesn't match "
|
||||
"HTTP response (%r)", self.key_authorization,
|
||||
http_response.text)
|
||||
challenge_response)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -126,6 +126,16 @@ class HTTP01ResponseTest(unittest.TestCase):
|
||||
self.assertFalse(self.response.simple_verify(
|
||||
self.chall, "local", KEY.public_key()))
|
||||
|
||||
@mock.patch("acme.challenges.requests.get")
|
||||
def test_simple_verify_whitespace_validation(self, mock_get):
|
||||
from acme.challenges import HTTP01Response
|
||||
mock_get.return_value = mock.MagicMock(
|
||||
text=(self.chall.validation(KEY) +
|
||||
HTTP01Response.WHITESPACE_CUTSET), headers=self.good_headers)
|
||||
self.assertTrue(self.response.simple_verify(
|
||||
self.chall, "local", KEY.public_key()))
|
||||
mock_get.assert_called_once_with(self.chall.uri("local"))
|
||||
|
||||
@mock.patch("acme.challenges.requests.get")
|
||||
def test_simple_verify_bad_content_type(self, mock_get):
|
||||
mock_get().text = self.chall.token
|
||||
|
||||
+31
-15
@@ -1,8 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tested with:
|
||||
# - Fedora 22 (x64)
|
||||
# - Centos 7 (x64: on AWS EC2 t2.micro, DigitalOcean droplet)
|
||||
# - Fedora 22, 23 (x64)
|
||||
# - Centos 7 (x64: onD igitalOcean droplet)
|
||||
|
||||
if type yum 2>/dev/null
|
||||
then
|
||||
@@ -15,17 +15,33 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Some distros and older versions of current distros use a "python27"
|
||||
# instead of "python" naming convention. Try both conventions.
|
||||
if ! $tool install -y \
|
||||
python \
|
||||
python-devel \
|
||||
python-virtualenv
|
||||
then
|
||||
if ! $tool install -y \
|
||||
python27 \
|
||||
python27-devel \
|
||||
python27-virtualenv
|
||||
then
|
||||
echo "Could not install Python dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# "git-core" seems to be an alias for "git" in CentOS 7 (yum search fails)
|
||||
# Amazon Linux 2015.03 needs python27-virtualenv rather than python-virtualenv
|
||||
$tool install -y \
|
||||
git-core \
|
||||
python \
|
||||
python-devel \
|
||||
python27-virtualenv \
|
||||
python-virtualenv \
|
||||
gcc \
|
||||
dialog \
|
||||
augeas-libs \
|
||||
openssl-devel \
|
||||
libffi-devel \
|
||||
ca-certificates \
|
||||
if ! $tool install -y \
|
||||
git-core \
|
||||
gcc \
|
||||
dialog \
|
||||
augeas-libs \
|
||||
openssl-devel \
|
||||
libffi-devel \
|
||||
ca-certificates
|
||||
then
|
||||
echo "Could not install additional dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
# SLE12 dont have python-virtualenv
|
||||
# SLE12 don't have python-virtualenv
|
||||
|
||||
zypper -nq in -l git-core \
|
||||
python \
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||
|
||||
and the version implemented by the Let's Encrypt client will be the
|
||||
version that was most current as of the release date of each client
|
||||
version. Mozilla offers three seperate sets of cryptographic options,
|
||||
version. Mozilla offers three separate sets of cryptographic options,
|
||||
which trade off security and compatibility differently. These are
|
||||
referred to as as the "Modern", "Intermediate", and "Old" configurations
|
||||
(in order from most secure to least secure, and least-backwards compatible
|
||||
|
||||
+88
-24
@@ -42,14 +42,24 @@ To install and run the client you just need to type:
|
||||
|
||||
./letsencrypt-auto
|
||||
|
||||
.. note:: On RedHat/CentOS 6 you will need to enable the EPEL_
|
||||
repository before install.
|
||||
|
||||
.. _EPEL: http://fedoraproject.org/wiki/EPEL
|
||||
|
||||
Throughout the documentation, whenever you see references to
|
||||
``letsencrypt`` script/binary, you can substitute in
|
||||
``letsencrypt-auto``. For example, to get the help you would type:
|
||||
``letsencrypt-auto``. For example, to get basic help you would type:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
./letsencrypt-auto --help
|
||||
|
||||
or for full help, type:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
./letsencrypt-auto --help all
|
||||
|
||||
Running with Docker
|
||||
-------------------
|
||||
@@ -94,6 +104,13 @@ Operating System Packages
|
||||
* Port: ``cd /usr/ports/security/py-letsencrypt && make install clean``
|
||||
* Package: ``pkg install py27-letsencrypt``
|
||||
|
||||
**Arch Linux**
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo pacman -S letsencrypt letsencrypt-nginx letsencrypt-apache \
|
||||
letshelp-letsencrypt
|
||||
|
||||
**Other Operating Systems**
|
||||
|
||||
Unfortunately, this is an ongoing effort. If you'd like to package
|
||||
@@ -128,32 +145,79 @@ SSL certificates!
|
||||
Plugins
|
||||
=======
|
||||
|
||||
Officially supported plugins:
|
||||
=========== = = ===============================================================
|
||||
Plugin A I Notes
|
||||
=========== = = ===============================================================
|
||||
apache_ Y Y Automates obtaining and installing a cert with Apache 2.4 on
|
||||
Debian-based distributions with ``libaugeas0`` 1.0+.
|
||||
standalone_ Y N Uses a "standalone" webserver to obtain a cert.
|
||||
webroot_ Y N Obtains a cert using an already running webserver.
|
||||
manual_ Y N Helps you obtain a cert by giving you instructions to perform
|
||||
domain validation yourself.
|
||||
nginx_ Y Y Very experimental and not included in letsencrypt-auto_.
|
||||
=========== = = ===============================================================
|
||||
|
||||
========== = = ================================================================
|
||||
Plugin A I Notes and status
|
||||
========== = = ================================================================
|
||||
standalone Y N Very stable. Uses port 80 (force by
|
||||
``--standalone-supported-challenges http-01``) or 443
|
||||
(force by ``--standalone-supported-challenges tls-sni-01``).
|
||||
apache Y Y Alpha. Automates Apache installation, works fairly well but on
|
||||
Debian-based distributions only for now.
|
||||
webroot Y N Works with already running webserver, by writing necessary files
|
||||
to the disk (``--webroot-path`` should be pointed to your
|
||||
``public_html``). Currently, when multiple domains are specified
|
||||
(`-d`), they must all use the same web root path.
|
||||
manual Y N Hidden from standard UI, use with ``-a manual``. Requires to
|
||||
copy and paste commands into a new terminal session. Allows to
|
||||
run client on machine different than target webserver, e.g. your
|
||||
laptop.
|
||||
nginx Y Y Very experimental. Not included in letsencrypt-auto_.
|
||||
========== = = ================================================================
|
||||
Apache
|
||||
------
|
||||
|
||||
Third party plugins are listed at
|
||||
https://github.com/letsencrypt/letsencrypt/wiki/Plugins. If
|
||||
that's not enough, you can always :ref:`write your own plugin
|
||||
<dev-plugin>`.
|
||||
If you're running Apache 2.4 on a Debian-based OS with version 1.0+ of
|
||||
the ``libaugeas0`` package available, you can use the Apache plugin.
|
||||
This automates both obtaining and installing certs on an Apache
|
||||
webserver. To specify this plugin on the command line, simply include
|
||||
``--apache``.
|
||||
|
||||
Standalone
|
||||
----------
|
||||
|
||||
To obtain a cert using a "standalone" webserver, you can use the
|
||||
standalone plugin by including ``certonly`` and ``--standalone``
|
||||
on the command line. This plugin needs to bind to port 80 or 443 in
|
||||
order to perform domain validation, so you may need to stop your
|
||||
existing webserver. To control which port the plugin uses, include
|
||||
one of the options shown below on the command line.
|
||||
|
||||
* ``--standalone-supported-challenges http-01`` to use port 80
|
||||
* ``--standalone-supported-challenges tls-sni-01`` to use port 443
|
||||
|
||||
Webroot
|
||||
-------
|
||||
|
||||
If you're running a webserver that you don't want to stop to use
|
||||
standalone, you can use the webroot plugin to obtain a cert by
|
||||
including ``certonly`` and ``-a webroot`` on the command line. In
|
||||
addition, you'll need to specify ``--webroot-path`` with the root
|
||||
directory of the files served by your webserver. For example,
|
||||
``--webroot-path /var/www/html`` or
|
||||
``--webroot-path /usr/share/nginx/html`` are two common webroot paths.
|
||||
If multiple domains are specified, they must all use the same path.
|
||||
Additionally, your server must be configured to serve files from
|
||||
hidden directories.
|
||||
|
||||
Manual
|
||||
------
|
||||
|
||||
If you'd like to obtain a cert running ``letsencrypt`` on a machine
|
||||
other than your target webserver or perform the steps for domain
|
||||
validation yourself, you can use the manual plugin. While hidden from
|
||||
the UI, you can use the plugin to obtain a cert by specifying
|
||||
``certonly`` and ``-a manual`` on the command line. This requires you
|
||||
to copy and paste commands into another terminal session.
|
||||
|
||||
Nginx
|
||||
-----
|
||||
|
||||
In the future, if you're running Nginx you can use this plugin to
|
||||
automatically obtain and install your certificate. The Nginx plugin
|
||||
is still experimental, however, and is not installed with
|
||||
letsencrypt-auto_. If installed, you can select this plugin on the
|
||||
command line by including ``--nginx``.
|
||||
|
||||
Third party plugins
|
||||
-------------------
|
||||
|
||||
These plugins are listed at
|
||||
https://github.com/letsencrypt/letsencrypt/wiki/Plugins. If you're
|
||||
interested, you can also :ref:`write your own plugin <dev-plugin>`.
|
||||
|
||||
Renewal
|
||||
=======
|
||||
|
||||
@@ -188,12 +188,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
# cert_key... can all be parsed appropriately
|
||||
self.prepare_server_https("443")
|
||||
|
||||
path = {}
|
||||
|
||||
path["cert_path"] = self.parser.find_dir(
|
||||
"SSLCertificateFile", None, vhost.path)
|
||||
path["cert_key"] = self.parser.find_dir(
|
||||
"SSLCertificateKeyFile", None, vhost.path)
|
||||
path = {"cert_path": self.parser.find_dir("SSLCertificateFile", None, vhost.path),
|
||||
"cert_key": self.parser.find_dir("SSLCertificateKeyFile", None, vhost.path)}
|
||||
|
||||
# Only include if a certificate chain is specified
|
||||
if chain_path is not None:
|
||||
|
||||
+3
-1
@@ -85,6 +85,8 @@ ExperimentalBootstrap() {
|
||||
DeterminePythonVersion() {
|
||||
if command -v python2.7 > /dev/null ; then
|
||||
export LE_PYTHON=${LE_PYTHON:-python2.7}
|
||||
elif command -v python27 > /dev/null ; then
|
||||
export LE_PYTHON=${LE_PYTHON:-python27}
|
||||
elif command -v python2 > /dev/null ; then
|
||||
export LE_PYTHON=${LE_PYTHON:-python2}
|
||||
elif command -v python > /dev/null ; then
|
||||
@@ -135,7 +137,7 @@ then
|
||||
elif uname | grep -iq Darwin ; then
|
||||
ExperimentalBootstrap "Mac OS X" mac.sh
|
||||
elif grep -iq "Amazon Linux" /etc/issue ; then
|
||||
ExperimentalBootstrap "Amazon Linux" amazon_linux.sh
|
||||
ExperimentalBootstrap "Amazon Linux" _rpm_common.sh
|
||||
else
|
||||
echo "Sorry, I don't know how to bootstrap Let's Encrypt on your operating system!"
|
||||
echo
|
||||
|
||||
@@ -34,6 +34,8 @@ def create_le_config(parent_dir):
|
||||
os.mkdir(config["work_dir"])
|
||||
os.mkdir(config["logs_dir"])
|
||||
|
||||
config["domains"] = None
|
||||
|
||||
return argparse.Namespace(**config) # pylint: disable=star-args
|
||||
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ class NginxConfigurator(common.Plugin):
|
||||
# These will be set in the prepare function
|
||||
self.parser = None
|
||||
self.version = version
|
||||
self._enhance_func = {} # TODO: Support at least redirects
|
||||
self._enhance_func = {"redirect": self._enable_redirect}
|
||||
|
||||
# Set up reverter
|
||||
self.reverter = reverter.Reverter(self.config)
|
||||
@@ -344,7 +344,7 @@ class NginxConfigurator(common.Plugin):
|
||||
##################################
|
||||
def supported_enhancements(self): # pylint: disable=no-self-use
|
||||
"""Returns currently supported enhancements."""
|
||||
return []
|
||||
return ['redirect']
|
||||
|
||||
def enhance(self, domain, enhancement, options=None):
|
||||
"""Enhance configuration.
|
||||
@@ -366,6 +366,26 @@ class NginxConfigurator(common.Plugin):
|
||||
except errors.PluginError:
|
||||
logger.warn("Failed %s for %s", enhancement, domain)
|
||||
|
||||
def _enable_redirect(self, vhost, unused_options):
|
||||
"""Redirect all equivalent HTTP traffic to ssl_vhost.
|
||||
|
||||
Add rewrite directive to non https traffic
|
||||
|
||||
.. note:: This function saves the configuration
|
||||
|
||||
:param vhost: Destination of traffic, an ssl enabled vhost
|
||||
:type vhost: :class:`~letsencrypt_nginx.obj.VirtualHost`
|
||||
|
||||
:param unused_options: Not currently used
|
||||
:type unused_options: Not Available
|
||||
"""
|
||||
redirect_block = [[['if', '($scheme != "https")'],
|
||||
[['return', '301 https://$host$request_uri']]
|
||||
]]
|
||||
self.parser.add_server_directives(vhost.filep, vhost.names,
|
||||
redirect_block)
|
||||
logger.info("Redirecting all traffic to ssl in %s", vhost.filep)
|
||||
|
||||
######################################
|
||||
# Nginx server management (IInstaller)
|
||||
######################################
|
||||
|
||||
@@ -257,6 +257,8 @@ class NginxParser(object):
|
||||
|
||||
..note :: If replace is True, this raises a misconfiguration error
|
||||
if the directive does not already exist.
|
||||
..note :: If replace is False nothing gets added if an identical
|
||||
block exists already.
|
||||
|
||||
..todo :: Doesn't match server blocks whose server_name directives are
|
||||
split across multiple conf files.
|
||||
@@ -411,7 +413,7 @@ def _regex_match(target_name, name):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except re.error:
|
||||
except re.error: # pragma: no cover
|
||||
# perl-compatible regexes are sometimes not recognized by python
|
||||
return False
|
||||
|
||||
@@ -448,10 +450,9 @@ def _parse_server(server):
|
||||
:rtype: dict
|
||||
|
||||
"""
|
||||
parsed_server = {}
|
||||
parsed_server['addrs'] = set()
|
||||
parsed_server['ssl'] = False
|
||||
parsed_server['names'] = set()
|
||||
parsed_server = {'addrs': set(),
|
||||
'ssl': False,
|
||||
'names': set()}
|
||||
|
||||
for directive in server:
|
||||
if directive[0] == 'listen':
|
||||
@@ -480,7 +481,9 @@ def _add_directives(block, directives, replace=False):
|
||||
if not replace:
|
||||
# We insert new directives at the top of the block, mostly
|
||||
# to work around https://trac.nginx.org/nginx/ticket/810
|
||||
block.insert(0, directive)
|
||||
# Only add directive if its not already in the block
|
||||
if directive not in block:
|
||||
block.insert(0, directive)
|
||||
else:
|
||||
changed = False
|
||||
if len(directive) == 0:
|
||||
|
||||
@@ -51,11 +51,11 @@ class NginxConfiguratorTest(util.NginxTest):
|
||||
"example.*", "www.example.org", "myhost"]))
|
||||
|
||||
def test_supported_enhancements(self):
|
||||
self.assertEqual([], self.config.supported_enhancements())
|
||||
self.assertEqual(['redirect'], self.config.supported_enhancements())
|
||||
|
||||
def test_enhance(self):
|
||||
self.assertRaises(
|
||||
errors.PluginError, self.config.enhance, 'myhost', 'redirect')
|
||||
errors.PluginError, self.config.enhance, 'myhost', 'unknown_enhancement')
|
||||
|
||||
def test_get_chall_pref(self):
|
||||
self.assertEqual([challenges.TLSSNI01],
|
||||
|
||||
@@ -133,11 +133,11 @@ class NginxParserTest(util.NginxTest):
|
||||
self.assertEqual(1, len(re.findall(ssl_re, dump)))
|
||||
|
||||
server_conf = nparser.abs_path('server.conf')
|
||||
nparser.add_server_directives(server_conf,
|
||||
set(['alias', 'another.alias',
|
||||
'somename']),
|
||||
names = set(['alias', 'another.alias', 'somename'])
|
||||
nparser.add_server_directives(server_conf, names,
|
||||
[['foo', 'bar'], ['ssl_certificate',
|
||||
'/etc/ssl/cert2.pem']])
|
||||
nparser.add_server_directives(server_conf, names, [['foo', 'bar']])
|
||||
self.assertEqual(nparser.parsed[server_conf],
|
||||
[['ssl_certificate', '/etc/ssl/cert2.pem'],
|
||||
['foo', 'bar'],
|
||||
|
||||
+26
-8
@@ -69,6 +69,7 @@ USAGE = SHORT_USAGE + """Choice of server plugins for obtaining and installing c
|
||||
%s
|
||||
--standalone Run a standalone webserver for authentication
|
||||
%s
|
||||
--webroot Place files in a server's webroot folder for authentication
|
||||
|
||||
OR use different servers to obtain (authenticate) the cert and then install it:
|
||||
|
||||
@@ -80,7 +81,7 @@ More detailed help:
|
||||
the available topics are:
|
||||
|
||||
all, automation, paths, security, testing, or any of the subcommands or
|
||||
plugins (certonly, install, nginx, apache, standalone, etc)
|
||||
plugins (certonly, install, nginx, apache, standalone, webroot, etc)
|
||||
"""
|
||||
|
||||
|
||||
@@ -139,10 +140,8 @@ def _determine_account(args, config):
|
||||
elif len(accounts) == 1:
|
||||
acc = accounts[0]
|
||||
else: # no account registered yet
|
||||
if args.email is None:
|
||||
if args.email is None and not args.register_unsafely_without_email:
|
||||
args.email = display_ops.get_email()
|
||||
if not args.email: # get_email might return ""
|
||||
args.email = None
|
||||
|
||||
def _tos_cb(regr):
|
||||
if args.tos:
|
||||
@@ -380,7 +379,7 @@ def diagnose_configurator_problem(cfg_type, requested, plugins):
|
||||
raise errors.PluginSelectionError(msg)
|
||||
|
||||
|
||||
def choose_configurator_plugins(args, config, plugins, verb):
|
||||
def choose_configurator_plugins(args, config, plugins, verb): # pylint: disable=too-many-branches
|
||||
"""
|
||||
Figure out which configurator we're going to use
|
||||
|
||||
@@ -408,6 +407,10 @@ def choose_configurator_plugins(args, config, plugins, verb):
|
||||
req_auth = set_configurator(req_auth, "apache")
|
||||
if args.standalone:
|
||||
req_auth = set_configurator(req_auth, "standalone")
|
||||
if args.webroot:
|
||||
req_auth = set_configurator(req_auth, "webroot")
|
||||
if args.manual:
|
||||
req_auth = set_configurator(req_auth, "manual")
|
||||
logger.debug("Requested authenticator %s and installer %s", req_auth, req_inst)
|
||||
|
||||
# Try to meet the user's request and/or ask them to pick plugins
|
||||
@@ -842,6 +845,16 @@ def prepare_and_parse_args(plugins, args):
|
||||
helpful.add(
|
||||
None, "-t", "--text", dest="text_mode", action="store_true",
|
||||
help="Use the text output instead of the curses UI.")
|
||||
helpful.add(
|
||||
None, "--register-unsafely-without-email", action="store_true",
|
||||
help="Specifying this flag enables registering an account with no "
|
||||
"email address. This is strongly discouraged, because in the "
|
||||
"event of key loss or account compromise you will irrevocably "
|
||||
"lose access to your account. You will also be unable to receive "
|
||||
"notice about impending expiration of revocation of your "
|
||||
"certificates. Updates to the Subscriber Agreement will still "
|
||||
"affect you, and will be effective N days after posting an "
|
||||
"update to the web site.")
|
||||
helpful.add(None, "-m", "--email", help=config_help("email"))
|
||||
# positional arg shadows --domains, instead of appending, and
|
||||
# --domains is useful, because it can be stored in config
|
||||
@@ -850,7 +863,7 @@ def prepare_and_parse_args(plugins, args):
|
||||
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"
|
||||
"multiple -d flags or enter a comma separated list of domains "
|
||||
"as a parameter.")
|
||||
helpful.add(
|
||||
None, "--duplicate", dest="duplicate", action="store_true",
|
||||
@@ -893,8 +906,9 @@ def prepare_and_parse_args(plugins, args):
|
||||
"testing", "--tls-sni-01-port", type=int,
|
||||
default=flag_default("tls_sni_01_port"),
|
||||
help=config_help("tls_sni_01_port"))
|
||||
helpful.add("testing", "--http-01-port", dest="http01_port", type=int,
|
||||
help=config_help("http01_port"))
|
||||
helpful.add(
|
||||
"testing", "--http-01-port", type=int, dest="http01_port",
|
||||
default=flag_default("http01_port"), help=config_help("http01_port"))
|
||||
|
||||
helpful.add_group(
|
||||
"security", description="Security parameters & server settings")
|
||||
@@ -1045,6 +1059,10 @@ def _plugins_parsing(helpful, plugins):
|
||||
help="Obtain and install certs using Nginx")
|
||||
helpful.add("plugins", "--standalone", action="store_true",
|
||||
help='Obtain certs using a "standalone" webserver.')
|
||||
helpful.add("plugins", "--manual", action="store_true",
|
||||
help='Provide laborious manual instructions for obtaining a cert')
|
||||
helpful.add("plugins", "--webroot", action="store_true",
|
||||
help='Obtain certs by placing files in a webroot directory.')
|
||||
|
||||
# things should not be reorder past/pre this comment:
|
||||
# plugins_group should be displayed in --help before plugin
|
||||
|
||||
+31
-2
@@ -100,6 +100,11 @@ def register(config, account_storage, tos_cb=None):
|
||||
if account_storage.find_all():
|
||||
logger.info("There are already existing accounts for %s", config.server)
|
||||
if config.email is None:
|
||||
if not config.register_unsafely_without_email:
|
||||
msg = ("No email was provided and "
|
||||
"--register-unsafely-without-email was not present.")
|
||||
logger.warn(msg)
|
||||
raise errors.Error(msg)
|
||||
logger.warn("Registering without email!")
|
||||
|
||||
# Each new registration shall use a fresh new key
|
||||
@@ -110,7 +115,7 @@ def register(config, account_storage, tos_cb=None):
|
||||
backend=default_backend())))
|
||||
acme = acme_from_config_key(config, key)
|
||||
# TODO: add phone?
|
||||
regr = acme.register(messages.NewRegistration.from_data(email=config.email))
|
||||
regr = perform_registration(acme, config)
|
||||
|
||||
if regr.terms_of_service is not None:
|
||||
if tos_cb is not None and not tos_cb(regr):
|
||||
@@ -126,6 +131,30 @@ def register(config, account_storage, tos_cb=None):
|
||||
return acc, acme
|
||||
|
||||
|
||||
def perform_registration(acme, config):
|
||||
"""
|
||||
Actually register new account, trying repeatedly if there are email
|
||||
problems
|
||||
|
||||
:param .IConfig config: Client configuration.
|
||||
:param acme.client.Client client: ACME client object.
|
||||
|
||||
:returns: Registration Resource.
|
||||
:rtype: `acme.messages.RegistrationResource`
|
||||
|
||||
:raises .UnexpectedUpdate:
|
||||
"""
|
||||
try:
|
||||
return acme.register(messages.NewRegistration.from_data(email=config.email))
|
||||
except messages.Error, e:
|
||||
err = repr(e)
|
||||
if "MX record" in err or "Validation of contact mailto" in err:
|
||||
config.namespace.email = display_ops.get_email(more=True, invalid=True)
|
||||
return perform_registration(acme, config)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""ACME protocol client.
|
||||
|
||||
@@ -455,7 +484,7 @@ class Client(object):
|
||||
except:
|
||||
# TODO: suggest letshelp-letsencypt here
|
||||
reporter.add_message(
|
||||
"An error occured and we failed to restore your config and "
|
||||
"An error occurred 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)
|
||||
|
||||
@@ -5,8 +5,6 @@ import re
|
||||
|
||||
import zope.interface
|
||||
|
||||
from acme import challenges
|
||||
|
||||
from letsencrypt import constants
|
||||
from letsencrypt import errors
|
||||
from letsencrypt import interfaces
|
||||
@@ -80,13 +78,6 @@ class NamespaceConfig(object):
|
||||
return os.path.join(
|
||||
self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR)
|
||||
|
||||
@property
|
||||
def http01_port(self): # pylint: disable=missing-docstring
|
||||
if self.namespace.http01_port is not None:
|
||||
return self.namespace.http01_port
|
||||
else:
|
||||
return challenges.HTTP01Response.PORT
|
||||
|
||||
|
||||
class RenewerConfiguration(object):
|
||||
"""Configuration wrapper for renewer."""
|
||||
@@ -155,8 +146,8 @@ def _check_config_domain_sanity(domains):
|
||||
"Punycode domains are not supported")
|
||||
# FQDN checks from
|
||||
# http://www.mkyong.com/regular-expressions/domain-name-regular-expression-example/
|
||||
# Characters used, domain parts < 63 chars, tld > 1 < 7 chars
|
||||
# Characters used, domain parts < 63 chars, tld > 1 < 64 chars
|
||||
# first and last char is not "-"
|
||||
fqdn = re.compile("^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$")
|
||||
fqdn = re.compile("^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,63}$")
|
||||
if any(True for d in domains if not fqdn.match(d)):
|
||||
raise errors.ConfigurationError("Requested domain is not a FQDN")
|
||||
|
||||
@@ -23,6 +23,7 @@ CLI_DEFAULTS = dict(
|
||||
work_dir="/var/lib/letsencrypt",
|
||||
logs_dir="/var/log/letsencrypt",
|
||||
no_verify_ssl=False,
|
||||
http01_port=challenges.HTTP01Response.PORT,
|
||||
tls_sni_01_port=challenges.TLSSNI01Response.PORT,
|
||||
|
||||
auth_cert_path="./cert.pem",
|
||||
|
||||
+31
-10
@@ -34,7 +34,15 @@ def choose_plugin(prepared, question):
|
||||
question, opts, help_label="More Info")
|
||||
|
||||
if code == display_util.OK:
|
||||
return prepared[index]
|
||||
plugin_ep = prepared[index]
|
||||
if plugin_ep.misconfigured:
|
||||
util(interfaces.IDisplay).notification(
|
||||
"The selected plugin encountered an error while parsing "
|
||||
"your server configuration and cannot be used. The error "
|
||||
"was:\n\n{0}".format(plugin_ep.prepare()),
|
||||
height=display_util.HEIGHT, pause=False)
|
||||
else:
|
||||
return plugin_ep
|
||||
elif code == display_util.HELP:
|
||||
if prepared[index].misconfigured:
|
||||
msg = "Reported Error: %s" % prepared[index].prepare()
|
||||
@@ -114,23 +122,36 @@ def pick_configurator(
|
||||
config, default, plugins, question,
|
||||
(interfaces.IAuthenticator, interfaces.IInstaller))
|
||||
|
||||
|
||||
def get_email():
|
||||
def get_email(more=False, invalid=False):
|
||||
"""Prompt for valid email address.
|
||||
|
||||
:param bool more: explain why the email is strongly advisable, but how to
|
||||
skip it
|
||||
:param bool invalid: true if the user just typed something, but it wasn't
|
||||
a valid-looking email
|
||||
|
||||
:returns: Email or ``None`` if cancelled by user.
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
while True:
|
||||
code, email = zope.component.getUtility(interfaces.IDisplay).input(
|
||||
"Enter email address (used for urgent notices and lost key recovery)")
|
||||
msg = "Enter email address (used for urgent notices and lost key recovery)"
|
||||
if invalid:
|
||||
msg = "There seem to be problems with that address. " + msg
|
||||
if more:
|
||||
msg += ('\n\nIf you really want to skip this, you can run the client with '
|
||||
'--register-unsafely-without-email but make sure you backup your '
|
||||
'account key from /etc/letsencrypt/accounts\n\n')
|
||||
code, email = zope.component.getUtility(interfaces.IDisplay).input(msg)
|
||||
|
||||
if code == display_util.OK:
|
||||
if le_util.safe_email(email):
|
||||
return email
|
||||
if code == display_util.OK:
|
||||
if le_util.safe_email(email):
|
||||
return email
|
||||
else:
|
||||
return None
|
||||
# TODO catch the server's ACME invalid email address error, and
|
||||
# make a similar call when that happens
|
||||
return get_email(more=True, invalid=(email != ""))
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def choose_account(accounts):
|
||||
|
||||
@@ -104,6 +104,7 @@ class NcursesDisplay(object):
|
||||
|
||||
return code, int(tag) - 1
|
||||
|
||||
|
||||
def input(self, message):
|
||||
"""Display an input box to the user.
|
||||
|
||||
@@ -114,7 +115,12 @@ class NcursesDisplay(object):
|
||||
`string` - input entered by the user
|
||||
|
||||
"""
|
||||
return self.dialog.inputbox(message, width=self.width)
|
||||
sections = message.split("\n")
|
||||
# each section takes at least one line, plus extras if it's longer than self.width
|
||||
wordlines = [1 + (len(section)/self.width) for section in sections]
|
||||
height = 6 + sum(wordlines) + len(sections)
|
||||
return self.dialog.inputbox(message, width=self.width, height=height)
|
||||
|
||||
|
||||
def yesno(self, message, yes_label="Yes", no_label="No"):
|
||||
"""Display a Yes/No dialog box.
|
||||
|
||||
@@ -173,17 +173,12 @@ s.serve_forever()" """
|
||||
uri=achall.chall.uri(achall.domain),
|
||||
ct=achall.CONTENT_TYPE, command=command))
|
||||
|
||||
if response.simple_verify(
|
||||
if not response.simple_verify(
|
||||
achall.chall, achall.domain,
|
||||
achall.account_key.public_key(), self.config.http01_port):
|
||||
return response
|
||||
else:
|
||||
logger.error(
|
||||
"Self-verify of challenge failed, authorization abandoned.")
|
||||
if self.conf("test-mode") and self._httpd.poll() is not None:
|
||||
# simply verify cause command failure...
|
||||
return False
|
||||
return None
|
||||
logger.warning("Self-verify of challenge failed.")
|
||||
|
||||
return response
|
||||
|
||||
def _notify_and_wait(self, message): # pylint: disable=no-self-use
|
||||
# TODO: IDisplay wraps messages, breaking the command
|
||||
|
||||
@@ -61,7 +61,9 @@ class AuthenticatorTest(unittest.TestCase):
|
||||
self.assertTrue(self.achalls[0].chall.encode("token") in message)
|
||||
|
||||
mock_verify.return_value = False
|
||||
self.assertEqual([None], self.auth.perform(self.achalls))
|
||||
with mock.patch("letsencrypt.plugins.manual.logger") as mock_logger:
|
||||
self.auth.perform(self.achalls)
|
||||
mock_logger.warning.assert_called_once_with(mock.ANY)
|
||||
|
||||
@mock.patch("letsencrypt.plugins.manual.zope.component.getUtility")
|
||||
@mock.patch("letsencrypt.plugins.manual.Authenticator._notify_and_wait")
|
||||
@@ -87,20 +89,6 @@ class AuthenticatorTest(unittest.TestCase):
|
||||
self.assertRaises(
|
||||
errors.Error, self.auth_test_mode.perform, self.achalls)
|
||||
|
||||
@mock.patch("letsencrypt.plugins.manual.socket.socket")
|
||||
@mock.patch("letsencrypt.plugins.manual.time.sleep", autospec=True)
|
||||
@mock.patch("acme.challenges.HTTP01Response.simple_verify",
|
||||
autospec=True)
|
||||
@mock.patch("letsencrypt.plugins.manual.subprocess.Popen", autospec=True)
|
||||
def test_perform_test_mode(self, mock_popen, mock_verify, mock_sleep,
|
||||
mock_socket):
|
||||
mock_popen.return_value.poll.side_effect = [None, 10]
|
||||
mock_popen.return_value.pid = 1234
|
||||
mock_verify.return_value = False
|
||||
self.assertEqual([False], self.auth_test_mode.perform(self.achalls))
|
||||
self.assertEqual(1, mock_sleep.call_count)
|
||||
self.assertEqual(1, mock_socket.call_count)
|
||||
|
||||
def test_cleanup_test_mode_already_terminated(self):
|
||||
# pylint: disable=protected-access
|
||||
self.auth_test_mode._httpd = httpd = mock.Mock()
|
||||
|
||||
@@ -20,6 +20,7 @@ from letsencrypt import errors
|
||||
from letsencrypt import le_util
|
||||
|
||||
from letsencrypt.plugins import disco
|
||||
from letsencrypt.plugins import manual
|
||||
|
||||
from letsencrypt.tests import renewer_test
|
||||
from letsencrypt.tests import test_util
|
||||
@@ -201,6 +202,16 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
self.assertTrue("Could not find configuration root" in ret)
|
||||
self.assertTrue("NoInstallationError" in ret)
|
||||
|
||||
args = ["certonly", "--webroot"]
|
||||
ret, _, _, _ = self._call(args)
|
||||
self.assertTrue("--webroot-path must be set" in ret)
|
||||
|
||||
with mock.patch("letsencrypt.cli._init_le_client") as mock_init:
|
||||
with mock.patch("letsencrypt.cli._auth_from_domains"):
|
||||
self._call(["certonly", "--manual", "-d", "foo.bar"])
|
||||
auth = mock_init.call_args[0][2]
|
||||
self.assertTrue(isinstance(auth, manual.Authenticator))
|
||||
|
||||
with MockedVerb("certonly") as mock_certonly:
|
||||
self._call(["auth", "--standalone"])
|
||||
self.assertEqual(1, mock_certonly.call_count)
|
||||
@@ -488,7 +499,8 @@ class DetermineAccountTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.cli._determine_account."""
|
||||
|
||||
def setUp(self):
|
||||
self.args = mock.MagicMock(account=None, email=None)
|
||||
self.args = mock.MagicMock(account=None, email=None,
|
||||
register_unsafely_without_email=False)
|
||||
self.config = configuration.NamespaceConfig(self.args)
|
||||
self.accs = [mock.MagicMock(id='x'), mock.MagicMock(id='y')]
|
||||
self.account_storage = account.AccountMemoryStorage()
|
||||
|
||||
@@ -33,7 +33,7 @@ class RegisterTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.client.register."""
|
||||
|
||||
def setUp(self):
|
||||
self.config = mock.MagicMock(rsa_key_size=1024)
|
||||
self.config = mock.MagicMock(rsa_key_size=1024, register_unsafely_without_email=False)
|
||||
self.account_storage = account.AccountMemoryStorage()
|
||||
self.tos_cb = mock.MagicMock()
|
||||
|
||||
@@ -56,10 +56,23 @@ class RegisterTest(unittest.TestCase):
|
||||
|
||||
def test_it(self):
|
||||
with mock.patch("letsencrypt.client.acme_client.Client"):
|
||||
with mock.patch("letsencrypt.account."
|
||||
"report_new_account"):
|
||||
with mock.patch("letsencrypt.account.report_new_account"):
|
||||
self._call()
|
||||
|
||||
@mock.patch("letsencrypt.account.report_new_account")
|
||||
@mock.patch("letsencrypt.client.display_ops.get_email")
|
||||
def test_email_retry(self, _rep, mock_get_email):
|
||||
from acme import messages
|
||||
msg = "Validation of contact mailto:sousaphone@improbablylongggstring.tld failed"
|
||||
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._call()
|
||||
self.assertEqual(mock_get_email.call_count, 1)
|
||||
|
||||
def test_needs_email(self):
|
||||
self.config.email = None
|
||||
self.assertRaises(errors.Error, self._call)
|
||||
|
||||
class ClientTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.client.Client."""
|
||||
|
||||
@@ -54,11 +54,6 @@ class NamespaceConfigTest(unittest.TestCase):
|
||||
self.assertEqual(self.config.key_dir, '/tmp/config/keys')
|
||||
self.assertEqual(self.config.temp_checkpoint_dir, '/tmp/foo/t')
|
||||
|
||||
def test_http01_port(self):
|
||||
self.assertEqual(4321, self.config.http01_port)
|
||||
self.namespace.http01_port = None
|
||||
self.assertEqual(80, self.config.http01_port)
|
||||
|
||||
def test_absolute_paths(self):
|
||||
from letsencrypt.configuration import NamespaceConfig
|
||||
|
||||
|
||||
@@ -41,9 +41,11 @@ class ChoosePluginTest(unittest.TestCase):
|
||||
return choose_plugin(self.plugins, "Question?")
|
||||
|
||||
@mock.patch("letsencrypt.display.ops.util")
|
||||
def test_successful_choice(self, mock_util):
|
||||
mock_util().menu.return_value = (display_util.OK, 0)
|
||||
self.assertEqual(self.mock_apache, self._call())
|
||||
def test_selection(self, mock_util):
|
||||
mock_util().menu.side_effect = [(display_util.OK, 0),
|
||||
(display_util.OK, 1)]
|
||||
self.assertEqual(self.mock_stand, self._call())
|
||||
self.assertEqual(mock_util().notification.call_count, 1)
|
||||
|
||||
@mock.patch("letsencrypt.display.ops.util")
|
||||
def test_more_info(self, mock_util):
|
||||
@@ -168,9 +170,9 @@ class GetEmailTest(unittest.TestCase):
|
||||
zope.component.provideUtility(mock_display, interfaces.IDisplay)
|
||||
|
||||
@classmethod
|
||||
def _call(cls):
|
||||
def _call(cls, **kwargs):
|
||||
from letsencrypt.display.ops import get_email
|
||||
return get_email()
|
||||
return get_email(**kwargs)
|
||||
|
||||
def test_cancel_none(self):
|
||||
self.input.return_value = (display_util.CANCEL, "foo@bar.baz")
|
||||
@@ -178,18 +180,38 @@ class GetEmailTest(unittest.TestCase):
|
||||
|
||||
def test_ok_safe(self):
|
||||
self.input.return_value = (display_util.OK, "foo@bar.baz")
|
||||
with mock.patch("letsencrypt.display.ops.le_util"
|
||||
".safe_email") as mock_safe_email:
|
||||
with mock.patch("letsencrypt.display.ops.le_util.safe_email") as mock_safe_email:
|
||||
mock_safe_email.return_value = True
|
||||
self.assertTrue(self._call() is "foo@bar.baz")
|
||||
|
||||
def test_ok_not_safe(self):
|
||||
self.input.return_value = (display_util.OK, "foo@bar.baz")
|
||||
with mock.patch("letsencrypt.display.ops.le_util"
|
||||
".safe_email") as mock_safe_email:
|
||||
with mock.patch("letsencrypt.display.ops.le_util.safe_email") as mock_safe_email:
|
||||
mock_safe_email.side_effect = [False, True]
|
||||
self.assertTrue(self._call() is "foo@bar.baz")
|
||||
|
||||
def test_more_and_invalid_flags(self):
|
||||
more_txt = "--register-unsafely-without-email"
|
||||
invalid_txt = "There seem to be problems"
|
||||
base_txt = "Enter email"
|
||||
self.input.return_value = (display_util.OK, "foo@bar.baz")
|
||||
with mock.patch("letsencrypt.display.ops.le_util.safe_email") as mock_safe_email:
|
||||
mock_safe_email.return_value = True
|
||||
self._call()
|
||||
msg = self.input.call_args[0][0]
|
||||
self.assertTrue(more_txt not in msg)
|
||||
self.assertTrue(invalid_txt not in msg)
|
||||
self.assertTrue(base_txt in msg)
|
||||
self._call(more=True)
|
||||
msg = self.input.call_args[0][0]
|
||||
self.assertTrue(more_txt in msg)
|
||||
self.assertTrue(invalid_txt not in msg)
|
||||
self._call(more=True, invalid=True)
|
||||
msg = self.input.call_args[0][0]
|
||||
self.assertTrue(more_txt in msg)
|
||||
self.assertTrue(invalid_txt in msg)
|
||||
self.assertTrue(base_txt in msg)
|
||||
|
||||
|
||||
class ChooseAccountTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.display.ops.choose_account."""
|
||||
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
# Download and run Boulder instance for integration testing
|
||||
|
||||
# ugh, go version output is like:
|
||||
# go version go1.4.2 linux/amd64
|
||||
GOVER=`go version | cut -d" " -f3 | cut -do -f2`
|
||||
|
||||
# version comparison
|
||||
function verlte {
|
||||
#OS X doesn't support version sorting; emulate with sed
|
||||
if [ `uname` == 'Darwin' ]; then
|
||||
[ "$1" = "`echo -e \"$1\n$2\" | sed 's/\b\([0-9]\)\b/0\1/g' \
|
||||
| sort | sed 's/\b0\([0-9]\)/\1/g' | head -n1`" ]
|
||||
else
|
||||
[ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
|
||||
fi
|
||||
}
|
||||
|
||||
if ! verlte 1.5 "$GOVER" ; then
|
||||
echo "We require go version 1.5 or later; you have... $GOVER"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -xe
|
||||
|
||||
# `/...` avoids `no buildable Go source files` errors, for more info
|
||||
# see `go help packages`
|
||||
go get -d github.com/letsencrypt/boulder/...
|
||||
cd $GOPATH/src/github.com/letsencrypt/boulder
|
||||
# goose is needed for ./test/create_db.sh
|
||||
wget https://github.com/jsha/boulder-tools/raw/master/goose.gz && \
|
||||
mkdir $GOPATH/bin && \
|
||||
zcat goose.gz > $GOPATH/bin/goose && \
|
||||
chmod +x $GOPATH/bin/goose
|
||||
./test/create_db.sh
|
||||
# listenbuddy is needed for ./start.py
|
||||
go get github.com/jsha/listenbuddy
|
||||
cd -
|
||||
|
||||
+3
-37
@@ -1,43 +1,9 @@
|
||||
#!/bin/bash
|
||||
# Download and run Boulder instance for integration testing
|
||||
|
||||
|
||||
# ugh, go version output is like:
|
||||
# go version go1.4.2 linux/amd64
|
||||
GOVER=`go version | cut -d" " -f3 | cut -do -f2`
|
||||
|
||||
# version comparison
|
||||
function verlte {
|
||||
#OS X doesn't support version sorting; emulate with sed
|
||||
if [ `uname` == 'Darwin' ]; then
|
||||
[ "$1" = "`echo -e \"$1\n$2\" | sed 's/\b\([0-9]\)\b/0\1/g' \
|
||||
| sort | sed 's/\b0\([0-9]\)/\1/g' | head -n1`" ]
|
||||
else
|
||||
[ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
|
||||
fi
|
||||
}
|
||||
|
||||
if ! verlte 1.5 "$GOVER" ; then
|
||||
echo "We require go version 1.5 or later; you have... $GOVER"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -xe
|
||||
|
||||
export GOPATH="${GOPATH:-/tmp/go}"
|
||||
export PATH="$GOPATH/bin:$PATH"
|
||||
|
||||
# `/...` avoids `no buildable Go source files` errors, for more info
|
||||
# see `go help packages`
|
||||
go get -d github.com/letsencrypt/boulder/...
|
||||
./tests/boulder-fetch.sh
|
||||
|
||||
cd $GOPATH/src/github.com/letsencrypt/boulder
|
||||
# goose is needed for ./test/create_db.sh
|
||||
wget https://github.com/jsha/boulder-tools/raw/master/goose.gz && \
|
||||
mkdir $GOPATH/bin && \
|
||||
zcat goose.gz > $GOPATH/bin/goose && \
|
||||
chmod +x $GOPATH/bin/goose
|
||||
./test/create_db.sh
|
||||
# listenbuddy is needed for ./start.py
|
||||
go get github.com/jsha/listenbuddy
|
||||
./start.py &
|
||||
# Hopefully start.py bootstraps before integration test is started...
|
||||
./start.py
|
||||
|
||||
@@ -23,7 +23,7 @@ letsencrypt_test () {
|
||||
--no-redirect \
|
||||
--agree-dev-preview \
|
||||
--agree-tos \
|
||||
--email "" \
|
||||
--register-unsafely-without-email \
|
||||
--renew-by-default \
|
||||
--debug \
|
||||
-vvvvvvv \
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o errexit
|
||||
|
||||
./tests/boulder-fetch.sh
|
||||
|
||||
source .tox/$TOXENV/bin/activate
|
||||
|
||||
export LETSENCRYPT_PATH=`pwd`
|
||||
|
||||
cd $GOPATH/src/github.com/letsencrypt/boulder/
|
||||
|
||||
# boulder's integration-test.py has code that knows to start and wait for the
|
||||
# boulder processes to start reliably and then will run the letsencrypt
|
||||
# boulder-interation.sh on its own. The --letsencrypt flag says to run only the
|
||||
# letsencrypt tests (instead of any other client tests it might run). We're
|
||||
# going to want to define a more robust interaction point between the boulder
|
||||
# and letsencrypt tests, but that will be better built off of this.
|
||||
python test/integration-test.py --letsencrypt
|
||||
@@ -23,7 +23,7 @@ mv "dist.$version" "dist.$version.$(date +%s).bak" || true
|
||||
git tag --delete "$tag" || true
|
||||
|
||||
tmpvenv=$(mktemp -d)
|
||||
virtualenv --no-site-packages $tmpvenv
|
||||
virtualenv --no-site-packages -p python2 $tmpvenv
|
||||
. $tmpvenv/bin/activate
|
||||
# update setuptools/pip just like in other places in the repo
|
||||
pip install -U setuptools
|
||||
@@ -49,7 +49,7 @@ done
|
||||
sed -i "s/^__version.*/__version__ = '$version'/" letsencrypt/__init__.py
|
||||
|
||||
git add -p # interactive user input
|
||||
git -c commit.gpgsign=true commit -m "Release $version"
|
||||
git commit --gpg-sign="$RELEASE_GPG_KEY" -m "Release $version"
|
||||
git tag --local-user "$RELEASE_GPG_KEY" \
|
||||
--sign --message "Release $version" "$tag"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# acme and letsencrypt are not yet on pypi, so when Tox invokes
|
||||
# "install *.zip", it will not find deps
|
||||
skipsdist = true
|
||||
envlist = py26,py27,py33,py34,cover,lint
|
||||
envlist = py26,py27,py33,py34,py35,cover,lint
|
||||
|
||||
# nosetest -v => more verbose output, allows to detect busy waiting
|
||||
# loops, especially on Travis
|
||||
@@ -41,6 +41,11 @@ commands =
|
||||
pip install -e acme[testing]
|
||||
nosetests -v acme
|
||||
|
||||
[testenv:py35]
|
||||
commands =
|
||||
pip install -e acme[testing]
|
||||
nosetests -v acme
|
||||
|
||||
[testenv:cover]
|
||||
basepython = python2.7
|
||||
commands =
|
||||
|
||||
Reference in New Issue
Block a user