Merge remote-tracking branch 'upstream/master' into no-sites-available

This commit is contained in:
Joona Hoikkala 2017-02-06 23:07:27 +02:00
commit 2401ac522e
No known key found for this signature in database
GPG key ID: C14AAE0F5ADCB854
88 changed files with 1089 additions and 1168 deletions

View file

@ -94,11 +94,13 @@ matrix:
# Only build pushes to the master branch, PRs, and branches beginning with
# `test-`. This reduces the number of simultaneous Travis runs, which speeds
# turnaround time on review since there is a cap of 5 simultaneous runs.
# `test-` or of the form `digit(s).digit(s).x`. This reduces the number of
# simultaneous Travis runs, which speeds turnaround time on review since there
# is a cap of on the number of simultaneous runs.
branches:
only:
- master
- /^\d+\.\d+\.x$/
- /^test-.*$/
# container-based infrastructure

View file

@ -9,7 +9,6 @@ from cryptography.hazmat.primitives import hashes
import OpenSSL
import requests
from acme import dns_resolver
from acme import errors
from acme import crypto_util
from acme import fields
@ -183,7 +182,7 @@ class KeyAuthorizationChallenge(_TokenChallenge):
Subclasses must implement this method, but they are likely to
return completely different data structures, depending on what's
necessary to complete the challenge. Interepretation of that
necessary to complete the challenge. Interpretation of that
return value must be known to the caller.
:param JWK account_key:
@ -214,36 +213,24 @@ class DNS01Response(KeyAuthorizationChallengeResponse):
def simple_verify(self, chall, domain, account_public_key):
"""Simple verify.
This method no longer checks DNS records and is a simple wrapper
around `KeyAuthorizationChallengeResponse.verify`.
:param challenges.DNS01 chall: Corresponding challenge.
:param unicode domain: Domain name being verified.
:param JWK account_public_key: Public key for the key pair
being authorized.
:returns: ``True`` iff validation with the TXT records resolved from a
DNS server is successful.
:return: ``True`` iff verification of the key authorization was
successful.
:rtype: bool
"""
if not self.verify(chall, account_public_key):
# pylint: disable=unused-argument
verified = self.verify(chall, account_public_key)
if not verified:
logger.debug("Verification of key authorization in response failed")
return False
validation_domain_name = chall.validation_domain_name(domain)
validation = chall.validation(account_public_key)
logger.debug("Verifying %s at %s...", chall.typ, validation_domain_name)
try:
txt_records = dns_resolver.txt_records_for_name(
validation_domain_name)
except errors.DependencyError:
raise errors.DependencyError("Local validation for 'dns-01' "
"challenges requires 'dnspython'")
exists = validation in txt_records
if not exists:
logger.debug("Key authorization from response (%r) doesn't match "
"any DNS response in %r", self.key_authorization,
txt_records)
return exists
return verified
@Challenge.register # pylint: disable=too-many-ancestors

View file

@ -10,7 +10,6 @@ from six.moves.urllib import parse as urllib_parse # pylint: disable=import-err
from acme import errors
from acme import jose
from acme import test_util
from acme.dns_resolver import DNS_REQUIREMENT
CERT = test_util.load_comparable_cert('cert.pem')
KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem'))
@ -92,7 +91,6 @@ class DNS01ResponseTest(unittest.TestCase):
from acme.challenges import DNS01
self.chall = DNS01(token=(b'x' * 16))
self.response = self.chall.response(KEY)
self.records_for_name_path = "acme.dns_resolver.txt_records_for_name"
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
@ -105,45 +103,16 @@ class DNS01ResponseTest(unittest.TestCase):
from acme.challenges import DNS01Response
hash(DNS01Response.from_json(self.jmsg))
def test_simple_verify_bad_key_authorization(self):
def test_simple_verify_failure(self):
key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem'))
self.response.simple_verify(self.chall, "local", key2.public_key())
public_key = key2.public_key()
verified = self.response.simple_verify(self.chall, "local", public_key)
self.assertFalse(verified)
@mock.patch('acme.dns_resolver.DNS_AVAILABLE', False)
def test_simple_verify_without_dns(self):
self.assertRaises(
errors.DependencyError, self.response.simple_verify,
self.chall, 'local', KEY.public_key())
@test_util.skip_unless(test_util.requirement_available(DNS_REQUIREMENT),
"optional dependency dnspython is not available")
def test_simple_verify_good_validation(self): # pragma: no cover
with mock.patch(self.records_for_name_path) as mock_resolver:
mock_resolver.return_value = [
self.chall.validation(KEY.public_key())]
self.assertTrue(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
mock_resolver.assert_called_once_with(
self.chall.validation_domain_name("local"))
@test_util.skip_unless(test_util.requirement_available(DNS_REQUIREMENT),
"optional dependency dnspython is not available")
def test_simple_verify_good_validation_multitxts(self): # pragma: no cover
with mock.patch(self.records_for_name_path) as mock_resolver:
mock_resolver.return_value = [
"!", self.chall.validation(KEY.public_key())]
self.assertTrue(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
mock_resolver.assert_called_once_with(
self.chall.validation_domain_name("local"))
@test_util.skip_unless(test_util.requirement_available(DNS_REQUIREMENT),
"optional dependency dnspython is not available")
def test_simple_verify_bad_validation(self): # pragma: no cover
with mock.patch(self.records_for_name_path) as mock_resolver:
mock_resolver.return_value = ["!"]
self.assertFalse(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
def test_simple_verify_success(self):
public_key = KEY.public_key()
verified = self.response.simple_verify(self.chall, "local", public_key)
self.assertTrue(verified)
class DNS01Test(unittest.TestCase):

View file

@ -674,8 +674,23 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
self._add_nonce(self.head(url))
return self._nonces.pop()
def post(self, url, obj, content_type=JOSE_CONTENT_TYPE, **kwargs):
"""POST object wrapped in `.JWS` and check response."""
def post(self, *args, **kwargs):
"""POST object wrapped in `.JWS` and check response.
If the server responded with a badNonce error, the request will
be retried once.
"""
try:
return self._post_once(*args, **kwargs)
except messages.Error as error:
if error.code == 'badNonce':
logger.debug('Retrying request after error:\n%s', error)
return self._post_once(*args, **kwargs)
else:
raise
def _post_once(self, url, obj, content_type=JOSE_CONTENT_TYPE, **kwargs):
data = self._wrap_in_jws(obj, self._get_nonce(url))
kwargs.setdefault('headers', {'Content-Type': content_type})
response = self._send_request('POST', url, data=data, **kwargs)

View file

@ -170,7 +170,7 @@ class ClientTest(unittest.TestCase):
self.directory.new_authz,
messages.NewAuthorization(identifier=self.identifier))
def test_requets_challenges_custom_uri(self):
def test_request_challenges_custom_uri(self):
self._prepare_response_for_request_challenges()
self.client.request_challenges(self.identifier, 'URI')
self.net.post.assert_called_once_with('URI', mock.ANY)
@ -388,7 +388,7 @@ class ClientTest(unittest.TestCase):
errors.PollError, self.client.poll_and_request_issuance,
csr, authzrs=(invalid_authzr,), mintime=mintime)
# exceeded max_attemps | TODO: move to a separate test
# exceeded max_attempts | TODO: move to a separate test
self.assertRaises(
errors.PollError, self.client.poll_and_request_issuance,
csr, authzrs, mintime=mintime, max_attempts=2)
@ -642,7 +642,9 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
self.wrapped_obj = mock.MagicMock()
self.content_type = mock.sentinel.content_type
self.all_nonces = [jose.b64encode(b'Nonce'), jose.b64encode(b'Nonce2')]
self.all_nonces = [
jose.b64encode(b'Nonce'),
jose.b64encode(b'Nonce2'), jose.b64encode(b'Nonce3')]
self.available_nonces = self.all_nonces[:]
def send_request(*args, **kwargs):
@ -690,7 +692,7 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
self.net._wrap_in_jws.assert_called_once_with(
self.obj, jose.b64decode(self.all_nonces.pop()))
assert not self.available_nonces
self.available_nonces = []
self.assertRaises(errors.MissingNonce, self.net.post,
'uri', self.obj, content_type=self.content_type)
self.net._wrap_in_jws.assert_called_with(
@ -706,6 +708,35 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
self.assertRaises(errors.BadNonce, self.net.post, 'uri',
self.obj, content_type=self.content_type)
def test_post_failed_retry(self):
check_response = mock.MagicMock()
check_response.side_effect = messages.Error.with_code('badNonce')
# pylint: disable=protected-access
self.net._check_response = check_response
self.assertRaises(messages.Error, self.net.post, 'uri',
self.obj, content_type=self.content_type)
def test_post_not_retried(self):
check_response = mock.MagicMock()
check_response.side_effect = [messages.Error.with_code('malformed'),
self.checked_response]
# pylint: disable=protected-access
self.net._check_response = check_response
self.assertRaises(messages.Error, self.net.post, 'uri',
self.obj, content_type=self.content_type)
def test_post_successful_retry(self):
check_response = mock.MagicMock()
check_response.side_effect = [messages.Error.with_code('badNonce'),
self.checked_response]
# pylint: disable=protected-access
self.net._check_response = check_response
self.assertEqual(self.checked_response, self.net.post(
'uri', self.obj, content_type=self.content_type))
def test_head_get_post_error_passthrough(self):
self.send_request.side_effect = requests.exceptions.RequestException
for method in self.net.head, self.net.get:

View file

@ -59,7 +59,7 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
def test_probe_not_recognized_name(self):
self.assertRaises(errors.Error, self._probe, b'bar')
# TODO: py33/py34 tox hangs forever on do_hendshake in second probe
# TODO: py33/py34 tox hangs forever on do_handshake in second probe
#def probe_connection_error(self):
# self._probe(b'foo')
# #time.sleep(1) # TODO: avoid race conditions in other way

View file

@ -1,45 +0,0 @@
"""DNS Resolver for ACME client.
Required only for local validation of 'dns-01' challenges.
"""
import logging
from acme import errors
from acme import util
DNS_REQUIREMENT = 'dnspython>=1.12'
try:
util.activate(DNS_REQUIREMENT)
# pragma: no cover
import dns.exception
import dns.resolver
DNS_AVAILABLE = True
except errors.DependencyError: # pragma: no cover
DNS_AVAILABLE = False
logger = logging.getLogger(__name__)
def txt_records_for_name(name):
"""Resolve the name and return the TXT records.
:param unicode name: Domain name being verified.
:returns: A list of txt records, if empty the name could not be resolved
:rtype: list of unicode
"""
if not DNS_AVAILABLE:
raise errors.DependencyError(
'{0} is required to use this function'.format(DNS_REQUIREMENT))
try:
dns_response = dns.resolver.query(name, 'TXT')
except dns.resolver.NXDOMAIN as error:
return []
except dns.exception.DNSException as error:
logger.error("Error resolving %s: %s", name, str(error))
return []
return [txt_rec.decode("utf-8") for rdata in dns_response
for txt_rec in rdata.strings]

View file

@ -1,77 +0,0 @@
"""Tests for acme.dns_resolver."""
import unittest
import mock
from six.moves import reload_module # pylint: disable=import-error
from acme import errors
from acme import test_util
from acme.dns_resolver import DNS_REQUIREMENT
if test_util.requirement_available(DNS_REQUIREMENT):
import dns
def create_txt_response(name, txt_records):
"""
Returns an RRSet containing the 'txt_records' as the result of a DNS
query for 'name'.
This takes advantage of the fact that an Answer object mostly behaves
like an RRset.
"""
return dns.rrset.from_text_list(name, 60, "IN", "TXT", txt_records)
class TxtRecordsForNameTest(unittest.TestCase):
"""Tests for acme.dns_resolver.txt_records_for_name."""
@classmethod
def _call(cls, *args, **kwargs):
from acme.dns_resolver import txt_records_for_name
return txt_records_for_name(*args, **kwargs)
@test_util.skip_unless(test_util.requirement_available(DNS_REQUIREMENT),
"optional dependency dnspython is not available")
class TxtRecordsForNameWithDnsTest(TxtRecordsForNameTest):
"""Tests for acme.dns_resolver.txt_records_for_name with dns."""
@mock.patch("acme.dns_resolver.dns.resolver.query")
def test_txt_records_for_name_with_single_response(self, mock_dns):
mock_dns.return_value = create_txt_response('name', ['response'])
self.assertEqual(['response'], self._call('name'))
@mock.patch("acme.dns_resolver.dns.resolver.query")
def test_txt_records_for_name_with_multiple_responses(self, mock_dns):
mock_dns.return_value = create_txt_response(
'name', ['response1', 'response2'])
self.assertEqual(['response1', 'response2'], self._call('name'))
@mock.patch("acme.dns_resolver.dns.resolver.query")
def test_txt_records_for_name_domain_not_found(self, mock_dns):
mock_dns.side_effect = dns.resolver.NXDOMAIN
self.assertEquals([], self._call('name'))
@mock.patch("acme.dns_resolver.dns.resolver.query")
def test_txt_records_for_name_domain_other_error(self, mock_dns):
mock_dns.side_effect = dns.exception.DNSException
self.assertEquals([], self._call('name'))
class TxtRecordsForNameWithoutDnsTest(TxtRecordsForNameTest):
"""Tests for acme.dns_resolver.txt_records_for_name without dns."""
def setUp(self):
from acme import dns_resolver
dns_resolver.DNS_AVAILABLE = False
def tearDown(self):
from acme import dns_resolver
reload_module(dns_resolver)
def test_exception_raised(self):
self.assertRaises(
errors.DependencyError, self._call, "example.org")
if __name__ == '__main__':
unittest.main() # pragma: no cover

View file

@ -1,6 +1,6 @@
"""Javascript Object Signing and Encryption (jose).
This package is a Python implementation of the stadards developed by
This package is a Python implementation of the standards developed by
IETF `Javascript Object Signing and Encryption (Active WG)`_, in
particular the following RFCs:

View file

@ -60,7 +60,7 @@ class Field(object):
@classmethod
def _empty(cls, value):
"""Is the provided value cosidered "empty" for this field?
"""Is the provided value considered "empty" for this field?
This is useful for subclasses that might want to override the
definition of being empty, e.g. for some more exotic data types.

View file

@ -111,7 +111,7 @@ class JWK(json_util.TypedJSONObjectWithFields):
try:
key = cls._load_cryptography_key(data, password, backend)
except errors.Error as error:
logger.debug('Loading symmetric key, assymentric failed: %s', error)
logger.debug('Loading symmetric key, asymmetric failed: %s', error)
return JWKOct(key=data)
if cls.typ is not NotImplemented and not isinstance(

View file

@ -26,7 +26,7 @@ class ErrorTest(unittest.TestCase):
'type': ERROR_PREFIX + 'malformed',
}
self.error_custom = Error(typ='custom', detail='bar')
self.jobj_cusom = {'type': 'custom', 'detail': 'bar'}
self.jobj_custom = {'type': 'custom', 'detail': 'bar'}
def test_default_typ(self):
from acme.messages import Error

View file

@ -11,9 +11,7 @@ from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import OpenSSL
from acme import errors
from acme import jose
from acme import util
def vector_path(*names):
@ -78,20 +76,6 @@ def load_pyopenssl_private_key(*names):
return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))
def requirement_available(requirement):
"""Checks if requirement can be imported.
:rtype: bool
:returns: ``True`` iff requirement can be imported
"""
try:
util.activate(requirement)
except errors.DependencyError: # pragma: no cover
return False
return True # pragma: no cover
def skip_unless(condition, reason): # pragma: no cover
"""Skip tests unless a condition holds.

View file

@ -1,25 +1,7 @@
"""ACME utilities."""
import pkg_resources
import six
from acme import errors
def map_keys(dikt, func):
"""Map dictionary keys."""
return dict((func(key), value) for key, value in six.iteritems(dikt))
def activate(requirement):
"""Make requirement importable.
:param str requirement: the distribution and version to activate
:raises acme.errors.DependencyError: if cannot activate requirement
"""
try:
for distro in pkg_resources.require(requirement): # pylint: disable=not-callable
distro.activate()
except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict):
raise errors.DependencyError('{0} is unavailable'.format(requirement))

View file

@ -1,8 +1,6 @@
"""Tests for acme.util."""
import unittest
from acme import errors
class MapKeysTest(unittest.TestCase):
"""Tests for acme.util.map_keys."""
@ -14,21 +12,5 @@ class MapKeysTest(unittest.TestCase):
self.assertEqual({2: 2, 4: 4}, map_keys({1: 2, 3: 4}, lambda x: x + 1))
class ActivateTest(unittest.TestCase):
"""Tests for acme.util.activate."""
@classmethod
def _call(cls, *args, **kwargs):
from acme.util import activate
return activate(*args, **kwargs)
def test_failure(self):
self.assertRaises(errors.DependencyError, self._call, 'acme>99.0.0')
def test_success(self):
self._call('acme')
import acme as unused_acme
if __name__ == '__main__':
unittest.main() # pragma: no cover

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.11.0.dev0'
version = '0.12.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
@ -33,11 +33,6 @@ if sys.version_info < (2, 7):
else:
install_requires.append('mock')
# dnspython 1.12 is required to support both Python 2 and Python 3.
dns_extras = [
'dnspython>=1.12',
]
dev_extras = [
'nose',
'tox',
@ -77,7 +72,6 @@ setup(
include_package_data=True,
install_requires=install_requires,
extras_require={
'dns': dns_extras,
'dev': dev_extras,
'docs': docs_extras,
},

View file

@ -472,7 +472,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"\n\nUnfortunately mod_macro is not yet supported".format(
"\n ".join(vhost_macro)), force_interactive=True)
return all_names
return util.get_filtered_names(all_names)
def get_name_from_ip(self, addr): # pylint: disable=no-self-use
"""Returns a reverse dns name if available.

View file

@ -8,7 +8,7 @@ class Addr(common.Addr):
"""Represents an Apache address."""
def __eq__(self, other):
"""This is defined as equalivalent within Apache.
"""This is defined as equivalent within Apache.
ip_addr:* == ip_addr

View file

@ -263,7 +263,7 @@
#
# Set the following policy settings here and they will be propagated to the 30 rules
# file (modsecurity_crs_30_http_policy.conf) by using macro expansion.
# If you run into false positves, you can adjust the settings here.
# If you run into false positives, you can adjust the settings here.
#
#SecAction \
"id:'900012', \
@ -349,7 +349,7 @@
#
# -- [[ Check UTF enconding ]] -----------------------------------------------------------
# -- [[ Check UTF encoding ]] -----------------------------------------------------------
#
# We only want to apply this check if UTF-8 encoding is actually used by the site, otherwise
# it will result in false positives.

View file

@ -126,8 +126,8 @@ class MultipleVhostsTest(util.ApacheTest):
mock_getutility.notification = mock.MagicMock(return_value=True)
names = self.config.get_all_names()
self.assertEqual(names, set(
["certbot.demo", "ocspvhost.com", "encryption-example.demo",
"ip-172-30-0-17", "*.blue.purple.com"]))
["certbot.demo", "ocspvhost.com", "encryption-example.demo"]
))
@certbot_util.patch_get_utility()
@mock.patch("certbot_apache.configurator.socket.gethostbyaddr")
@ -146,7 +146,8 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.vhosts.append(vhost)
names = self.config.get_all_names()
self.assertEqual(len(names), 7)
# Names get filtered, only 5 are returned
self.assertEqual(len(names), 5)
self.assertTrue("zombo.com" in names)
self.assertTrue("google.com" in names)
self.assertTrue("certbot.demo" in names)

View file

@ -178,7 +178,7 @@ class ParserInitTest(util.ApacheTest):
shutil.rmtree(self.work_dir)
@mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg")
def test_unparsable(self, mock_cfg):
def test_unparseable(self, mock_cfg):
from certbot_apache.parser import ApacheParser
mock_cfg.return_value = ('Define: TEST')
self.assertRaises(

View file

@ -177,7 +177,7 @@ class ApacheTlsSni01(common.TLSSNI01):
ips = " ".join(str(i) for i in ip_addrs)
document_root = os.path.join(
self.configurator.config.work_dir, "tls_sni_01_page/")
# TODO: Python docs is not clear how mutliline string literal
# TODO: Python docs is not clear how multiline string literal
# newlines are parsed on different platforms. At least on
# Linux (Debian sid), when source file uses CRLF, Python still
# parses it as "\n"... c.f.:

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.11.0.dev0'
version = '0.12.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View file

@ -23,7 +23,7 @@ if [ -z "$VENV_PATH" ]; then
VENV_PATH="$XDG_DATA_HOME/$VENV_NAME"
fi
VENV_BIN="$VENV_PATH/bin"
LE_AUTO_VERSION="0.10.1"
LE_AUTO_VERSION="0.11.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -38,8 +38,9 @@ Help for certbot itself cannot be provided until it is installed.
-n, --non-interactive, --noninteractive run without asking for user input
--no-self-upgrade do not download updates
--os-packages-only install OS dependencies and exit
-q, --quiet provide only update/error output
-v, --verbose provide more output
-q, --quiet provide only update/error output;
implies --non-interactive
All arguments are accepted and forwarded to the Certbot client when run."
@ -84,6 +85,11 @@ if [ $BASENAME = "letsencrypt-auto" ]; then
HELP=0
fi
# Set ASSUME_YES to 1 if QUIET (i.e. --quiet implies --non-interactive)
if [ "$QUIET" = 1 ]; then
ASSUME_YES=1
fi
# Support for busybox and others where there is no "command",
# but "which" instead
if command -v command > /dev/null 2>&1 ; then
@ -99,7 +105,7 @@ fi
# certbot itself needs root access for almost all modes of operation
# The "normal" case is that sudo is used for the steps that need root, but
# this script *can* be run as root (not recommended), or fall back to using
# `su`. Auto-detection can be overrided by explicitly setting the
# `su`. Auto-detection can be overridden by explicitly setting the
# environment variable LE_AUTO_SUDO to 'sudo', 'sudo_su' or '' as used below.
# Because the parameters in `su -c` has to be a string,
@ -207,7 +213,11 @@ BootstrapDebCommon() {
#
# - Debian 6.0.10 "squeeze" (x64)
$SUDO apt-get update || echo apt-get update hit problems but continuing anyway...
if [ "$QUIET" = 1 ]; then
QUIET_FLAG='-qq'
fi
$SUDO apt-get $QUIET_FLAG update || echo apt-get update hit problems but continuing anyway...
# virtualenv binary can be found in different packages depending on
# distro version (#346)
@ -215,74 +225,74 @@ BootstrapDebCommon() {
virtualenv=
# virtual env is known to apt and is installable
if apt-cache show virtualenv > /dev/null 2>&1 ; then
if ! LC_ALL=C apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
virtualenv="virtualenv"
fi
if ! LC_ALL=C apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
virtualenv="virtualenv"
fi
fi
if apt-cache show python-virtualenv > /dev/null 2>&1; then
virtualenv="$virtualenv python-virtualenv"
virtualenv="$virtualenv python-virtualenv"
fi
augeas_pkg="libaugeas0 augeas-lenses"
AUGVERSION=`LC_ALL=C apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2`
if [ "$ASSUME_YES" = 1 ]; then
YES_FLAG="-y"
YES_FLAG="-y"
fi
AddBackportRepo() {
# ARGS:
BACKPORT_NAME="$1"
BACKPORT_SOURCELINE="$2"
echo "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME."
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then
if [ "$ASSUME_YES" = 1 ]; then
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
sleep 1s
add_backports=1
else
read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response
case $response in
[yY][eE][sS]|[yY]|"")
add_backports=1;;
*)
add_backports=0;;
esac
fi
if [ "$add_backports" = 1 ]; then
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO apt-get update
fi
fi
fi
if [ "$add_backports" != 0 ]; then
$SUDO apt-get install $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
augeas_pkg=
# ARGS:
BACKPORT_NAME="$1"
BACKPORT_SOURCELINE="$2"
echo "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME."
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then
if [ "$ASSUME_YES" = 1 ]; then
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
sleep 1s
add_backports=1
else
read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response
case $response in
[yY][eE][sS]|[yY]|"")
add_backports=1;;
*)
add_backports=0;;
esac
fi
if [ "$add_backports" = 1 ]; then
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO apt-get $QUIET_FLAG update
fi
fi
fi
if [ "$add_backports" != 0 ]; then
$SUDO apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
augeas_pkg=
fi
}
if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then
if lsb_release -a | grep -q wheezy ; then
AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main"
elif lsb_release -a | grep -q precise ; then
# XXX add ARM case
AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
else
echo "No libaugeas0 version is available that's new enough to run the"
echo "Certbot apache plugin..."
fi
# XXX add a case for ubuntu PPAs
if lsb_release -a | grep -q wheezy ; then
AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main"
elif lsb_release -a | grep -q precise ; then
# XXX add ARM case
AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
else
echo "No libaugeas0 version is available that's new enough to run the"
echo "Certbot apache plugin..."
fi
# XXX add a case for ubuntu PPAs
fi
$SUDO apt-get install $YES_FLAG --no-install-recommends \
$SUDO apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends \
python \
python-dev \
$virtualenv \
@ -294,7 +304,6 @@ BootstrapDebCommon() {
ca-certificates \
if ! $EXISTS virtualenv > /dev/null ; then
echo Failed to install a working \"virtualenv\" command, exiting
exit 1
@ -323,6 +332,9 @@ BootstrapRpmCommon() {
if [ "$ASSUME_YES" = 1 ]; then
yes_flag="-y"
fi
if [ "$QUIET" = 1 ]; then
QUIET_FLAG='--quiet'
fi
if ! $SUDO $tool list *virtualenv >/dev/null 2>&1; then
echo "To use Certbot, packages from the EPEL repository need to be installed."
@ -331,14 +343,14 @@ BootstrapRpmCommon() {
exit 1
fi
if [ "$ASSUME_YES" = 1 ]; then
/bin/echo -n "Enabling the EPEL repository in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 seconds..."
sleep 1s
/bin/echo -n "Enabling the EPEL repository in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 seconds..."
sleep 1s
fi
if ! $SUDO $tool install $yes_flag epel-release; then
if ! $SUDO $tool install $yes_flag $QUIET_FLAG epel-release; then
echo "Could not enable EPEL. Aborting bootstrap!"
exit 1
fi
@ -380,9 +392,9 @@ BootstrapRpmCommon() {
"
fi
if ! $SUDO $tool install $yes_flag $pkgs; then
echo "Could not install OS dependencies. Aborting bootstrap!"
exit 1
if ! $SUDO $tool install $yes_flag $QUIET_FLAG $pkgs; then
echo "Could not install OS dependencies. Aborting bootstrap!"
exit 1
fi
}
@ -394,7 +406,11 @@ BootstrapSuseCommon() {
install_flags="-l"
fi
$SUDO zypper $zypper_flags in $install_flags \
if [ "$QUIET" = 1 ]; then
QUIET_FLAG='-qq'
fi
$SUDO zypper $QUIET_FLAG $zypper_flags in $install_flags \
python \
python-devel \
python-virtualenv \
@ -432,7 +448,11 @@ BootstrapArchCommon() {
fi
if [ "$missing" ]; then
$SUDO pacman -S --needed $missing $noconfirm
if [ "$QUIET" = 1]; then
$SUDO pacman -S --needed $missing $noconfirm > /dev/null
else
$SUDO pacman -S --needed $missing $noconfirm
fi
fi
}
@ -465,7 +485,11 @@ BootstrapGentooCommon() {
}
BootstrapFreeBsd() {
$SUDO pkg install -Ay \
if [ "$QUIET" = 1 ]; then
QUIET_FLAG="--quiet"
fi
$SUDO pkg install -Ay $QUIET_FLAG \
python \
py27-virtualenv \
augeas \
@ -505,15 +529,15 @@ BootstrapMac() {
fi
if ! hash pip 2>/dev/null; then
echo "pip not installed"
echo "Installing pip..."
curl --silent --show-error --retry 5 https://bootstrap.pypa.io/get-pip.py | python
echo "pip not installed"
echo "Installing pip..."
curl --silent --show-error --retry 5 https://bootstrap.pypa.io/get-pip.py | python
fi
if ! hash virtualenv 2>/dev/null; then
echo "virtualenv not installed."
echo "Installing with pip..."
pip install virtualenv
echo "virtualenv not installed."
echo "Installing with pip..."
pip install virtualenv
fi
}
@ -523,26 +547,29 @@ BootstrapSmartOS() {
}
BootstrapMageiaCommon() {
if ! $SUDO urpmi --force \
python \
libpython-devel \
python-virtualenv
if [ "$QUIET" = 1 ]; then
QUIET_FLAG='--quiet'
fi
if ! $SUDO urpmi --force $QUIET_FLAG \
python \
libpython-devel \
python-virtualenv
then
echo "Could not install Python dependencies. Aborting bootstrap!"
exit 1
fi
fi
if ! $SUDO urpmi --force \
git \
gcc \
python-augeas \
openssl \
libopenssl-devel \
libffi-devel \
rootcerts
if ! $SUDO urpmi --force $QUIET_FLAG \
git \
gcc \
python-augeas \
libopenssl-devel \
libffi-devel \
rootcerts
then
echo "Could not install additional dependencies. Aborting bootstrap!"
exit 1
echo "Could not install additional dependencies. Aborting bootstrap!"
exit 1
fi
}
@ -609,6 +636,11 @@ if [ "$1" = "--le-auto-phase2" ]; then
# --version output ran through grep due to python-cryptography DeprecationWarnings
# grep for both certbot and letsencrypt until certbot and shim packages have been released
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep "^certbot\|^letsencrypt" | cut -d " " -f 2)
if [ -z "$INSTALLED_VERSION" ]; then
echo "Error: couldn't get currently installed version for $VENV_BIN/letsencrypt: " 1>&2
"$VENV_BIN/letsencrypt" --version
exit 1
fi
else
INSTALLED_VERSION="none"
fi
@ -801,18 +833,18 @@ letsencrypt==0.7.0 \
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.10.1 \
--hash=sha256:1dd5124078bc44739065409f3be51765608a90994c83460578c2680c582c1026 \
--hash=sha256:f51c2fb0a31646364abeb7fdd8cfc7c8a4e63b0641b14ab3ce1b2e3a8921a211
certbot==0.10.1 \
--hash=sha256:9a56fc76f726beeed2f5a08d690088377cd430907f8a38c50e2aa9a258ee1253 \
--hash=sha256:e0d699adb3f8ca3e077a4db339de29ebb3f790fbc5f3f02e446e227ed40aa743
certbot-apache==0.10.1 \
--hash=sha256:1252fd7e435ba48484b0bd9b72535a9755b03d8f0440f164b9c1c560d96cadb8 \
--hash=sha256:134f46690da55262125defa58aa74472eb4a1555c9ed83edb3c8667df5a561b5
certbot-nginx==0.10.1 \
--hash=sha256:afd15ed9e4f3076056b63916f272b7287084d871cb8136477d16b08f64d514f0 \
--hash=sha256:da1b7ea4831ead3f9eb526ee11bf1bf197da0fea4defeeb7b1ce24c5d3f45b51
acme==0.11.0 \
--hash=sha256:9c084f9a62241a11231af63266f2f12ad696be590393a4ab4974276a47d63404 \
--hash=sha256:1513ae74ee8424c739a953a552890830315669d95d105469d1cff5b7db9ae888
certbot==0.11.0 \
--hash=sha256:cc94d890a8697b3bdbdc555bcf4ba93955f64324bc256b2ea710fd053fc03b8a \
--hash=sha256:0f91fee360f9ce5e0584d0954fa3123832435f77f465915389032a90ac0248b1
certbot-apache==0.11.0 \
--hash=sha256:9a01883ca7e1159cff2a6e36bf97b83793c899c62944335f6ac2f59f9b3e8b5d \
--hash=sha256:2b67871e7ae8bbfa7a2779fcd6444f28847fbb7a347ef4bfdb19fb55a0d75673
certbot-nginx==0.11.0 \
--hash=sha256:cfae45a42560e39889eebd287437556084c421a9e07a2deb3cbc0aeef92d2dab \
--hash=sha256:dbddffe47c8e8b2d1cf47fe393b434003270a45aec896f133492c855c77e6f08
UNLIKELY_EOF
# -------------------------------------------------------------------------
@ -1024,7 +1056,7 @@ UNLIKELY_EOF
fi
else
# Phase 1: Upgrade certbot-auto if neceesary, then self-invoke.
# Phase 1: Upgrade certbot-auto if necessary, then self-invoke.
#
# Each phase checks the version of only the thing it is responsible for
# upgrading. Phase 1 checks the version of the latest release of

View file

@ -8,6 +8,7 @@ import zope.interface
from certbot import configuration
from certbot import errors as le_errors
from certbot import util as certbot_util
from certbot_apache import configurator
from certbot_apache import constants
from certbot_compatibility_test import errors
@ -106,4 +107,7 @@ def _get_names(config):
not util.IP_REGEX.match(words[1]) and
words[1].find(".") != -1):
all_names.add(words[1])
return all_names, non_ip_names
return (
certbot_util.get_filtered_names(all_names),
certbot_util.get_filtered_names(non_ip_names)
)

View file

@ -147,7 +147,7 @@ def test_deploy_cert(plugin, temp_dir, domains):
plugin.deploy_cert(domain, cert_path, util.KEY_PATH, cert_path, cert_path)
plugin.save() # Needed by the Apache plugin
except le_errors.Error as error:
logger.error("Plugin failed to deploy ceritificate for %s:", domain)
logger.error("Plugin failed to deploy certificate for %s:", domain)
logger.exception(error)
return False
@ -202,7 +202,7 @@ def test_enhancements(plugin, domains):
success = False
if success:
logger.info("Enhancments test succeeded")
logger.info("Enhancements test succeeded")
return success

View file

@ -38,7 +38,7 @@ http {
## Define a zone for limiting the number of simultaneous
## connections nginx accepts. 1m means 32000 simultaneous
## sessions. We need to define for each server the limit_conn
## value refering to this or other zones.
## value referring to this or other zones.
## ** This syntax requires nginx version >=
## ** 1.1.8. Cf. http://nginx.org/en/CHANGES. If using an older
## ** version then use the limit_zone directive below

View file

@ -37,7 +37,7 @@ charset_map windows-1251 utf-8 {
AA D084 ; # capital Ukrainian YE
AB C2AB ; # left-pointing double angle quotation mark
AC C2AC ; # not sign
AD C2AD ; # soft hypen
AD C2AD ; # soft hyphen
AE C2AE ; # (R)
AF D087 ; # capital Ukrainian YI

View file

@ -8,7 +8,7 @@ http {
keepalive_timeout 60;
# http-redirects to https; even if using of hsts;
# usefull if users are typing your server-name w/out https://
# useful if users are typing your server-name w/out https://
# logjam and a good idea anyway
@ -98,7 +98,7 @@ http {
#ssl_ciphers ALL:!ADH:!EXP:!LOW:!RC2:!3DES:!SEED:!RC4:+HIGH:+MEDIUM;
#
# suggestions by mozilla-server-team - good compatibility, pfs, preferrable ciphers
# suggestions by mozilla-server-team - good compatibility, pfs, preferable ciphers
#
# modern ciphers
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK';
@ -106,7 +106,7 @@ http {
# intermediate ciphers
#ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
# old ciphers (would need SSLv3, but is not recommende as of oct 2014
# old ciphers (would need SSLv3, but is not recommended as of oct 2014
#ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
# logjam / cipher suggested from weakdh.org

View file

@ -29,7 +29,7 @@ server {
location ~* (index\.php|upload\.php|connector\.php|dl\.php|ut\.php|lt\.php|download\.php)$ {
fastcgi_split_path_info ^(.|\.php)(/.+)$;
include /etc/nginx/fastcgi_params.conf; #standar fastcgi config file
include /etc/nginx/fastcgi_params.conf; #standard fastcgi config file
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors on;
fastcgi_pass 127.0.0.1:9000;

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.11.0.dev0'
version = '0.12.0.dev0'
install_requires = [
'certbot',

View file

@ -403,25 +403,7 @@ class NginxConfigurator(common.Plugin):
except (socket.error, socket.herror, socket.timeout):
continue
return self._get_filtered_names(all_names)
def _get_filtered_names(self, all_names):
"""Removes names that aren't considered valid by Let's Encrypt.
:param set all_names: all names found in the Nginx configuration
:returns: all found names that are considered valid by LE
:rtype: set
"""
filtered_names = set()
for name in all_names:
try:
filtered_names.add(util.enforce_le_validity(name))
except errors.ConfigurationError as error:
logger.debug('Not suggesting name "%s"', name)
logger.debug(error)
return filtered_names
return util.get_filtered_names(all_names)
def _get_snakeoil_paths(self):
# TODO: generate only once

View file

@ -52,10 +52,10 @@ class RawNginxParser(object):
map_statement = space + Literal("map") + space + nonspace + space + dollar_var + space
# This is NOT an accurate way to parse nginx map entries; it's almost
# certianly too permissive and may be wrong in other ways, but it should
# certainly too permissive and may be wrong in other ways, but it should
# preserve things correctly in mmmmost or all cases.
#
# - I can neither prove nor disprove that it is corect wrt all escaped
# - I can neither prove nor disprove that it is correct wrt all escaped
# semicolon situations
# Addresses https://github.com/fatiherikli/nginxparser/issues/19
map_pattern = Regex(r'".*"') | Regex(r"'.*'") | nonspace
@ -143,7 +143,7 @@ class RawNginxDumper(object):
def loads(source):
"""Parses from a string.
:param str souce: The string to parse
:param str source: The string to parse
:returns: The parsed tree
:rtype: list

View file

@ -67,7 +67,7 @@ MainRule "rx:%[2|3]." "msg:double encoding !" "mz:ARGS|URL|BODY|$HEADERS_VAR:Co
####################################
MainRule "str:&#" "msg: utf7/8 encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1400;
MainRule "str:%U" "msg: M$ encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1401;
MainRule negative "rx:multipart/form-data|application/x-www-form-urlencoded" "msg:Content is neither mulipart/x-www-form.." "mz:$HEADERS_VAR:Content-type" "s:$EVADE:4" id:1402;
MainRule negative "rx:multipart/form-data|application/x-www-form-urlencoded" "msg:Content is neither multipart/x-www-form.." "mz:$HEADERS_VAR:Content-type" "s:$EVADE:4" id:1402;
#############################
## File uploads: 1500-1600 ##

View file

@ -36,7 +36,7 @@ charset_map windows-1251 utf-8 {
AA D084; # capital Ukrainian YE
AB C2AB; # left-pointing double angle quotation mark
AC C2AC; # not sign
AD C2AD; # soft hypen
AD C2AD; # soft hyphen
AE C2AE; # (R)
AF D087; # capital Ukrainian YI

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.11.0.dev0'
version = '0.12.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View file

@ -1,4 +1,4 @@
"""Certbot client."""
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
__version__ = '0.11.0.dev0'
__version__ = '0.12.0.dev0'

View file

@ -1,6 +1,6 @@
"""Client annotated ACME challenges.
Please use names such as ``achall`` to distiguish from variables "of type"
Please use names such as ``achall`` to distinguish from variables "of type"
:class:`acme.challenges.Challenge` (denoted by ``chall``)
and :class:`.ChallengeBody` (denoted by ``challb``)::

View file

@ -34,8 +34,7 @@ class AuthHandler(object):
:ivar list achalls: DV challenges in the form of
:class:`certbot.achallenges.AnnotatedChallenge`
:ivar list pref_challs: sorted user specified preferred challenges
in the form of subclasses of :class:`acme.challenges.Challenge`
with the most preferred challenge listed first
type strings with the most preferred challenge listed first
"""
def __init__(self, auth, acme, account, pref_challs):
@ -252,8 +251,10 @@ class AuthHandler(object):
# Make sure to make a copy...
plugin_pref = self.auth.get_chall_pref(domain)
if self.pref_challs:
chall_prefs.extend(pref for pref in self.pref_challs
if pref in plugin_pref)
plugin_pref_types = set(chall.typ for chall in plugin_pref)
for typ in self.pref_challs:
if typ in plugin_pref_types:
chall_prefs.append(challenges.Challenge.TYPES[typ])
if chall_prefs:
return chall_prefs
raise errors.AuthorizationError(

View file

@ -95,27 +95,23 @@ def delete(config):
# Public Helpers
###################
def lineage_for_certname(config, certname):
def lineage_for_certname(cli_config, certname):
"""Find a lineage object with name certname."""
def update_cert_for_name_match(candidate_lineage, rv):
"""Return cert if it has name certname, else return rv
"""
matching_lineage_name_cert = rv
if candidate_lineage.lineagename == certname:
matching_lineage_name_cert = candidate_lineage
return matching_lineage_name_cert
return _search_lineages(config, update_cert_for_name_match, None)
configs_dir = cli_config.renewal_configs_dir
# Verify the directory is there
util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid())
renewal_file = storage.renewal_file_for_certname(cli_config, certname)
try:
return storage.RenewableCert(renewal_file, cli_config)
except (errors.CertStorageError, IOError):
logger.debug("Renewal conf file %s is broken.", renewal_file)
logger.debug("Traceback was:\n%s", traceback.format_exc())
return None
def domains_for_certname(config, certname):
"""Find the domains in the cert with name certname."""
def update_domains_for_name_match(candidate_lineage, rv):
"""Return domains if certname matches, else return rv
"""
matching_domains = rv
if candidate_lineage.lineagename == certname:
matching_domains = candidate_lineage.names()
return matching_domains
return _search_lineages(config, update_domains_for_name_match, None)
lineage = lineage_for_certname(config, certname)
return lineage.names() if lineage else None
def find_duplicative_certs(config, domains):
"""Find existing certs that duplicate the request."""

View file

@ -1,4 +1,5 @@
"""Certbot command line argument & config processing."""
# pylint: disable=too-many-lines
from __future__ import print_function
import argparse
import copy
@ -149,7 +150,7 @@ def possible_deprecation_warning(config):
if cli_command != LEAUTO:
return
if config.no_self_upgrade:
# users setting --no-self-upgrade might be hanging on a clent version like 0.3.0
# users setting --no-self-upgrade might be hanging on a client version like 0.3.0
# or 0.5.0 which is the new script, but doesn't set CERTBOT_AUTO; they don't
# need warnings
return
@ -317,7 +318,7 @@ class CustomHelpFormatter(argparse.HelpFormatter):
# The attributes here are:
# short: a string that will be displayed by "certbot -h commands"
# opts: a string that heads the section of flags with which this command is documented,
# both for "cerbot -h SUBCOMMAND" and "certbot -h all"
# both for "certbot -h SUBCOMMAND" and "certbot -h all"
# usage: an optional string that overrides the header of "certbot -h SUBCOMMAND"
VERB_HELP = [
("run (default)", {
@ -333,7 +334,7 @@ VERB_HELP = [
"This command obtains a TLS/SSL certificate without installing it anywhere.")
}),
("renew", {
"short": "Renew all certificates (or one specifed with --cert-name)",
"short": "Renew all certificates (or one specified with --cert-name)",
"opts": ("The 'renew' subcommand will attempt to renew all"
" certificates (or more precisely, certificate lineages) you have"
" previously obtained if they are close to expiry, and print a"
@ -438,7 +439,7 @@ class HelpfulArgumentParser(object):
self.detect_defaults = detect_defaults
self.args = args
if self.args[0] == 'help':
if self.args and self.args[0] == 'help':
self.args[0] = '--help'
self.determine_verb()
@ -495,7 +496,7 @@ class HelpfulArgumentParser(object):
if "apache" in plugins:
apache_doc = "--apache Use the Apache plugin for authentication & installation"
else:
apache_doc = "(the cerbot apache plugin is not installed)"
apache_doc = "(the certbot apache plugin is not installed)"
usage = SHORT_USAGE
if help_arg == True:
@ -879,6 +880,12 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
helpful.add(
["register", "unregister", "automation"], "-m", "--email",
help=config_help("email"))
helpful.add(["register", "automation"], "--eff-email", action="store_true",
default=None, dest="eff_email",
help="Share your e-mail address with EFF")
helpful.add(["register", "automation"], "--no-eff-email", action="store_false",
default=None, dest="eff_email",
help="Don't share your e-mail address with EFF")
helpful.add(
["automation", "certonly", "run"],
"--keep-until-expiring", "--keep", "--reinstall",
@ -1236,13 +1243,31 @@ class _PrefChallAction(argparse.Action):
"""Action class for parsing preferred challenges."""
def __call__(self, parser, namespace, pref_challs, option_string=None):
aliases = {"dns": "dns-01", "http": "http-01", "tls-sni": "tls-sni-01"}
challs = [c.strip() for c in pref_challs.split(",")]
challs = [aliases[c] if c in aliases else c for c in challs]
unrecognized = ", ".join(name for name in challs
if name not in challenges.Challenge.TYPES)
if unrecognized:
raise argparse.ArgumentTypeError(
"Unrecognized challenges: {0}".format(unrecognized))
namespace.pref_challs.extend(challenges.Challenge.TYPES[name]
for name in challs)
try:
challs = parse_preferred_challenges(pref_challs.split(","))
except errors.Error as error:
raise argparse.ArgumentTypeError(str(error))
namespace.pref_challs.extend(challs)
def parse_preferred_challenges(pref_challs):
"""Translate and validate preferred challenges.
:param pref_challs: list of preferred challenge types
:type pref_challs: `list` of `str`
:returns: validated list of preferred challenge types
:rtype: `list` of `str`
:raises errors.Error: if pref_challs is invalid
"""
aliases = {"dns": "dns-01", "http": "http-01", "tls-sni": "tls-sni-01"}
challs = [c.strip() for c in pref_challs]
challs = [aliases.get(c, c) for c in challs]
unrecognized = ", ".join(name for name in challs
if name not in challenges.Challenge.TYPES)
if unrecognized:
raise errors.Error(
"Unrecognized challenges: {0}".format(unrecognized))
return challs

View file

@ -15,15 +15,16 @@ import certbot
from certbot import account
from certbot import auth_handler
from certbot import cli
from certbot import constants
from certbot import crypto_util
from certbot import errors
from certbot import eff
from certbot import error_handler
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot import reverter
from certbot import storage
from certbot import cli
from certbot import util
from certbot.display import ops as display_ops
from certbot.display import enhancements
@ -93,7 +94,7 @@ def register(config, account_storage, tos_cb=None):
Terms of Service present in the contained
`.Registration.terms_of_service` is accepted by the client, and
``False`` otherwise. ``tos_cb`` will be called only if the
client acction is necessary, i.e. when ``terms_of_service is not
client action is necessary, i.e. when ``terms_of_service is not
None``. This argument is optional, if not supplied it will
default to automatic acceptance!
@ -139,6 +140,8 @@ def register(config, account_storage, tos_cb=None):
account.report_new_account(acc, config)
account_storage.save(acc)
eff.handle_subscription(config)
return acc, acme
@ -438,7 +441,7 @@ class Client(object):
self.installer.restart()
def apply_enhancement(self, domains, enhancement, options=None):
"""Applies an enhacement on all domains.
"""Applies an enhancement on all domains.
:param domains: list of ssl_vhosts
:type list of str
@ -494,7 +497,7 @@ class Client(object):
self.installer.rollback_checkpoints()
self.installer.restart()
except:
# TODO: suggest letshelp-letsencypt here
# TODO: suggest letshelp-letsencrypt here
reporter.add_message(
"An error occurred and we failed to restore your config and "
"restart your server. Please submit a bug report to "

View file

@ -106,3 +106,6 @@ RENEWAL_CONFIGS_DIR = "renewal"
FORCE_INTERACTIVE_FLAG = "--force-interactive"
"""Flag to disable TTY checking in IDisplay."""
EFF_SUBSCRIBE_URI = "https://supporters.eff.org/subscribe/certbot"
"""EFF URI used to submit the e-mail address of users who opt-in."""

95
certbot/eff.py Normal file
View file

@ -0,0 +1,95 @@
"""Subscribes users to the EFF newsletter."""
import logging
import requests
import zope.component
from certbot import constants
from certbot import interfaces
logger = logging.getLogger(__name__)
def handle_subscription(config):
"""High level function to take care of EFF newsletter subscriptions.
The user may be asked if they want to sign up for the newsletter if
they have not already specified.
:param .IConfig config: Client configuration.
"""
if config.email is None:
if config.eff_email:
_report_failure("you didn't provide an e-mail address")
return
if config.eff_email is None:
config.eff_email = _want_subscription()
if config.eff_email:
subscribe(config.email)
def _want_subscription():
"""Does the user want to be subscribed to the EFF newsletter?
:returns: True if we should subscribe the user, otherwise, False
:rtype: bool
"""
prompt = (
'Would you be willing to share your email address with the '
"Electronic Frontier Foundation, a founding partner of the Let's "
'Encrypt project and the non-profit organization that develops '
"Certbot? We'd like to send you email about EFF and our work to "
'encrypt the web, protect its users and defend digital rights.')
display = zope.component.getUtility(interfaces.IDisplay)
return display.yesno(prompt, default=False)
def subscribe(email):
"""Subscribe the user to the EFF mailing list.
:param str email: the e-mail address to subscribe
"""
url = constants.EFF_SUBSCRIBE_URI
data = {'data_type': 'json',
'email': email,
'form_id': 'eff_supporters_library_subscribe_form'}
logger.debug('Sending POST request to %s:\n%s', url, data)
_check_response(requests.post(url, data=data))
def _check_response(response):
"""Check for errors in the server's response.
If an error occurred, it will be reported to the user.
:param requests.Response response: the server's response to the
subscription request
"""
logger.debug('Received response:\n%s', response.content)
if response.ok:
if not response.json()['status']:
_report_failure('your e-mail address appears to be invalid')
else:
_report_failure()
def _report_failure(reason=None):
"""Notify the user of failing to sign them up for the newsletter.
:param reason: a phrase describing what the problem was
beginning with a lowercase letter and no closing punctuation
:type reason: `str` or `None`
"""
msg = ['We were unable to subscribe you the EFF mailing list']
if reason is not None:
msg.append(' because ')
msg.append(reason)
msg.append('. You can try again later by visiting https://act.eff.org.')
reporter = zope.component.getUtility(interfaces.IReporter)
reporter.add_message(''.join(msg), reporter.LOW_PRIORITY)

View file

@ -24,6 +24,7 @@ from certbot import crypto_util
from certbot import colored_logging
from certbot import configuration
from certbot import constants
from certbot import eff
from certbot import errors
from certbot import hooks
from certbot import interfaces
@ -474,6 +475,7 @@ def register(config, unused_plugins):
acc.regr = acme_client.acme.update_registration(acc.regr.update(
body=acc.regr.body.update(contact=('mailto:' + config.email,))))
account_storage.save_regr(acc)
eff.handle_subscription(config)
add_msg("Your e-mail address was updated to {0}.".format(config.email))
@ -812,7 +814,7 @@ def make_or_verify_core_dir(directory, mode, uid, strict):
raise errors.Error(_PERM_ERR_FMT.format(error))
def make_or_verify_needed_dirs(config):
"""Create or verify existance of config, work, or logs directories"""
"""Create or verify existence of config, work, or logs directories"""
make_or_verify_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE,
os.geteuid(), config.strict_permissions)
make_or_verify_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE,

View file

@ -79,7 +79,7 @@ class PluginEntryPoint(object):
return self._initialized is not None
def init(self, config=None):
"""Memoized plugin inititialization."""
"""Memoized plugin initialization."""
if not self.initialized:
self.entry_point.require() # fetch extras!
self._initialized = self.plugin_cls(config, self.name)
@ -230,7 +230,7 @@ class PluginsRegistry(collections.Mapping):
def available(self):
"""Filter plugins based on availability."""
return self.filter(lambda p_ep: p_ep.available)
# succefully prepared + misconfigured
# successfully prepared + misconfigured
def find_init(self, plugin):
"""Find an initialized plugin.

View file

@ -255,7 +255,7 @@ class PluginsRegistryTest(unittest.TestCase):
def test_find_init(self):
self.assertTrue(self.reg.find_init(mock.Mock()) is None)
self.plugin_ep.initalized = True
self.plugin_ep.initialized = True
self.assertTrue(
self.reg.find_init(self.plugin_ep.init()) is self.plugin_ep)

View file

@ -1,4 +1,4 @@
"""Tests for letsenecrypt.plugins.selection"""
"""Tests for letsencrypt.plugins.selection"""
import sys
import unittest

View file

@ -18,7 +18,6 @@ from certbot import errors
from certbot import interfaces
from certbot.plugins import common
from certbot.plugins import util
logger = logging.getLogger(__name__)
@ -208,74 +207,38 @@ class Authenticator(common.Plugin):
# pylint: disable=unused-argument,missing-docstring
return self.supported_challenges
def _verify_ports_are_available(self, achalls):
"""Confirm the ports are available to solve all achalls.
:param list achalls: list of
:class:`~certbot.achallenges.AnnotatedChallenge`
:raises .errors.MisconfigurationError: if required port is
unavailable
"""
ports = []
if any(isinstance(ac.chall, challenges.HTTP01) for ac in achalls):
ports.append(self.config.http01_port)
if any(isinstance(ac.chall, challenges.TLSSNI01) for ac in achalls):
ports.append(self.config.tls_sni_01_port)
renewer = (self.config.verb == "renew")
if any(util.already_listening(port, renewer) for port in ports):
raise errors.MisconfigurationError(
"At least one of the required ports is already taken.")
def perform(self, achalls): # pylint: disable=missing-docstring
self._verify_ports_are_available(achalls)
return [self._try_perform_single(achall) for achall in achalls]
try:
return self.perform2(achalls)
except errors.StandaloneBindError as error:
display = zope.component.getUtility(interfaces.IDisplay)
def _try_perform_single(self, achall):
while True:
try:
return self._perform_single(achall)
except errors.StandaloneBindError as error:
_handle_perform_error(error)
if error.socket_error.errno == socket.errno.EACCES:
display.notification(
"Could not bind TCP port {0} because you don't have "
"the appropriate permissions (for example, you "
"aren't running this program as "
"root).".format(error.port), force_interactive=True)
elif error.socket_error.errno == socket.errno.EADDRINUSE:
display.notification(
"Could not bind TCP port {0} because it is already in "
"use by another process on this system (such as a web "
"server). Please stop the program in question and then "
"try again.".format(error.port), force_interactive=True)
else:
raise # XXX: How to handle unknown errors in binding?
def _perform_single(self, achall):
if isinstance(achall.chall, challenges.HTTP01):
server, response = self._perform_http_01(achall)
else: # tls-sni-01
server, response = self._perform_tls_sni_01(achall)
self.served[server].add(achall)
return response
def perform2(self, achalls):
"""Perform achallenges without IDisplay interaction."""
responses = []
def _perform_http_01(self, achall):
server = self.servers.run(self.config.http01_port, challenges.HTTP01)
response, validation = achall.response_and_validation()
resource = acme_standalone.HTTP01RequestHandler.HTTP01Resource(
chall=achall.chall, response=response, validation=validation)
self.http_01_resources.add(resource)
return server, response
for achall in achalls:
if isinstance(achall.chall, challenges.HTTP01):
server = self.servers.run(
self.config.http01_port, challenges.HTTP01)
response, validation = achall.response_and_validation()
self.http_01_resources.add(
acme_standalone.HTTP01RequestHandler.HTTP01Resource(
chall=achall.chall, response=response,
validation=validation))
else: # tls-sni-01
server = self.servers.run(
self.config.tls_sni_01_port, challenges.TLSSNI01)
response, (cert, _) = achall.response_and_validation(
cert_key=self.key)
self.certs[response.z_domain] = (self.key, cert)
self.served[server].add(achall)
responses.append(response)
return responses
def _perform_tls_sni_01(self, achall):
port = self.config.tls_sni_01_port
server = self.servers.run(port, challenges.TLSSNI01)
response, (cert, _) = achall.response_and_validation(cert_key=self.key)
self.certs[response.z_domain] = (self.key, cert)
return server, response
def cleanup(self, achalls): # pylint: disable=missing-docstring
# reduce self.served and close servers if none challenges are served
@ -286,3 +249,25 @@ class Authenticator(common.Plugin):
for port, server in six.iteritems(self.servers.running()):
if not self.served[server]:
self.servers.stop(port)
def _handle_perform_error(error):
if error.socket_error.errno == socket.errno.EACCES:
raise errors.PluginError(
"Could not bind TCP port {0} because you don't have "
"the appropriate permissions (for example, you "
"aren't running this program as "
"root).".format(error.port))
elif error.socket_error.errno == socket.errno.EADDRINUSE:
display = zope.component.getUtility(interfaces.IDisplay)
msg = (
"Could not bind TCP port {0} because it is already in "
"use by another process on this system (such as a web "
"server). Please stop the program in question and "
"then try again.".format(error.port))
should_retry = display.yesno(msg, "Retry",
"Cancel", default=False)
if not should_retry:
raise errors.PluginError(msg)
else:
raise

View file

@ -8,11 +8,9 @@ import six
from acme import challenges
from acme import jose
from acme import standalone as acme_standalone
from certbot import achallenges
from certbot import errors
from certbot import interfaces
from certbot.tests import acme_util
from certbot.tests import util as test_util
@ -114,6 +112,7 @@ def get_open_port():
open_socket.close()
return port
class AuthenticatorTest(unittest.TestCase):
"""Tests for certbot.plugins.standalone.Authenticator."""
@ -124,6 +123,7 @@ class AuthenticatorTest(unittest.TestCase):
tls_sni_01_port=get_open_port(), http01_port=get_open_port(),
standalone_supported_challenges="tls-sni-01,http-01")
self.auth = Authenticator(self.config, name="standalone")
self.auth.servers = mock.MagicMock()
def test_supported_challenges(self):
self.assertEqual(self.auth.supported_challenges,
@ -146,6 +146,52 @@ class AuthenticatorTest(unittest.TestCase):
self.assertEqual(self.auth.get_chall_pref(domain=None),
[challenges.TLSSNI01])
def test_perform(self):
achalls = self._get_achalls()
response = self.auth.perform(achalls)
expected = [achall.response(achall.account_key) for achall in achalls]
self.assertEqual(response, expected)
@test_util.patch_get_utility()
def test_perform_eaddrinuse_retry(self, mock_get_utility):
errno = socket.errno.EADDRINUSE
error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1)
self.auth.servers.run.side_effect = [error] + 2 * [mock.MagicMock()]
mock_yesno = mock_get_utility.return_value.yesno
mock_yesno.return_value = True
self.test_perform()
self._assert_correct_yesno_call(mock_yesno)
@test_util.patch_get_utility()
def test_perform_eaddrinuse_no_retry(self, mock_get_utility):
mock_yesno = mock_get_utility.return_value.yesno
mock_yesno.return_value = False
errno = socket.errno.EADDRINUSE
self.assertRaises(errors.PluginError, self._fail_perform, errno)
self._assert_correct_yesno_call(mock_yesno)
def _assert_correct_yesno_call(self, mock_yesno):
yesno_args, yesno_kwargs = mock_yesno.call_args
self.assertTrue("in use" in yesno_args[0])
self.assertFalse(yesno_kwargs.get("default", True))
def test_perform_eacces(self):
errno = socket.errno.EACCES
self.assertRaises(errors.PluginError, self._fail_perform, errno)
def test_perform_unexpected_socket_error(self):
errno = socket.errno.ENOTCONN
self.assertRaises(
errors.StandaloneBindError, self._fail_perform, errno)
def _fail_perform(self, errno):
error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1)
self.auth.servers.run.side_effect = error
self.auth.perform(self._get_achalls())
@classmethod
def _get_achalls(cls):
domain = b'localhost'
@ -157,84 +203,7 @@ class AuthenticatorTest(unittest.TestCase):
return [http_01, tls_sni_01]
@mock.patch("certbot.plugins.standalone.util")
def test_perform_already_listening(self, mock_util):
http_01, tls_sni_01 = self._get_achalls()
for achall, port in ((http_01, self.config.http01_port,),
(tls_sni_01, self.config.tls_sni_01_port)):
mock_util.already_listening.return_value = True
self.assertRaises(
errors.MisconfigurationError, self.auth.perform, [achall])
mock_util.already_listening.assert_called_once_with(port, False)
mock_util.already_listening.reset_mock()
@test_util.patch_get_utility()
def test_perform(self, unused_mock_get_utility):
achalls = self._get_achalls()
self.auth.perform2 = mock.Mock(return_value=mock.sentinel.responses)
self.assertEqual(mock.sentinel.responses, self.auth.perform(achalls))
self.auth.perform2.assert_called_once_with(achalls)
@test_util.patch_get_utility()
def _test_perform_bind_errors(self, errno, achalls, mock_get_utility):
port = get_open_port()
def _perform2(unused_achalls):
raise errors.StandaloneBindError(mock.Mock(errno=errno), port)
self.auth.perform2 = mock.MagicMock(side_effect=_perform2)
self.auth.perform(achalls)
mock_get_utility.assert_called_once_with(interfaces.IDisplay)
notification = mock_get_utility.return_value.notification
self.assertEqual(1, notification.call_count)
self.assertTrue(str(port) in notification.call_args[0][0])
def test_perform_eacces(self):
# pylint: disable=no-value-for-parameter
self._test_perform_bind_errors(socket.errno.EACCES, [])
def test_perform_eaddrinuse(self):
# pylint: disable=no-value-for-parameter
self._test_perform_bind_errors(socket.errno.EADDRINUSE, [])
def test_perfom_unknown_bind_error(self):
self.assertRaises(
errors.StandaloneBindError, self._test_perform_bind_errors,
socket.errno.ENOTCONN, [])
def test_perform2(self):
http_01, tls_sni_01 = self._get_achalls()
self.auth.servers = mock.MagicMock()
def _run(port, tls): # pylint: disable=unused-argument
return "server{0}".format(port)
self.auth.servers.run.side_effect = _run
responses = self.auth.perform2([http_01, tls_sni_01])
self.assertTrue(isinstance(responses, list))
self.assertEqual(2, len(responses))
self.assertTrue(isinstance(responses[0], challenges.HTTP01Response))
self.assertTrue(isinstance(responses[1], challenges.TLSSNI01Response))
self.assertEqual(self.auth.servers.run.mock_calls, [
mock.call(self.config.http01_port, challenges.HTTP01),
mock.call(self.config.tls_sni_01_port, challenges.TLSSNI01),
])
self.assertEqual(self.auth.served, {
"server" + str(self.config.tls_sni_01_port): set([tls_sni_01]),
"server" + str(self.config.http01_port): set([http_01]),
})
self.assertEqual(1, len(self.auth.http_01_resources))
self.assertEqual(1, len(self.auth.certs))
self.assertEqual(list(self.auth.http_01_resources), [
acme_standalone.HTTP01RequestHandler.HTTP01Resource(
acme_util.HTTP01, responses[0], mock.ANY)])
def test_cleanup(self):
self.auth.servers = mock.Mock()
self.auth.servers.running.return_value = {
1: "server1",
2: "server2",

View file

@ -1,34 +1,11 @@
"""Plugin utilities."""
import logging
import os
import socket
import zope.component
from acme import errors as acme_errors
from acme import util as acme_util
from certbot import interfaces
from certbot import util
PSUTIL_REQUIREMENT = "psutil>=2.2.1"
try:
acme_util.activate(PSUTIL_REQUIREMENT)
import psutil # pragma: no cover
USE_PSUTIL = True
except acme_errors.DependencyError: # pragma: no cover
USE_PSUTIL = False
logger = logging.getLogger(__name__)
RENEWER_EXTRA_MSG = (
" For automated renewal, you may want to use a script that stops"
" and starts your webserver. You can find an example at"
" https://certbot.eff.org/docs/using.html#renewal ."
" Alternatively you can use the webroot plugin to renew without"
" needing to stop and start your webserver.")
def path_surgery(cmd):
"""Attempt to perform PATH surgery to find cmd
@ -59,105 +36,3 @@ def path_surgery(cmd):
logger.warning("Failed to find %s in%s PATH: %s", cmd,
expanded, path)
return False
def already_listening(port, renewer=False):
"""Check if a process is already listening on the port.
If so, also tell the user via a display notification.
.. warning::
On some operating systems, this function can only usefully be
run as root.
:param int port: The TCP port in question.
:returns: True or False.
"""
if USE_PSUTIL:
return already_listening_psutil(port, renewer=renewer)
else:
logger.debug("Psutil not found, using simple socket check.")
return already_listening_socket(port, renewer=renewer)
def already_listening_socket(port, renewer=False):
"""Simple socket based check to find out if port is already in use
:param int port: The TCP port in question.
:returns: True or False
"""
try:
testsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
testsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
testsocket.bind(("", port))
except socket.error:
display = zope.component.getUtility(interfaces.IDisplay)
extra = ""
if renewer:
extra = RENEWER_EXTRA_MSG
display.notification(
"Port {0} is already in use by another process. This will "
"prevent us from binding to that port. Please stop the "
"process that is populating the port in question and try "
"again. {1}".format(port, extra), force_interactive=True)
return True
finally:
testsocket.close()
except socket.error:
pass
return False
def already_listening_psutil(port, renewer=False):
"""Psutil variant of the open port check
:param int port: The TCP port in question.
:returns: True or False.
"""
try:
net_connections = psutil.net_connections()
except psutil.AccessDenied as error:
logger.info("Access denied when trying to list network "
"connections: %s. Are you root?", error)
# this function is just a pre-check that often causes false
# positives and problems in testing (c.f. #680 on Mac, #255
# generally); we will fail later in bind() anyway
return False
listeners = [conn.pid for conn in net_connections
if conn.status == 'LISTEN' and
conn.type == socket.SOCK_STREAM and
conn.laddr[1] == port]
try:
if listeners and listeners[0] is not None:
# conn.pid may be None if the current process doesn't have
# permission to identify the listening process! Additionally,
# listeners may have more than one element if separate
# sockets have bound the same port on separate interfaces.
# We currently only have UI to notify the user about one
# of them at a time.
pid = listeners[0]
name = psutil.Process(pid).name()
display = zope.component.getUtility(interfaces.IDisplay)
extra = ""
if renewer:
extra = RENEWER_EXTRA_MSG
display.notification(
"The program {0} (process ID {1}) is already listening "
"on TCP port {2}. This will prevent us from binding to "
"that port. Please stop the {0} program temporarily "
"and then try again.{3}".format(name, pid, port, extra),
force_interactive=True)
return True
except (psutil.NoSuchProcess, psutil.AccessDenied):
# Perhaps the result of a race where the process could have
# exited or relinquished the port (NoSuchProcess), or the result
# of an OS policy where we're not allowed to look up the process
# name (AccessDenied).
pass
return False

View file

@ -1,13 +1,9 @@
"""Tests for certbot.plugins.util."""
import os
import socket
import unittest
import mock
from certbot.plugins.util import PSUTIL_REQUIREMENT
from certbot.tests import util as test_util
class PathSurgeryTest(unittest.TestCase):
"""Tests for certbot.plugins.path_surgery."""
@ -34,140 +30,5 @@ class PathSurgeryTest(unittest.TestCase):
self.assertTrue("/tmp" in os.environ["PATH"])
class AlreadyListeningTest(unittest.TestCase):
"""Tests for certbot.plugins.already_listening."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot.plugins.util import already_listening
return already_listening(*args, **kwargs)
class AlreadyListeningTestNoPsutil(AlreadyListeningTest):
"""Tests for certbot.plugins.already_listening when
psutil is not available"""
@classmethod
def _call(cls, *args, **kwargs):
with mock.patch("certbot.plugins.util.USE_PSUTIL", False):
return super(
AlreadyListeningTestNoPsutil, cls)._call(*args, **kwargs)
@test_util.patch_get_utility()
def test_ports_available(self, mock_getutil):
# Ensure we don't get error
with mock.patch("socket.socket.bind"):
self.assertFalse(self._call(80))
self.assertFalse(self._call(80, True))
self.assertEqual(mock_getutil.call_count, 0)
@test_util.patch_get_utility()
def test_ports_blocked(self, mock_getutil):
with mock.patch("certbot.plugins.util.socket.socket.bind") as mock_bind:
mock_bind.side_effect = socket.error
self.assertTrue(self._call(80))
self.assertTrue(self._call(80, True))
with mock.patch("certbot.plugins.util.socket.socket") as mock_socket:
mock_socket.side_effect = socket.error
self.assertFalse(self._call(80))
self.assertEqual(mock_getutil.call_count, 2)
@test_util.skip_unless(test_util.requirement_available(PSUTIL_REQUIREMENT),
"optional dependency psutil is not available")
class AlreadyListeningTestPsutil(AlreadyListeningTest):
"""Tests for certbot.plugins.already_listening."""
@mock.patch("certbot.plugins.util.psutil.net_connections")
@mock.patch("certbot.plugins.util.psutil.Process")
@test_util.patch_get_utility()
def test_race_condition(self, mock_get_utility, mock_process, mock_net):
# This tests a race condition, or permission problem, or OS
# incompatibility in which, for some reason, no process name can be
# found to match the identified listening PID.
import psutil
from psutil._common import sconn
conns = [
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
raddr=(), status="LISTEN", pid=None),
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
raddr=(), status="LISTEN", pid=4416)]
mock_net.return_value = conns
mock_process.side_effect = psutil.NoSuchProcess("No such PID")
# We simulate being unable to find the process name of PID 4416,
# which results in returning False.
self.assertFalse(self._call(17))
self.assertEqual(mock_get_utility.generic_notification.call_count, 0)
mock_process.assert_called_once_with(4416)
@mock.patch("certbot.plugins.util.psutil.net_connections")
@mock.patch("certbot.plugins.util.psutil.Process")
@test_util.patch_get_utility()
def test_not_listening(self, mock_get_utility, mock_process, mock_net):
from psutil._common import sconn
conns = [
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
raddr=(), status="LISTEN", pid=None),
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
raddr=("::1", 111), status="CLOSE_WAIT", pid=None)]
mock_net.return_value = conns
mock_process.name.return_value = "inetd"
self.assertFalse(self._call(17))
self.assertEqual(mock_get_utility.generic_notification.call_count, 0)
self.assertEqual(mock_process.call_count, 0)
@mock.patch("certbot.plugins.util.psutil.net_connections")
@mock.patch("certbot.plugins.util.psutil.Process")
@test_util.patch_get_utility()
def test_listening_ipv4(self, mock_get_utility, mock_process, mock_net):
from psutil._common import sconn
conns = [
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
raddr=(), status="LISTEN", pid=None),
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
raddr=(), status="LISTEN", pid=4416)]
mock_net.return_value = conns
mock_process.name.return_value = "inetd"
result = self._call(17, True)
self.assertTrue(result)
self.assertEqual(mock_get_utility.call_count, 1)
mock_process.assert_called_once_with(4416)
@mock.patch("certbot.plugins.util.psutil.net_connections")
@mock.patch("certbot.plugins.util.psutil.Process")
@test_util.patch_get_utility()
def test_listening_ipv6(self, mock_get_utility, mock_process, mock_net):
from psutil._common import sconn
conns = [
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
raddr=(), status="LISTEN", pid=None),
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
sconn(fd=3, family=10, type=1, laddr=("::", 12345), raddr=(),
status="LISTEN", pid=4420),
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
raddr=(), status="LISTEN", pid=4416)]
mock_net.return_value = conns
mock_process.name.return_value = "inetd"
result = self._call(12345)
self.assertTrue(result)
self.assertEqual(mock_get_utility.call_count, 1)
mock_process.assert_called_once_with(4420)
@mock.patch("certbot.plugins.util.psutil.net_connections")
def test_access_denied_exception(self, mock_net):
import psutil
mock_net.side_effect = psutil.AccessDenied("")
self.assertFalse(self._call(12345))
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -35,7 +35,7 @@ INT_CONFIG_ITEMS = ["rsa_key_size", "tls_sni_01_port", "http01_port"]
BOOL_CONFIG_ITEMS = ["must_staple", "allow_subset_of_names"]
CONFIG_ITEMS = set(itertools.chain(
BOOL_CONFIG_ITEMS, INT_CONFIG_ITEMS, STR_CONFIG_ITEMS))
BOOL_CONFIG_ITEMS, INT_CONFIG_ITEMS, STR_CONFIG_ITEMS, ('pref_challs',)))
def _reconstitute(config, full_path):
@ -165,6 +165,7 @@ def restore_required_config_elements(config, renewalparams):
"""
required_items = itertools.chain(
(("pref_challs", _restore_pref_challs),),
six.moves.zip(BOOL_CONFIG_ITEMS, itertools.repeat(_restore_bool)),
six.moves.zip(INT_CONFIG_ITEMS, itertools.repeat(_restore_int)),
six.moves.zip(STR_CONFIG_ITEMS, itertools.repeat(_restore_str)))
@ -174,6 +175,28 @@ def restore_required_config_elements(config, renewalparams):
setattr(config.namespace, item_name, value)
def _restore_pref_challs(unused_name, value):
"""Restores preferred challenges from a renewal config file.
If value is a `str`, it should be a single challenge type.
:param str unused_name: option name
:param value: option value
:type value: `list` of `str` or `str`
:returns: converted option value to be stored in the runtime config
:rtype: `list` of `str`
:raises errors.Error: if value can't be converted to an bool
"""
# If pref_challs has only one element, configobj saves the value
# with a trailing comma so it's parsed as a list. If this comma is
# removed by the user, the value is parsed as a str.
value = [value] if isinstance(value, str) else value
return cli.parse_preferred_challenges(value)
def _restore_bool(name, value):
"""Restores an boolean key-value pair from a renewal config file.

View file

@ -716,7 +716,7 @@ class RenewableCert(object):
:returns: ``True`` if there is a complete version of this
lineage with a larger version number than the current
version, and ``False`` otherwis
version, and ``False`` otherwise
:rtype: bool
"""

View file

@ -176,7 +176,8 @@ class GetAuthorizationsTest(unittest.TestCase):
mock_poll.side_effect = self._validate_all
self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01)
self.handler.pref_challs.extend((challenges.HTTP01, challenges.DNS01,))
self.handler.pref_challs.extend((challenges.HTTP01.typ,
challenges.DNS01.typ,))
self.handler.get_authorizations(["0"])
@ -187,7 +188,7 @@ class GetAuthorizationsTest(unittest.TestCase):
def test_preferred_challenges_not_supported(self):
self.mock_net.request_domain_challenges.side_effect = functools.partial(
gen_dom_authzr, challs=acme_util.CHALLENGES)
self.handler.pref_challs.append(challenges.HTTP01)
self.handler.pref_challs.append(challenges.HTTP01.typ)
self.assertRaises(
errors.AuthorizationError, self.handler.get_authorizations, ["0"])

View file

@ -268,11 +268,11 @@ class LineageForCertnameTest(BaseCertManagerTest):
"""Tests for certbot.cert_manager.lineage_for_certname"""
@mock.patch('certbot.util.make_or_verify_dir')
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.renewal_file_for_certname')
@mock.patch('certbot.storage.RenewableCert')
def test_found_match(self, mock_renewable_cert, mock_renewal_conf_files,
def test_found_match(self, mock_renewable_cert, mock_renewal_conf_file,
mock_make_or_verify_dir):
mock_renewal_conf_files.return_value = ["somefile.conf"]
mock_renewal_conf_file.return_value = "somefile.conf"
mock_match = mock.Mock(lineagename="example.com")
mock_renewable_cert.return_value = mock_match
from certbot import cert_manager
@ -281,13 +281,10 @@ class LineageForCertnameTest(BaseCertManagerTest):
self.assertTrue(mock_make_or_verify_dir.called)
@mock.patch('certbot.util.make_or_verify_dir')
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.RenewableCert')
def test_no_match(self, mock_renewable_cert, mock_renewal_conf_files,
@mock.patch('certbot.storage.renewal_file_for_certname')
def test_no_match(self, mock_renewal_conf_file,
mock_make_or_verify_dir):
mock_renewal_conf_files.return_value = ["somefile.conf"]
mock_match = mock.Mock(lineagename="other.com")
mock_renewable_cert.return_value = mock_match
mock_renewal_conf_file.return_value = "other.com.conf"
from certbot import cert_manager
self.assertEqual(cert_manager.lineage_for_certname(self.cli_config, "example.com"),
None)
@ -298,11 +295,11 @@ class DomainsForCertnameTest(BaseCertManagerTest):
"""Tests for certbot.cert_manager.domains_for_certname"""
@mock.patch('certbot.util.make_or_verify_dir')
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.renewal_file_for_certname')
@mock.patch('certbot.storage.RenewableCert')
def test_found_match(self, mock_renewable_cert, mock_renewal_conf_files,
def test_found_match(self, mock_renewable_cert, mock_renewal_conf_file,
mock_make_or_verify_dir):
mock_renewal_conf_files.return_value = ["somefile.conf"]
mock_renewal_conf_file.return_value = "somefile.conf"
mock_match = mock.Mock(lineagename="example.com")
domains = ["example.com", "example.org"]
mock_match.names.return_value = domains
@ -313,15 +310,10 @@ class DomainsForCertnameTest(BaseCertManagerTest):
self.assertTrue(mock_make_or_verify_dir.called)
@mock.patch('certbot.util.make_or_verify_dir')
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.RenewableCert')
def test_no_match(self, mock_renewable_cert, mock_renewal_conf_files,
@mock.patch('certbot.storage.renewal_file_for_certname')
def test_no_match(self, mock_renewal_conf_file,
mock_make_or_verify_dir):
mock_renewal_conf_files.return_value = ["somefile.conf"]
mock_match = mock.Mock(lineagename="example.com")
domains = ["example.com", "example.org"]
mock_match.names.return_value = domains
mock_renewable_cert.return_value = mock_match
mock_renewal_conf_file.return_value = "somefile.conf"
from certbot import cert_manager
self.assertEqual(cert_manager.domains_for_certname(self.cli_config, "other.com"),
None)

View file

@ -8,6 +8,8 @@ import mock
import six
from six.moves import reload_module # pylint: disable=import-error
from acme import challenges
from certbot import cli
from certbot import constants
from certbot import errors
@ -50,7 +52,7 @@ class ParseTest(unittest.TestCase):
return cli.prepare_and_parse_args(PLUGINS, *args, **kwargs)
def _help_output(self, args):
"Run a command, and return the ouput string for scrutiny"
"Run a command, and return the output string for scrutiny"
output = six.StringIO()
with mock.patch('certbot.main.sys.stdout', new=output):
@ -58,6 +60,11 @@ class ParseTest(unittest.TestCase):
self.assertRaises(SystemExit, self.parse, args, output)
return output.getvalue()
def test_no_args(self):
namespace = self.parse([])
for d in ('config_dir', 'logs_dir', 'work_dir'):
self.assertEqual(getattr(namespace, d), cli.flag_default(d))
def test_install_abspath(self):
cert = 'cert'
key = 'key'
@ -178,12 +185,12 @@ class ParseTest(unittest.TestCase):
self.assertEqual(namespace.domains, ['example.com', 'another.net'])
def test_preferred_challenges(self):
from acme.challenges import HTTP01, TLSSNI01, DNS01
short_args = ['--preferred-challenges', 'http, tls-sni-01, dns']
namespace = self.parse(short_args)
self.assertEqual(namespace.pref_challs, [HTTP01, TLSSNI01, DNS01])
expected = [challenges.HTTP01.typ,
challenges.TLSSNI01.typ, challenges.DNS01.typ]
self.assertEqual(namespace.pref_challs, expected)
short_args = ['--preferred-challenges', 'jumping-over-the-moon']
self.assertRaises(argparse.ArgumentTypeError, self.parse, short_args)

View file

@ -44,20 +44,25 @@ class RegisterTest(unittest.TestCase):
def test_no_tos(self):
with mock.patch("certbot.client.acme_client.Client") as mock_client:
mock_client.register().terms_of_service = "http://tos"
with mock.patch("certbot.account.report_new_account"):
self.tos_cb.return_value = False
self.assertRaises(errors.Error, self._call)
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
with mock.patch("certbot.account.report_new_account"):
self.tos_cb.return_value = False
self.assertRaises(errors.Error, self._call)
self.assertFalse(mock_handle.called)
self.tos_cb.return_value = True
self._call()
self.tos_cb.return_value = True
self._call()
self.assertTrue(mock_handle.called)
self.tos_cb = None
self._call()
self.tos_cb = None
self._call()
self.assertEqual(mock_handle.call_count, 2)
def test_it(self):
with mock.patch("certbot.client.acme_client.Client"):
with mock.patch("certbot.account.report_new_account"):
self._call()
with mock.patch("certbot.eff.handle_subscription"):
self._call()
@mock.patch("certbot.account.report_new_account")
@mock.patch("certbot.client.display_ops.get_email")
@ -67,9 +72,11 @@ class RegisterTest(unittest.TestCase):
msg = "DNS problem: NXDOMAIN looking up MX for example.com"
mx_err = messages.Error.with_code('invalidContact', detail=msg)
with mock.patch("certbot.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)
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
self._call()
self.assertEqual(mock_get_email.call_count, 1)
self.assertTrue(mock_handle.called)
@mock.patch("certbot.account.report_new_account")
def test_email_invalid_noninteractive(self, _rep):
@ -77,8 +84,9 @@ class RegisterTest(unittest.TestCase):
msg = "DNS problem: NXDOMAIN looking up MX for example.com"
mx_err = messages.Error.with_code('invalidContact', detail=msg)
with mock.patch("certbot.client.acme_client.Client") as mock_client:
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
self.assertRaises(errors.Error, self._call)
with mock.patch("certbot.eff.handle_subscription"):
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
self.assertRaises(errors.Error, self._call)
def test_needs_email(self):
self.config.email = None
@ -86,21 +94,25 @@ class RegisterTest(unittest.TestCase):
@mock.patch("certbot.client.logger")
def test_without_email(self, mock_logger):
with mock.patch("certbot.client.acme_client.Client"):
with mock.patch("certbot.account.report_new_account"):
self.config.email = None
self.config.register_unsafely_without_email = True
self.config.dry_run = False
self._call()
mock_logger.warning.assert_called_once_with(mock.ANY)
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
with mock.patch("certbot.client.acme_client.Client"):
with mock.patch("certbot.account.report_new_account"):
self.config.email = None
self.config.register_unsafely_without_email = True
self.config.dry_run = False
self._call()
mock_logger.warning.assert_called_once_with(mock.ANY)
self.assertTrue(mock_handle.called)
def test_unsupported_error(self):
from acme import messages
msg = "Test"
mx_err = messages.Error(detail=msg, typ="malformed", title="title")
with mock.patch("certbot.client.acme_client.Client") as mock_client:
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
self.assertRaises(messages.Error, self._call)
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
self.assertRaises(messages.Error, self._call)
self.assertFalse(mock_handle.called)
class ClientTestCommon(unittest.TestCase):

135
certbot/tests/eff_test.py Normal file
View file

@ -0,0 +1,135 @@
"""Tests for certbot.eff."""
import unittest
import mock
from certbot import constants
from certbot.tests import util
class HandleSubscriptionTest(unittest.TestCase):
"""Tests for certbot.eff.handle_subscription."""
def setUp(self):
self.email = 'certbot@example.org'
self.config = mock.Mock(email=self.email, eff_email=None)
def _call(self):
from certbot.eff import handle_subscription
return handle_subscription(self.config)
@util.patch_get_utility()
@mock.patch('certbot.eff.subscribe')
def test_failure(self, mock_subscribe, mock_get_utility):
self.config.email = None
self.config.eff_email = True
self._call()
self.assertFalse(mock_subscribe.called)
self.assertFalse(mock_get_utility().yesno.called)
actual = mock_get_utility().add_message.call_args[0][0]
expected_part = "because you didn't provide an e-mail address"
self.assertTrue(expected_part in actual)
@mock.patch('certbot.eff.subscribe')
def test_no_subscribe_with_no_prompt(self, mock_subscribe):
self.config.eff_email = False
with util.patch_get_utility() as mock_get_utility:
self._call()
self.assertFalse(mock_subscribe.called)
self._assert_no_get_utility_calls(mock_get_utility)
@util.patch_get_utility()
@mock.patch('certbot.eff.subscribe')
def test_subscribe_with_no_prompt(self, mock_subscribe, mock_get_utility):
self.config.eff_email = True
self._call()
self._assert_subscribed(mock_subscribe)
self._assert_no_get_utility_calls(mock_get_utility)
def _assert_no_get_utility_calls(self, mock_get_utility):
self.assertFalse(mock_get_utility().yesno.called)
self.assertFalse(mock_get_utility().add_message.called)
@util.patch_get_utility()
@mock.patch('certbot.eff.subscribe')
def test_subscribe_with_prompt(self, mock_subscribe, mock_get_utility):
mock_get_utility().yesno.return_value = True
self._call()
self._assert_subscribed(mock_subscribe)
self.assertFalse(mock_get_utility().add_message.called)
self._assert_correct_yesno_call(mock_get_utility)
def _assert_subscribed(self, mock_subscribe):
self.assertTrue(mock_subscribe.called)
self.assertEqual(mock_subscribe.call_args[0][0], self.email)
@util.patch_get_utility()
@mock.patch('certbot.eff.subscribe')
def test_no_subscribe_with_prompt(self, mock_subscribe, mock_get_utility):
mock_get_utility().yesno.return_value = False
self._call()
self.assertFalse(mock_subscribe.called)
self.assertFalse(mock_get_utility().add_message.called)
self._assert_correct_yesno_call(mock_get_utility)
def _assert_correct_yesno_call(self, mock_get_utility):
self.assertTrue(mock_get_utility().yesno.called)
call_args, call_kwargs = mock_get_utility().yesno.call_args
actual = call_args[0]
expected_part = 'Electronic Frontier Foundation'
self.assertTrue(expected_part in actual)
self.assertFalse(call_kwargs.get('default', True))
class SubscribeTest(unittest.TestCase):
"""Tests for certbot.eff.subscribe."""
def setUp(self):
self.email = 'certbot@example.org'
self.json = {'status': True}
self.response = mock.Mock(ok=True)
self.response.json.return_value = self.json
@mock.patch('certbot.eff.requests.post')
def _call(self, mock_post):
mock_post.return_value = self.response
from certbot.eff import subscribe
subscribe(self.email)
self._check_post_call(mock_post)
def _check_post_call(self, mock_post):
self.assertEqual(mock_post.call_count, 1)
call_args, call_kwargs = mock_post.call_args
self.assertEqual(call_args[0], constants.EFF_SUBSCRIBE_URI)
data = call_kwargs.get('data')
self.assertFalse(data is None)
self.assertEqual(data.get('email'), self.email)
@util.patch_get_utility()
def test_bad_status(self, mock_get_utility):
self.json['status'] = False
self._call() # pylint: disable=no-value-for-parameter
actual = self._get_reported_message(mock_get_utility)
expected_part = 'because your e-mail address appears to be invalid.'
self.assertTrue(expected_part in actual)
@util.patch_get_utility()
def test_not_ok(self, mock_get_utility):
self.response.ok = False
self._call() # pylint: disable=no-value-for-parameter
actual = self._get_reported_message(mock_get_utility)
unexpected_part = 'because'
self.assertFalse(unexpected_part in actual)
def _get_reported_message(self, mock_get_utility):
self.assertTrue(mock_get_utility().add_message.called)
return mock_get_utility().add_message.call_args[0][0]
@util.patch_get_utility()
def test_subscribe(self, mock_get_utility):
self._call() # pylint: disable=no-value-for-parameter
self.assertFalse(mock_get_utility.called)
if __name__ == '__main__':
unittest.main() # pragma: no cover

View file

@ -70,7 +70,7 @@ class ErrorHandlerTest(unittest.TestCase):
send_signal(self.signals[0])
should_be_42 *= 10
# check exectuion stoped when the signal was sent
# check execution stoped when the signal was sent
self.assertEqual(42, should_be_42)
# assert signals were caught
self.assertEqual([self.signals[0]], signals_received)

View file

@ -9,7 +9,7 @@ from certbot import achallenges
from certbot.tests import acme_util
class FaiiledChallengesTest(unittest.TestCase):
class FailedChallengesTest(unittest.TestCase):
"""Tests for certbot.errors.FailedChallenges."""
def setUp(self):

View file

@ -1145,9 +1145,9 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
def test_update_registration_with_email(self, mock_utility, mock_email):
email = "user@example.com"
mock_email.return_value = email
with mock.patch('certbot.main.client') as mocked_client:
with mock.patch('certbot.main.account') as mocked_account:
with mock.patch('certbot.main._determine_account') as mocked_det:
with mock.patch('certbot.eff.handle_subscription') as mock_handle:
with mock.patch('certbot.main._determine_account') as mocked_det:
with mock.patch('certbot.main.account') as mocked_account:
with mock.patch('certbot.main.client') as mocked_client:
mocked_storage = mock.MagicMock()
mocked_account.AccountFileStorage.return_value = mocked_storage
@ -1168,6 +1168,7 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertTrue(mocked_storage.save_regr.called)
self.assertTrue(
email in mock_utility().add_message.call_args[0][0])
self.assertTrue(mock_handle.called)
class UnregisterTest(unittest.TestCase):

View file

@ -5,6 +5,8 @@ import unittest
import shutil
import tempfile
from acme import challenges
from certbot import configuration
from certbot import errors
from certbot import storage
@ -59,6 +61,29 @@ class RestoreRequiredConfigElementsTest(unittest.TestCase):
self.assertRaises(
errors.Error, self._call, self.config, renewalparams)
@mock.patch('certbot.renewal.cli.set_by_cli')
def test_pref_challs_list(self, mock_set_by_cli):
mock_set_by_cli.return_value = False
renewalparams = {'pref_challs': 'tls-sni, http-01, dns'.split(',')}
self._call(self.config, renewalparams)
expected = [challenges.TLSSNI01.typ,
challenges.HTTP01.typ, challenges.DNS01.typ]
self.assertEqual(self.config.namespace.pref_challs, expected)
@mock.patch('certbot.renewal.cli.set_by_cli')
def test_pref_challs_str(self, mock_set_by_cli):
mock_set_by_cli.return_value = False
renewalparams = {'pref_challs': 'dns'}
self._call(self.config, renewalparams)
expected = [challenges.DNS01.typ]
self.assertEqual(self.config.namespace.pref_challs, expected)
@mock.patch('certbot.renewal.cli.set_by_cli')
def test_pref_challs_failure(self, mock_set_by_cli):
mock_set_by_cli.return_value = False
renewalparams = {'pref_challs': 'finding-a-shrubbery'}
self.assertRaises(errors.Error, self._call, self.config, renewalparams)
@mock.patch('certbot.renewal.cli.set_by_cli')
def test_must_staple_success(self, mock_set_by_cli):
mock_set_by_cli.return_value = False

View file

@ -394,7 +394,7 @@ class TestFullCheckpointsReverter(unittest.TestCase):
self.assertTrue(mock_logger.info.call_count > 0)
def test_view_config_changes_bad_backups_dir(self):
# There shouldn't be any "in progess directories when this is called
# There shouldn't be any "in progress directories when this is called
# It must just be clean checkpoints
os.makedirs(os.path.join(self.config.backup_dir, "in_progress"))

View file

@ -13,9 +13,7 @@ from cryptography.hazmat.primitives import serialization
import mock
import OpenSSL
from acme import errors
from acme import jose
from acme import util
from certbot import constants
from certbot import interfaces
@ -86,20 +84,6 @@ def load_pyopenssl_private_key(*names):
return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))
def requirement_available(requirement):
"""Checks if requirement can be imported.
:rtype: bool
:returns: ``True`` iff requirement can be imported
"""
try:
util.activate(requirement)
except errors.DependencyError: # pragma: no cover
return False
return True # pragma: no cover
def skip_unless(condition, reason): # pragma: no cover
"""Skip tests unless a condition holds.

View file

@ -219,6 +219,25 @@ def safely_remove(path):
raise
def get_filtered_names(all_names):
"""Removes names that aren't considered valid by Let's Encrypt.
:param set all_names: all names found in the configuration
:returns: all found names that are considered valid by LE
:rtype: set
"""
filtered_names = set()
for name in all_names:
try:
filtered_names.add(enforce_le_validity(name))
except errors.ConfigurationError as error:
logger.debug('Not suggesting name "%s"', name)
logger.debug(error)
return filtered_names
def get_os_info(filepath="/etc/os-release"):
"""
Get OS name and version

View file

@ -206,7 +206,7 @@ or a family of enhancements, one per selectable ciphersuite configuration.
Feedback
========
We receive lots of feedback on the type of ciphersuites that Let's Encrypt supports and list some coallated feedback below. This section aims to track suggestions and references that people have offered or identified to improve the ciphersuites that Let's Encrypt enables when configuring TLS on servers.
We receive lots of feedback on the type of ciphersuites that Let's Encrypt supports and list some collated feedback below. This section aims to track suggestions and references that people have offered or identified to improve the ciphersuites that Let's Encrypt enables when configuring TLS on servers.
Because of the Chatham House Rule applicable to some of the discussions, people are *not* individually credited for their suggestions, but most suggestions here were made or found by other people, and I thank them for their contributions.

View file

@ -86,7 +86,7 @@ optional arguments:
statistics about success rates by OS and plugin. If
you wish to hide your server OS version from the Let's
Encrypt server, set this to "". (default:
CertbotACMEClient/0.10.1 (Ubuntu 16.04.1 LTS)
CertbotACMEClient/0.11.0 (Ubuntu 16.04.1 LTS)
Authenticator/XXX Installer/YYY)
automation:
@ -120,7 +120,6 @@ automation:
system. This option cannot be used with --csr.
(default: False)
--agree-tos Agree to the ACME Subscriber Agreement (default: Ask)
--account ACCOUNT_ID Account ID to use (default: None)
--duplicate Allow making a certificate lineage that duplicates an
existing one (both can be renewed in parallel)
(default: False)
@ -210,7 +209,7 @@ manage:
certificates List certificates managed by Certbot
delete Clean up all files related to a certificate
renew Renew all certificates (or one specifed with --cert-
renew Renew all certificates (or one specified with --cert-
name)
revoke Revoke a certificate specified with --cert-path
update_symlinks Recreate symlinks in your /etc/letsencrypt/live/
@ -280,6 +279,9 @@ delete:
revoke:
Options for revocation of certs
--reason {keycompromise,affiliationchanged,superseded,unspecified,cessationofoperation}
Specify reason for revoking certificate. (default: 0)
register:
Options for account registration & modification
@ -301,6 +303,14 @@ register:
-m EMAIL, --email EMAIL
Email used for registration and recovery contact.
(default: Ask)
--eff-email Share your e-mail address with EFF (default: None)
--no-eff-email Don't share your e-mail address with EFF (default:
None)
unregister:
Options for account deactivation.
--account ACCOUNT_ID Account ID to use (default: None)
install:
Options for modifying how a cert is deployed

View file

@ -336,7 +336,7 @@ the ``letsencrypt-auto-source`` and
Building letsencrypt-auto-source/letsencrypt-auto
-------------------------------------------------
Once changes to any of the aforementioned files have been made, the
``letesncrypt-auto-source/letsencrypt-auto`` script should be updated. In lieu of
``letsencrypt-auto-source/letsencrypt-auto`` script should be updated. In lieu of
manually updating this script, run the build script, which lives at
``letsencrypt-auto-source/build.py``:

View file

@ -30,7 +30,7 @@ control the domain(s) you are requesting a cert for, obtains a cert for the spec
domain(s), and places the cert in the ``/etc/letsencrypt`` directory on your
machine. The authenticator does not install the cert (it does not edit any of your server's configuration files to serve the
obtained certificate). If you specify multiple domains to authenticate, they will
all be listed in a single certificate. To obtain multiple seperate certificates
all be listed in a single certificate. To obtain multiple separate certificates
you will need to run Certbot multiple times.
Installers are Plugins used with the ``install`` command to install a cert.
@ -62,7 +62,7 @@ manual_ Y N | Helps you obtain a cert by giving you instructions to pe
Under the hood, plugins use one of several ACME protocol "Challenges_" to
prove you control a domain. The options are http-01_ (which uses port 80),
tls-sni-01_ (port 443) and dns-01_ (requring configuration of a DNS server on
tls-sni-01_ (port 443) and dns-01_ (requiring configuration of a DNS server on
port 53, though that's often not the same machine as your webserver). A few
plugins support more than one challenge type, in which case you can choose one
with ``--preferred-challenges``.
@ -433,7 +433,7 @@ variables to these scripts:
- ``CERTBOT_DOMAIN``: The domain being authenticated
- ``CERTBOT_VALIDATION``: The validation string
- ``CERTBOT_TOKEN``: Resource name part of the HTTP-01 challenege (HTTP-01 only)
- ``CERTBOT_TOKEN``: Resource name part of the HTTP-01 challenge (HTTP-01 only)
Additionally for cleanup:

View file

@ -23,7 +23,7 @@ if [ -z "$VENV_PATH" ]; then
VENV_PATH="$XDG_DATA_HOME/$VENV_NAME"
fi
VENV_BIN="$VENV_PATH/bin"
LE_AUTO_VERSION="0.10.1"
LE_AUTO_VERSION="0.11.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -38,8 +38,9 @@ Help for certbot itself cannot be provided until it is installed.
-n, --non-interactive, --noninteractive run without asking for user input
--no-self-upgrade do not download updates
--os-packages-only install OS dependencies and exit
-q, --quiet provide only update/error output
-v, --verbose provide more output
-q, --quiet provide only update/error output;
implies --non-interactive
All arguments are accepted and forwarded to the Certbot client when run."
@ -84,6 +85,11 @@ if [ $BASENAME = "letsencrypt-auto" ]; then
HELP=0
fi
# Set ASSUME_YES to 1 if QUIET (i.e. --quiet implies --non-interactive)
if [ "$QUIET" = 1 ]; then
ASSUME_YES=1
fi
# Support for busybox and others where there is no "command",
# but "which" instead
if command -v command > /dev/null 2>&1 ; then
@ -99,7 +105,7 @@ fi
# certbot itself needs root access for almost all modes of operation
# The "normal" case is that sudo is used for the steps that need root, but
# this script *can* be run as root (not recommended), or fall back to using
# `su`. Auto-detection can be overrided by explicitly setting the
# `su`. Auto-detection can be overridden by explicitly setting the
# environment variable LE_AUTO_SUDO to 'sudo', 'sudo_su' or '' as used below.
# Because the parameters in `su -c` has to be a string,
@ -207,7 +213,11 @@ BootstrapDebCommon() {
#
# - Debian 6.0.10 "squeeze" (x64)
$SUDO apt-get update || echo apt-get update hit problems but continuing anyway...
if [ "$QUIET" = 1 ]; then
QUIET_FLAG='-qq'
fi
$SUDO apt-get $QUIET_FLAG update || echo apt-get update hit problems but continuing anyway...
# virtualenv binary can be found in different packages depending on
# distro version (#346)
@ -215,74 +225,74 @@ BootstrapDebCommon() {
virtualenv=
# virtual env is known to apt and is installable
if apt-cache show virtualenv > /dev/null 2>&1 ; then
if ! LC_ALL=C apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
virtualenv="virtualenv"
fi
if ! LC_ALL=C apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
virtualenv="virtualenv"
fi
fi
if apt-cache show python-virtualenv > /dev/null 2>&1; then
virtualenv="$virtualenv python-virtualenv"
virtualenv="$virtualenv python-virtualenv"
fi
augeas_pkg="libaugeas0 augeas-lenses"
AUGVERSION=`LC_ALL=C apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2`
if [ "$ASSUME_YES" = 1 ]; then
YES_FLAG="-y"
YES_FLAG="-y"
fi
AddBackportRepo() {
# ARGS:
BACKPORT_NAME="$1"
BACKPORT_SOURCELINE="$2"
echo "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME."
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then
if [ "$ASSUME_YES" = 1 ]; then
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
sleep 1s
add_backports=1
else
read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response
case $response in
[yY][eE][sS]|[yY]|"")
add_backports=1;;
*)
add_backports=0;;
esac
fi
if [ "$add_backports" = 1 ]; then
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO apt-get update
fi
fi
fi
if [ "$add_backports" != 0 ]; then
$SUDO apt-get install $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
augeas_pkg=
# ARGS:
BACKPORT_NAME="$1"
BACKPORT_SOURCELINE="$2"
echo "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME."
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then
if [ "$ASSUME_YES" = 1 ]; then
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
sleep 1s
add_backports=1
else
read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response
case $response in
[yY][eE][sS]|[yY]|"")
add_backports=1;;
*)
add_backports=0;;
esac
fi
if [ "$add_backports" = 1 ]; then
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO apt-get $QUIET_FLAG update
fi
fi
fi
if [ "$add_backports" != 0 ]; then
$SUDO apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
augeas_pkg=
fi
}
if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then
if lsb_release -a | grep -q wheezy ; then
AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main"
elif lsb_release -a | grep -q precise ; then
# XXX add ARM case
AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
else
echo "No libaugeas0 version is available that's new enough to run the"
echo "Certbot apache plugin..."
fi
# XXX add a case for ubuntu PPAs
if lsb_release -a | grep -q wheezy ; then
AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main"
elif lsb_release -a | grep -q precise ; then
# XXX add ARM case
AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
else
echo "No libaugeas0 version is available that's new enough to run the"
echo "Certbot apache plugin..."
fi
# XXX add a case for ubuntu PPAs
fi
$SUDO apt-get install $YES_FLAG --no-install-recommends \
$SUDO apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends \
python \
python-dev \
$virtualenv \
@ -294,7 +304,6 @@ BootstrapDebCommon() {
ca-certificates \
if ! $EXISTS virtualenv > /dev/null ; then
echo Failed to install a working \"virtualenv\" command, exiting
exit 1
@ -323,6 +332,9 @@ BootstrapRpmCommon() {
if [ "$ASSUME_YES" = 1 ]; then
yes_flag="-y"
fi
if [ "$QUIET" = 1 ]; then
QUIET_FLAG='--quiet'
fi
if ! $SUDO $tool list *virtualenv >/dev/null 2>&1; then
echo "To use Certbot, packages from the EPEL repository need to be installed."
@ -331,14 +343,14 @@ BootstrapRpmCommon() {
exit 1
fi
if [ "$ASSUME_YES" = 1 ]; then
/bin/echo -n "Enabling the EPEL repository in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 seconds..."
sleep 1s
/bin/echo -n "Enabling the EPEL repository in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 seconds..."
sleep 1s
fi
if ! $SUDO $tool install $yes_flag epel-release; then
if ! $SUDO $tool install $yes_flag $QUIET_FLAG epel-release; then
echo "Could not enable EPEL. Aborting bootstrap!"
exit 1
fi
@ -380,9 +392,9 @@ BootstrapRpmCommon() {
"
fi
if ! $SUDO $tool install $yes_flag $pkgs; then
echo "Could not install OS dependencies. Aborting bootstrap!"
exit 1
if ! $SUDO $tool install $yes_flag $QUIET_FLAG $pkgs; then
echo "Could not install OS dependencies. Aborting bootstrap!"
exit 1
fi
}
@ -394,7 +406,11 @@ BootstrapSuseCommon() {
install_flags="-l"
fi
$SUDO zypper $zypper_flags in $install_flags \
if [ "$QUIET" = 1 ]; then
QUIET_FLAG='-qq'
fi
$SUDO zypper $QUIET_FLAG $zypper_flags in $install_flags \
python \
python-devel \
python-virtualenv \
@ -432,7 +448,11 @@ BootstrapArchCommon() {
fi
if [ "$missing" ]; then
$SUDO pacman -S --needed $missing $noconfirm
if [ "$QUIET" = 1]; then
$SUDO pacman -S --needed $missing $noconfirm > /dev/null
else
$SUDO pacman -S --needed $missing $noconfirm
fi
fi
}
@ -465,7 +485,11 @@ BootstrapGentooCommon() {
}
BootstrapFreeBsd() {
$SUDO pkg install -Ay \
if [ "$QUIET" = 1 ]; then
QUIET_FLAG="--quiet"
fi
$SUDO pkg install -Ay $QUIET_FLAG \
python \
py27-virtualenv \
augeas \
@ -505,15 +529,15 @@ BootstrapMac() {
fi
if ! hash pip 2>/dev/null; then
echo "pip not installed"
echo "Installing pip..."
curl --silent --show-error --retry 5 https://bootstrap.pypa.io/get-pip.py | python
echo "pip not installed"
echo "Installing pip..."
curl --silent --show-error --retry 5 https://bootstrap.pypa.io/get-pip.py | python
fi
if ! hash virtualenv 2>/dev/null; then
echo "virtualenv not installed."
echo "Installing with pip..."
pip install virtualenv
echo "virtualenv not installed."
echo "Installing with pip..."
pip install virtualenv
fi
}
@ -523,26 +547,29 @@ BootstrapSmartOS() {
}
BootstrapMageiaCommon() {
if ! $SUDO urpmi --force \
python \
libpython-devel \
python-virtualenv
if [ "$QUIET" = 1 ]; then
QUIET_FLAG='--quiet'
fi
if ! $SUDO urpmi --force $QUIET_FLAG \
python \
libpython-devel \
python-virtualenv
then
echo "Could not install Python dependencies. Aborting bootstrap!"
exit 1
fi
fi
if ! $SUDO urpmi --force \
git \
gcc \
python-augeas \
openssl \
libopenssl-devel \
libffi-devel \
rootcerts
if ! $SUDO urpmi --force $QUIET_FLAG \
git \
gcc \
python-augeas \
libopenssl-devel \
libffi-devel \
rootcerts
then
echo "Could not install additional dependencies. Aborting bootstrap!"
exit 1
echo "Could not install additional dependencies. Aborting bootstrap!"
exit 1
fi
}
@ -609,6 +636,11 @@ if [ "$1" = "--le-auto-phase2" ]; then
# --version output ran through grep due to python-cryptography DeprecationWarnings
# grep for both certbot and letsencrypt until certbot and shim packages have been released
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep "^certbot\|^letsencrypt" | cut -d " " -f 2)
if [ -z "$INSTALLED_VERSION" ]; then
echo "Error: couldn't get currently installed version for $VENV_BIN/letsencrypt: " 1>&2
"$VENV_BIN/letsencrypt" --version
exit 1
fi
else
INSTALLED_VERSION="none"
fi
@ -801,18 +833,18 @@ letsencrypt==0.7.0 \
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.10.1 \
--hash=sha256:1dd5124078bc44739065409f3be51765608a90994c83460578c2680c582c1026 \
--hash=sha256:f51c2fb0a31646364abeb7fdd8cfc7c8a4e63b0641b14ab3ce1b2e3a8921a211
certbot==0.10.1 \
--hash=sha256:9a56fc76f726beeed2f5a08d690088377cd430907f8a38c50e2aa9a258ee1253 \
--hash=sha256:e0d699adb3f8ca3e077a4db339de29ebb3f790fbc5f3f02e446e227ed40aa743
certbot-apache==0.10.1 \
--hash=sha256:1252fd7e435ba48484b0bd9b72535a9755b03d8f0440f164b9c1c560d96cadb8 \
--hash=sha256:134f46690da55262125defa58aa74472eb4a1555c9ed83edb3c8667df5a561b5
certbot-nginx==0.10.1 \
--hash=sha256:afd15ed9e4f3076056b63916f272b7287084d871cb8136477d16b08f64d514f0 \
--hash=sha256:da1b7ea4831ead3f9eb526ee11bf1bf197da0fea4defeeb7b1ce24c5d3f45b51
acme==0.11.0 \
--hash=sha256:9c084f9a62241a11231af63266f2f12ad696be590393a4ab4974276a47d63404 \
--hash=sha256:1513ae74ee8424c739a953a552890830315669d95d105469d1cff5b7db9ae888
certbot==0.11.0 \
--hash=sha256:cc94d890a8697b3bdbdc555bcf4ba93955f64324bc256b2ea710fd053fc03b8a \
--hash=sha256:0f91fee360f9ce5e0584d0954fa3123832435f77f465915389032a90ac0248b1
certbot-apache==0.11.0 \
--hash=sha256:9a01883ca7e1159cff2a6e36bf97b83793c899c62944335f6ac2f59f9b3e8b5d \
--hash=sha256:2b67871e7ae8bbfa7a2779fcd6444f28847fbb7a347ef4bfdb19fb55a0d75673
certbot-nginx==0.11.0 \
--hash=sha256:cfae45a42560e39889eebd287437556084c421a9e07a2deb3cbc0aeef92d2dab \
--hash=sha256:dbddffe47c8e8b2d1cf47fe393b434003270a45aec896f133492c855c77e6f08
UNLIKELY_EOF
# -------------------------------------------------------------------------
@ -1024,7 +1056,7 @@ UNLIKELY_EOF
fi
else
# Phase 1: Upgrade certbot-auto if neceesary, then self-invoke.
# Phase 1: Upgrade certbot-auto if necessary, then self-invoke.
#
# Each phase checks the version of only the thing it is responsible for
# upgrading. Phase 1 checks the version of the latest release of

View file

@ -8,26 +8,13 @@ other, special definitions.
"""
from os.path import abspath, dirname, join
import re
from sys import argv
from version import certbot_version, file_contents
DIR = dirname(abspath(__file__))
def certbot_version(build_script_dir):
"""Return the version number stamped in certbot/__init__.py."""
return re.search('''^__version__ = ['"](.+)['"].*''',
file_contents(join(dirname(build_script_dir),
'certbot',
'__init__.py')),
re.M).group(1)
def file_contents(path):
with open(path) as file:
return file.read()
def build(version=None, requirements=None):
"""Return the built contents of the letsencrypt-auto script.

View file

@ -1,11 +1,11 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQEcBAABAgAGBQJYeWiuAAoJEE0XyZXNl3Xy4DsIALpCrYVDK9g1nQtdNzBJuFq7
WlMEk3ofMrh+3sTEit1jr9+zJ2hV3POa3RtCRfRZsP0hsiNx7XbuR8t6yM6d4j3S
pAO0L5brrNoJKvKPx5v8AVGKm1EIDH/lWx/hH+HiE7OE3z/w0ppwoIy0/xIqMXt9
O7Q70qV5Rvzh0K3KwSS9pb82ybV/mQ35uugravSOTMo2hZ82Ifh2ZEkJIsS4si2j
Oc26ruLi6ru4vwHJU2DkHZzl0oncyQZFolMZJmB47vNOCDtC303cR/poeQS9LSmF
vq3Lr0FAunCBoCjuyIGMk2SfmIhtJMS1v5dxOpZppVexedBYkoqU0FoUp/10/rM=
=7Xh1
iQEcBAABAgAGBQJYkh+9AAoJEE0XyZXNl3XyDFcH/RvYdrkHaLNfc7HX6cRgZvkM
9XgP5j+Fsb44NP/U7FwVNHfgqe7OSSSHzNuXPt4/wZTRtXgQZjKU4fWlg8OTgNe8
MvTx7FL78GXOhMPC0DX3ZkYYOllApiAFHQuwOXroGb099PTr7msnatLunLk1yCUN
wx/i+z0PHkJp+VDDb71sNOIwDtSzRx8w8/dtnnlODkDQWbbijkMUfslmEZnd5bRH
7vd2miuDkSR2dMdYLi+Zx0tkSib06kR8ahakPEIcDuZ5CI3fRESkmRUjOxFZOt/z
KqMVLMeEq0P81anlK3QCslwibhC88BlbafEma/FNPBZdzHz4UcQALWDGtYDn2tg=
=+aXk
-----END PGP SIGNATURE-----

View file

@ -23,7 +23,7 @@ if [ -z "$VENV_PATH" ]; then
VENV_PATH="$XDG_DATA_HOME/$VENV_NAME"
fi
VENV_BIN="$VENV_PATH/bin"
LE_AUTO_VERSION="0.11.0.dev0"
LE_AUTO_VERSION="0.12.0.dev0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -105,7 +105,7 @@ fi
# certbot itself needs root access for almost all modes of operation
# The "normal" case is that sudo is used for the steps that need root, but
# this script *can* be run as root (not recommended), or fall back to using
# `su`. Auto-detection can be overrided by explicitly setting the
# `su`. Auto-detection can be overridden by explicitly setting the
# environment variable LE_AUTO_SUDO to 'sudo', 'sudo_su' or '' as used below.
# Because the parameters in `su -c` has to be a string,
@ -833,18 +833,18 @@ letsencrypt==0.7.0 \
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.10.1 \
--hash=sha256:1dd5124078bc44739065409f3be51765608a90994c83460578c2680c582c1026 \
--hash=sha256:f51c2fb0a31646364abeb7fdd8cfc7c8a4e63b0641b14ab3ce1b2e3a8921a211
certbot==0.10.1 \
--hash=sha256:9a56fc76f726beeed2f5a08d690088377cd430907f8a38c50e2aa9a258ee1253 \
--hash=sha256:e0d699adb3f8ca3e077a4db339de29ebb3f790fbc5f3f02e446e227ed40aa743
certbot-apache==0.10.1 \
--hash=sha256:1252fd7e435ba48484b0bd9b72535a9755b03d8f0440f164b9c1c560d96cadb8 \
--hash=sha256:134f46690da55262125defa58aa74472eb4a1555c9ed83edb3c8667df5a561b5
certbot-nginx==0.10.1 \
--hash=sha256:afd15ed9e4f3076056b63916f272b7287084d871cb8136477d16b08f64d514f0 \
--hash=sha256:da1b7ea4831ead3f9eb526ee11bf1bf197da0fea4defeeb7b1ce24c5d3f45b51
acme==0.11.0 \
--hash=sha256:9c084f9a62241a11231af63266f2f12ad696be590393a4ab4974276a47d63404 \
--hash=sha256:1513ae74ee8424c739a953a552890830315669d95d105469d1cff5b7db9ae888
certbot==0.11.0 \
--hash=sha256:cc94d890a8697b3bdbdc555bcf4ba93955f64324bc256b2ea710fd053fc03b8a \
--hash=sha256:0f91fee360f9ce5e0584d0954fa3123832435f77f465915389032a90ac0248b1
certbot-apache==0.11.0 \
--hash=sha256:9a01883ca7e1159cff2a6e36bf97b83793c899c62944335f6ac2f59f9b3e8b5d \
--hash=sha256:2b67871e7ae8bbfa7a2779fcd6444f28847fbb7a347ef4bfdb19fb55a0d75673
certbot-nginx==0.11.0 \
--hash=sha256:cfae45a42560e39889eebd287437556084c421a9e07a2deb3cbc0aeef92d2dab \
--hash=sha256:dbddffe47c8e8b2d1cf47fe393b434003270a45aec896f133492c855c77e6f08
UNLIKELY_EOF
# -------------------------------------------------------------------------
@ -1056,7 +1056,7 @@ UNLIKELY_EOF
fi
else
# Phase 1: Upgrade certbot-auto if neceesary, then self-invoke.
# Phase 1: Upgrade certbot-auto if necessary, then self-invoke.
#
# Each phase checks the version of only the thing it is responsible for
# upgrading. Phase 1 checks the version of the latest release of

View file

@ -105,7 +105,7 @@ fi
# certbot itself needs root access for almost all modes of operation
# The "normal" case is that sudo is used for the steps that need root, but
# this script *can* be run as root (not recommended), or fall back to using
# `su`. Auto-detection can be overrided by explicitly setting the
# `su`. Auto-detection can be overridden by explicitly setting the
# environment variable LE_AUTO_SUDO to 'sudo', 'sudo_su' or '' as used below.
# Because the parameters in `su -c` has to be a string,
@ -355,7 +355,7 @@ UNLIKELY_EOF
fi
else
# Phase 1: Upgrade certbot-auto if neceesary, then self-invoke.
# Phase 1: Upgrade certbot-auto if necessary, then self-invoke.
#
# Each phase checks the version of only the thing it is responsible for
# upgrading. Phase 1 checks the version of the latest release of

View file

@ -171,15 +171,15 @@ letsencrypt==0.7.0 \
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.10.1 \
--hash=sha256:1dd5124078bc44739065409f3be51765608a90994c83460578c2680c582c1026 \
--hash=sha256:f51c2fb0a31646364abeb7fdd8cfc7c8a4e63b0641b14ab3ce1b2e3a8921a211
certbot==0.10.1 \
--hash=sha256:9a56fc76f726beeed2f5a08d690088377cd430907f8a38c50e2aa9a258ee1253 \
--hash=sha256:e0d699adb3f8ca3e077a4db339de29ebb3f790fbc5f3f02e446e227ed40aa743
certbot-apache==0.10.1 \
--hash=sha256:1252fd7e435ba48484b0bd9b72535a9755b03d8f0440f164b9c1c560d96cadb8 \
--hash=sha256:134f46690da55262125defa58aa74472eb4a1555c9ed83edb3c8667df5a561b5
certbot-nginx==0.10.1 \
--hash=sha256:afd15ed9e4f3076056b63916f272b7287084d871cb8136477d16b08f64d514f0 \
--hash=sha256:da1b7ea4831ead3f9eb526ee11bf1bf197da0fea4defeeb7b1ce24c5d3f45b51
acme==0.11.0 \
--hash=sha256:9c084f9a62241a11231af63266f2f12ad696be590393a4ab4974276a47d63404 \
--hash=sha256:1513ae74ee8424c739a953a552890830315669d95d105469d1cff5b7db9ae888
certbot==0.11.0 \
--hash=sha256:cc94d890a8697b3bdbdc555bcf4ba93955f64324bc256b2ea710fd053fc03b8a \
--hash=sha256:0f91fee360f9ce5e0584d0954fa3123832435f77f465915389032a90ac0248b1
certbot-apache==0.11.0 \
--hash=sha256:9a01883ca7e1159cff2a6e36bf97b83793c899c62944335f6ac2f59f9b3e8b5d \
--hash=sha256:2b67871e7ae8bbfa7a2779fcd6444f28847fbb7a347ef4bfdb19fb55a0d75673
certbot-nginx==0.11.0 \
--hash=sha256:cfae45a42560e39889eebd287437556084c421a9e07a2deb3cbc0aeef92d2dab \
--hash=sha256:dbddffe47c8e8b2d1cf47fe393b434003270a45aec896f133492c855c77e6f08

View file

@ -0,0 +1,28 @@
#!/usr/bin/env python
"""Get the current Certbot version number.
Provides simple utilities for determining the Certbot version number and
building letsencrypt-auto.
"""
from __future__ import print_function
from os.path import abspath, dirname, join
import re
def certbot_version(build_script_dir):
"""Return the version number stamped in certbot/__init__.py."""
return re.search('''^__version__ = ['"](.+)['"].*''',
file_contents(join(dirname(build_script_dir),
'certbot',
'__init__.py')),
re.M).group(1)
def file_contents(path):
with open(path) as file:
return file.read()
if __name__ == '__main__':
print(certbot_version(dirname(abspath(__file__))))

View file

@ -31,6 +31,9 @@ changes = read_file(os.path.join(here, 'CHANGES.rst'))
version = meta['version']
# Please update tox.ini when modifying dependency version requirements
# This package relies on requests, however, it isn't specified here to avoid
# masking the more specific request requirements in acme. See
# https://github.com/pypa/pip/issues/988 for more info.
install_requires = [
'acme=={0}'.format(version),
# We technically need ConfigArgParse 0.10.0 for Python 2.6 support, but
@ -67,7 +70,6 @@ dev_extras = [
'astroid==1.3.5',
'coverage',
'nose',
'psutil>=2.2.1', # for tests, optional
'pylint==1.4.2', # upstream #248
'tox',
'twine',

View file

@ -47,23 +47,24 @@ common() {
export HOOK_TEST="/tmp/hook$$"
CheckHooks() {
COMMON="wtf2.auth\nwtf2.cleanup\nrenew\nrenew"
EXPECTED="/tmp/expected$$"
if [ $(head -n1 $HOOK_TEST) = "wtf.pre" ]; then
echo "wtf.pre" > "$EXPECTED"
echo "wtf2.pre" >> "$EXPECTED"
echo $COMMON >> "$EXPECTED"
echo "renew" >> "$EXPECTED"
echo "renew" >> "$EXPECTED"
echo "wtf.post" >> "$EXPECTED"
echo "wtf2.post" >> "$EXPECTED"
else
echo "wtf2.pre" > "$EXPECTED"
echo "wtf.pre" >> "$EXPECTED"
echo $COMMON >> "$EXPECTED"
echo "renew" >> "$EXPECTED"
echo "renew" >> "$EXPECTED"
echo "wtf2.post" >> "$EXPECTED"
echo "wtf.post" >> "$EXPECTED"
fi
if cmp --quiet "$EXPECTED" "$HOOK_TEST" ; then
if ! cmp --quiet "$EXPECTED" "$HOOK_TEST" ; then
echo Hooks did not run as expected\; got
cat "$HOOK_TEST"
echo Expected
@ -91,12 +92,12 @@ common --domains le2.wtf --preferred-challenges http-01 run \
kill $python_server_pid
common certonly -a manual -d le.wtf --rsa-key-size 4096 \
--manual-auth-hook 'echo wtf2.auth >> "$HOOK_TEST" && ./tests/manual-http-auth.sh' \
--manual-cleanup-hook 'echo wtf2.cleanup >> "$HOOK_TEST" && ./tests/manual-http-cleanup.sh' \
--manual-auth-hook ./tests/manual-http-auth.sh \
--manual-cleanup-hook ./tests/manual-http-cleanup.sh \
--pre-hook 'echo wtf2.pre >> "$HOOK_TEST"' \
--post-hook 'echo wtf2.post >> "$HOOK_TEST"'
common certonly -a manual -d dns.le.wtf --preferred-challenges dns-01 \
common certonly -a manual -d dns.le.wtf --preferred-challenges dns,tls-sni \
--manual-auth-hook ./tests/manual-dns-auth.sh
export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \
@ -113,31 +114,32 @@ common --domains le3.wtf install \
--key-path "${root}/csr/key.pem"
CheckCertCount() {
CERTCOUNT=`ls "${root}/conf/archive/le.wtf/cert"* | wc -l`
if [ "$CERTCOUNT" -ne "$1" ] ; then
echo Wrong cert count, not "$1" `ls "${root}/conf/archive/le.wtf/"*`
CERTCOUNT=`ls "${root}/conf/archive/$1/cert"* | wc -l`
if [ "$CERTCOUNT" -ne "$2" ] ; then
echo Wrong cert count, not "$2" `ls "${root}/conf/archive/$1/"*`
exit 1
fi
}
CheckCertCount 1
CheckCertCount "le.wtf" 1
# This won't renew (because it's not time yet)
common_no_force_renew renew
CheckCertCount 1
CheckCertCount "le.wtf" 1
# --renew-by-default is used, so renewal should occur
[ -f "$HOOK_TEST" ] && rm -f "$HOOK_TEST"
common renew
CheckCertCount 2
CheckHooks
# renew using HTTP manual auth hooks
common renew --cert-name le.wtf --authenticator manual
CheckCertCount "le.wtf" 2
# renew using DNS manual auth hooks
common renew --cert-name dns.le.wtf --authenticator manual
CheckCertCount "dns.le.wtf" 2
# This will renew because the expiry is less than 10 years from now
sed -i "4arenew_before_expiry = 4 years" "$root/conf/renewal/le.wtf.conf"
common_no_force_renew renew --rsa-key-size 2048
CheckCertCount 3
CheckCertCount "le.wtf" 3
# The 4096 bit setting should persist to the first renewal, but be overriden in the second
# The 4096 bit setting should persist to the first renewal, but be overridden in the second
size1=`wc -c ${root}/conf/archive/le.wtf/privkey1.pem | cut -d" " -f1`
size2=`wc -c ${root}/conf/archive/le.wtf/privkey2.pem | cut -d" " -f1`
@ -149,6 +151,12 @@ if [ "$size1" -lt 3000 ] || [ "$size2" -lt 3000 ] || [ "$size3" -gt 1800 ] ; the
exit 1
fi
# --renew-by-default is used, so renewal should occur
[ -f "$HOOK_TEST" ] && rm -f "$HOOK_TEST"
common renew
CheckCertCount "le.wtf" 4
CheckHooks
# ECDSA
openssl ecparam -genkey -name secp384r1 -out "${root}/privkey-p384.pem"
SAN="DNS:ecdsa.le.wtf" openssl req -new -sha256 \

View file

@ -29,7 +29,8 @@ unset PIP_INDEX_URL
export PIP_EXTRA_INDEX_URL="$SAVE"
git checkout -f "$BRANCH"
if ! ./letsencrypt-auto -v --debug --version | grep 0.9.0 ; then
EXPECTED_VERSION=$(grep -m1 LE_AUTO_VERSION letsencrypt-auto | cut -d\" -f2)
if ! ./letsencrypt-auto -v --debug --version --no-self-upgrade | grep $EXPECTED_VERSION ; then
echo upgrade appeared to fail
exit 1
fi

View file

@ -21,9 +21,9 @@ letsencrypt-auto certonly --no-self-upgrade -v --standalone --debug \
# 1. be in the right directory
cd tests/letstest/testdata/
# 2. refer to the config with the same level of relativitity that it itself
# 2. refer to the config with the same level of relativity that it itself
# contains :/
OUT=`letsencrypt-auto certificates --config-dir sample-config -v`
OUT=`letsencrypt-auto certificates --config-dir sample-config -v --no-self-upgrade`
TEST_CERTS=`echo "$OUT" | grep TEST_CERT | wc -l`
REVOKED=`echo "$OUT" | grep REVOKED | wc -l`

View file

@ -1,39 +0,0 @@
#!/bin/bash -x
# $PUBLIC_IP $PRIVATE_IP $PUBLIC_HOSTNAME $BOULDER_URL are dynamically set at execution
# with curl, instance metadata available from EC2 metadata service:
#public_host=$(curl -s http://169.254.169.254/2014-11-05/meta-data/public-hostname)
#public_ip=$(curl -s http://169.254.169.254/2014-11-05/meta-data/public-ipv4)
#private_ip=$(curl -s http://169.254.169.254/2014-11-05/meta-data/local-ipv4)
cd letsencrypt
export PATH="$PWD/letsencrypt-auto-source:$PATH"
letsencrypt-auto-source/letsencrypt-auto --os-packages-only --debug --version
tools/venv.sh
sudo venv/bin/certbot certonly --no-self-upgrade -v --standalone --debug \
--text --agree-dev-preview --agree-tos \
--renew-by-default --redirect \
--register-unsafely-without-email \
--domain $PUBLIC_HOSTNAME --server $BOULDER_URL
# we have to jump through some hoops to cope with relative paths in renewal
# conf files ...
# 1. be in the right directory
cd tests/letstest/testdata/
# 2. refer to the config with the same level of relativitity that it itself
# contains :/
OUT=`sudo ../../../venv/bin/certbot certificates -v --config-dir sample-config`
TEST_CERTS=`echo "$OUT" | grep TEST_CERT | wc -l`
REVOKED=`echo "$OUT" | grep REVOKED | wc -l`
if [ "$TEST_CERTS" != 2 ] ; then
echo "Did not find two test certs as expected ($TEST_CERTS)"
exit 1
fi
if [ "$REVOKED" != 1 ] ; then
echo "Did not find one revoked cert as expected ($REVOKED)"
exit 1
fi

View file

@ -0,0 +1,36 @@
#!/bin/sh -xe
cd letsencrypt
./certbot-auto --os-packages-only -n --debug
PLUGINS="certbot-apache certbot-nginx"
PYTHON=$(command -v python2.7 || command -v python27 || command -v python2 || command -v python)
TEMP_DIR=$(mktemp -d)
VERSION=$(letsencrypt-auto-source/version.py)
# setup venv
virtualenv --no-site-packages -p $PYTHON --setuptools venv
. ./venv/bin/activate
pip install -U pip
pip install -U setuptools
# build sdists
for pkg_dir in acme . $PLUGINS; do
cd $pkg_dir
python setup.py clean
rm -rf build dist
python setup.py sdist
mv dist/* $TEMP_DIR
cd -
done
# test sdists
cd $TEMP_DIR
for pkg in acme certbot $PLUGINS; do
tar -xvf "$pkg-$VERSION.tar.gz"
cd "$pkg-$VERSION"
python setup.py build
python setup.py test
python setup.py install
cd -
done

View file

@ -46,6 +46,7 @@ cd ${SCRIPT_PATH}/../
cp letsencrypt-auto-source/letsencrypt-auto ${temp_dir}/original-lea
python letsencrypt-auto-source/build.py
cp letsencrypt-auto-source/letsencrypt-auto ${temp_dir}/build-lea
cp ${temp_dir}/original-lea letsencrypt-auto-source/letsencrypt-auto
cd $temp_dir
@ -60,8 +61,8 @@ else
build.py."
fi
rm -rf $temp_dir
if $FLAG ; then
exit 1
fi
rm -rf temp_dir

View file

@ -2,7 +2,7 @@
VENV_NAME=${VENV_NAME:-venv}
# .egg-info directories tend to cause bizzaire problems (e.g. `pip -e
# .egg-info directories tend to cause bizarre problems (e.g. `pip -e
# .` might unexpectedly install letshelp-certbot only, in case
# `python letshelp-certbot/setup.py build` has been called
# earlier)
@ -12,13 +12,13 @@ rm -rf *.egg-info
# `/home/jakub/dev/letsencrypt/letsencrypt/venv/bin/python2` and
# `venv/bin/python2` are the same file
mv $VENV_NAME "$VENV_NAME.$(date +%s).bak" || true
virtualenv --no-site-packages $VENV_NAME $VENV_ARGS
virtualenv --no-site-packages --setuptools $VENV_NAME $VENV_ARGS
. ./$VENV_NAME/bin/activate
# Separately install setuptools and pip to make sure following
# invocations use latest
pip install -U setuptools
pip install -U pip
pip install -U setuptools
pip install "$@"
set +x

View file

@ -72,7 +72,7 @@ pip install -U virtualenv
root_without_le="$version.$$"
root="./releases/le.$root_without_le"
echo "Cloning into fresh copy at $root" # clean repo = no artificats
echo "Cloning into fresh copy at $root" # clean repo = no artifacts
git clone . $root
git rev-parse HEAD
cd $root

18
tox.ini
View file

@ -13,7 +13,7 @@ envlist = modification,py{26,33,34,35,36},cover,lint
# packages installed separately to ensure that downstream deps problems
# are detected, c.f. #1002
commands =
pip install -e acme[dns,dev]
pip install -e acme[dev]
nosetests -v acme --processes=-1
pip install -e .[dev]
nosetests -v certbot --processes=-1 --process-timeout=100
@ -37,35 +37,33 @@ deps =
py{26,27}-oldest: cffi<=1.7
py{26,27}-oldest: cryptography==0.8
py{26,27}-oldest: configargparse==0.10.0
py{26,27}-oldest: dnspython>=1.12
py{26,27}-oldest: psutil==2.1.0
py{26,27}-oldest: PyOpenSSL==0.13
py{26,27}-oldest: requests<=2.11.1
[testenv:py33]
commands =
pip install -e acme[dns,dev]
pip install -e acme[dev]
nosetests -v acme --processes=-1
pip install -e .[dev]
nosetests -v certbot --processes=-1 --process-timeout=100
[testenv:py34]
commands =
pip install -e acme[dns,dev]
pip install -e acme[dev]
nosetests -v acme --processes=-1
pip install -e .[dev]
nosetests -v certbot --processes=-1 --process-timeout=100
[testenv:py35]
commands =
pip install -e acme[dns,dev]
pip install -e acme[dev]
nosetests -v acme --processes=-1
pip install -e .[dev]
nosetests -v certbot --processes=-1 --process-timeout=100
[testenv:py36]
commands =
pip install -e acme[dns,dev]
pip install -e acme[dev]
nosetests -v acme --processes=-1
pip install -e .[dev]
nosetests -v certbot --processes=-1 --process-timeout=100
@ -73,12 +71,12 @@ commands =
[testenv:py27_install]
basepython = python2.7
commands =
pip install -e acme[dns,dev] -e .[dev] -e certbot-apache -e certbot-nginx -e letshelp-certbot
pip install -e acme[dev] -e .[dev] -e certbot-apache -e certbot-nginx -e letshelp-certbot
[testenv:cover]
basepython = python2.7
commands =
pip install -e acme[dns,dev] -e .[dev] -e certbot-apache -e certbot-nginx -e letshelp-certbot
pip install -e acme[dev] -e .[dev] -e certbot-apache -e certbot-nginx -e letshelp-certbot
./tox.cover.sh
[testenv:lint]
@ -88,7 +86,7 @@ basepython = python2.7
# duplicate code checking; if one of the commands fails, others will
# continue, but tox return code will reflect previous error
commands =
pip install -q -e acme[dns,dev] -e .[dev] -e certbot-apache -e certbot-nginx -e certbot-compatibility-test -e letshelp-certbot
pip install -q -e acme[dev] -e .[dev] -e certbot-apache -e certbot-nginx -e certbot-compatibility-test -e letshelp-certbot
pylint --reports=n --rcfile=.pylintrc acme/acme certbot certbot-apache/certbot_apache certbot-nginx/certbot_nginx certbot-compatibility-test/certbot_compatibility_test letshelp-certbot/letshelp_certbot
[testenv:apacheconftest]