Merge pull request #6651 from certbot/update-test-everything2

First, this PR merges master into the test-everything branch to resolve test failures created by #6597 and #6647.

Second, installing packages through Homebrew started failing. I fixed this by making use of the Homebrew options in .travis.yml which didn't exist when our current Homebrew scripts were written.

A summary of the changes there are:

I removed tests/travis-macos-setup.sh (which only existed in the test-everything branch).
I switched the packages installed by Homebrew to only install what's actually needed by the test.
I started being explicit about the Python version we wanted because Homebrew has flipped back and forth on what the python package refers to.
We stop calling brew link python. I think this is OK because brew link <package> just makes the package available in your PATH but we can see this is already the case with Python because the tests are using 2.7.15 which is available through Homebrew but not macOS.
This commit is contained in:
Brad Warren 2019-01-09 16:30:27 -08:00 committed by GitHub
commit 7277c8b0cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
112 changed files with 1888 additions and 771 deletions

View file

@ -12,10 +12,10 @@ before_install:
- cp .travis.yml /tmp/travis.yml
- git pull origin master --strategy=recursive --strategy-option=theirs --no-edit
- if ! git diff .travis.yml /tmp/travis.yml ; then echo "Please merge master into test-everything"; exit 1; fi
- '[ "$TRAVIS_OS_NAME" != osx ] || tests/travis-macos-setup.sh'
before_script:
- 'if [ $TRAVIS_OS_NAME = osx ] ; then ulimit -n 1024 ; fi'
- export TOX_TESTENV_PASSENV=TRAVIS
matrix:
include:
@ -75,8 +75,9 @@ matrix:
packages: # don't install nginx and apache
- libaugeas0
- python: "2.7"
env: TOXENV=apacheconftest
env: TOXENV=apacheconftest-with-pebble
sudo: required
services: docker
- python: "3.4"
env: TOXENV=py34 BOULDER_INTEGRATION=v1
sudo: required
@ -120,9 +121,19 @@ matrix:
- language: generic
env: TOXENV=py27
os: osx
addons:
homebrew:
packages:
- augeas
- python2
- language: generic
env: TOXENV=py3
os: osx
addons:
homebrew:
packages:
- augeas
- python3
# Only build pushes to the master branch, PRs, and branches beginning with

View file

@ -1,8 +1,8 @@
# Certbot change log
Certbot adheres to [Semantic Versioning](http://semver.org/).
Certbot adheres to [Semantic Versioning](https://semver.org/).
## 0.29.0 - master
## 0.31.0 - master
### Added
@ -14,14 +14,102 @@ Certbot adheres to [Semantic Versioning](http://semver.org/).
### Fixed
*
* Fixed accessing josepy contents through acme.jose when the full acme.jose
path is used.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* acme
More details about these changes can be found on our GitHub repo.
## 0.30.0 - 2019-01-02
### Added
* Added the `update_account` subcommand for account management commands.
### Changed
* Copied account management functionality from the `register` subcommand
to the `update_account` subcommand.
* Marked usage `register --update-registration` for deprecation and
removal in a future release.
### Fixed
* Older modules in the josepy library can now be accessed through acme.jose
like it could in previous versions of acme. This is only done to preserve
backwards compatibility and support for doing this with new modules in josepy
will not be added. Users of the acme library should switch to using josepy
directly if they haven't done so already.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* acme
More details about these changes can be found on our GitHub repo.
## 0.29.1 - 2018-12-05
### Added
*
### Changed
*
### Fixed
* The default work and log directories have been changed back to
/var/lib/letsencrypt and /var/log/letsencrypt respectively.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* certbot
More details about these changes can be found on our GitHub repo.
## 0.29.0 - 2018-12-05
### Added
* Noninteractive renewals with `certbot renew` (those not started from a
terminal) now randomly sleep 1-480 seconds before beginning work in
order to spread out load spikes on the server side.
* Added External Account Binding support in cli and acme library.
Command line arguments --eab-kid and --eab-hmac-key added.
### Changed
* Private key permissioning changes: Renewal preserves existing group mode
& gid of previous private key material. Private keys for new
lineages (i.e. new certs, not renewed) default to 0o600.
### Fixed
* Update code and dependencies to clean up Resource and Deprecation Warnings.
* Only depend on imgconverter extension for Sphinx >= 1.6
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* acme
* certbot
* certbot-apache
* certbot-dns-cloudflare
* certbot-dns-digitalocean
* certbot-dns-google
* certbot-nginx
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/62?closed=1

View file

@ -10,3 +10,18 @@ supported version: `draft-ietf-acme-01`_.
https://github.com/ietf-wg-acme/acme/tree/draft-ietf-acme-acme-01
"""
import sys
# This code exists to keep backwards compatibility with people using acme.jose
# before it became the standalone josepy package.
#
# It is based on
# https://github.com/requests/requests/blob/1278ecdf71a312dc2268f3bfc0aabfab3c006dcf/requests/packages.py
import josepy as jose
for mod in list(sys.modules):
# This traversal is apparently necessary such that the identities are
# preserved (acme.jose.* is josepy.*)
if mod == 'josepy' or mod.startswith('josepy.'):
sys.modules['acme.' + mod.replace('josepy', 'jose', 1)] = sys.modules[mod]

View file

@ -33,6 +33,7 @@ logger = logging.getLogger(__name__)
# https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning
if sys.version_info < (2, 7, 9): # pragma: no cover
try:
# pylint: disable=no-member
requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3() # type: ignore
except AttributeError:
import urllib3.contrib.pyopenssl # pylint: disable=import-error
@ -199,22 +200,6 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
return datetime.datetime.now() + datetime.timedelta(seconds=seconds)
def poll(self, authzr):
"""Poll Authorization Resource for status.
:param authzr: Authorization Resource
:type authzr: `.AuthorizationResource`
:returns: Updated Authorization Resource and HTTP response.
:rtype: (`.AuthorizationResource`, `requests.Response`)
"""
response = self.net.get(authzr.uri)
updated_authzr = self._authzr_from_response(
response, authzr.body.identifier, authzr.uri)
return updated_authzr, response
def _revoke(self, cert, rsn, url):
"""Revoke certificate.
@ -236,6 +221,7 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
raise errors.ClientError(
'Successful revocation must return HTTP OK status')
class Client(ClientBase):
"""ACME client for a v1 API.
@ -388,6 +374,22 @@ class Client(ClientBase):
body=jose.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_ASN1, response.content)))
def poll(self, authzr):
"""Poll Authorization Resource for status.
:param authzr: Authorization Resource
:type authzr: `.AuthorizationResource`
:returns: Updated Authorization Resource and HTTP response.
:rtype: (`.AuthorizationResource`, `requests.Response`)
"""
response = self.net.get(authzr.uri)
updated_authzr = self._authzr_from_response(
response, authzr.body.identifier, authzr.uri)
return updated_authzr, response
def poll_and_request_issuance(
self, csr, authzrs, mintime=5, max_attempts=10):
"""Poll and request issuance.
@ -651,13 +653,29 @@ class ClientV2(ClientBase):
body = messages.Order.from_json(response.json())
authorizations = []
for url in body.authorizations:
authorizations.append(self._authzr_from_response(self.net.get(url), uri=url))
authorizations.append(self._authzr_from_response(self._post_as_get(url), uri=url))
return messages.OrderResource(
body=body,
uri=response.headers.get('Location'),
authorizations=authorizations,
csr_pem=csr_pem)
def poll(self, authzr):
"""Poll Authorization Resource for status.
:param authzr: Authorization Resource
:type authzr: `.AuthorizationResource`
:returns: Updated Authorization Resource and HTTP response.
:rtype: (`.AuthorizationResource`, `requests.Response`)
"""
response = self._post_as_get(authzr.uri)
updated_authzr = self._authzr_from_response(
response, authzr.body.identifier, authzr.uri)
return updated_authzr, response
def poll_and_finalize(self, orderr, deadline=None):
"""Poll authorizations and finalize the order.
@ -681,7 +699,7 @@ class ClientV2(ClientBase):
responses = []
for url in orderr.body.authorizations:
while datetime.datetime.now() < deadline:
authzr = self._authzr_from_response(self.net.get(url), uri=url)
authzr = self._authzr_from_response(self._post_as_get(url), uri=url)
if authzr.body.status != messages.STATUS_PENDING:
responses.append(authzr)
break
@ -716,12 +734,12 @@ class ClientV2(ClientBase):
self._post(orderr.body.finalize, wrapped_csr)
while datetime.datetime.now() < deadline:
time.sleep(1)
response = self.net.get(orderr.uri)
response = self._post_as_get(orderr.uri)
body = messages.Order.from_json(response.json())
if body.error is not None:
raise errors.IssuanceError(body.error)
if body.certificate is not None:
certificate_response = self.net.get(body.certificate,
certificate_response = self._post_as_get(body.certificate,
content_type=DER_CONTENT_TYPE).text
return orderr.update(body=body, fullchain_pem=certificate_response)
raise errors.TimeoutError()
@ -739,6 +757,39 @@ class ClientV2(ClientBase):
"""
return self._revoke(cert, rsn, self.directory['revokeCert'])
def external_account_required(self):
"""Checks if ACME server requires External Account Binding authentication."""
if hasattr(self.directory, 'meta') and self.directory.meta.external_account_required:
return True
else:
return False
def _post_as_get(self, *args, **kwargs):
"""
Send GET request using the POST-as-GET protocol if needed.
The request will be first issued using POST-as-GET for ACME v2. If the ACME CA servers do
not support this yet and return an error, request will be retried using GET.
For ACME v1, only GET request will be tried, as POST-as-GET is not supported.
:param args:
:param kwargs:
:return:
"""
if self.acme_version >= 2:
# We add an empty payload for POST-as-GET requests
new_args = args[:1] + (None,) + args[1:]
try:
return self._post(*new_args, **kwargs) # pylint: disable=star-args
except messages.Error as error:
if error.code == 'malformed':
logger.debug('Error during a POST-as-GET request, '
'your ACME CA may not support it:\n%s', error)
logger.debug('Retrying request with GET.')
else: # pragma: no cover
raise
# If POST-as-GET is not supported yet, we use a GET instead.
return self.net.get(*args, **kwargs)
class BackwardsCompatibleClientV2(object):
"""ACME client wrapper that tends towards V2-style calls, but
@ -768,12 +819,7 @@ class BackwardsCompatibleClientV2(object):
self.client = ClientV2(directory, net=net)
def __getattr__(self, name):
if name in vars(self.client):
return getattr(self.client, name)
elif name in dir(ClientBase):
return getattr(self.client, name)
else:
raise AttributeError()
return getattr(self.client, name)
def new_account_and_tos(self, regr, check_tos_cb=None):
"""Combined register and agree_tos for V1, new_account for V2
@ -880,6 +926,15 @@ class BackwardsCompatibleClientV2(object):
else:
return 1
def external_account_required(self):
"""Checks if the server requires an external account for ACMEv2 servers.
Always return False for ACMEv1 servers, as it doesn't use External Account Binding."""
if self.acme_version == 1:
return False
else:
return self.client.external_account_required()
class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
"""Wrapper around requests that signs POSTs for authentication.
@ -943,7 +998,7 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
:rtype: `josepy.JWS`
"""
jobj = obj.json_dumps(indent=2).encode()
jobj = obj.json_dumps(indent=2).encode() if obj else b''
logger.debug('JWS payload:\n%s', jobj)
kwargs = {
"alg": self.alg,

View file

@ -1,4 +1,5 @@
"""Tests for acme.client."""
# pylint: disable=too-many-lines
import copy
import datetime
import json
@ -283,6 +284,37 @@ class BackwardsCompatibleClientV2Test(ClientTestBase):
client.update_registration(mock.sentinel.regr, None)
mock_client().update_registration.assert_called_once_with(mock.sentinel.regr, None)
# newNonce present means it will pick acme_version 2
def test_external_account_required_true(self):
self.response.json.return_value = messages.Directory({
'newNonce': 'http://letsencrypt-test.com/acme/new-nonce',
'meta': messages.Directory.Meta(external_account_required=True),
}).to_json()
client = self._init()
self.assertTrue(client.external_account_required())
# newNonce present means it will pick acme_version 2
def test_external_account_required_false(self):
self.response.json.return_value = messages.Directory({
'newNonce': 'http://letsencrypt-test.com/acme/new-nonce',
'meta': messages.Directory.Meta(external_account_required=False),
}).to_json()
client = self._init()
self.assertFalse(client.external_account_required())
def test_external_account_required_false_v1(self):
self.response.json.return_value = messages.Directory({
'meta': messages.Directory.Meta(external_account_required=False),
}).to_json()
client = self._init()
self.assertFalse(client.external_account_required())
class ClientTest(ClientTestBase):
"""Tests for acme.client.Client."""
@ -665,7 +697,7 @@ class ClientTest(ClientTestBase):
def test_revocation_payload(self):
obj = messages.Revocation(certificate=self.certr.body, reason=self.rsn)
self.assertTrue('reason' in obj.to_partial_json().keys())
self.assertEquals(self.rsn, obj.to_partial_json()['reason'])
self.assertEqual(self.rsn, obj.to_partial_json()['reason'])
def test_revoke_bad_status_raises_error(self):
self.response.status_code = http_client.METHOD_NOT_ALLOWED
@ -675,6 +707,7 @@ class ClientTest(ClientTestBase):
self.certr,
self.rsn)
class ClientV2Test(ClientTestBase):
"""Tests for acme.client.ClientV2."""
@ -730,9 +763,10 @@ class ClientV2Test(ClientTestBase):
authz_response2 = self.response
authz_response2.json.return_value = self.authz2.to_json()
authz_response2.headers['Location'] = self.authzr2.uri
self.net.get.side_effect = (authz_response, authz_response2)
self.assertEqual(self.client.new_order(CSR_SAN_PEM), self.orderr)
with mock.patch('acme.client.ClientV2._post_as_get') as mock_post_as_get:
mock_post_as_get.side_effect = (authz_response, authz_response2)
self.assertEqual(self.client.new_order(CSR_SAN_PEM), self.orderr)
@mock.patch('acme.client.datetime')
def test_poll_and_finalize(self, mock_datetime):
@ -821,6 +855,47 @@ class ClientV2Test(ClientTestBase):
self.response.json.return_value = self.regr.body.update(
contact=()).to_json()
def test_external_account_required_true(self):
self.client.directory = messages.Directory({
'meta': messages.Directory.Meta(external_account_required=True)
})
self.assertTrue(self.client.external_account_required())
def test_external_account_required_false(self):
self.client.directory = messages.Directory({
'meta': messages.Directory.Meta(external_account_required=False)
})
self.assertFalse(self.client.external_account_required())
def test_external_account_required_default(self):
self.assertFalse(self.client.external_account_required())
def test_post_as_get(self):
with mock.patch('acme.client.ClientV2._authzr_from_response') as mock_client:
mock_client.return_value = self.authzr2
self.client.poll(self.authzr2) # pylint: disable=protected-access
self.client.net.post.assert_called_once_with(
self.authzr2.uri, None, acme_version=2,
new_nonce_url='https://www.letsencrypt-demo.org/acme/new-nonce')
self.client.net.get.assert_not_called()
class FakeError(messages.Error): # pylint: disable=too-many-ancestors
"""Fake error to reproduce a malformed request ACME error"""
def __init__(self): # pylint: disable=super-init-not-called
pass
@property
def code(self):
return 'malformed'
self.client.net.post.side_effect = FakeError()
self.client.poll(self.authzr2) # pylint: disable=protected-access
self.client.net.get.assert_called_once_with(self.authzr2.uri)
class MockJSONDeSerializable(jose.JSONDeSerializable):
# pylint: disable=missing-docstring
@ -876,7 +951,6 @@ class ClientNetworkTest(unittest.TestCase):
self.assertEqual(jws.signature.combined.kid, u'acct-uri')
self.assertEqual(jws.signature.combined.url, u'url')
def test_check_response_not_ok_jobj_no_error(self):
self.response.ok = False
self.response.json.return_value = {}
@ -1039,8 +1113,8 @@ class ClientNetworkTest(unittest.TestCase):
# Requests Library Exceptions
except requests.exceptions.ConnectionError as z: #pragma: no cover
self.assertTrue("('Connection aborted.', error(111, 'Connection refused'))"
== str(z) or "[WinError 10061]" in str(z))
self.assertTrue("'Connection aborted.'" in str(z) or "[WinError 10061]" in str(z))
class ClientNetworkWithMockedResponseTest(unittest.TestCase):
"""Tests for acme.client.ClientNetwork which mock out response."""

View file

@ -209,8 +209,8 @@ class MakeCSRTest(unittest.TestCase):
# have a get_extensions() method, so we skip this test if the method
# isn't available.
if hasattr(csr, 'get_extensions'):
self.assertEquals(len(csr.get_extensions()), 1)
self.assertEquals(csr.get_extensions()[0].get_data(),
self.assertEqual(len(csr.get_extensions()), 1)
self.assertEqual(csr.get_extensions()[0].get_data(),
OpenSSL.crypto.X509Extension(
b'subjectAltName',
critical=False,
@ -227,7 +227,7 @@ class MakeCSRTest(unittest.TestCase):
# have a get_extensions() method, so we skip this test if the method
# isn't available.
if hasattr(csr, 'get_extensions'):
self.assertEquals(len(csr.get_extensions()), 2)
self.assertEqual(len(csr.get_extensions()), 2)
# NOTE: Ideally we would filter by the TLS Feature OID, but
# OpenSSL.crypto.X509Extension doesn't give us the extension's raw OID,
# and the shortname field is just "UNDEF"

53
acme/acme/jose_test.py Normal file
View file

@ -0,0 +1,53 @@
"""Tests for acme.jose shim."""
import importlib
import unittest
class JoseTest(unittest.TestCase):
"""Tests for acme.jose shim."""
def _test_it(self, submodule, attribute):
if submodule:
acme_jose_path = 'acme.jose.' + submodule
josepy_path = 'josepy.' + submodule
else:
acme_jose_path = 'acme.jose'
josepy_path = 'josepy'
acme_jose_mod = importlib.import_module(acme_jose_path)
josepy_mod = importlib.import_module(josepy_path)
self.assertIs(acme_jose_mod, josepy_mod)
self.assertIs(getattr(acme_jose_mod, attribute), getattr(josepy_mod, attribute))
# We use the imports below with eval, but pylint doesn't
# understand that.
# pylint: disable=eval-used,unused-variable
import acme
import josepy
acme_jose_mod = eval(acme_jose_path)
josepy_mod = eval(josepy_path)
self.assertIs(acme_jose_mod, josepy_mod)
self.assertIs(getattr(acme_jose_mod, attribute), getattr(josepy_mod, attribute))
def test_top_level(self):
self._test_it('', 'RS512')
def test_submodules(self):
# This test ensures that the modules in josepy that were
# available at the time it was moved into its own package are
# available under acme.jose. Backwards compatibility with new
# modules or testing code is not maintained.
mods_and_attrs = [('b64', 'b64decode',),
('errors', 'Error',),
('interfaces', 'JSONDeSerializable',),
('json_util', 'Field',),
('jwa', 'HS256',),
('jwk', 'JWK',),
('jws', 'JWS',),
('util', 'ImmutableMap',),]
for mod, attr in mods_and_attrs:
self._test_it(mod, attr)
if __name__ == '__main__':
unittest.main() # pragma: no cover

View file

@ -1,6 +1,7 @@
"""ACME protocol messages."""
import collections
import six
import json
import josepy as jose
@ -8,6 +9,7 @@ from acme import challenges
from acme import errors
from acme import fields
from acme import util
from acme import jws
OLD_ERROR_PREFIX = "urn:acme:error:"
ERROR_PREFIX = "urn:ietf:params:acme:error:"
@ -27,6 +29,7 @@ ERROR_CODES = {
'tls': 'The server experienced a TLS error during domain verification',
'unauthorized': 'The client lacks sufficient authorization',
'unknownHost': 'The server could not resolve a domain name',
'externalAccountRequired': 'The server requires external account binding',
}
ERROR_TYPE_DESCRIPTIONS = dict(
@ -176,6 +179,7 @@ class Directory(jose.JSONDeSerializable):
_terms_of_service_v2 = jose.Field('termsOfService', omitempty=True)
website = jose.Field('website', omitempty=True)
caa_identities = jose.Field('caaIdentities', omitempty=True)
external_account_required = jose.Field('externalAccountRequired', omitempty=True)
def __init__(self, **kwargs):
kwargs = dict((self._internal_name(k), v) for k, v in kwargs.items())
@ -258,6 +262,24 @@ class ResourceBody(jose.JSONObjectWithFields):
"""ACME Resource Body."""
class ExternalAccountBinding(object):
"""ACME External Account Binding"""
@classmethod
def from_data(cls, account_public_key, kid, hmac_key, directory):
"""Create External Account Binding Resource from contact details, kid and hmac."""
key_json = json.dumps(account_public_key.to_partial_json()).encode()
decoded_hmac_key = jose.b64.b64decode(hmac_key)
url = directory["newAccount"]
eab = jws.JWS.sign(key_json, jose.jwk.JWKOct(key=decoded_hmac_key),
jose.jwa.HS256, None,
url, kid)
return eab.to_partial_json()
class Registration(ResourceBody):
"""Registration Resource Body.
@ -275,12 +297,13 @@ class Registration(ResourceBody):
status = jose.Field('status', omitempty=True)
terms_of_service_agreed = jose.Field('termsOfServiceAgreed', omitempty=True)
only_return_existing = jose.Field('onlyReturnExisting', omitempty=True)
external_account_binding = jose.Field('externalAccountBinding', omitempty=True)
phone_prefix = 'tel:'
email_prefix = 'mailto:'
@classmethod
def from_data(cls, phone=None, email=None, **kwargs):
def from_data(cls, phone=None, email=None, external_account_binding=None, **kwargs):
"""Create registration resource from contact details."""
details = list(kwargs.pop('contact', ()))
if phone is not None:
@ -288,6 +311,10 @@ class Registration(ResourceBody):
if email is not None:
details.extend([cls.email_prefix + mail for mail in email.split(',')])
kwargs['contact'] = tuple(details)
if external_account_binding:
kwargs['external_account_binding'] = external_account_binding
return cls(**kwargs)
def _filter_contact(self, prefix):

View file

@ -174,6 +174,24 @@ class DirectoryTest(unittest.TestCase):
self.assertTrue(result)
class ExternalAccountBindingTest(unittest.TestCase):
def setUp(self):
from acme.messages import Directory
self.key = jose.jwk.JWKRSA(key=KEY.public_key())
self.kid = "kid-for-testing"
self.hmac_key = "hmac-key-for-testing"
self.dir = Directory({
'newAccount': 'http://url/acme/new-account',
})
def test_from_data(self):
from acme.messages import ExternalAccountBinding
eab = ExternalAccountBinding.from_data(self.key, self.kid, self.hmac_key, self.dir)
self.assertEqual(len(eab), 3)
self.assertEqual(sorted(eab.keys()), sorted(['protected', 'payload', 'signature']))
class RegistrationTest(unittest.TestCase):
"""Tests for acme.messages.Registration."""
@ -205,6 +223,22 @@ class RegistrationTest(unittest.TestCase):
'mailto:admin@foo.com',
))
def test_new_registration_from_data_with_eab(self):
from acme.messages import NewRegistration, ExternalAccountBinding, Directory
key = jose.jwk.JWKRSA(key=KEY.public_key())
kid = "kid-for-testing"
hmac_key = "hmac-key-for-testing"
directory = Directory({
'newAccount': 'http://url/acme/new-account',
})
eab = ExternalAccountBinding.from_data(key, kid, hmac_key, directory)
reg = NewRegistration.from_data(email='admin@foo.com', external_account_binding=eab)
self.assertEqual(reg.contact, (
'mailto:admin@foo.com',
))
self.assertEqual(sorted(reg.external_account_binding.keys()),
sorted(['protected', 'payload', 'signature']))
def test_phones(self):
self.assertEqual(('1234',), self.reg.phones)

View file

@ -3,21 +3,21 @@ from setuptools import find_packages
from setuptools.command.test import test as TestCommand
import sys
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
# load_pem_private/public_key (>=0.6)
# rsa_recover_prime_factors (>=0.8)
'cryptography>=0.8',
'cryptography>=1.2.3',
# formerly known as acme.jose:
'josepy>=1.0.0',
# Connection.set_tlsext_host_name (>=0.13)
'mock',
'PyOpenSSL>=0.13',
'PyOpenSSL>=0.13.1',
'pyrfc3339',
'pytz',
'requests[security]>=2.4.1', # security extras added in 2.4.1
'requests[security]>=2.6.0', # security extras added in 2.4.1
'requests-toolbelt>=0.3.0',
'setuptools',
'six>=1.9.0', # needed for python_2_unicode_compatible

View file

@ -1,17 +1,9 @@
image: Visual Studio 2015
environment:
matrix:
- FYI: Python 3.4 on Windows Server 2012 R2
TOXENV: py34
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
- FYI: Python 3.4 on Windows Server 2016
TOXENV: py34
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
- FYI: Python 3.5 on Windows Server 2016
TOXENV: py35
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
- FYI: Python 3.7 on Windows Server 2016 + code coverage
TOXENV: py37-cover
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
- TOXENV: py35
- TOXENV: py37-cover
branches:
only:
@ -23,7 +15,6 @@ install:
# Use Python 3.7 by default
- "SET PATH=C:\\Python37;C:\\Python37\\Scripts;%PATH%"
# Check env
- "echo %APPVEYOR_BUILD_WORKER_IMAGE%"
- "python --version"
# Upgrade pip to avoid warnings
- "python -m pip install --upgrade pip"
@ -33,6 +24,7 @@ install:
build: off
test_script:
- set TOX_TESTENV_PASSENV=APPVEYOR
# Test env is set by TOXENV env variable
- tox

View file

@ -91,7 +91,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
description = "Apache Web Server plugin - Beta"
description = "Apache Web Server plugin"
OS_DEFAULTS = dict(
server_root="/etc/apache2",

View file

@ -3,6 +3,11 @@
# A hackish script to see if the client is behaving as expected
# with each of the "passing" conf files.
if [ -z "$SERVER" ]; then
echo "Please set SERVER to the ACME server's directory URL."
exit 1
fi
export EA=/etc/apache2/
TESTDIR="`dirname $0`"
cd $TESTDIR/passing
@ -56,13 +61,16 @@ if [ "$1" = --debian-modules ] ; then
done
fi
CERTBOT_CMD="sudo $(command -v certbot) --server $SERVER -vvvv"
CERTBOT_CMD="$CERTBOT_CMD --debug --apache --register-unsafely-without-email"
CERTBOT_CMD="$CERTBOT_CMD --agree-tos certonly -t --no-verify-ssl"
FAILS=0
trap CleanupExit INT
for f in *.conf ; do
echo -n testing "$f"...
Setup
RESULT=`echo c | sudo $(command -v certbot) -vvvv --debug --staging --apache --register-unsafely-without-email --agree-tos certonly -t 2>&1`
RESULT=`echo c | $CERTBOT_CMD 2>&1`
if echo $RESULT | grep -Eq \("Which names would you like"\|"mod_macro is not yet"\) ; then
echo passed
else

View file

@ -64,12 +64,12 @@ class AutoHSTSTest(util.ApacheTest):
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
# Verify initial value
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path),
initial_val)
# Increase
self.config.update_autohsts(mock.MagicMock())
# Verify increased value
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path),
inc_val)
self.assertTrue(mock_prepare.called)
@ -80,7 +80,7 @@ class AutoHSTSTest(util.ApacheTest):
initial_val = maxage.format(constants.AUTOHSTS_STEPS[0])
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
# Verify initial value
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path),
initial_val)
self.config.update_autohsts(mock.MagicMock())
@ -112,19 +112,19 @@ class AutoHSTSTest(util.ApacheTest):
for i in range(len(constants.AUTOHSTS_STEPS)-1):
# Ensure that value is not made permanent prematurely
self.config.deploy_autohsts(mock_lineage)
self.assertNotEquals(self.get_autohsts_value(self.vh_truth[7].path),
self.assertNotEqual(self.get_autohsts_value(self.vh_truth[7].path),
max_val)
self.config.update_autohsts(mock.MagicMock())
# Value should match pre-permanent increment step
cur_val = maxage.format(constants.AUTOHSTS_STEPS[i+1])
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path),
cur_val)
# Ensure that the value is raised to max
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path),
maxage.format(constants.AUTOHSTS_STEPS[-1]))
# Make permanent
self.config.deploy_autohsts(mock_lineage)
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path),
max_val)
def test_autohsts_update_noop(self):
@ -156,7 +156,7 @@ class AutoHSTSTest(util.ApacheTest):
mock_id.return_value = "1234567"
self.config.enable_autohsts(mock.MagicMock(),
["ocspvhost.com", "ocspvhost.com"])
self.assertEquals(mock_id.call_count, 1)
self.assertEqual(mock_id.call_count, 1)
def test_autohsts_remove_orphaned(self):
# pylint: disable=protected-access

View file

@ -81,9 +81,9 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
mock_osi.return_value = ("centos", "7")
self.config.parser.update_runtime_variables()
self.assertEquals(mock_get.call_count, 3)
self.assertEquals(len(self.config.parser.modules), 4)
self.assertEquals(len(self.config.parser.variables), 2)
self.assertEqual(mock_get.call_count, 3)
self.assertEqual(len(self.config.parser.modules), 4)
self.assertEqual(len(self.config.parser.variables), 2)
self.assertTrue("TEST2" in self.config.parser.variables.keys())
self.assertTrue("mod_another.c" in self.config.parser.modules)
@ -127,7 +127,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
def test_alt_restart_works(self, mock_run_script):
mock_run_script.side_effect = [None, errors.SubprocessError, None]
self.config.restart()
self.assertEquals(mock_run_script.call_count, 3)
self.assertEqual(mock_run_script.call_count, 3)
@mock.patch("certbot_apache.configurator.util.run_script")
def test_alt_restart_errors(self, mock_run_script):

View file

@ -1402,11 +1402,11 @@ class MultipleVhostsTest(util.ApacheTest):
vhs = self.config._choose_vhosts_wildcard("*.certbot.demo",
create_ssl=True)
# Check that the dialog was called with one vh: certbot.demo
self.assertEquals(mock_select_vhs.call_args[0][0][0], self.vh_truth[3])
self.assertEquals(len(mock_select_vhs.call_args_list), 1)
self.assertEqual(mock_select_vhs.call_args[0][0][0], self.vh_truth[3])
self.assertEqual(len(mock_select_vhs.call_args_list), 1)
# And the actual returned values
self.assertEquals(len(vhs), 1)
self.assertEqual(len(vhs), 1)
self.assertTrue(vhs[0].name == "certbot.demo")
self.assertTrue(vhs[0].ssl)
@ -1421,7 +1421,7 @@ class MultipleVhostsTest(util.ApacheTest):
vhs = self.config._choose_vhosts_wildcard("*.certbot.demo",
create_ssl=False)
self.assertFalse(mock_makessl.called)
self.assertEquals(vhs[0], self.vh_truth[1])
self.assertEqual(vhs[0], self.vh_truth[1])
@mock.patch("certbot_apache.configurator.ApacheConfigurator._vhosts_for_wildcard")
@mock.patch("certbot_apache.configurator.ApacheConfigurator.make_vhost_ssl")
@ -1434,15 +1434,15 @@ class MultipleVhostsTest(util.ApacheTest):
mock_select_vhs.return_value = [self.vh_truth[7]]
vhs = self.config._choose_vhosts_wildcard("whatever",
create_ssl=True)
self.assertEquals(mock_select_vhs.call_args[0][0][0], self.vh_truth[7])
self.assertEquals(len(mock_select_vhs.call_args_list), 1)
self.assertEqual(mock_select_vhs.call_args[0][0][0], self.vh_truth[7])
self.assertEqual(len(mock_select_vhs.call_args_list), 1)
# Ensure that make_vhost_ssl was not called, vhost.ssl == true
self.assertFalse(mock_makessl.called)
# And the actual returned values
self.assertEquals(len(vhs), 1)
self.assertEqual(len(vhs), 1)
self.assertTrue(vhs[0].ssl)
self.assertEquals(vhs[0], self.vh_truth[7])
self.assertEqual(vhs[0], self.vh_truth[7])
def test_deploy_cert_wildcard(self):
@ -1455,7 +1455,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.deploy_cert("*.wildcard.example.org", "/tmp/path",
"/tmp/path", "/tmp/path", "/tmp/path")
self.assertTrue(mock_dep.called)
self.assertEquals(len(mock_dep.call_args_list), 1)
self.assertEqual(len(mock_dep.call_args_list), 1)
self.assertEqual(self.vh_truth[7], mock_dep.call_args_list[0][0][0])
@mock.patch("certbot_apache.display_ops.select_vhost_multiple")
@ -1651,7 +1651,8 @@ class MultiVhostsTest(util.ApacheTest):
self.assertTrue(self.config.parser.find_dir(
"RewriteEngine", "on", ssl_vhost.path, False))
conf_text = open(ssl_vhost.filep).read()
with open(ssl_vhost.filep) as the_file:
conf_text = the_file.read()
commented_rewrite_rule = ("# RewriteRule \"^/secrets/(.+)\" "
"\"https://new.example.com/docs/$1\" [R,L]")
uncommented_rewrite_rule = ("RewriteRule \"^/docs/(.+)\" "
@ -1667,7 +1668,8 @@ class MultiVhostsTest(util.ApacheTest):
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[3])
conf_lines = open(ssl_vhost.filep).readlines()
with open(ssl_vhost.filep) as the_file:
conf_lines = the_file.readlines()
conf_line_set = [l.strip() for l in conf_lines]
not_commented_cond1 = ("RewriteCond "
"%{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f")

View file

@ -121,15 +121,15 @@ class MultipleVhostsTestGentoo(util.ApacheTest):
mock_osi.return_value = ("gentoo", "123")
self.config.parser.update_runtime_variables()
self.assertEquals(mock_get.call_count, 1)
self.assertEquals(len(self.config.parser.modules), 4)
self.assertEqual(mock_get.call_count, 1)
self.assertEqual(len(self.config.parser.modules), 4)
self.assertTrue("mod_another.c" in self.config.parser.modules)
@mock.patch("certbot_apache.configurator.util.run_script")
def test_alt_restart_works(self, mock_run_script):
mock_run_script.side_effect = [None, errors.SubprocessError, None]
self.config.restart()
self.assertEquals(mock_run_script.call_count, 3)
self.assertEqual(mock_run_script.call_count, 3)
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -84,7 +84,7 @@ class BasicParserTest(util.ParserTest):
self.assertEqual(self.parser.aug.get(match), str(i + 1))
def test_empty_arg(self):
self.assertEquals(None,
self.assertEqual(None,
self.parser.get_arg("/files/whatever/nonexistent"))
def test_add_dir_to_ifmodssl(self):
@ -303,7 +303,7 @@ class BasicParserTest(util.ParserTest):
from certbot_apache.parser import get_aug_path
self.parser.add_comment(get_aug_path(self.parser.loc["name"]), "123456")
comm = self.parser.find_comments("123456")
self.assertEquals(len(comm), 1)
self.assertEqual(len(comm), 1)
self.assertTrue(self.parser.loc["name"] in comm[0])

View file

@ -1,2 +1,2 @@
acme[dev]==0.25.0
-e .[dev]
certbot[dev]==0.26.0

View file

@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
LE_AUTO_VERSION="0.28.0"
LE_AUTO_VERSION="0.30.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -593,8 +593,7 @@ BootstrapArchCommon() {
# - ArchLinux (x86_64)
#
# "python-virtualenv" is Python3, but "python2-virtualenv" provides
# only "virtualenv2" binary, not "virtualenv" necessary in
# ./tools/_venv_common.sh
# only "virtualenv2" binary, not "virtualenv".
deps="
python2
@ -912,6 +911,35 @@ OldVenvExists() {
[ -n "$OLD_VENV_PATH" -a -f "$OLD_VENV_PATH/bin/letsencrypt" ]
}
# Given python path, version 1 and version 2, check if version 1 is outdated compared to version 2.
# An unofficial version provided as version 1 (eg. 0.28.0.dev0) will be treated
# specifically by printing "UNOFFICIAL". Otherwise, print "OUTDATED" if version 1
# is outdated, and "UP_TO_DATE" if not.
# This function relies only on installed python environment (2.x or 3.x) by certbot-auto.
CompareVersions() {
"$1" - "$2" "$3" << "UNLIKELY_EOF"
import sys
from distutils.version import StrictVersion
try:
current = StrictVersion(sys.argv[1])
except ValueError:
sys.stdout.write('UNOFFICIAL')
sys.exit()
try:
remote = StrictVersion(sys.argv[2])
except ValueError:
sys.stdout.write('UP_TO_DATE')
sys.exit()
if current < remote:
sys.stdout.write('OUTDATED')
else:
sys.stdout.write('UP_TO_DATE')
UNLIKELY_EOF
}
if [ "$1" = "--le-auto-phase2" ]; then
# Phase 2: Create venv, install LE, and run.
@ -1017,43 +1045,39 @@ pycparser==2.14 \
asn1crypto==0.22.0 \
--hash=sha256:d232509fefcfcdb9a331f37e9c9dc20441019ad927c7d2176cf18ed5da0ba097 \
--hash=sha256:cbbadd640d3165ab24b06ef25d1dca09a3441611ac15f6a6b452474fdf0aed1a
cffi==1.10.0 \
--hash=sha256:446699c10f3c390633d0722bc19edbc7ac4b94761918a4a4f7908a24e86ebbd0 \
--hash=sha256:562326fc7f55a59ef3fef5e82908fe938cdc4bbda32d734c424c7cd9ed73e93a \
--hash=sha256:7f732ad4a30db0b39400c3f7011249f7d0701007d511bf09604729aea222871f \
--hash=sha256:94fb8410c6c4fc48e7ea759d3d1d9ca561171a88d00faddd4aa0306f698ad6a0 \
--hash=sha256:587a5043df4b00a2130e09fed42da02a4ed3c688bd9bf07a3ac89d2271f4fb07 \
--hash=sha256:ec08b88bef627ec1cea210e1608c85d3cf44893bcde74e41b7f7dbdfd2c1bad6 \
--hash=sha256:a41406f6d62abcdf3eef9fd998d8dcff04fd2a7746644143045feeebd76352d1 \
--hash=sha256:b560916546b2f209d74b82bdbc3223cee9a165b0242fa00a06dfc48a2054864a \
--hash=sha256:e74896774e437f4715c57edeb5cf3d3a40d7727f541c2c12156617b5a15d1829 \
--hash=sha256:9a31c18ba4881a116e448c52f3f5d3e14401cf7a9c43cc88f06f2a7f5428da0e \
--hash=sha256:80796ea68e11624a0279d3b802f88a7fe7214122b97a15a6c97189934a2cc776 \
--hash=sha256:f4019826a2dec066c909a1f483ef0dcf9325d6740cc0bd15308942b28b0930f7 \
--hash=sha256:7248506981eeba23888b4140a69a53c4c0c0a386abcdca61ed8dd790a73e64b9 \
--hash=sha256:a8955265d146e86fe2ce116394be4eaf0cb40314a79b19f11c4fa574cd639572 \
--hash=sha256:c49187260043bd4c1d6a52186f9774f17d9b1da0a406798ebf4bfc12da166ade \
--hash=sha256:c1d8b3d8dcb5c23ac1a8bf56422036f3f305a3c5a8bc8c354256579a1e2aa2c1 \
--hash=sha256:9e389615bcecb8c782a87939d752340bb0a3a097e90bae54d7f0915bc12f45bd \
--hash=sha256:d09ff358f75a874f69fa7d1c2b4acecf4282a950293fcfcf89aa606da8a9a500 \
--hash=sha256:b69b4557aae7de18b7c174a917fe19873529d927ac592762d9771661875bbd40 \
--hash=sha256:5de52b081a2775e76b971de9d997d85c4457fc0a09079e12d66849548ae60981 \
--hash=sha256:e7d88fecb7b6250a1fd432e6dc64890342c372fce13dbfe4bb6f16348ad00c14 \
--hash=sha256:1426e67e855ef7f5030c9184f4f1a9f4bfa020c31c962cd41fd129ec5aef4a6a \
--hash=sha256:267dd2c66a5760c5f4d47e2ebcf8eeac7ef01e1ae6ae7a6d0d241a290068bc38 \
--hash=sha256:e553eb489511cacf19eda6e52bc9e151316f0d721724997dda2c4d3079b778db \
--hash=sha256:98b89b2c57f97ce2db7aeba60db173c84871d73b40e41a11ea95de1500ddc57e \
--hash=sha256:e2b7e090188833bc58b2ae03fb864c22688654ebd2096bcf38bc860c4f38a3d8 \
--hash=sha256:afa7d8b8d38ad40db8713ee053d41b36d87d6ae5ec5ad36f9210b548a18dc214 \
--hash=sha256:4fc9c2ff7924b3a1fa326e1799e5dd58cac585d7fb25fe53ccaa1333b0453d65 \
--hash=sha256:937db39a1ec5af3003b16357b2042bba67c88d43bc11aaa203fa8a5924524209 \
--hash=sha256:ab22285797631df3b513b2cd3ecdc51cd8e3d36788e3991d93d0759d6883b027 \
--hash=sha256:96e599b924ef009aa867f725b3249ee51d76489f484d3a45b4bd219c5ec6ed59 \
--hash=sha256:bea842a0512be6a8007e585790bccd5d530520fc025ce63b03e139be373b0063 \
--hash=sha256:e7175287f7fe7b1cc203bb958b17db40abd732690c1e18e700f10e0843a58598 \
--hash=sha256:285ab352552f52f1398c912556d4d36d4ea9b8450e5c65d03809bf9886755533 \
--hash=sha256:5576644b859197da7bbd8f8c7c2fb5dcc6cd505cadb42992d5f104c013f8a214 \
--hash=sha256:b3b02911eb1f6ada203b0763ba924234629b51586f72a21faacc638269f4ced5
cffi==1.11.5 \
--hash=sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50 \
--hash=sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596 \
--hash=sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef \
--hash=sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743 \
--hash=sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f \
--hash=sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31 \
--hash=sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04 \
--hash=sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6 \
--hash=sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3 \
--hash=sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6 \
--hash=sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b \
--hash=sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca \
--hash=sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e \
--hash=sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb \
--hash=sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd \
--hash=sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1 \
--hash=sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917 \
--hash=sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359 \
--hash=sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f \
--hash=sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95 \
--hash=sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801 \
--hash=sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257 \
--hash=sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184 \
--hash=sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc \
--hash=sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085 \
--hash=sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93 \
--hash=sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2 \
--hash=sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30 \
--hash=sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5 \
--hash=sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e \
--hash=sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b \
--hash=sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4
ConfigArgParse==0.12.0 \
--hash=sha256:28cd7d67669651f2a4518367838c49539457504584a139709b2b8f6c208ef339 \
--no-binary ConfigArgParse
@ -1146,9 +1170,9 @@ pytz==2015.7 \
--hash=sha256:fbd26746772c24cb93c8b97cbdad5cb9e46c86bbdb1b9d8a743ee00e2fb1fc5d \
--hash=sha256:99266ef30a37e43932deec2b7ca73e83c8dbc3b9ff703ec73eca6b1dae6befea \
--hash=sha256:8b6ce1c993909783bc96e0b4f34ea223bff7a4df2c90bdb9c4e0f1ac928689e3
requests==2.12.1 \
--hash=sha256:3f3f27a9d0f9092935efc78054ef324eb9f8166718270aefe036dfa1e4f68e1e \
--hash=sha256:2109ecea94df90980be040490ff1d879971b024861539abb00054062388b612e
requests==2.20.0 \
--hash=sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c \
--hash=sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279
six==1.10.0 \
--hash=sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1 \
--hash=sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a
@ -1185,6 +1209,15 @@ zope.interface==4.1.3 \
requests-toolbelt==0.8.0 \
--hash=sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237 \
--hash=sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5
chardet==3.0.2 \
--hash=sha256:4f7832e7c583348a9eddd927ee8514b3bf717c061f57b21dbe7697211454d9bb \
--hash=sha256:6ebf56457934fdce01fb5ada5582762a84eed94cad43ed877964aebbdd8174c0
urllib3==1.24.1 \
--hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \
--hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22
certifi==2017.4.17 \
--hash=sha256:f4318671072f030a33c7ca6acaef720ddd50ff124d1388e50c1bda4cbd6d7010 \
--hash=sha256:f7527ebf7461582ce95f7a9e03dd141ce810d40590834f4ec20cddd54234c10a
# Contains the requirements for the letsencrypt package.
#
@ -1197,31 +1230,29 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==0.28.0 \
--hash=sha256:f2f7c816acd695cbcda713a779b0db2b08e9de407146b46e1c4ef5561e0f5417 \
--hash=sha256:31e3e2ee2a25c009a621c59ac9182f85d937a897c7bd1d47d0e01f3c712a090a
acme==0.28.0 \
--hash=sha256:d3a564031155fece3f6edce8c5246d4cf34be0977b4c7c5ce84e86c68601c895 \
--hash=sha256:bf7c2f1c24a26ab5b9fce3a6abca1d74a5914d46919649ae00ad5817db62bb85
certbot-apache==0.28.0 \
--hash=sha256:a57d7bac4f13ae5ecea4f4cbd479d381c02316c4832b25ab5a9d7c6826166370 \
--hash=sha256:3f93f5de4a548e973c493a6cac5eeeb3dbbcae2988b61299ea0727d04a00f5bb
certbot-nginx==0.28.0 \
--hash=sha256:1822a65910f0801087fa20a3af3fc94f878f93e0f11809483bb5387df861e296 \
--hash=sha256:426fb403b0a7b203629f4e350a862cbc3bc1f69936fdab8ec7eafe0d8a3b5ddb
certbot==0.30.0 \
--hash=sha256:b3468e128e74d2295598f6d3fbf9d0edfb67fe5abaca3b985a9e858395bd027f \
--hash=sha256:d631fe6c75700ce9b2fdae194ff8b53c7518545d87dd451a1704f7572dcd49e8
acme==0.30.0 \
--hash=sha256:eed9389f802ebf4988c9e43c28ad3d5c2734237371d78e97450a1d61189a15aa \
--hash=sha256:984b6d00bec73dcfa616636a760e80ca14bd246fb908710a656547f542f09445
certbot-apache==0.30.0 \
--hash=sha256:d38c70fc6930db298ea992a3145362eebdce460d3d2651f86a8f2f43d838c6d0 \
--hash=sha256:1d4bc207d53a3e5d37e5d9ebd05f26089aa21d1fbf384113ed9d1829b4d1e9bf
certbot-nginx==0.30.0 \
--hash=sha256:6163c7d0080f59b4ebe510afcc6af2d2eebf15469275c3835866690db4d465d6 \
--hash=sha256:e39a3f3d77cd4c653949cf066fb2211039fd2032665697c27b6e8501c7c2dd92
UNLIKELY_EOF
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/pipstrap.py"
#!/usr/bin/env python
"""A small script that can act as a trust root for installing pip >=8
Embed this in your project, and your VCS checkout is all you have to trust. In
a post-peep era, this lets you claw your way to a hash-checking version of pip,
with which you can install the rest of your dependencies safely. All it assumes
is Python 2.6 or better and *some* version of pip already installed. If
anything goes wrong, it will exit with a non-zero status code.
"""
# This is here so embedded copies are MIT-compliant:
# Copyright (c) 2016 Erik Rose
@ -1348,10 +1379,8 @@ def hashed_download(url, temp, digest):
def get_index_base():
"""Return the URL to the dir containing the "packages" folder.
Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the
end if it's there; that is likely to give us the right dir.
"""
env_var = environ.get('PIP_INDEX_URL', '').rstrip('/')
if env_var:
@ -1641,7 +1670,12 @@ UNLIKELY_EOF
error "WARNING: couldn't find Python $MIN_PYTHON_VERSION+ to check for updates."
elif ! REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version` ; then
error "WARNING: unable to check for updates."
elif [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
fi
LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"`
if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then
say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION"
elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then
say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
# Now we drop into Python so we don't have to install even more

View file

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

View file

@ -122,7 +122,7 @@ class _CloudflareClient(object):
self.cf.zones.dns_records.delete(zone_id, record_id)
logger.debug('Successfully deleted TXT record.')
except CloudFlare.exceptions.CloudFlareAPIError as e:
logger.warn('Encountered CloudFlareAPIError deleting TXT record: %s', e)
logger.warning('Encountered CloudFlareAPIError deleting TXT record: %s', e)
else:
logger.debug('TXT record not found; no cleanup needed.')
else:

View file

@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -134,7 +134,7 @@ class _DigitalOceanClient(object):
logger.debug('Removing TXT record with id: %s', record.id)
record.destroy()
except digitalocean.Error as e:
logger.warn('Error deleting TXT record %s using the DigitalOcean API: %s',
logger.warning('Error deleting TXT record %s using the DigitalOcean API: %s',
record.id, e)
def _find_domain(self, domain_name):

View file

@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -1,10 +1,8 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View file

@ -98,7 +98,7 @@ Examples
certbot certonly \\
--dns-google \\
--dns-google-credentials ~/.secrets/certbot/google.ini \\
--dns-google-credentials ~/.secrets/certbot/google.json \\
--dns-google-propagation-seconds 120 \\
-d example.com

View file

@ -179,7 +179,7 @@ class _GoogleClient(object):
try:
zone_id = self._find_managed_zone_id(domain)
except errors.PluginError as e:
logger.warn('Error finding zone. Skipping cleanup.')
logger.warning('Error finding zone. Skipping cleanup.')
return
record_contents = self.get_existing_txt_rrset(zone_id, record_name)
@ -219,7 +219,7 @@ class _GoogleClient(object):
request = changes.create(project=self.project_id, managedZone=zone_id, body=data)
request.execute()
except googleapiclient_errors.Error as e:
logger.warn('Encountered error deleting TXT record: %s', e)
logger.warning('Encountered error deleting TXT record: %s', e)
def get_existing_txt_rrset(self, zone_id, record_name):
"""

View file

@ -276,9 +276,9 @@ class GoogleClientTest(unittest.TestCase):
[{'managedZones': [{'id': self.zone}]}])
# Record name mocked in setUp
found = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org")
self.assertEquals(found, ["\"example-txt-contents\""])
self.assertEqual(found, ["\"example-txt-contents\""])
not_found = client.get_existing_txt_rrset(self.zone, "nonexistent.tld")
self.assertEquals(not_found, None)
self.assertEqual(not_found, None)
@mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name')
@mock.patch('certbot_dns_google.dns_google.open',

View file

@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -1,9 +1,7 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View file

@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -1,10 +1,8 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -1,7 +1,7 @@
from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -1,10 +1,8 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View file

@ -415,7 +415,7 @@ def _parse_ssl_options(ssl_options):
with open(ssl_options) as _file:
return nginxparser.load(_file)
except IOError:
logger.warn("Missing NGINX TLS options file: %s", ssl_options)
logger.warning("Missing NGINX TLS options file: %s", ssl_options)
except pyparsing.ParseBaseException as err:
logger.debug("Could not parse file: %s due to %s", ssl_options, err)
return []

View file

@ -194,9 +194,9 @@ class NginxConfiguratorTest(util.NginxTest):
def test_ipv6only(self):
# ipv6_info: (ipv6_active, ipv6only_present)
self.assertEquals((True, False), self.config.ipv6_info("80"))
self.assertEqual((True, False), self.config.ipv6_info("80"))
# Port 443 has ipv6only=on because of ipv6ssl.com vhost
self.assertEquals((True, True), self.config.ipv6_info("443"))
self.assertEqual((True, True), self.config.ipv6_info("443"))
def test_ipv6only_detection(self):
self.config.version = (1, 3, 1)
@ -807,7 +807,7 @@ class NginxConfiguratorTest(util.NginxTest):
self.assertTrue(vhost in mock_select_vhs.call_args[0][0])
# And the actual returned values
self.assertEquals(len(vhs), 1)
self.assertEqual(len(vhs), 1)
self.assertEqual(vhs[0], vhost)
def test_choose_vhosts_wildcard_redirect(self):
@ -823,7 +823,7 @@ class NginxConfiguratorTest(util.NginxTest):
self.assertTrue(vhost in mock_select_vhs.call_args[0][0])
# And the actual returned values
self.assertEquals(len(vhs), 1)
self.assertEqual(len(vhs), 1)
self.assertEqual(vhs[0], vhost)
def test_deploy_cert_wildcard(self):
@ -838,7 +838,7 @@ class NginxConfiguratorTest(util.NginxTest):
self.config.deploy_cert("*.com", "/tmp/path",
"/tmp/path", "/tmp/path", "/tmp/path")
self.assertTrue(mock_dep.called)
self.assertEquals(len(mock_dep.call_args_list), 1)
self.assertEqual(len(mock_dep.call_args_list), 1)
self.assertEqual(vhost, mock_dep.call_args_list[0][0][0])
@mock.patch("certbot_nginx.display_ops.select_vhost_multiple")

View file

@ -103,37 +103,37 @@ class SentenceTest(unittest.TestCase):
def test_parse_sentence_words_hides_spaces(self):
og_sentence = ['\r\n', 'hello', ' ', ' ', '\t\n ', 'lol', ' ', 'spaces']
self.sentence.parse(og_sentence)
self.assertEquals(self.sentence.words, ['hello', 'lol', 'spaces'])
self.assertEquals(self.sentence.dump(), ['hello', 'lol', 'spaces'])
self.assertEquals(self.sentence.dump(True), og_sentence)
self.assertEqual(self.sentence.words, ['hello', 'lol', 'spaces'])
self.assertEqual(self.sentence.dump(), ['hello', 'lol', 'spaces'])
self.assertEqual(self.sentence.dump(True), og_sentence)
def test_parse_sentence_with_add_spaces(self):
self.sentence.parse(['hi', 'there'], add_spaces=True)
self.assertEquals(self.sentence.dump(True), ['hi', ' ', 'there'])
self.assertEqual(self.sentence.dump(True), ['hi', ' ', 'there'])
self.sentence.parse(['one', ' ', 'space', 'none'], add_spaces=True)
self.assertEquals(self.sentence.dump(True), ['one', ' ', 'space', ' ', 'none'])
self.assertEqual(self.sentence.dump(True), ['one', ' ', 'space', ' ', 'none'])
def test_iterate(self):
expected = [['1', '2', '3']]
self.sentence.parse(['1', ' ', '2', ' ', '3'])
for i, sentence in enumerate(self.sentence.iterate()):
self.assertEquals(sentence.dump(), expected[i])
self.assertEqual(sentence.dump(), expected[i])
def test_set_tabs(self):
self.sentence.parse(['tabs', 'pls'], add_spaces=True)
self.sentence.set_tabs()
self.assertEquals(self.sentence.dump(True)[0], '\n ')
self.assertEqual(self.sentence.dump(True)[0], '\n ')
self.sentence.parse(['tabs', 'pls'], add_spaces=True)
def test_get_tabs(self):
self.sentence.parse(['no', 'tabs'])
self.assertEquals(self.sentence.get_tabs(), '')
self.assertEqual(self.sentence.get_tabs(), '')
self.sentence.parse(['\n \n ', 'tabs'])
self.assertEquals(self.sentence.get_tabs(), ' ')
self.assertEqual(self.sentence.get_tabs(), ' ')
self.sentence.parse(['\n\t ', 'tabs'])
self.assertEquals(self.sentence.get_tabs(), '\t ')
self.assertEqual(self.sentence.get_tabs(), '\t ')
self.sentence.parse(['\n\t \n', 'tabs'])
self.assertEquals(self.sentence.get_tabs(), '')
self.assertEqual(self.sentence.get_tabs(), '')
class BlockTest(unittest.TestCase):
def setUp(self):
@ -145,11 +145,11 @@ class BlockTest(unittest.TestCase):
def test_iterate(self):
# Iterates itself normally
self.assertEquals(self.bloc, next(self.bloc.iterate()))
self.assertEqual(self.bloc, next(self.bloc.iterate()))
# Iterates contents while expanded
expected = [self.bloc.dump()] + self.contents
for i, elem in enumerate(self.bloc.iterate(expanded=True)):
self.assertEquals(expected[i], elem.dump())
self.assertEqual(expected[i], elem.dump())
def test_iterate_match(self):
# can match on contents while expanded
@ -157,17 +157,17 @@ class BlockTest(unittest.TestCase):
expected = [['thing', '1'], ['thing', '2']]
for i, elem in enumerate(self.bloc.iterate(expanded=True,
match=lambda x: isinstance(x, Sentence) and 'thing' in x.words)):
self.assertEquals(expected[i], elem.dump())
self.assertEqual(expected[i], elem.dump())
# can match on self
self.assertEquals(self.bloc, next(self.bloc.iterate(
self.assertEqual(self.bloc, next(self.bloc.iterate(
expanded=True,
match=lambda x: isinstance(x, Block) and 'server' in x.names)))
def test_parse_with_added_spaces(self):
import copy
self.bloc.parse([copy.copy(self.name), self.contents], add_spaces=True)
self.assertEquals(self.bloc.dump(), [self.name, self.contents])
self.assertEquals(self.bloc.dump(True), [
self.assertEqual(self.bloc.dump(), [self.name, self.contents])
self.assertEqual(self.bloc.dump(True), [
['server', ' ', 'name', ' '],
[['thing', ' ', '1'],
['thing', ' ', '2'],
@ -181,14 +181,14 @@ class BlockTest(unittest.TestCase):
def test_set_tabs(self):
self.bloc.set_tabs()
self.assertEquals(self.bloc.names.dump(True)[0], '\n ')
self.assertEqual(self.bloc.names.dump(True)[0], '\n ')
for elem in self.bloc.contents.dump(True)[:-1]:
self.assertEquals(elem[0], '\n ')
self.assertEquals(self.bloc.contents.dump(True)[-1][0], '\n')
self.assertEqual(elem[0], '\n ')
self.assertEqual(self.bloc.contents.dump(True)[-1][0], '\n')
def test_get_tabs(self):
self.bloc.parse([[' \n \t', 'lol'], []])
self.assertEquals(self.bloc.get_tabs(), ' \t')
self.assertEqual(self.bloc.get_tabs(), ' \t')
class StatementsTest(unittest.TestCase):
def setUp(self):
@ -210,7 +210,7 @@ class StatementsTest(unittest.TestCase):
self.statements.parse(self.raw)
self.statements.set_tabs()
for statement in self.statements.iterate():
self.assertEquals(statement.dump(True)[0], '\n ')
self.assertEqual(statement.dump(True)[0], '\n ')
def test_set_tabs_with_parent(self):
# Trailing whitespace should inherit from parent tabbing.
@ -219,19 +219,19 @@ class StatementsTest(unittest.TestCase):
self.statements.parent.get_tabs.return_value = '\t\t'
self.statements.set_tabs()
for statement in self.statements.iterate():
self.assertEquals(statement.dump(True)[0], '\n ')
self.assertEquals(self.statements.dump(True)[-1], '\n\t\t')
self.assertEqual(statement.dump(True)[0], '\n ')
self.assertEqual(self.statements.dump(True)[-1], '\n\t\t')
def test_get_tabs(self):
self.raw[0].insert(0, '\n \n \t')
self.statements.parse(self.raw)
self.assertEquals(self.statements.get_tabs(), ' \t')
self.assertEqual(self.statements.get_tabs(), ' \t')
self.statements.parse([])
self.assertEquals(self.statements.get_tabs(), '')
self.assertEqual(self.statements.get_tabs(), '')
def test_parse_with_added_spaces(self):
self.statements.parse(self.raw, add_spaces=True)
self.assertEquals(self.statements.dump(True)[0], ['sentence', ' ', 'one'])
self.assertEqual(self.statements.dump(True)[0], ['sentence', ' ', 'one'])
def test_parse_bad_list_raises_error(self):
from certbot import errors
@ -241,13 +241,13 @@ class StatementsTest(unittest.TestCase):
self.statements.parse(self.raw + ['\n\n '])
self.assertTrue(isinstance(self.statements.dump()[-1], list))
self.assertTrue(self.statements.dump(True)[-1].isspace())
self.assertEquals(self.statements.dump(True)[-1], '\n\n ')
self.assertEqual(self.statements.dump(True)[-1], '\n\n ')
def test_iterate(self):
self.statements.parse(self.raw)
expected = [['sentence', 'one'], ['sentence', 'two']]
for i, elem in enumerate(self.statements.iterate(match=lambda x: 'sentence' in x)):
self.assertEquals(expected[i], elem.dump())
self.assertEqual(expected[i], elem.dump())
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.29.0.dev0'
version = '0.31.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -85,7 +85,7 @@ class PostConfTest(unittest.TestCase):
self.config.set('extra_param', 'another_value')
self.config.flush()
arguments = mock_out.call_args_list[-1][0][0]
self.assertEquals('-e', arguments[0])
self.assertEqual('-e', arguments[0])
self.assertTrue('default_parameter=new_value' in arguments)
self.assertTrue('extra_param=another_value' in arguments)

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.29.0.dev0'
__version__ = '0.31.0.dev0'

View file

@ -101,6 +101,7 @@ manage certificates:
manage your account with Let's Encrypt:
register Create a Let's Encrypt ACME account
update_account Update a Let's Encrypt ACME account
--agree-tos Agree to the ACME server's Subscriber Agreement
-m EMAIL Email address for important account notifications
"""
@ -172,10 +173,11 @@ def possible_deprecation_warning(config):
# need warnings
return
if "CERTBOT_AUTO" not in os.environ:
logger.warning("You are running with an old copy of letsencrypt-auto that does "
"not receive updates, and is less reliable than more recent versions. "
"We recommend upgrading to the latest certbot-auto script, or using native "
"OS packages.")
logger.warning("You are running with an old copy of letsencrypt-auto"
" that does not receive updates, and is less reliable than more"
" recent versions. The letsencrypt client has also been renamed"
" to Certbot. We recommend upgrading to the latest certbot-auto"
" script, or using native OS packages.")
logger.debug("Deprecation warning circumstances: %s / %s", sys.argv[0], os.environ)
@ -286,7 +288,9 @@ def read_file(filename, mode="rb"):
"""
try:
filename = os.path.abspath(filename)
return filename, open(filename, mode).read()
with open(filename, mode) as the_file:
contents = the_file.read()
return filename, contents
except IOError as exc:
raise argparse.ArgumentTypeError(exc.strerror)
@ -394,9 +398,14 @@ VERB_HELP = [
}),
("register", {
"short": "Register for account with Let's Encrypt / other ACME server",
"opts": "Options for account registration & modification",
"opts": "Options for account registration",
"usage": "\n\n certbot register --email user@example.com [options]\n\n"
}),
("update_account", {
"short": "Update existing account with Let's Encrypt / other ACME server",
"opts": "Options for account modification",
"usage": "\n\n certbot update_account --email updated_email@example.com [options]\n\n"
}),
("unregister", {
"short": "Irrevocably deactivate your account",
"opts": "Options for account deactivation.",
@ -462,6 +471,7 @@ class HelpfulArgumentParser(object):
"install": main.install,
"plugins": main.plugins_cmd,
"register": main.register,
"update_account": main.update_account,
"unregister": main.unregister,
"renew": main.renew,
"revoke": main.revoke,
@ -856,7 +866,9 @@ class HelpfulArgumentParser(object):
if chosen_topic == "everything":
chosen_topic = "run"
if chosen_topic == "all":
return dict([(t, True) for t in self.help_topics])
# Addition of condition closes #6209 (removal of duplicate route53 option).
return dict([(t, True) if t != 'certbot-route53:auth' else (t, False)
for t in self.help_topics])
elif not chosen_topic:
return dict([(t, False) for t in self.help_topics])
else:
@ -941,6 +953,18 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
"specified or you already have a certificate with the same "
"name. In the case of a name collision it will append a number "
"like 0001 to the file path name. (default: Ask)")
helpful.add(
[None, "run", "certonly", "register"],
"--eab-kid", dest="eab_kid",
metavar="EAB_KID",
help="Key Identifier for External Account Binding"
)
helpful.add(
[None, "run", "certonly", "register"],
"--eab-hmac-key", dest="eab_hmac_key",
metavar="EAB_HMAC_KEY",
help="HMAC key for External Account Binding"
)
helpful.add(
[None, "run", "certonly", "manage", "delete", "certificates",
"renew", "enhance"], "--cert-name", dest="certname",
@ -976,21 +1000,21 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
"certificates. Updates to the Subscriber Agreement will still "
"affect you, and will be effective 14 days after posting an "
"update to the web site.")
# TODO: When `certbot register --update-registration` is fully deprecated,
# delete following helpful.add
helpful.add(
"register", "--update-registration", action="store_true",
default=flag_default("update_registration"),
help="With the register verb, indicates that details associated "
"with an existing registration, such as the e-mail address, "
"should be updated, rather than registering a new account.")
default=flag_default("update_registration"), dest="update_registration",
help=argparse.SUPPRESS)
helpful.add(
["register", "unregister", "automation"], "-m", "--email",
["register", "update_account", "unregister", "automation"], "-m", "--email",
default=flag_default("email"),
help=config_help("email"))
helpful.add(["register", "automation"], "--eff-email", action="store_true",
helpful.add(["register", "update_account", "automation"], "--eff-email", action="store_true",
default=flag_default("eff_email"), dest="eff_email",
help="Share your e-mail address with EFF")
helpful.add(["register", "automation"], "--no-eff-email", action="store_false",
default=flag_default("eff_email"), dest="eff_email",
helpful.add(["register", "update_account", "automation"], "--no-eff-email",
action="store_false", default=flag_default("eff_email"), dest="eff_email",
help="Don't share your e-mail address with EFF")
helpful.add(
["automation", "certonly", "run"],
@ -1191,6 +1215,10 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
" one will be run.")
helpful.add("renew", "--renew-hook",
action=_RenewHookAction, help=argparse.SUPPRESS)
helpful.add(
"renew", "--no-random-sleep-on-renew", action="store_false",
default=flag_default("random_sleep_on_renew"), dest="random_sleep_on_renew",
help=argparse.SUPPRESS)
helpful.add(
"renew", "--deploy-hook", action=_DeployHookAction,
help='Command to be run in a shell once for each successfully'

View file

@ -203,9 +203,27 @@ def perform_registration(acme, config, tos_cb):
:returns: Registration Resource.
:rtype: `acme.messages.RegistrationResource`
"""
eab_credentials_supplied = config.eab_kid and config.eab_hmac_key
if eab_credentials_supplied:
account_public_key = acme.client.net.key.public_key()
eab = messages.ExternalAccountBinding.from_data(account_public_key=account_public_key,
kid=config.eab_kid,
hmac_key=config.eab_hmac_key,
directory=acme.client.directory)
else:
eab = None
if acme.external_account_required():
if not eab_credentials_supplied:
msg = ("Server requires external account binding."
" Please use --eab-kid and --eab-hmac-key.")
raise errors.Error(msg)
try:
return acme.new_account_and_tos(messages.NewRegistration.from_data(email=config.email),
tos_cb)
newreg = messages.NewRegistration.from_data(email=config.email,
external_account_binding=eab)
return acme.new_account_and_tos(newreg, tos_cb)
except messages.Error as e:
if e.code == "invalidEmail" or e.code == "invalidContact":
if config.noninteractive_mode:

View file

@ -172,3 +172,30 @@ def compare_file_modes(mode1, mode2):
# Windows specific: most of mode bits are ignored on Windows. Only check user R/W rights.
return (stat.S_IMODE(mode1) & stat.S_IREAD == stat.S_IMODE(mode2) & stat.S_IREAD
and stat.S_IMODE(mode1) & stat.S_IWRITE == stat.S_IMODE(mode2) & stat.S_IWRITE)
WINDOWS_DEFAULT_FOLDERS = {
'config': 'C:\\Certbot',
'work': 'C:\\Certbot\\lib',
'logs': 'C:\\Certbot\\log',
}
LINUX_DEFAULT_FOLDERS = {
'config': '/etc/letsencrypt',
'work': '/var/lib/letsencrypt',
'logs': '/var/log/letsencrypt',
}
def get_default_folder(folder_type):
"""
Return the relevant default folder for the current OS
:param str folder_type: The type of folder to retrieve (config, work or logs)
:returns: The relevant default folder.
:rtype: str
"""
if 'fcntl' in sys.modules:
# Linux specific
return LINUX_DEFAULT_FOLDERS[folder_type]
# Windows specific
return WINDOWS_DEFAULT_FOLDERS[folder_type]

View file

@ -4,7 +4,7 @@ import os
import pkg_resources
from acme import challenges
from certbot import compat
SETUPTOOLS_PLUGINS_ENTRY_POINT = "certbot.plugins"
"""Setuptools entry point group name for plugins."""
@ -14,7 +14,7 @@ OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins"
CLI_DEFAULTS = dict(
config_files=[
"/etc/letsencrypt/cli.ini",
os.path.join(compat.get_default_folder('config'), 'cli.ini'),
# http://freedesktop.org/wiki/Software/xdg-user-dirs/
os.path.join(os.environ.get("XDG_CONFIG_HOME", "~/.config"),
"letsencrypt", "cli.ini"),
@ -68,6 +68,9 @@ CLI_DEFAULTS = dict(
directory_hooks=True,
reuse_key=False,
disable_renew_updates=False,
random_sleep_on_renew=True,
eab_hmac_key=None,
eab_kid=None,
# Subparsers
num=None,
@ -85,9 +88,9 @@ CLI_DEFAULTS = dict(
auth_cert_path="./cert.pem",
auth_chain_path="./chain.pem",
key_path=None,
config_dir="/etc/letsencrypt",
work_dir="/var/lib/letsencrypt",
logs_dir="/var/log/letsencrypt",
config_dir=compat.get_default_folder('config'),
work_dir=compat.get_default_folder('work'),
logs_dir=compat.get_default_folder('logs'),
server="https://acme-v02.api.letsencrypt.org/directory",
# Plugins parsers

View file

@ -458,7 +458,7 @@ def sha256sum(filename):
:rtype: str
"""
sha256 = hashlib.sha256()
with open(filename, 'rU') as file_d:
with open(filename, 'r') as file_d:
sha256.update(file_d.read().encode('UTF-8'))
return sha256.hexdigest()

View file

@ -4,9 +4,11 @@ import os
import zope.component
from certbot import compat
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.display import util as display_util
logger = logging.getLogger(__name__)
@ -33,7 +35,8 @@ def get_email(invalid=False, optional=True):
unsafe_suggestion = ("\n\nIf you really want to skip this, you can run "
"the client with --register-unsafely-without-email "
"but make sure you then backup your account key from "
"/etc/letsencrypt/accounts\n\n")
"{0}\n\n".format(os.path.join(
compat.get_default_folder('config'), 'accounts')))
if optional:
if invalid:
msg += unsafe_suggestion

View file

@ -652,7 +652,45 @@ def unregister(config, unused_plugins):
def register(config, unused_plugins):
"""Create or modify accounts on the server.
"""Create accounts on the server.
:param config: Configuration object
:type config: interfaces.IConfig
:param unused_plugins: List of plugins (deprecated)
:type unused_plugins: `list` of `str`
:returns: `None` or a string indicating and error
:rtype: None or str
"""
# TODO: When `certbot register --update-registration` is fully deprecated,
# delete the true case of if block
if config.update_registration:
msg = ("Usage 'certbot register --update-registration' is deprecated.\n"
"Please use 'cerbot update_account [options]' instead.\n")
logger.warning(msg)
return update_account(config, unused_plugins)
# Portion of _determine_account logic to see whether accounts already
# exist or not.
account_storage = account.AccountFileStorage(config)
accounts = account_storage.find_all()
if len(accounts) > 0:
# TODO: add a flag to register a duplicate account (this will
# also require extending _determine_account's behavior
# or else extracting the registration code from there)
return ("There is an existing account; registration of a "
"duplicate account with this command is currently "
"unsupported.")
# _determine_account will register an account
_determine_account(config)
return
def update_account(config, unused_plugins):
"""Modify accounts on the server.
:param config: Configuration object
:type config: interfaces.IConfig
@ -671,20 +709,6 @@ def register(config, unused_plugins):
reporter_util = zope.component.getUtility(interfaces.IReporter)
add_msg = lambda m: reporter_util.add_message(m, reporter_util.MEDIUM_PRIORITY)
# registering a new account
if not config.update_registration:
if len(accounts) > 0:
# TODO: add a flag to register a duplicate account (this will
# also require extending _determine_account's behavior
# or else extracting the registration code from there)
return ("There is an existing account; registration of a "
"duplicate account with this command is currently "
"unsupported.")
# _determine_account will register an account
_determine_account(config)
return
# --update-registration
if len(accounts) == 0:
return "Could not find an existing account to update."
if config.email is None:

View file

@ -114,7 +114,7 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors):
logger.info("OCSP revocation warning: %s", warning)
return True
else:
logger.warn("Unable to properly parse OCSP output: %s\nstderr:%s",
logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s",
ocsp_output, ocsp_errors)
return False

View file

@ -37,11 +37,11 @@ class EnhancementTest(test_util.ConfigTestCase):
self.assertTrue([i for i in enabled if i["name"] == "somethingelse"])
def test_are_requested(self):
self.assertEquals(
self.assertEqual(
len([i for i in enhancements.enabled_enhancements(self.config)]), 0)
self.assertFalse(enhancements.are_requested(self.config))
self.config.auto_hsts = True
self.assertEquals(
self.assertEqual(
len([i for i in enhancements.enabled_enhancements(self.config)]), 1)
self.assertTrue(enhancements.are_requested(self.config))
@ -57,7 +57,7 @@ class EnhancementTest(test_util.ConfigTestCase):
lineage = "lineage"
enhancements.enable(lineage, domains, self.mockinstaller, self.config)
self.assertTrue(self.mockinstaller.enable_autohsts.called)
self.assertEquals(self.mockinstaller.enable_autohsts.call_args[0],
self.assertEqual(self.mockinstaller.enable_autohsts.call_args[0],
(lineage, domains))

View file

@ -197,7 +197,7 @@ class GetUnpreparedInstallerTest(test_util.ConfigTestCase):
def test_no_installer_defined(self):
self.config.configurator = None
self.assertEquals(self._call(), None)
self.assertEqual(self._call(), None)
def test_no_available_installers(self):
self.config.configurator = "apache"

View file

@ -72,6 +72,8 @@ class ServerManagerTest(unittest.TestCase):
errors.StandaloneBindError, self.mgr.run, port,
challenge_type=challenges.HTTP01)
self.assertEqual(self.mgr.running(), {})
some_server.close()
maybe_another_server.close()
class SupportedChallengesActionTest(unittest.TestCase):

View file

@ -5,6 +5,9 @@ import itertools
import logging
import os
import traceback
import sys
import time
import random
import six
import zope.component
@ -276,8 +279,10 @@ def _avoid_invalidating_lineage(config, lineage, original_server):
"Do not renew a valid cert with one from a staging server!"
# Some lineages may have begun with --staging, but then had production certs
# added to them
with open(lineage.cert) as the_file:
contents = the_file.read()
latest_cert = OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, open(lineage.cert).read())
OpenSSL.crypto.FILETYPE_PEM, contents)
# all our test certs are from happy hacker fake CA, though maybe one day
# we should test more methodically
now_valid = "fake" not in repr(latest_cert.get_issuer()).lower()
@ -370,7 +375,7 @@ def _renew_describe_results(config, renew_successes, renew_failures,
disp.notification("\n".join(out), wrap=False)
def handle_renewal_request(config):
def handle_renewal_request(config): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""Examine each lineage; renew if due and report results"""
# This is trivially False if config.domains is empty
@ -394,6 +399,14 @@ def handle_renewal_request(config):
renew_failures = []
renew_skipped = []
parse_failures = []
# Noninteractive renewals include a random delay in order to spread
# out the load on the certificate authority servers, even if many
# users all pick the same time for renewals. This delay precedes
# running any hooks, so that side effects of the hooks (such as
# shutting down a web service) aren't prolonged unnecessarily.
apply_random_sleep = not sys.stdin.isatty() and config.random_sleep_on_renew
for renewal_file in conf_files:
disp = zope.component.getUtility(interfaces.IDisplay)
disp.notification("Processing " + renewal_file, pause=False)
@ -422,6 +435,15 @@ def handle_renewal_request(config):
from certbot import main
plugins = plugins_disco.PluginsRegistry.find_all()
if should_renew(lineage_config, renewal_candidate):
# Apply random sleep upon first renewal if needed
if apply_random_sleep:
sleep_time = random.randint(1, 60 * 8)
logger.info("Non-interactive renewal: random delay of %s seconds",
sleep_time)
time.sleep(sleep_time)
# We will sleep only once this day, folks.
apply_random_sleep = False
# domains have been restored into lineage_config by reconstitute
# but they're unnecessary anyway because renew_cert here
# will just grab them from the certificate

View file

@ -29,6 +29,7 @@ logger = logging.getLogger(__name__)
ALL_FOUR = ("cert", "privkey", "chain", "fullchain")
README = "README"
CURRENT_VERSION = util.get_strict_version(certbot.__version__)
BASE_PRIVKEY_MODE = 0o600
def renewal_conf_files(config):
@ -40,7 +41,9 @@ def renewal_conf_files(config):
:rtype: `list` of `str`
"""
return glob.glob(os.path.join(config.renewal_configs_dir, "*.conf"))
result = glob.glob(os.path.join(config.renewal_configs_dir, "*.conf"))
result.sort()
return result
def renewal_file_for_certname(config, certname):
"""Return /path/to/certname.conf in the renewal conf directory"""
@ -792,7 +795,7 @@ class RenewableCert(object):
May need to recover from rare interrupted / crashed states."""
if self.has_pending_deployment():
logger.warn("Found a new cert /archive/ that was not linked to in /live/; "
logger.warning("Found a new cert /archive/ that was not linked to in /live/; "
"fixing...")
self.update_all_links_to(self.latest_common_version())
return False
@ -1035,9 +1038,11 @@ class RenewableCert(object):
archive = full_archive_path(None, cli_config, lineagename)
live_dir = _full_live_path(cli_config, lineagename)
if os.path.exists(archive):
config_file.close()
raise errors.CertStorageError(
"archive directory exists for " + lineagename)
if os.path.exists(live_dir):
config_file.close()
raise errors.CertStorageError(
"live directory exists for " + lineagename)
os.mkdir(archive)
@ -1048,13 +1053,14 @@ class RenewableCert(object):
# Put the data into the appropriate files on disk
target = dict([(kind, os.path.join(live_dir, kind + ".pem"))
for kind in ALL_FOUR])
archive_target = dict([(kind, os.path.join(archive, kind + "1.pem"))
for kind in ALL_FOUR])
for kind in ALL_FOUR:
os.symlink(os.path.join(_relpath_from_file(archive, target[kind]), kind + "1.pem"),
target[kind])
os.symlink(_relpath_from_file(archive_target[kind], target[kind]), target[kind])
with open(target["cert"], "wb") as f:
logger.debug("Writing certificate to %s.", target["cert"])
f.write(cert)
with open(target["privkey"], "wb") as f:
with util.safe_open(archive_target["privkey"], "wb", chmod=BASE_PRIVKEY_MODE) as f:
logger.debug("Writing private key to %s.", target["privkey"])
f.write(privkey)
# XXX: Let's make sure to get the file permissions right here
@ -1118,14 +1124,15 @@ class RenewableCert(object):
os.path.join(self.archive_dir, "{0}{1}.pem".format(kind, target_version)))
for kind in ALL_FOUR])
old_privkey = os.path.join(
self.archive_dir, "privkey{0}.pem".format(prior_version))
# Distinguish the cases where the privkey has changed and where it
# has not changed (in the latter case, making an appropriate symlink
# to an earlier privkey version)
if new_privkey is None:
# The behavior below keeps the prior key by creating a new
# symlink to the old key or the target of the old key symlink.
old_privkey = os.path.join(
self.archive_dir, "privkey{0}.pem".format(prior_version))
if os.path.islink(old_privkey):
old_privkey = os.readlink(old_privkey)
else:
@ -1133,9 +1140,16 @@ class RenewableCert(object):
logger.debug("Writing symlink to old private key, %s.", old_privkey)
os.symlink(old_privkey, target["privkey"])
else:
with open(target["privkey"], "wb") as f:
with util.safe_open(target["privkey"], "wb", chmod=BASE_PRIVKEY_MODE) as f:
logger.debug("Writing new private key to %s.", target["privkey"])
f.write(new_privkey)
# Preserve gid and (mode & 074) from previous privkey in this lineage.
old_mode = stat.S_IMODE(os.stat(old_privkey).st_mode) & \
(stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | \
stat.S_IROTH)
mode = BASE_PRIVKEY_MODE | old_mode
os.chown(target["privkey"], -1, os.stat(old_privkey).st_gid)
os.chmod(target["privkey"], mode)
# Save everything else
with open(target["cert"], "wb") as f:

View file

@ -39,9 +39,8 @@ class BaseCertManagerTest(test_util.ConfigTestCase):
# We also create a file that isn't a renewal config in the same
# location to test that logic that reads in all-and-only renewal
# configs will ignore it and NOT attempt to parse it.
junk = open(os.path.join(self.config.renewal_configs_dir, "IGNORE.THIS"), "w")
junk.write("This file should be ignored!")
junk.close()
with open(os.path.join(self.config.renewal_configs_dir, "IGNORE.THIS"), "w") as junk:
junk.write("This file should be ignored!")
def _set_up_config(self, domain, custom_archive):
# TODO: maybe provide NamespaceConfig.make_dirs?
@ -589,7 +588,7 @@ class GetCertnameTest(unittest.TestCase):
from certbot import cert_manager
prompt = "Which certificate would you"
self.mock_get_utility().menu.return_value = (display_util.OK, 0)
self.assertEquals(
self.assertEqual(
cert_manager.get_certnames(
self.config, "verb", allow_multiple=False), ['example.com'])
self.assertTrue(
@ -603,11 +602,11 @@ class GetCertnameTest(unittest.TestCase):
from certbot import cert_manager
prompt = "custom prompt"
self.mock_get_utility().menu.return_value = (display_util.OK, 0)
self.assertEquals(
self.assertEqual(
cert_manager.get_certnames(
self.config, "verb", allow_multiple=False, custom_prompt=prompt),
['example.com'])
self.assertEquals(self.mock_get_utility().menu.call_args[0][0],
self.assertEqual(self.mock_get_utility().menu.call_args[0][0],
prompt)
@mock.patch('certbot.storage.renewal_conf_files')
@ -631,7 +630,7 @@ class GetCertnameTest(unittest.TestCase):
prompt = "Which certificate(s) would you"
self.mock_get_utility().checklist.return_value = (display_util.OK,
['example.com'])
self.assertEquals(
self.assertEqual(
cert_manager.get_certnames(
self.config, "verb", allow_multiple=True), ['example.com'])
self.assertTrue(
@ -646,11 +645,11 @@ class GetCertnameTest(unittest.TestCase):
prompt = "custom prompt"
self.mock_get_utility().checklist.return_value = (display_util.OK,
['example.com'])
self.assertEquals(
self.assertEqual(
cert_manager.get_certnames(
self.config, "verb", allow_multiple=True, custom_prompt=prompt),
['example.com'])
self.assertEquals(
self.assertEqual(
self.mock_get_utility().checklist.call_args[0][0],
prompt)

View file

@ -4,6 +4,7 @@ import unittest
import os
import tempfile
import copy
import sys
import mock
import six
@ -41,6 +42,15 @@ class TestReadFile(TempDirTestCase):
self.assertEqual(contents, test_contents)
class FlagDefaultTest(unittest.TestCase):
"""Tests cli.flag_default"""
def test_linux_directories(self):
if 'fcntl' in sys.modules:
self.assertEqual(cli.flag_default('config_dir'), '/etc/letsencrypt')
self.assertEqual(cli.flag_default('work_dir'), '/var/lib/letsencrypt')
self.assertEqual(cli.flag_default('logs_dir'), '/var/log/letsencrypt')
class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods
'''Test the cli args entrypoint'''
@ -431,6 +441,11 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertRaises(errors.Error, self.parse,
"--allow-subset-of-names -d *.example.org".split())
def test_route53_no_revert(self):
for help_flag in ['-h', '--help']:
for topic in ['all', 'plugins', 'dns-route53']:
self.assertFalse('certbot-route53:auth' in self._help_output([help_flag, topic]))
class DefaultTest(unittest.TestCase):
"""Tests for certbot.cli._Default."""

View file

@ -13,6 +13,8 @@ from certbot import util
import certbot.tests.util as test_util
from josepy import interfaces
KEY = test_util.load_vector("rsa512_key.pem")
CSR_SAN = test_util.load_vector("csr-san_512.pem")
@ -64,9 +66,28 @@ class RegisterTest(test_util.ConfigTestCase):
tos_cb = mock.MagicMock()
return register(self.config, self.account_storage, tos_cb)
@staticmethod
def _public_key_mock():
m = mock.Mock(__class__=interfaces.JSONDeSerializable)
m.to_partial_json.return_value = '{"a": 1}'
return m
@staticmethod
def _new_acct_dir_mock():
return "/acme/new-account"
@staticmethod
def _true_mock():
return True
@staticmethod
def _false_mock():
return False
def test_no_tos(self):
with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client:
mock_client.new_account_and_tos().terms_of_service = "http://tos"
mock_client().external_account_required.side_effect = self._false_mock
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
with mock.patch("certbot.account.report_new_account"):
mock_client().new_account_and_tos.side_effect = errors.Error
@ -78,7 +99,8 @@ class RegisterTest(test_util.ConfigTestCase):
self.assertTrue(mock_handle.called)
def test_it(self):
with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2"):
with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client:
mock_client().external_account_required.side_effect = self._false_mock
with mock.patch("certbot.account.report_new_account"):
with mock.patch("certbot.eff.handle_subscription"):
self._call()
@ -91,6 +113,7 @@ class RegisterTest(test_util.ConfigTestCase):
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.BackwardsCompatibleClientV2") as mock_client:
mock_client().external_account_required.side_effect = self._false_mock
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
mock_client().new_account_and_tos.side_effect = [mx_err, mock.MagicMock()]
self._call()
@ -104,6 +127,7 @@ class RegisterTest(test_util.ConfigTestCase):
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.BackwardsCompatibleClientV2") as mock_client:
mock_client().external_account_required.side_effect = self._false_mock
with mock.patch("certbot.eff.handle_subscription"):
mock_client().new_account_and_tos.side_effect = [mx_err, mock.MagicMock()]
self.assertRaises(errors.Error, self._call)
@ -115,7 +139,8 @@ class RegisterTest(test_util.ConfigTestCase):
@mock.patch("certbot.client.logger")
def test_without_email(self, mock_logger):
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2"):
with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_clnt:
mock_clnt().external_account_required.side_effect = self._false_mock
with mock.patch("certbot.account.report_new_account"):
self.config.email = None
self.config.register_unsafely_without_email = True
@ -129,6 +154,7 @@ class RegisterTest(test_util.ConfigTestCase):
def test_dry_run_no_staging_account(self, _rep, mock_get_email):
"""Tests dry-run for no staging account, expect account created with no email"""
with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client:
mock_client().external_account_required.side_effect = self._false_mock
with mock.patch("certbot.eff.handle_subscription"):
with mock.patch("certbot.account.report_new_account"):
self.config.dry_run = True
@ -138,11 +164,53 @@ class RegisterTest(test_util.ConfigTestCase):
# check Certbot created an account with no email. Contact should return empty
self.assertFalse(mock_client().new_account_and_tos.call_args[0][0].contact)
def test_with_eab_arguments(self):
with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client:
mock_client().client.directory.__getitem__ = mock.Mock(
side_effect=self._new_acct_dir_mock
)
mock_client().external_account_required.side_effect = self._false_mock
with mock.patch("certbot.eff.handle_subscription"):
target = "certbot.client.messages.ExternalAccountBinding.from_data"
with mock.patch(target) as mock_eab_from_data:
self.config.eab_kid = "test-kid"
self.config.eab_hmac_key = "J2OAqW4MHXsrHVa_PVg0Y-L_R4SYw0_aL1le6mfblbE"
self._call()
self.assertTrue(mock_eab_from_data.called)
def test_without_eab_arguments(self):
with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client:
mock_client().external_account_required.side_effect = self._false_mock
with mock.patch("certbot.eff.handle_subscription"):
target = "certbot.client.messages.ExternalAccountBinding.from_data"
with mock.patch(target) as mock_eab_from_data:
self.config.eab_kid = None
self.config.eab_hmac_key = None
self._call()
self.assertFalse(mock_eab_from_data.called)
def test_external_account_required_without_eab_arguments(self):
with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client:
mock_client().client.net.key.public_key = mock.Mock(side_effect=self._public_key_mock)
mock_client().external_account_required.side_effect = self._true_mock
with mock.patch("certbot.eff.handle_subscription"):
with mock.patch("certbot.client.messages.ExternalAccountBinding.from_data"):
self.config.eab_kid = None
self.config.eab_hmac_key = None
self.assertRaises(errors.Error, self._call)
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.BackwardsCompatibleClientV2") as mock_client:
mock_client().client.directory.__getitem__ = mock.Mock(
side_effect=self._new_acct_dir_mock
)
mock_client().external_account_required.side_effect = self._false_mock
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
mock_client().new_account_and_tos.side_effect = [mx_err, mock.MagicMock()]
self.assertRaises(messages.Error, self._call)
@ -487,7 +555,7 @@ class EnhanceConfigTest(ClientTestCommon):
self.config.hsts = True
self._test_with_already_existing()
self.assertTrue(mock_log.warning.called)
self.assertEquals(mock_log.warning.call_args[0][1],
self.assertEqual(mock_log.warning.call_args[0][1],
'Strict-Transport-Security')
@mock.patch("certbot.client.logger")
@ -495,7 +563,7 @@ class EnhanceConfigTest(ClientTestCommon):
self.config.redirect = True
self._test_with_already_existing()
self.assertTrue(mock_log.warning.called)
self.assertEquals(mock_log.warning.call_args[0][1],
self.assertEqual(mock_log.warning.call_args[0][1],
'redirect')
def test_no_ask_hsts(self):

View file

@ -502,9 +502,9 @@ class ChooseValuesTest(unittest.TestCase):
items = ["first", "second", "third"]
mock_util().checklist.return_value = (display_util.OK, [items[2]])
result = self._call(items, None)
self.assertEquals(result, [items[2]])
self.assertEqual(result, [items[2]])
self.assertTrue(mock_util().checklist.called)
self.assertEquals(mock_util().checklist.call_args[0][0], None)
self.assertEqual(mock_util().checklist.call_args[0][0], None)
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_choose_names_success_question(self, mock_util):
@ -512,9 +512,9 @@ class ChooseValuesTest(unittest.TestCase):
question = "Which one?"
mock_util().checklist.return_value = (display_util.OK, [items[1]])
result = self._call(items, question)
self.assertEquals(result, [items[1]])
self.assertEqual(result, [items[1]])
self.assertTrue(mock_util().checklist.called)
self.assertEquals(mock_util().checklist.call_args[0][0], question)
self.assertEqual(mock_util().checklist.call_args[0][0], question)
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_choose_names_user_cancel(self, mock_util):
@ -522,9 +522,9 @@ class ChooseValuesTest(unittest.TestCase):
question = "Want to cancel?"
mock_util().checklist.return_value = (display_util.CANCEL, [])
result = self._call(items, question)
self.assertEquals(result, [])
self.assertEqual(result, [])
self.assertTrue(mock_util().checklist.called)
self.assertEquals(mock_util().checklist.call_args[0][0], question)
self.assertEqual(mock_util().checklist.call_args[0][0], question)
if __name__ == "__main__":

View file

@ -49,6 +49,7 @@ class InputWithTimeoutTest(unittest.TestCase):
stdin.listen(1)
with mock.patch("certbot.display.util.sys.stdin", stdin):
self.assertRaises(errors.Error, self._call, timeout=0.001)
stdin.close()
class FileOutputDisplayTest(unittest.TestCase):
@ -314,7 +315,11 @@ class FileOutputDisplayTest(unittest.TestCase):
# Every IDisplay method implemented by FileDisplay must take
# force_interactive to prevent workflow regressions.
for name in interfaces.IDisplay.names(): # pylint: disable=no-member
arg_spec = inspect.getargspec(getattr(self.displayer, name))
if six.PY2:
getargspec = inspect.getargspec # pylint: disable=no-member
else:
getargspec = inspect.getfullargspec # pylint: disable=no-member
arg_spec = getargspec(getattr(self.displayer, name))
self.assertTrue("force_interactive" in arg_spec.args)
@ -371,7 +376,12 @@ class NoninteractiveDisplayTest(unittest.TestCase):
for name in interfaces.IDisplay.names(): # pylint: disable=no-member
method = getattr(self.displayer, name)
# asserts method accepts arbitrary keyword arguments
self.assertFalse(inspect.getargspec(method).keywords is None)
if six.PY2:
result = inspect.getargspec(method).keywords # pylint: disable=no-member
self.assertFalse(result is None)
else:
result = inspect.getfullargspec(method).varkw # pylint: disable=no-member
self.assertFalse(result is None)
class SeparateListInputTest(unittest.TestCase):

View file

@ -86,6 +86,7 @@ class PostArgParseSetupTest(test_util.ConfigTestCase):
self.memory_handler.close()
self.stream_handler.close()
self.temp_handler.close()
self.devnull.close()
super(PostArgParseSetupTest, self).tearDown()
def test_common(self):

View file

@ -520,6 +520,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
'--work-dir', self.config.work_dir,
'--logs-dir', self.config.logs_dir, '--text']
self.mock_sleep = mock.patch('time.sleep').start()
def tearDown(self):
# Reset globals in cli
reload_module(cli)
@ -944,8 +946,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
@mock.patch('certbot.crypto_util.notAfter')
@test_util.patch_get_utility()
def test_certonly_new_request_success(self, mock_get_utility, mock_notAfter):
cert_path = '/etc/letsencrypt/live/foo.bar'
key_path = '/etc/letsencrypt/live/baz.qux'
cert_path = os.path.normpath(os.path.join(self.config.config_dir, 'live/foo.bar'))
key_path = os.path.normpath(os.path.join(self.config.config_dir, 'live/baz.qux'))
date = '1970-01-01'
mock_notAfter().date.return_value = date
@ -975,7 +977,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
reuse_key=False):
# pylint: disable=too-many-locals,too-many-arguments,too-many-branches
cert_path = test_util.vector_path('cert_512.pem')
chain_path = '/etc/letsencrypt/live/foo.bar/fullchain.pem'
chain_path = os.path.normpath(os.path.join(self.config.config_dir,
'live/foo.bar/fullchain.pem'))
mock_lineage = mock.MagicMock(cert=cert_path, fullchain=chain_path,
cert_path=cert_path, fullchain_path=chain_path)
mock_lineage.should_autorenew.return_value = due_for_renewal
@ -1092,6 +1095,26 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
args = ["renew", "--reuse-key"]
self._test_renewal_common(True, [], args=args, should_renew=True, reuse_key=True)
@mock.patch('sys.stdin')
def test_noninteractive_renewal_delay(self, stdin):
stdin.isatty.return_value = False
test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf')
args = ["renew", "--dry-run", "-tvv"]
self._test_renewal_common(True, [], args=args, should_renew=True)
self.assertEqual(self.mock_sleep.call_count, 1)
# in main.py:
# sleep_time = random.randint(1, 60*8)
sleep_call_arg = self.mock_sleep.call_args[0][0]
self.assertTrue(1 <= sleep_call_arg <= 60*8)
@mock.patch('sys.stdin')
def test_interactive_no_renewal_delay(self, stdin):
stdin.isatty.return_value = True
test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf')
args = ["renew", "--dry-run", "-tvv"]
self._test_renewal_common(True, [], args=args, should_renew=True)
self.assertEqual(self.mock_sleep.call_count, 0)
@mock.patch('certbot.renewal.should_renew')
def test_renew_skips_recent_certs(self, should_renew):
should_renew.return_value = False
@ -1375,7 +1398,20 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
x = self._call_no_clientmock(["register", "--email", "user@example.org"])
self.assertTrue("There is an existing account" in x[0])
def test_update_registration_no_existing_accounts(self):
def test_update_account_no_existing_accounts(self):
# with mock.patch('certbot.main.client') as mocked_client:
with mock.patch('certbot.main.account') as mocked_account:
mocked_storage = mock.MagicMock()
mocked_account.AccountFileStorage.return_value = mocked_storage
mocked_storage.find_all.return_value = []
x = self._call_no_clientmock(
["update_account", "--email",
"user@example.org"])
self.assertTrue("Could not find an existing account" in x[0])
# TODO: When `certbot register --update-registration` is fully deprecated,
# delete the following test
def test_update_registration_no_existing_accounts_deprecated(self):
# with mock.patch('certbot.main.client') as mocked_client:
with mock.patch('certbot.main.account') as mocked_account:
mocked_storage = mock.MagicMock()
@ -1386,7 +1422,9 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
"user@example.org"])
self.assertTrue("Could not find an existing account" in x[0])
def test_update_registration_unsafely(self):
# TODO: When `certbot register --update-registration` is fully deprecated,
# delete the following test
def test_update_registration_unsafely_deprecated(self):
# This test will become obsolete when register --update-registration
# supports removing an e-mail address from the account
with mock.patch('certbot.main.account') as mocked_account:
@ -1400,7 +1438,39 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
@mock.patch('certbot.main.display_ops.get_email')
@test_util.patch_get_utility()
def test_update_registration_with_email(self, mock_utility, mock_email):
def test_update_account_with_email(self, mock_utility, mock_email):
email = "user@example.com"
mock_email.return_value = email
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
mocked_storage.find_all.return_value = ["an account"]
mocked_det.return_value = (mock.MagicMock(), "foo")
cb_client = mock.MagicMock()
mocked_client.Client.return_value = cb_client
x = self._call_no_clientmock(
["update_account"])
# When registration change succeeds, the return value
# of register() is None
self.assertTrue(x[0] is None)
# and we got supposedly did update the registration from
# the server
self.assertTrue(
cb_client.acme.update_registration.called)
# and we saved the updated registration on disk
self.assertTrue(mocked_storage.save_regr.called)
self.assertTrue(
email in mock_utility().add_message.call_args[0][0])
self.assertTrue(mock_handle.called)
# TODO: When `certbot register --update-registration` is fully deprecated,
# delete the following test
@mock.patch('certbot.main.display_ops.get_email')
@test_util.patch_get_utility()
def test_update_registration_with_email_deprecated(self, mock_utility, mock_email):
email = "user@example.com"
mock_email.return_value = email
with mock.patch('certbot.eff.handle_subscription') as mock_handle:
@ -1652,7 +1722,7 @@ class EnhanceTest(test_util.ConfigTestCase):
mock_lineage.return_value = mock.MagicMock(chain_path="/tmp/nonexistent")
self._call(['enhance', '--auto-hsts'])
self.assertTrue(self.mockinstaller.enable_autohsts.called)
self.assertEquals(self.mockinstaller.enable_autohsts.call_args[0][1],
self.assertEqual(self.mockinstaller.enable_autohsts.call_args[0][1],
["example.com", "another.tld"])
@mock.patch('certbot.cert_manager.lineage_for_certname')

View file

@ -96,15 +96,15 @@ class OCSPTest(unittest.TestCase):
self.assertEqual(ocsp._translate_ocsp_query(*openssl_happy), False)
self.assertEqual(ocsp._translate_ocsp_query(*openssl_confused), False)
self.assertEqual(mock_log.debug.call_count, 1)
self.assertEqual(mock_log.warn.call_count, 0)
self.assertEqual(mock_log.warning.call_count, 0)
mock_log.debug.call_count = 0
self.assertEqual(ocsp._translate_ocsp_query(*openssl_unknown), False)
self.assertEqual(mock_log.debug.call_count, 1)
self.assertEqual(mock_log.warn.call_count, 0)
self.assertEqual(mock_log.warning.call_count, 0)
self.assertEqual(ocsp._translate_ocsp_query(*openssl_expired_ocsp), False)
self.assertEqual(mock_log.debug.call_count, 2)
self.assertEqual(ocsp._translate_ocsp_query(*openssl_broken), False)
self.assertEqual(mock_log.warn.call_count, 1)
self.assertEqual(mock_log.warning.call_count, 1)
mock_log.info.call_count = 0
self.assertEqual(ocsp._translate_ocsp_query(*openssl_revoked), True)
self.assertEqual(mock_log.info.call_count, 0)

View file

@ -53,7 +53,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase):
self.config.dry_run = True
updater.run_generic_updaters(self.config, None, None)
self.assertTrue(mock_log.called)
self.assertEquals(mock_log.call_args[0][0],
self.assertEqual(mock_log.call_args[0][0],
"Skipping updaters in dry-run mode.")
@mock.patch("certbot.updater.logger.debug")
@ -61,7 +61,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase):
self.config.dry_run = True
updater.run_renewal_deployer(self.config, None, None)
self.assertTrue(mock_log.called)
self.assertEquals(mock_log.call_args[0][0],
self.assertEqual(mock_log.call_args[0][0],
"Skipping renewal deployer in dry-run mode.")
@mock.patch('certbot.plugins.selection.get_unprepared_installer')

View file

@ -13,6 +13,7 @@ import six
import certbot
from certbot import cli
from certbot import compat
from certbot import errors
from certbot.storage import ALL_FOUR
@ -73,9 +74,8 @@ class BaseRenewableCertTest(test_util.ConfigTestCase):
# We also create a file that isn't a renewal config in the same
# location to test that logic that reads in all-and-only renewal
# configs will ignore it and NOT attempt to parse it.
junk = open(os.path.join(self.config.config_dir, "renewal", "IGNORE.THIS"), "w")
junk.write("This file should be ignored!")
junk.close()
with open(os.path.join(self.config.config_dir, "renewal", "IGNORE.THIS"), "w") as junk:
junk.write("This file should be ignored!")
self.defaults = configobj.ConfigObj()
@ -92,6 +92,8 @@ class BaseRenewableCertTest(test_util.ConfigTestCase):
link)
with open(link, "wb") as f:
f.write(kind.encode('ascii') if value is None else value)
if kind == "privkey":
os.chmod(link, 0o600)
def _write_out_ex_kinds(self):
for kind in ALL_FOUR:
@ -264,12 +266,12 @@ class RenewableCertTests(BaseRenewableCertTest):
mock_has_pending.return_value = False
self.assertEqual(self.test_rc.ensure_deployed(), True)
self.assertEqual(mock_update.call_count, 0)
self.assertEqual(mock_logger.warn.call_count, 0)
self.assertEqual(mock_logger.warning.call_count, 0)
mock_has_pending.return_value = True
self.assertEqual(self.test_rc.ensure_deployed(), False)
self.assertEqual(mock_update.call_count, 1)
self.assertEqual(mock_logger.warn.call_count, 1)
self.assertEqual(mock_logger.warning.call_count, 1)
def test_update_link_to(self):
@ -544,6 +546,47 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertFalse(os.path.islink(self.test_rc.version("privkey", 10)))
self.assertFalse(os.path.exists(temp_config_file))
@test_util.broken_on_windows
@mock.patch("certbot.storage.relevant_values")
def test_save_successor_maintains_group_mode(self, mock_rv):
# Mock relevant_values() to claim that all values are relevant here
# (to avoid instantiating parser)
mock_rv.side_effect = lambda x: x
for kind in ALL_FOUR:
self._write_out_kind(kind, 1)
self.test_rc.update_all_links_to(1)
self.assertTrue(compat.compare_file_modes(
os.stat(self.test_rc.version("privkey", 1)).st_mode, 0o600))
os.chmod(self.test_rc.version("privkey", 1), 0o444)
# If no new key, permissions should be the same (we didn't write any keys)
self.test_rc.save_successor(1, b"newcert", None, b"new chain", self.config)
self.assertTrue(compat.compare_file_modes(
os.stat(self.test_rc.version("privkey", 2)).st_mode, 0o444))
# If new key, permissions should be kept as 644
self.test_rc.save_successor(2, b"newcert", b"new_privkey", b"new chain", self.config)
self.assertTrue(compat.compare_file_modes(
os.stat(self.test_rc.version("privkey", 3)).st_mode, 0o644))
# If permissions reverted, next renewal will also revert permissions of new key
os.chmod(self.test_rc.version("privkey", 3), 0o400)
self.test_rc.save_successor(3, b"newcert", b"new_privkey", b"new chain", self.config)
self.assertTrue(compat.compare_file_modes(
os.stat(self.test_rc.version("privkey", 4)).st_mode, 0o600))
@test_util.broken_on_windows
@mock.patch("certbot.storage.relevant_values")
@mock.patch("certbot.storage.os.chown")
def test_save_successor_maintains_gid(self, mock_chown, mock_rv):
# Mock relevant_values() to claim that all values are relevant here
# (to avoid instantiating parser)
mock_rv.side_effect = lambda x: x
for kind in ALL_FOUR:
self._write_out_kind(kind, 1)
self.test_rc.update_all_links_to(1)
self.test_rc.save_successor(1, b"newcert", None, b"new chain", self.config)
self.assertFalse(mock_chown.called)
self.test_rc.save_successor(2, b"newcert", b"new_privkey", b"new chain", self.config)
self.assertTrue(mock_chown.called)
def _test_relevant_values_common(self, values):
defaults = dict((option, cli.flag_default(option))
for option in ("authenticator", "installer",
@ -630,6 +673,7 @@ class RenewableCertTests(BaseRenewableCertTest):
self.config.live_dir, "README")))
self.assertTrue(os.path.exists(os.path.join(
self.config.live_dir, "the-lineage.com", "README")))
self.assertTrue(compat.compare_file_modes(os.stat(result.key_path).st_mode, 0o600))
with open(result.fullchain, "rb") as f:
self.assertEqual(f.read(), b"cert" + b"chain")
# Let's do it again and make sure it makes a different lineage

View file

@ -210,16 +210,21 @@ class UniqueFileTest(test_util.TempDirTestCase):
fd, name = self._call()
fd.write("bar")
fd.close()
self.assertEqual(open(name).read(), "bar")
with open(name) as f:
self.assertEqual(f.read(), "bar")
def test_right_mode(self):
self.assertTrue(compat.compare_file_modes(0o700, os.stat(self._call(0o700)[1]).st_mode))
self.assertTrue(compat.compare_file_modes(0o600, os.stat(self._call(0o600)[1]).st_mode))
fd1, name1 = self._call(0o700)
fd2, name2 = self._call(0o600)
self.assertTrue(compat.compare_file_modes(0o700, os.stat(name1).st_mode))
self.assertTrue(compat.compare_file_modes(0o600, os.stat(name2).st_mode))
fd1.close()
fd2.close()
def test_default_exists(self):
name1 = self._call()[1] # create 0000_foo.txt
name2 = self._call()[1]
name3 = self._call()[1]
fd1, name1 = self._call() # create 0000_foo.txt
fd2, name2 = self._call()
fd3, name3 = self._call()
self.assertNotEqual(name1, name2)
self.assertNotEqual(name1, name3)
@ -236,6 +241,10 @@ class UniqueFileTest(test_util.TempDirTestCase):
basename3 = os.path.basename(name3)
self.assertTrue(basename3.endswith("foo.txt"))
fd1.close()
fd2.close()
fd3.close()
try:
file_type = file
@ -255,13 +264,18 @@ class UniqueLineageNameTest(test_util.TempDirTestCase):
f, path = self._call("wow")
self.assertTrue(isinstance(f, file_type))
self.assertEqual(os.path.join(self.tempdir, "wow.conf"), path)
f.close()
def test_multiple(self):
items = []
for _ in six.moves.range(10):
f, name = self._call("wow")
items.append(self._call("wow"))
f, name = items[-1]
self.assertTrue(isinstance(f, file_type))
self.assertTrue(isinstance(name, six.string_types))
self.assertTrue("wow-0009.conf" in name)
for f, _ in items:
f.close()
@mock.patch("certbot.util.os.fdopen")
def test_failure(self, mock_fdopen):

View file

@ -3,10 +3,9 @@ Challenges
To receive a certificate from Let's Encrypt certificate authority (CA), you must pass a *challenge* to
prove you control each of the domain names that will be listed in the certificate. A challenge is one of
three tasks that only someone who controls the domain should be able to accomplish:
a list of specified tasks that only someone who controls the domain should be able to accomplish, such as:
* Posting a specified file in a specified location on a web site (the HTTP-01 challenge)
* Offering a specified temporary certificate on a web site (the TLS-SNI-01 challenge)
* Posting a specified DNS record in the domain name system (the DNS-01 challenge)
Its possible to complete each type of challenge *automatically* (Certbot directly makes the necessary
@ -16,21 +15,21 @@ design favors performing challenges automatically, and this is the normal case f
Some plugins offer an *authenticator*, meaning that they can satisfy challenges:
* Apache plugin: (TLS-SNI-01) Tries to edit your Apache configuration files to temporarily serve
a Certbot-generated certificate for a specified name. Use the Apache plugin when you're running
Certbot on a web server with Apache listening on port 443.
* NGINX plugin: (TLS-SNI-01) Tries to edit your NGINX configuration files to temporarily serve a
Certbot-generated certificate for a specified name. Use the NGINX plugin when you're running
Certbot on a web server with NGINX listening on port 443.
* Apache plugin: (HTTP-01) Tries to edit your Apache configuration files to temporarily serve files to
satisfy challenges from the certificate authority. Use the Apache plugin when you're running Certbot on a
web server with Apache listening on port 80.
* Nginx plugin: (HTTP-01) Tries to edit your nginx configuration files to temporarily serve files to
satisfy challenges from the certificate authority. Use the nginx plugin when you're running Certbot on a
web server with nginx listening on port 80.
* Webroot plugin: (HTTP-01) Tries to place a file where it can be served over HTTP on port 80 by a
web server running on your system. Use the Webroot plugin when you're running Certbot on
a web server with any server application listening on port 80 serving files from a folder on disk in response.
* Standalone plugin: (TLS-SNI-01 or HTTP-01) Tries to run a temporary web server listening on either HTTP on
port 80 (for HTTP-01) or HTTPS on port 443 (for TLS-SNI-01). Use the Standalone plugin if no existing program
is listening to these ports. Choose TLS-SNI-01 or HTTP-01 using the `--preferred-challenges` option.
* Standalone plugin: (HTTP-01) Tries to run a temporary web server listening on HTTP on port 80. Use the
Standalone plugin if no existing program is listening to this port.
* Manual plugin: (DNS-01 or HTTP-01) Either tells you what changes to make to your configuration or updates
your DNS records using an external script (for DNS-01) or your webroot (for HTTP-01). Use the Manual
plugin if you have the technical knowledge to make configuration changes yourself when asked to do so.
plugin if you have the technical knowledge to make configuration changes yourself when asked to do so,
and are prepared to repeat these steps every time the certificate needs to be renewed.
Tips for Challenges
-------------------
@ -63,20 +62,6 @@ HTTP-01 Challenge
* When using the Standalone plugin, make sure another program is not already listening to port 80 on the server.
* When using the Webroot plugin, make sure there is a web server listening on port 80.
TLS-SNI-01 Challenge
~~~~~~~~~~~~~~~~~~~~
* The TLS-SNI-01 challenge doesnt work with content delivery networks (CDNs)
like CloudFlare and Akamai because the domain name is pointed at the CDN, not directly at your server.
* Make sure port 443 is open, publicly reachable from the Internet, and not blocked by a router or firewall.
* When using the Apache plugin, make sure you are running Apache and no other web server on port 443.
* When using the NGINX plugin, make sure you are running NGINX and no other web server on port 443.
* With either the Apache or NGINX plugin, certbot modifies your web server configuration. If you get
an error after successfully completing the challenge, then you have received a certificate but the
plugin was unable to modify your web server configuration, meaning that you'll have to install the certificate manually.
In that case, please file a bug to help us improve certbot!
* When using the Standalone plugin, make sure another program is not already listening to port 443 on the server.
DNS-01 Challenge
~~~~~~~~~~~~~~~~

View file

@ -29,6 +29,7 @@ manage certificates:
manage your account with Let's Encrypt:
register Create a Let's Encrypt ACME account
update_account Update a Let's Encrypt ACME account
--agree-tos Agree to the ACME server's Subscriber Agreement
-m EMAIL Email address for important account notifications
@ -67,6 +68,10 @@ optional arguments:
with the same name. In the case of a name collision it
will append a number like 0001 to the file path name.
(default: Ask)
--eab-kid EAB_KID Key Identifier for External Account Binding (default:
None)
--eab-hmac-key EAB_HMAC_KEY
HMAC key for External Account Binding (default: None)
--cert-name CERTNAME Certificate name to apply. This name is used by
Certbot for housekeeping and in file paths; it doesn't
affect the content of the certificate itself. To see
@ -108,7 +113,7 @@ optional arguments:
case, and to know when to deprecate support for past
Python versions and flags. If you wish to hide this
information from the Let's Encrypt server, set this to
"". (default: CertbotACMEClient/0.28.0
"". (default: CertbotACMEClient/0.30.0
(certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX
Installer/YYY (SUBCOMMAND; flags: FLAGS)
Py/major.minor.patchlevel). The flags encoded in the
@ -355,7 +360,7 @@ revoke:
certificates. (default: None)
register:
Options for account registration & modification
Options for account registration
--register-unsafely-without-email
Specifying this flag enables registering an account
@ -367,11 +372,6 @@ register:
to the Subscriber Agreement will still affect you, and
will be effective 14 days after posting an update to
the web site. (default: False)
--update-registration
With the register verb, indicates that details
associated with an existing registration, such as the
e-mail address, should be updated, rather than
registering a new account. (default: False)
-m EMAIL, --email EMAIL
Email used for registration and recovery contact. Use
comma to register multiple emails, ex:
@ -380,6 +380,9 @@ register:
--no-eff-email Don't share your e-mail address with EFF (default:
None)
update_account:
Options for account modification
unregister:
Options for account deactivation.
@ -473,12 +476,13 @@ plugins:
using Sakura Cloud for DNS). (default: False)
apache:
Apache Web Server plugin - Beta
Apache Web Server plugin
--apache-enmod APACHE_ENMOD
Path to the Apache 'a2enmod' binary (default: None)
Path to the Apache 'a2enmod' binary (default: a2enmod)
--apache-dismod APACHE_DISMOD
Path to the Apache 'a2dismod' binary (default: None)
Path to the Apache 'a2dismod' binary (default:
a2dismod)
--apache-le-vhost-ext APACHE_LE_VHOST_EXT
SSL vhost configuration extension (default: -le-
ssl.conf)
@ -492,25 +496,16 @@ apache:
/var/log/apache2)
--apache-challenge-location APACHE_CHALLENGE_LOCATION
Directory path for challenge configuration (default:
/etc/apache2/other)
/etc/apache2)
--apache-handle-modules APACHE_HANDLE_MODULES
Let installer handle enabling required modules for you
(Only Ubuntu/Debian currently) (default: False)
(Only Ubuntu/Debian currently) (default: True)
--apache-handle-sites APACHE_HANDLE_SITES
Let installer handle enabling sites for you (Only
Ubuntu/Debian currently) (default: False)
Ubuntu/Debian currently) (default: True)
--apache-ctl APACHE_CTL
Full path to Apache control script (default:
apachectl)
certbot-route53:auth:
Obtain certificates using a DNS TXT record (if you are using AWS Route53
for DNS).
--certbot-route53:auth-propagation-seconds CERTBOT_ROUTE53:AUTH_PROPAGATION_SECONDS
The number of seconds to wait for DNS to propagate
before asking the ACME server to verify the DNS
record. (default: 10)
apache2ctl)
dns-cloudflare:
Obtain certificates using a DNS TXT record (if you are using Cloudflare

View file

@ -17,6 +17,8 @@ import os
import re
import sys
import sphinx
here = os.path.abspath(os.path.dirname(__file__))
@ -33,14 +35,13 @@ sys.path.insert(0, os.path.abspath(os.path.join(here, '..')))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = '1.0'
needs_sphinx = '1.2'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.imgconverter',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.coverage',
@ -48,6 +49,9 @@ extensions = [
'repoze.sphinx.autointerface',
]
if sphinx.version_info >= (1, 6):
extensions.append('sphinx.ext.imgconverter')
autodoc_member_order = 'bysource'
autodoc_default_flags = ['show-inheritance', 'private-members']

View file

@ -186,8 +186,8 @@ Authenticators
--------------
Authenticators are plugins that prove control of a domain name by solving a
challenge provided by the ACME server. ACME currently defines three types of
challenges: HTTP, TLS-SNI, and DNS, represented by classes in `acme.challenges`.
challenge provided by the ACME server. ACME currently defines several types of
challenges: HTTP, TLS-SNI (deprecated), TLS-ALPR, and DNS, represented by classes in `acme.challenges`.
An authenticator plugin should implement support for at least one challenge type.
An Authenticator indicates which challenges it supports by implementing
@ -215,7 +215,7 @@ support for IIS, Icecast and Plesk.
Installers and Authenticators will oftentimes be the same class/object
(because for instance both tasks can be performed by a webserver like nginx)
though this is not always the case (the standalone plugin is an authenticator
that listens on port 443, but it cannot install certs; a postfix plugin would
that listens on port 80, but it cannot install certs; a postfix plugin would
be an installer but not an authenticator).
Installers and Authenticators are kept separate because
@ -359,7 +359,10 @@ Steps:
4. Run ``tox --skip-missing-interpreters`` to run the entire test suite
including coverage. The ``--skip-missing-interpreters`` argument ignores
missing versions of Python needed for running the tests. Fix any errors.
5. Submit the PR.
5. Submit the PR. Once your PR is open, please do not force push to the branch
containing your pull request to squash or amend commits. We use `squash
merges <https://github.com/blog/2141-squash-your-commits>`_ on PRs and
rewriting commits makes changes harder to track between reviews.
6. Did your tests pass on Travis? If they didn't, fix any errors.
Asking for help

View file

@ -29,7 +29,7 @@ System Requirements
Certbot currently requires Python 2.7 or 3.4+ running on a UNIX-like operating
system. By default, it requires root access in order to write to
``/etc/letsencrypt``, ``/var/log/letsencrypt``, ``/var/lib/letsencrypt``; to
bind to ports 80 and 443 (if you use the ``standalone`` plugin) and to read and
bind to port 80 (if you use the ``standalone`` plugin) and to read and
modify webserver configurations (if you use the ``apache`` or ``nginx``
plugins). If none of these apply to you, it is theoretically possible to run
without root privileges, but for most users who want to avoid running an ACME

View file

@ -44,14 +44,13 @@ a combination_ of distinct authenticator and installer plugins.
=========== ==== ==== =============================================================== =============================
Plugin Auth Inst Notes Challenge types (and port)
=========== ==== ==== =============================================================== =============================
apache_ Y Y | Automates obtaining and installing a certificate with Apache tls-sni-01_ (443)
apache_ Y Y | Automates obtaining and installing a certificate with Apache http-01_ (80)
| 2.4 on OSes with ``libaugeas0`` 1.0+.
nginx_ Y Y | Automates obtaining and installing a certificate with Nginx. http-01_ (80)
webroot_ Y N | Obtains a certificate by writing to the webroot directory of http-01_ (80)
| an already running webserver.
nginx_ Y Y | Automates obtaining and installing a certificate with Nginx. tls-sni-01_ (443)
| Shipped with Certbot 0.9.0.
standalone_ Y N | Uses a "standalone" webserver to obtain a certificate. http-01_ (80) or
| Requires port 80 or 443 to be available. This is useful on tls-sni-01_ (443)
standalone_ Y N | Uses a "standalone" webserver to obtain a certificate. http-01_ (80)
| Requires port 80 to be available. This is useful on
| systems with no webserver, or when direct integration with
| the local webserver is not supported or not desired.
|dns_plugs| Y N | This category of plugins automates obtaining a certificate by dns-01_ (53)
@ -59,17 +58,17 @@ standalone_ Y N | Uses a "standalone" webserver to obtain a certificate.
| domain. Doing domain validation in this way is
| the only way to obtain wildcard certificates from Let's
| Encrypt.
manual_ Y N | Helps you obtain a certificate by giving you instructions to http-01_ (80),
| perform domain validation yourself. Additionally allows you dns-01_ (53) or
| to specify scripts to automate the validation task in a tls-sni-01_ (443)
manual_ Y N | Helps you obtain a certificate by giving you instructions to http-01_ (80) or
| perform domain validation yourself. Additionally allows you dns-01_ (53)
| to specify scripts to automate the validation task in a
| customized way.
=========== ==== ==== =============================================================== =============================
.. |dns_plugs| replace:: :ref:`DNS plugins <dns_plugins>`
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_ (requiring configuration of a DNS server on
prove you control a domain. The options are http-01_ (which uses port 80)
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``.
@ -78,7 +77,6 @@ There are also many third-party-plugins_ available. Below we describe in more de
the circumstances in which each plugin can be used, and how to use it.
.. _challenges: https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7
.. _tls-sni-01: https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.3
.. _http-01: https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.2
.. _dns-01: https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.4
@ -159,13 +157,9 @@ software running on the machine where you obtain the certificate.
To obtain a certificate using a "standalone" webserver, you can use the
standalone plugin by including ``certonly`` and ``--standalone``
on the command line. This plugin needs to bind to port 80 or 443 in
on the command line. This plugin needs to bind to port 80 in
order to perform domain validation, so you may need to stop your
existing webserver. To control which port the plugin uses, include
one of the options shown below on the command line.
* ``--preferred-challenges http`` to use port 80
* ``--preferred-challenges tls-sni`` to use port 443
existing webserver.
It must still be possible for your machine to accept inbound connections from
the Internet on the specified port using each requested domain name.
@ -222,8 +216,7 @@ the UI, you can use the plugin to obtain a certificate by specifying
to copy and paste commands into another terminal session, which may
be on a different computer.
The manual plugin can use either the ``http``, ``dns`` or the
``tls-sni`` challenge. You can use the ``--preferred-challenges`` option
The manual plugin can use either the ``http`` or the ``dns`` challenge. You can use the ``--preferred-challenges`` option
to choose the challenge of your preference.
The ``http`` challenge will ask you to place a file with a specific name and
@ -241,11 +234,6 @@ For example, for the domain ``example.com``, a zone file entry would look like:
_acme-challenge.example.com. 300 IN TXT "gfj9Xq...Rg85nM"
When using the ``tls-sni`` challenge, ``certbot`` will prepare a self-signed
SSL certificate for you with the challenge validation appropriately
encoded into a subjectAlternatNames entry. You will need to configure
your SSL server to present this challenge SSL certificate to the ACME
server using SNI.
Additionally you can specify scripts to prepare for validation and
perform the authentication procedure and/or clean up after it by using
@ -262,16 +250,20 @@ installer plugins. To do so, specify the authenticator plugin with
``--authenticator`` or ``-a`` and the installer plugin with ``--installer`` or
``-i``.
For instance, you may want to create a certificate using the webroot_ plugin
for authentication and the apache_ plugin for installation, perhaps because you
use a proxy or CDN for SSL and only want to secure the connection between them
and your origin server, which cannot use the tls-sni-01_ challenge due to the
intermediate proxy.
For instance, you could create a certificate using the webroot_ plugin
for authentication and the apache_ plugin for installation.
::
certbot run -a webroot -i apache -w /var/www/html -d example.com
Or you could create a certificate using the manual_ plugin for authentication
and the nginx_ plugin for installation. (Note that this certificate cannot
be renewed automatically.)
::
certbot run -a manual -i nginx -d example.com
.. _third-party-plugins:
Third-party plugins
@ -696,7 +688,9 @@ Where are my certificates?
==========================
All generated keys and issued certificates can be found in
``/etc/letsencrypt/live/$domain``. Rather than copying, please point
``/etc/letsencrypt/live/$domain``. In the case of creating a SAN certificate
with multiple alternative names, ``$domain`` is the first domain passed in
via -d parameter. Rather than copying, please point
your (web) server configuration directly to those files (or create
symlinks). During the renewal_, ``/etc/letsencrypt/live`` is updated
with the latest necessary files.
@ -715,6 +709,10 @@ The following files are available:
put it into a safe, however - your server still needs to access
this file in order for SSL/TLS to work.
.. note:: As of Certbot version 0.29.0, private keys for new certificate
default to ``0600``. Any changes to the group mode or group owner (gid)
of this file will be preserved on renewals.
This is what Apache needs for `SSLCertificateKeyFile
<https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatekeyfile>`_,
and Nginx for `ssl_certificate_key
@ -775,9 +773,6 @@ variables to these scripts:
- ``CERTBOT_DOMAIN``: The domain being authenticated
- ``CERTBOT_VALIDATION``: The validation string (HTTP-01 and DNS-01 only)
- ``CERTBOT_TOKEN``: Resource name part of the HTTP-01 challenge (HTTP-01 only)
- ``CERTBOT_CERT_PATH``: The challenge SSL certificate (TLS-SNI-01 only)
- ``CERTBOT_KEY_PATH``: The private key associated with the aforementioned SSL certificate (TLS-SNI-01 only)
- ``CERTBOT_SNI_DOMAIN``: The SNI name for which the ACME server expects to be presented the self-signed certificate located at ``$CERTBOT_CERT_PATH`` (TLS-SNI-01 only)
Additionally for cleanup:

View file

@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
LE_AUTO_VERSION="0.28.0"
LE_AUTO_VERSION="0.30.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -593,8 +593,7 @@ BootstrapArchCommon() {
# - ArchLinux (x86_64)
#
# "python-virtualenv" is Python3, but "python2-virtualenv" provides
# only "virtualenv2" binary, not "virtualenv" necessary in
# ./tools/_venv_common.sh
# only "virtualenv2" binary, not "virtualenv".
deps="
python2
@ -912,6 +911,35 @@ OldVenvExists() {
[ -n "$OLD_VENV_PATH" -a -f "$OLD_VENV_PATH/bin/letsencrypt" ]
}
# Given python path, version 1 and version 2, check if version 1 is outdated compared to version 2.
# An unofficial version provided as version 1 (eg. 0.28.0.dev0) will be treated
# specifically by printing "UNOFFICIAL". Otherwise, print "OUTDATED" if version 1
# is outdated, and "UP_TO_DATE" if not.
# This function relies only on installed python environment (2.x or 3.x) by certbot-auto.
CompareVersions() {
"$1" - "$2" "$3" << "UNLIKELY_EOF"
import sys
from distutils.version import StrictVersion
try:
current = StrictVersion(sys.argv[1])
except ValueError:
sys.stdout.write('UNOFFICIAL')
sys.exit()
try:
remote = StrictVersion(sys.argv[2])
except ValueError:
sys.stdout.write('UP_TO_DATE')
sys.exit()
if current < remote:
sys.stdout.write('OUTDATED')
else:
sys.stdout.write('UP_TO_DATE')
UNLIKELY_EOF
}
if [ "$1" = "--le-auto-phase2" ]; then
# Phase 2: Create venv, install LE, and run.
@ -1017,43 +1045,39 @@ pycparser==2.14 \
asn1crypto==0.22.0 \
--hash=sha256:d232509fefcfcdb9a331f37e9c9dc20441019ad927c7d2176cf18ed5da0ba097 \
--hash=sha256:cbbadd640d3165ab24b06ef25d1dca09a3441611ac15f6a6b452474fdf0aed1a
cffi==1.10.0 \
--hash=sha256:446699c10f3c390633d0722bc19edbc7ac4b94761918a4a4f7908a24e86ebbd0 \
--hash=sha256:562326fc7f55a59ef3fef5e82908fe938cdc4bbda32d734c424c7cd9ed73e93a \
--hash=sha256:7f732ad4a30db0b39400c3f7011249f7d0701007d511bf09604729aea222871f \
--hash=sha256:94fb8410c6c4fc48e7ea759d3d1d9ca561171a88d00faddd4aa0306f698ad6a0 \
--hash=sha256:587a5043df4b00a2130e09fed42da02a4ed3c688bd9bf07a3ac89d2271f4fb07 \
--hash=sha256:ec08b88bef627ec1cea210e1608c85d3cf44893bcde74e41b7f7dbdfd2c1bad6 \
--hash=sha256:a41406f6d62abcdf3eef9fd998d8dcff04fd2a7746644143045feeebd76352d1 \
--hash=sha256:b560916546b2f209d74b82bdbc3223cee9a165b0242fa00a06dfc48a2054864a \
--hash=sha256:e74896774e437f4715c57edeb5cf3d3a40d7727f541c2c12156617b5a15d1829 \
--hash=sha256:9a31c18ba4881a116e448c52f3f5d3e14401cf7a9c43cc88f06f2a7f5428da0e \
--hash=sha256:80796ea68e11624a0279d3b802f88a7fe7214122b97a15a6c97189934a2cc776 \
--hash=sha256:f4019826a2dec066c909a1f483ef0dcf9325d6740cc0bd15308942b28b0930f7 \
--hash=sha256:7248506981eeba23888b4140a69a53c4c0c0a386abcdca61ed8dd790a73e64b9 \
--hash=sha256:a8955265d146e86fe2ce116394be4eaf0cb40314a79b19f11c4fa574cd639572 \
--hash=sha256:c49187260043bd4c1d6a52186f9774f17d9b1da0a406798ebf4bfc12da166ade \
--hash=sha256:c1d8b3d8dcb5c23ac1a8bf56422036f3f305a3c5a8bc8c354256579a1e2aa2c1 \
--hash=sha256:9e389615bcecb8c782a87939d752340bb0a3a097e90bae54d7f0915bc12f45bd \
--hash=sha256:d09ff358f75a874f69fa7d1c2b4acecf4282a950293fcfcf89aa606da8a9a500 \
--hash=sha256:b69b4557aae7de18b7c174a917fe19873529d927ac592762d9771661875bbd40 \
--hash=sha256:5de52b081a2775e76b971de9d997d85c4457fc0a09079e12d66849548ae60981 \
--hash=sha256:e7d88fecb7b6250a1fd432e6dc64890342c372fce13dbfe4bb6f16348ad00c14 \
--hash=sha256:1426e67e855ef7f5030c9184f4f1a9f4bfa020c31c962cd41fd129ec5aef4a6a \
--hash=sha256:267dd2c66a5760c5f4d47e2ebcf8eeac7ef01e1ae6ae7a6d0d241a290068bc38 \
--hash=sha256:e553eb489511cacf19eda6e52bc9e151316f0d721724997dda2c4d3079b778db \
--hash=sha256:98b89b2c57f97ce2db7aeba60db173c84871d73b40e41a11ea95de1500ddc57e \
--hash=sha256:e2b7e090188833bc58b2ae03fb864c22688654ebd2096bcf38bc860c4f38a3d8 \
--hash=sha256:afa7d8b8d38ad40db8713ee053d41b36d87d6ae5ec5ad36f9210b548a18dc214 \
--hash=sha256:4fc9c2ff7924b3a1fa326e1799e5dd58cac585d7fb25fe53ccaa1333b0453d65 \
--hash=sha256:937db39a1ec5af3003b16357b2042bba67c88d43bc11aaa203fa8a5924524209 \
--hash=sha256:ab22285797631df3b513b2cd3ecdc51cd8e3d36788e3991d93d0759d6883b027 \
--hash=sha256:96e599b924ef009aa867f725b3249ee51d76489f484d3a45b4bd219c5ec6ed59 \
--hash=sha256:bea842a0512be6a8007e585790bccd5d530520fc025ce63b03e139be373b0063 \
--hash=sha256:e7175287f7fe7b1cc203bb958b17db40abd732690c1e18e700f10e0843a58598 \
--hash=sha256:285ab352552f52f1398c912556d4d36d4ea9b8450e5c65d03809bf9886755533 \
--hash=sha256:5576644b859197da7bbd8f8c7c2fb5dcc6cd505cadb42992d5f104c013f8a214 \
--hash=sha256:b3b02911eb1f6ada203b0763ba924234629b51586f72a21faacc638269f4ced5
cffi==1.11.5 \
--hash=sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50 \
--hash=sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596 \
--hash=sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef \
--hash=sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743 \
--hash=sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f \
--hash=sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31 \
--hash=sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04 \
--hash=sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6 \
--hash=sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3 \
--hash=sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6 \
--hash=sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b \
--hash=sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca \
--hash=sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e \
--hash=sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb \
--hash=sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd \
--hash=sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1 \
--hash=sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917 \
--hash=sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359 \
--hash=sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f \
--hash=sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95 \
--hash=sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801 \
--hash=sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257 \
--hash=sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184 \
--hash=sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc \
--hash=sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085 \
--hash=sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93 \
--hash=sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2 \
--hash=sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30 \
--hash=sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5 \
--hash=sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e \
--hash=sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b \
--hash=sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4
ConfigArgParse==0.12.0 \
--hash=sha256:28cd7d67669651f2a4518367838c49539457504584a139709b2b8f6c208ef339 \
--no-binary ConfigArgParse
@ -1146,9 +1170,9 @@ pytz==2015.7 \
--hash=sha256:fbd26746772c24cb93c8b97cbdad5cb9e46c86bbdb1b9d8a743ee00e2fb1fc5d \
--hash=sha256:99266ef30a37e43932deec2b7ca73e83c8dbc3b9ff703ec73eca6b1dae6befea \
--hash=sha256:8b6ce1c993909783bc96e0b4f34ea223bff7a4df2c90bdb9c4e0f1ac928689e3
requests==2.12.1 \
--hash=sha256:3f3f27a9d0f9092935efc78054ef324eb9f8166718270aefe036dfa1e4f68e1e \
--hash=sha256:2109ecea94df90980be040490ff1d879971b024861539abb00054062388b612e
requests==2.20.0 \
--hash=sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c \
--hash=sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279
six==1.10.0 \
--hash=sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1 \
--hash=sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a
@ -1185,6 +1209,15 @@ zope.interface==4.1.3 \
requests-toolbelt==0.8.0 \
--hash=sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237 \
--hash=sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5
chardet==3.0.2 \
--hash=sha256:4f7832e7c583348a9eddd927ee8514b3bf717c061f57b21dbe7697211454d9bb \
--hash=sha256:6ebf56457934fdce01fb5ada5582762a84eed94cad43ed877964aebbdd8174c0
urllib3==1.24.1 \
--hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \
--hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22
certifi==2017.4.17 \
--hash=sha256:f4318671072f030a33c7ca6acaef720ddd50ff124d1388e50c1bda4cbd6d7010 \
--hash=sha256:f7527ebf7461582ce95f7a9e03dd141ce810d40590834f4ec20cddd54234c10a
# Contains the requirements for the letsencrypt package.
#
@ -1197,31 +1230,29 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==0.28.0 \
--hash=sha256:f2f7c816acd695cbcda713a779b0db2b08e9de407146b46e1c4ef5561e0f5417 \
--hash=sha256:31e3e2ee2a25c009a621c59ac9182f85d937a897c7bd1d47d0e01f3c712a090a
acme==0.28.0 \
--hash=sha256:d3a564031155fece3f6edce8c5246d4cf34be0977b4c7c5ce84e86c68601c895 \
--hash=sha256:bf7c2f1c24a26ab5b9fce3a6abca1d74a5914d46919649ae00ad5817db62bb85
certbot-apache==0.28.0 \
--hash=sha256:a57d7bac4f13ae5ecea4f4cbd479d381c02316c4832b25ab5a9d7c6826166370 \
--hash=sha256:3f93f5de4a548e973c493a6cac5eeeb3dbbcae2988b61299ea0727d04a00f5bb
certbot-nginx==0.28.0 \
--hash=sha256:1822a65910f0801087fa20a3af3fc94f878f93e0f11809483bb5387df861e296 \
--hash=sha256:426fb403b0a7b203629f4e350a862cbc3bc1f69936fdab8ec7eafe0d8a3b5ddb
certbot==0.30.0 \
--hash=sha256:b3468e128e74d2295598f6d3fbf9d0edfb67fe5abaca3b985a9e858395bd027f \
--hash=sha256:d631fe6c75700ce9b2fdae194ff8b53c7518545d87dd451a1704f7572dcd49e8
acme==0.30.0 \
--hash=sha256:eed9389f802ebf4988c9e43c28ad3d5c2734237371d78e97450a1d61189a15aa \
--hash=sha256:984b6d00bec73dcfa616636a760e80ca14bd246fb908710a656547f542f09445
certbot-apache==0.30.0 \
--hash=sha256:d38c70fc6930db298ea992a3145362eebdce460d3d2651f86a8f2f43d838c6d0 \
--hash=sha256:1d4bc207d53a3e5d37e5d9ebd05f26089aa21d1fbf384113ed9d1829b4d1e9bf
certbot-nginx==0.30.0 \
--hash=sha256:6163c7d0080f59b4ebe510afcc6af2d2eebf15469275c3835866690db4d465d6 \
--hash=sha256:e39a3f3d77cd4c653949cf066fb2211039fd2032665697c27b6e8501c7c2dd92
UNLIKELY_EOF
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/pipstrap.py"
#!/usr/bin/env python
"""A small script that can act as a trust root for installing pip >=8
Embed this in your project, and your VCS checkout is all you have to trust. In
a post-peep era, this lets you claw your way to a hash-checking version of pip,
with which you can install the rest of your dependencies safely. All it assumes
is Python 2.6 or better and *some* version of pip already installed. If
anything goes wrong, it will exit with a non-zero status code.
"""
# This is here so embedded copies are MIT-compliant:
# Copyright (c) 2016 Erik Rose
@ -1348,10 +1379,8 @@ def hashed_download(url, temp, digest):
def get_index_base():
"""Return the URL to the dir containing the "packages" folder.
Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the
end if it's there; that is likely to give us the right dir.
"""
env_var = environ.get('PIP_INDEX_URL', '').rstrip('/')
if env_var:
@ -1641,7 +1670,12 @@ UNLIKELY_EOF
error "WARNING: couldn't find Python $MIN_PYTHON_VERSION+ to check for updates."
elif ! REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version` ; then
error "WARNING: unable to check for updates."
elif [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
fi
LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"`
if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then
say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION"
elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then
say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
# Now we drop into Python so we don't have to install even more

View file

@ -1,11 +1,11 @@
-----BEGIN PGP SIGNATURE-----
iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlvjV5wACgkQTRfJlc2X
dfKkRwf+MJ/Yo5ix7rxGMoliJl3GUUC2KvuYxObvbsAZW69Zl4aZVNeUP3Pe/EZj
zJlSMuiCPeTMmmr0+q78dk5Qk0vf+9D5qSQyy2U+RvPvX6z1PfaFXwjETwOEhE4i
7pABP4m/rIhlZbh336gou4XZK8sXsKHXBLQEyqmzPm6YFZ+5vowIoEinrN73PBuq
rgvoTFKi2NTjYNkQffYUeCIgO0pXlaOa8hkaupqoejHHEjjiXS2C9m0gAT2Wk2cO
zya5WQNcCCLWy/ChhPE2M7yRSpwqrszsHP0qo7QGL8vvsdXvNeJ7vwpAlq/9aipg
PpzSXy/ek8YAgApaj8+/w4OfdDhQ4Q==
=1hD2
iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlwtH9cACgkQTRfJlc2X
dfIqUwf/RXLZAeFF/59PjTAzcV+eEISlvEmFcV0zL3vv23PsY3S5Iuuwcd6rTm5M
UWNtmUTmFVo0xmxAj6Eqfpnt0P+JPpPcnbLNIGKFekBWIshgH84RRFWPJjNh/hu1
pyzkkcWaOB86egdVfjvuRJ0j7AGd0ih6ur2rlgfHVjTYR+0EdWszFDEFBlq8cpct
9d1gCgH7VWKSIQMhzGLMsmdMxNoDl4hiqVPU0FP5/mn2xGF7FgeKNW3+NiTouKuB
mZOeEl3f3uOze/suHPyfOu+49jk+TWWE05Xfqfowjf486nKPg6/uSA2izW/MwIKN
HuIuY3bBf+lx5yUVIraoZhH2MxODDQ==
=BZqz
-----END PGP SIGNATURE-----

View file

@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
LE_AUTO_VERSION="0.29.0.dev0"
LE_AUTO_VERSION="0.31.0.dev0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -593,8 +593,7 @@ BootstrapArchCommon() {
# - ArchLinux (x86_64)
#
# "python-virtualenv" is Python3, but "python2-virtualenv" provides
# only "virtualenv2" binary, not "virtualenv" necessary in
# ./tools/_venv_common.py
# only "virtualenv2" binary, not "virtualenv".
deps="
python2
@ -912,6 +911,35 @@ OldVenvExists() {
[ -n "$OLD_VENV_PATH" -a -f "$OLD_VENV_PATH/bin/letsencrypt" ]
}
# Given python path, version 1 and version 2, check if version 1 is outdated compared to version 2.
# An unofficial version provided as version 1 (eg. 0.28.0.dev0) will be treated
# specifically by printing "UNOFFICIAL". Otherwise, print "OUTDATED" if version 1
# is outdated, and "UP_TO_DATE" if not.
# This function relies only on installed python environment (2.x or 3.x) by certbot-auto.
CompareVersions() {
"$1" - "$2" "$3" << "UNLIKELY_EOF"
import sys
from distutils.version import StrictVersion
try:
current = StrictVersion(sys.argv[1])
except ValueError:
sys.stdout.write('UNOFFICIAL')
sys.exit()
try:
remote = StrictVersion(sys.argv[2])
except ValueError:
sys.stdout.write('UP_TO_DATE')
sys.exit()
if current < remote:
sys.stdout.write('OUTDATED')
else:
sys.stdout.write('UP_TO_DATE')
UNLIKELY_EOF
}
if [ "$1" = "--le-auto-phase2" ]; then
# Phase 2: Create venv, install LE, and run.
@ -1017,43 +1045,39 @@ pycparser==2.14 \
asn1crypto==0.22.0 \
--hash=sha256:d232509fefcfcdb9a331f37e9c9dc20441019ad927c7d2176cf18ed5da0ba097 \
--hash=sha256:cbbadd640d3165ab24b06ef25d1dca09a3441611ac15f6a6b452474fdf0aed1a
cffi==1.10.0 \
--hash=sha256:446699c10f3c390633d0722bc19edbc7ac4b94761918a4a4f7908a24e86ebbd0 \
--hash=sha256:562326fc7f55a59ef3fef5e82908fe938cdc4bbda32d734c424c7cd9ed73e93a \
--hash=sha256:7f732ad4a30db0b39400c3f7011249f7d0701007d511bf09604729aea222871f \
--hash=sha256:94fb8410c6c4fc48e7ea759d3d1d9ca561171a88d00faddd4aa0306f698ad6a0 \
--hash=sha256:587a5043df4b00a2130e09fed42da02a4ed3c688bd9bf07a3ac89d2271f4fb07 \
--hash=sha256:ec08b88bef627ec1cea210e1608c85d3cf44893bcde74e41b7f7dbdfd2c1bad6 \
--hash=sha256:a41406f6d62abcdf3eef9fd998d8dcff04fd2a7746644143045feeebd76352d1 \
--hash=sha256:b560916546b2f209d74b82bdbc3223cee9a165b0242fa00a06dfc48a2054864a \
--hash=sha256:e74896774e437f4715c57edeb5cf3d3a40d7727f541c2c12156617b5a15d1829 \
--hash=sha256:9a31c18ba4881a116e448c52f3f5d3e14401cf7a9c43cc88f06f2a7f5428da0e \
--hash=sha256:80796ea68e11624a0279d3b802f88a7fe7214122b97a15a6c97189934a2cc776 \
--hash=sha256:f4019826a2dec066c909a1f483ef0dcf9325d6740cc0bd15308942b28b0930f7 \
--hash=sha256:7248506981eeba23888b4140a69a53c4c0c0a386abcdca61ed8dd790a73e64b9 \
--hash=sha256:a8955265d146e86fe2ce116394be4eaf0cb40314a79b19f11c4fa574cd639572 \
--hash=sha256:c49187260043bd4c1d6a52186f9774f17d9b1da0a406798ebf4bfc12da166ade \
--hash=sha256:c1d8b3d8dcb5c23ac1a8bf56422036f3f305a3c5a8bc8c354256579a1e2aa2c1 \
--hash=sha256:9e389615bcecb8c782a87939d752340bb0a3a097e90bae54d7f0915bc12f45bd \
--hash=sha256:d09ff358f75a874f69fa7d1c2b4acecf4282a950293fcfcf89aa606da8a9a500 \
--hash=sha256:b69b4557aae7de18b7c174a917fe19873529d927ac592762d9771661875bbd40 \
--hash=sha256:5de52b081a2775e76b971de9d997d85c4457fc0a09079e12d66849548ae60981 \
--hash=sha256:e7d88fecb7b6250a1fd432e6dc64890342c372fce13dbfe4bb6f16348ad00c14 \
--hash=sha256:1426e67e855ef7f5030c9184f4f1a9f4bfa020c31c962cd41fd129ec5aef4a6a \
--hash=sha256:267dd2c66a5760c5f4d47e2ebcf8eeac7ef01e1ae6ae7a6d0d241a290068bc38 \
--hash=sha256:e553eb489511cacf19eda6e52bc9e151316f0d721724997dda2c4d3079b778db \
--hash=sha256:98b89b2c57f97ce2db7aeba60db173c84871d73b40e41a11ea95de1500ddc57e \
--hash=sha256:e2b7e090188833bc58b2ae03fb864c22688654ebd2096bcf38bc860c4f38a3d8 \
--hash=sha256:afa7d8b8d38ad40db8713ee053d41b36d87d6ae5ec5ad36f9210b548a18dc214 \
--hash=sha256:4fc9c2ff7924b3a1fa326e1799e5dd58cac585d7fb25fe53ccaa1333b0453d65 \
--hash=sha256:937db39a1ec5af3003b16357b2042bba67c88d43bc11aaa203fa8a5924524209 \
--hash=sha256:ab22285797631df3b513b2cd3ecdc51cd8e3d36788e3991d93d0759d6883b027 \
--hash=sha256:96e599b924ef009aa867f725b3249ee51d76489f484d3a45b4bd219c5ec6ed59 \
--hash=sha256:bea842a0512be6a8007e585790bccd5d530520fc025ce63b03e139be373b0063 \
--hash=sha256:e7175287f7fe7b1cc203bb958b17db40abd732690c1e18e700f10e0843a58598 \
--hash=sha256:285ab352552f52f1398c912556d4d36d4ea9b8450e5c65d03809bf9886755533 \
--hash=sha256:5576644b859197da7bbd8f8c7c2fb5dcc6cd505cadb42992d5f104c013f8a214 \
--hash=sha256:b3b02911eb1f6ada203b0763ba924234629b51586f72a21faacc638269f4ced5
cffi==1.11.5 \
--hash=sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50 \
--hash=sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596 \
--hash=sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef \
--hash=sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743 \
--hash=sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f \
--hash=sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31 \
--hash=sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04 \
--hash=sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6 \
--hash=sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3 \
--hash=sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6 \
--hash=sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b \
--hash=sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca \
--hash=sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e \
--hash=sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb \
--hash=sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd \
--hash=sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1 \
--hash=sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917 \
--hash=sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359 \
--hash=sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f \
--hash=sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95 \
--hash=sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801 \
--hash=sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257 \
--hash=sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184 \
--hash=sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc \
--hash=sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085 \
--hash=sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93 \
--hash=sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2 \
--hash=sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30 \
--hash=sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5 \
--hash=sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e \
--hash=sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b \
--hash=sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4
ConfigArgParse==0.12.0 \
--hash=sha256:28cd7d67669651f2a4518367838c49539457504584a139709b2b8f6c208ef339 \
--no-binary ConfigArgParse
@ -1146,9 +1170,9 @@ pytz==2015.7 \
--hash=sha256:fbd26746772c24cb93c8b97cbdad5cb9e46c86bbdb1b9d8a743ee00e2fb1fc5d \
--hash=sha256:99266ef30a37e43932deec2b7ca73e83c8dbc3b9ff703ec73eca6b1dae6befea \
--hash=sha256:8b6ce1c993909783bc96e0b4f34ea223bff7a4df2c90bdb9c4e0f1ac928689e3
requests==2.12.1 \
--hash=sha256:3f3f27a9d0f9092935efc78054ef324eb9f8166718270aefe036dfa1e4f68e1e \
--hash=sha256:2109ecea94df90980be040490ff1d879971b024861539abb00054062388b612e
requests==2.20.0 \
--hash=sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c \
--hash=sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279
six==1.10.0 \
--hash=sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1 \
--hash=sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a
@ -1185,6 +1209,15 @@ zope.interface==4.1.3 \
requests-toolbelt==0.8.0 \
--hash=sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237 \
--hash=sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5
chardet==3.0.2 \
--hash=sha256:4f7832e7c583348a9eddd927ee8514b3bf717c061f57b21dbe7697211454d9bb \
--hash=sha256:6ebf56457934fdce01fb5ada5582762a84eed94cad43ed877964aebbdd8174c0
urllib3==1.24.1 \
--hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \
--hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22
certifi==2017.4.17 \
--hash=sha256:f4318671072f030a33c7ca6acaef720ddd50ff124d1388e50c1bda4cbd6d7010 \
--hash=sha256:f7527ebf7461582ce95f7a9e03dd141ce810d40590834f4ec20cddd54234c10a
# Contains the requirements for the letsencrypt package.
#
@ -1197,31 +1230,29 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==0.28.0 \
--hash=sha256:f2f7c816acd695cbcda713a779b0db2b08e9de407146b46e1c4ef5561e0f5417 \
--hash=sha256:31e3e2ee2a25c009a621c59ac9182f85d937a897c7bd1d47d0e01f3c712a090a
acme==0.28.0 \
--hash=sha256:d3a564031155fece3f6edce8c5246d4cf34be0977b4c7c5ce84e86c68601c895 \
--hash=sha256:bf7c2f1c24a26ab5b9fce3a6abca1d74a5914d46919649ae00ad5817db62bb85
certbot-apache==0.28.0 \
--hash=sha256:a57d7bac4f13ae5ecea4f4cbd479d381c02316c4832b25ab5a9d7c6826166370 \
--hash=sha256:3f93f5de4a548e973c493a6cac5eeeb3dbbcae2988b61299ea0727d04a00f5bb
certbot-nginx==0.28.0 \
--hash=sha256:1822a65910f0801087fa20a3af3fc94f878f93e0f11809483bb5387df861e296 \
--hash=sha256:426fb403b0a7b203629f4e350a862cbc3bc1f69936fdab8ec7eafe0d8a3b5ddb
certbot==0.30.0 \
--hash=sha256:b3468e128e74d2295598f6d3fbf9d0edfb67fe5abaca3b985a9e858395bd027f \
--hash=sha256:d631fe6c75700ce9b2fdae194ff8b53c7518545d87dd451a1704f7572dcd49e8
acme==0.30.0 \
--hash=sha256:eed9389f802ebf4988c9e43c28ad3d5c2734237371d78e97450a1d61189a15aa \
--hash=sha256:984b6d00bec73dcfa616636a760e80ca14bd246fb908710a656547f542f09445
certbot-apache==0.30.0 \
--hash=sha256:d38c70fc6930db298ea992a3145362eebdce460d3d2651f86a8f2f43d838c6d0 \
--hash=sha256:1d4bc207d53a3e5d37e5d9ebd05f26089aa21d1fbf384113ed9d1829b4d1e9bf
certbot-nginx==0.30.0 \
--hash=sha256:6163c7d0080f59b4ebe510afcc6af2d2eebf15469275c3835866690db4d465d6 \
--hash=sha256:e39a3f3d77cd4c653949cf066fb2211039fd2032665697c27b6e8501c7c2dd92
UNLIKELY_EOF
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/pipstrap.py"
#!/usr/bin/env python
"""A small script that can act as a trust root for installing pip >=8
Embed this in your project, and your VCS checkout is all you have to trust. In
a post-peep era, this lets you claw your way to a hash-checking version of pip,
with which you can install the rest of your dependencies safely. All it assumes
is Python 2.6 or better and *some* version of pip already installed. If
anything goes wrong, it will exit with a non-zero status code.
"""
# This is here so embedded copies are MIT-compliant:
# Copyright (c) 2016 Erik Rose
@ -1260,7 +1291,7 @@ except ImportError:
cmd = popenargs[0]
raise CalledProcessError(retcode, cmd)
return output
from sys import exit, version_info, executable
from sys import exit, version_info
from tempfile import mkdtemp
try:
from urllib2 import build_opener, HTTPHandler, HTTPSHandler
@ -1272,7 +1303,7 @@ except ImportError:
from urllib.parse import urlparse # 3.4
__version__ = 2, 0, 0
__version__ = 1, 5, 1
PIP_VERSION = '9.0.1'
DEFAULT_INDEX_BASE = 'https://pypi.python.org'
@ -1348,10 +1379,8 @@ def hashed_download(url, temp, digest):
def get_index_base():
"""Return the URL to the dir containing the "packages" folder.
Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the
end if it's there; that is likely to give us the right dir.
"""
env_var = environ.get('PIP_INDEX_URL', '').rstrip('/')
if env_var:
@ -1365,7 +1394,7 @@ def get_index_base():
def main():
pip_version = StrictVersion(check_output([executable, '-m', 'pip', '--version'])
pip_version = StrictVersion(check_output(['pip', '--version'])
.decode('utf-8').split()[1])
min_pip_version = StrictVersion(PIP_VERSION)
if pip_version >= min_pip_version:
@ -1378,7 +1407,7 @@ def main():
temp,
digest)
for path, digest in PACKAGES]
check_output('{0} -m pip install --no-index --no-deps -U '.format(quote(executable)) +
check_output('pip install --no-index --no-deps -U ' +
# Disable cache since we're not using it and it otherwise
# sometimes throws permission warnings:
('--no-cache-dir ' if has_pip_cache else '') +
@ -1397,6 +1426,7 @@ def main():
if __name__ == '__main__':
exit(main())
UNLIKELY_EOF
# -------------------------------------------------------------------------
# Set PATH so pipstrap upgrades the right (v)env:
@ -1640,7 +1670,12 @@ UNLIKELY_EOF
error "WARNING: couldn't find Python $MIN_PYTHON_VERSION+ to check for updates."
elif ! REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version` ; then
error "WARNING: unable to check for updates."
elif [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
fi
LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"`
if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then
say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION"
elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then
say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
# Now we drop into Python so we don't have to install even more

View file

@ -451,6 +451,35 @@ OldVenvExists() {
[ -n "$OLD_VENV_PATH" -a -f "$OLD_VENV_PATH/bin/letsencrypt" ]
}
# Given python path, version 1 and version 2, check if version 1 is outdated compared to version 2.
# An unofficial version provided as version 1 (eg. 0.28.0.dev0) will be treated
# specifically by printing "UNOFFICIAL". Otherwise, print "OUTDATED" if version 1
# is outdated, and "UP_TO_DATE" if not.
# This function relies only on installed python environment (2.x or 3.x) by certbot-auto.
CompareVersions() {
"$1" - "$2" "$3" << "UNLIKELY_EOF"
import sys
from distutils.version import StrictVersion
try:
current = StrictVersion(sys.argv[1])
except ValueError:
sys.stdout.write('UNOFFICIAL')
sys.exit()
try:
remote = StrictVersion(sys.argv[2])
except ValueError:
sys.stdout.write('UP_TO_DATE')
sys.exit()
if current < remote:
sys.stdout.write('OUTDATED')
else:
sys.stdout.write('UP_TO_DATE')
UNLIKELY_EOF
}
if [ "$1" = "--le-auto-phase2" ]; then
# Phase 2: Create venv, install LE, and run.
@ -635,7 +664,12 @@ UNLIKELY_EOF
error "WARNING: couldn't find Python $MIN_PYTHON_VERSION+ to check for updates."
elif ! REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version` ; then
error "WARNING: unable to check for updates."
elif [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
fi
LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"`
if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then
say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION"
elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then
say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
# Now we drop into Python so we don't have to install even more

View file

@ -7,8 +7,7 @@ BootstrapArchCommon() {
# - ArchLinux (x86_64)
#
# "python-virtualenv" is Python3, but "python2-virtualenv" provides
# only "virtualenv2" binary, not "virtualenv" necessary in
# ./tools/_venv_common.py
# only "virtualenv2" binary, not "virtualenv".
deps="
python2

View file

@ -1,12 +1,12 @@
certbot==0.28.0 \
--hash=sha256:f2f7c816acd695cbcda713a779b0db2b08e9de407146b46e1c4ef5561e0f5417 \
--hash=sha256:31e3e2ee2a25c009a621c59ac9182f85d937a897c7bd1d47d0e01f3c712a090a
acme==0.28.0 \
--hash=sha256:d3a564031155fece3f6edce8c5246d4cf34be0977b4c7c5ce84e86c68601c895 \
--hash=sha256:bf7c2f1c24a26ab5b9fce3a6abca1d74a5914d46919649ae00ad5817db62bb85
certbot-apache==0.28.0 \
--hash=sha256:a57d7bac4f13ae5ecea4f4cbd479d381c02316c4832b25ab5a9d7c6826166370 \
--hash=sha256:3f93f5de4a548e973c493a6cac5eeeb3dbbcae2988b61299ea0727d04a00f5bb
certbot-nginx==0.28.0 \
--hash=sha256:1822a65910f0801087fa20a3af3fc94f878f93e0f11809483bb5387df861e296 \
--hash=sha256:426fb403b0a7b203629f4e350a862cbc3bc1f69936fdab8ec7eafe0d8a3b5ddb
certbot==0.30.0 \
--hash=sha256:b3468e128e74d2295598f6d3fbf9d0edfb67fe5abaca3b985a9e858395bd027f \
--hash=sha256:d631fe6c75700ce9b2fdae194ff8b53c7518545d87dd451a1704f7572dcd49e8
acme==0.30.0 \
--hash=sha256:eed9389f802ebf4988c9e43c28ad3d5c2734237371d78e97450a1d61189a15aa \
--hash=sha256:984b6d00bec73dcfa616636a760e80ca14bd246fb908710a656547f542f09445
certbot-apache==0.30.0 \
--hash=sha256:d38c70fc6930db298ea992a3145362eebdce460d3d2651f86a8f2f43d838c6d0 \
--hash=sha256:1d4bc207d53a3e5d37e5d9ebd05f26089aa21d1fbf384113ed9d1829b4d1e9bf
certbot-nginx==0.30.0 \
--hash=sha256:6163c7d0080f59b4ebe510afcc6af2d2eebf15469275c3835866690db4d465d6 \
--hash=sha256:e39a3f3d77cd4c653949cf066fb2211039fd2032665697c27b6e8501c7c2dd92

View file

@ -21,43 +21,39 @@ pycparser==2.14 \
asn1crypto==0.22.0 \
--hash=sha256:d232509fefcfcdb9a331f37e9c9dc20441019ad927c7d2176cf18ed5da0ba097 \
--hash=sha256:cbbadd640d3165ab24b06ef25d1dca09a3441611ac15f6a6b452474fdf0aed1a
cffi==1.10.0 \
--hash=sha256:446699c10f3c390633d0722bc19edbc7ac4b94761918a4a4f7908a24e86ebbd0 \
--hash=sha256:562326fc7f55a59ef3fef5e82908fe938cdc4bbda32d734c424c7cd9ed73e93a \
--hash=sha256:7f732ad4a30db0b39400c3f7011249f7d0701007d511bf09604729aea222871f \
--hash=sha256:94fb8410c6c4fc48e7ea759d3d1d9ca561171a88d00faddd4aa0306f698ad6a0 \
--hash=sha256:587a5043df4b00a2130e09fed42da02a4ed3c688bd9bf07a3ac89d2271f4fb07 \
--hash=sha256:ec08b88bef627ec1cea210e1608c85d3cf44893bcde74e41b7f7dbdfd2c1bad6 \
--hash=sha256:a41406f6d62abcdf3eef9fd998d8dcff04fd2a7746644143045feeebd76352d1 \
--hash=sha256:b560916546b2f209d74b82bdbc3223cee9a165b0242fa00a06dfc48a2054864a \
--hash=sha256:e74896774e437f4715c57edeb5cf3d3a40d7727f541c2c12156617b5a15d1829 \
--hash=sha256:9a31c18ba4881a116e448c52f3f5d3e14401cf7a9c43cc88f06f2a7f5428da0e \
--hash=sha256:80796ea68e11624a0279d3b802f88a7fe7214122b97a15a6c97189934a2cc776 \
--hash=sha256:f4019826a2dec066c909a1f483ef0dcf9325d6740cc0bd15308942b28b0930f7 \
--hash=sha256:7248506981eeba23888b4140a69a53c4c0c0a386abcdca61ed8dd790a73e64b9 \
--hash=sha256:a8955265d146e86fe2ce116394be4eaf0cb40314a79b19f11c4fa574cd639572 \
--hash=sha256:c49187260043bd4c1d6a52186f9774f17d9b1da0a406798ebf4bfc12da166ade \
--hash=sha256:c1d8b3d8dcb5c23ac1a8bf56422036f3f305a3c5a8bc8c354256579a1e2aa2c1 \
--hash=sha256:9e389615bcecb8c782a87939d752340bb0a3a097e90bae54d7f0915bc12f45bd \
--hash=sha256:d09ff358f75a874f69fa7d1c2b4acecf4282a950293fcfcf89aa606da8a9a500 \
--hash=sha256:b69b4557aae7de18b7c174a917fe19873529d927ac592762d9771661875bbd40 \
--hash=sha256:5de52b081a2775e76b971de9d997d85c4457fc0a09079e12d66849548ae60981 \
--hash=sha256:e7d88fecb7b6250a1fd432e6dc64890342c372fce13dbfe4bb6f16348ad00c14 \
--hash=sha256:1426e67e855ef7f5030c9184f4f1a9f4bfa020c31c962cd41fd129ec5aef4a6a \
--hash=sha256:267dd2c66a5760c5f4d47e2ebcf8eeac7ef01e1ae6ae7a6d0d241a290068bc38 \
--hash=sha256:e553eb489511cacf19eda6e52bc9e151316f0d721724997dda2c4d3079b778db \
--hash=sha256:98b89b2c57f97ce2db7aeba60db173c84871d73b40e41a11ea95de1500ddc57e \
--hash=sha256:e2b7e090188833bc58b2ae03fb864c22688654ebd2096bcf38bc860c4f38a3d8 \
--hash=sha256:afa7d8b8d38ad40db8713ee053d41b36d87d6ae5ec5ad36f9210b548a18dc214 \
--hash=sha256:4fc9c2ff7924b3a1fa326e1799e5dd58cac585d7fb25fe53ccaa1333b0453d65 \
--hash=sha256:937db39a1ec5af3003b16357b2042bba67c88d43bc11aaa203fa8a5924524209 \
--hash=sha256:ab22285797631df3b513b2cd3ecdc51cd8e3d36788e3991d93d0759d6883b027 \
--hash=sha256:96e599b924ef009aa867f725b3249ee51d76489f484d3a45b4bd219c5ec6ed59 \
--hash=sha256:bea842a0512be6a8007e585790bccd5d530520fc025ce63b03e139be373b0063 \
--hash=sha256:e7175287f7fe7b1cc203bb958b17db40abd732690c1e18e700f10e0843a58598 \
--hash=sha256:285ab352552f52f1398c912556d4d36d4ea9b8450e5c65d03809bf9886755533 \
--hash=sha256:5576644b859197da7bbd8f8c7c2fb5dcc6cd505cadb42992d5f104c013f8a214 \
--hash=sha256:b3b02911eb1f6ada203b0763ba924234629b51586f72a21faacc638269f4ced5
cffi==1.11.5 \
--hash=sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50 \
--hash=sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596 \
--hash=sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef \
--hash=sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743 \
--hash=sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f \
--hash=sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31 \
--hash=sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04 \
--hash=sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6 \
--hash=sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3 \
--hash=sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6 \
--hash=sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b \
--hash=sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca \
--hash=sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e \
--hash=sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb \
--hash=sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd \
--hash=sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1 \
--hash=sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917 \
--hash=sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359 \
--hash=sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f \
--hash=sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95 \
--hash=sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801 \
--hash=sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257 \
--hash=sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184 \
--hash=sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc \
--hash=sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085 \
--hash=sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93 \
--hash=sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2 \
--hash=sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30 \
--hash=sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5 \
--hash=sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e \
--hash=sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b \
--hash=sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4
ConfigArgParse==0.12.0 \
--hash=sha256:28cd7d67669651f2a4518367838c49539457504584a139709b2b8f6c208ef339 \
--no-binary ConfigArgParse
@ -150,9 +146,9 @@ pytz==2015.7 \
--hash=sha256:fbd26746772c24cb93c8b97cbdad5cb9e46c86bbdb1b9d8a743ee00e2fb1fc5d \
--hash=sha256:99266ef30a37e43932deec2b7ca73e83c8dbc3b9ff703ec73eca6b1dae6befea \
--hash=sha256:8b6ce1c993909783bc96e0b4f34ea223bff7a4df2c90bdb9c4e0f1ac928689e3
requests==2.12.1 \
--hash=sha256:3f3f27a9d0f9092935efc78054ef324eb9f8166718270aefe036dfa1e4f68e1e \
--hash=sha256:2109ecea94df90980be040490ff1d879971b024861539abb00054062388b612e
requests==2.20.0 \
--hash=sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c \
--hash=sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279
six==1.10.0 \
--hash=sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1 \
--hash=sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a
@ -189,3 +185,12 @@ zope.interface==4.1.3 \
requests-toolbelt==0.8.0 \
--hash=sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237 \
--hash=sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5
chardet==3.0.2 \
--hash=sha256:4f7832e7c583348a9eddd927ee8514b3bf717c061f57b21dbe7697211454d9bb \
--hash=sha256:6ebf56457934fdce01fb5ada5582762a84eed94cad43ed877964aebbdd8174c0
urllib3==1.24.1 \
--hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \
--hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22
certifi==2017.4.17 \
--hash=sha256:f4318671072f030a33c7ca6acaef720ddd50ff124d1388e50c1bda4cbd6d7010 \
--hash=sha256:f7527ebf7461582ce95f7a9e03dd141ce810d40590834f4ec20cddd54234c10a

View file

@ -1,12 +1,10 @@
#!/usr/bin/env python
"""A small script that can act as a trust root for installing pip >=8
Embed this in your project, and your VCS checkout is all you have to trust. In
a post-peep era, this lets you claw your way to a hash-checking version of pip,
with which you can install the rest of your dependencies safely. All it assumes
is Python 2.6 or better and *some* version of pip already installed. If
anything goes wrong, it will exit with a non-zero status code.
"""
# This is here so embedded copies are MIT-compliant:
# Copyright (c) 2016 Erik Rose
@ -45,7 +43,7 @@ except ImportError:
cmd = popenargs[0]
raise CalledProcessError(retcode, cmd)
return output
from sys import exit, version_info, executable
from sys import exit, version_info
from tempfile import mkdtemp
try:
from urllib2 import build_opener, HTTPHandler, HTTPSHandler
@ -57,7 +55,7 @@ except ImportError:
from urllib.parse import urlparse # 3.4
__version__ = 2, 0, 0
__version__ = 1, 5, 1
PIP_VERSION = '9.0.1'
DEFAULT_INDEX_BASE = 'https://pypi.python.org'
@ -133,10 +131,8 @@ def hashed_download(url, temp, digest):
def get_index_base():
"""Return the URL to the dir containing the "packages" folder.
Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the
end if it's there; that is likely to give us the right dir.
"""
env_var = environ.get('PIP_INDEX_URL', '').rstrip('/')
if env_var:
@ -150,7 +146,7 @@ def get_index_base():
def main():
pip_version = StrictVersion(check_output([executable, '-m', 'pip', '--version'])
pip_version = StrictVersion(check_output(['pip', '--version'])
.decode('utf-8').split()[1])
min_pip_version = StrictVersion(PIP_VERSION)
if pip_version >= min_pip_version:
@ -163,7 +159,7 @@ def main():
temp,
digest)
for path, digest in PACKAGES]
check_output('{0} -m pip install --no-index --no-deps -U '.format(quote(executable)) +
check_output('pip install --no-index --no-deps -U ' +
# Disable cache since we're not using it and it otherwise
# sometimes throws permission warnings:
('--no-cache-dir ' if has_pip_cache else '') +
@ -181,4 +177,4 @@ def main():
if __name__ == '__main__':
exit(main())
exit(main())

View file

@ -27,6 +27,19 @@ def tests_dir():
return dirname(abspath(__file__))
def copy_stable(src, dst):
"""
Copy letsencrypt-auto, and replace its current version to its equivalent stable one.
This is needed to test correctly the self-upgrade functionality.
"""
copy(src, dst)
with open(dst, 'r') as file:
filedata = file.read()
filedata = re.sub(r'LE_AUTO_VERSION="(.*)\.dev0"', r'LE_AUTO_VERSION="\1"', filedata)
with open(dst, 'w') as file:
file.write(filedata)
sys.path.insert(0, dirname(tests_dir()))
from build import build as build_le_auto
@ -343,7 +356,7 @@ class AutoTests(TestCase):
'v99.9.9/letsencrypt-auto': build_le_auto(version='99.9.9'),
'v99.9.9/letsencrypt-auto.sig': signed('something else')}
with serving(resources) as base_url:
copy(LE_AUTO_PATH, le_auto_path)
copy_stable(LE_AUTO_PATH, le_auto_path)
try:
out, err = run_le_auto(le_auto_path, venv_dir, base_url)
except CalledProcessError as exc:

View file

@ -1 +1 @@
acme[dev]==0.26.0
acme[dev]==0.29.0

View file

@ -1 +1,3 @@
Be sure to edit the `master` section of `CHANGELOG.md` with a line describing this PR before it gets merged.
Be sure to edit the `master` section of `CHANGELOG.md`. This includes a
description of the change and ensuring the modified package(s) are listed as
having been changed.

12
pytest.ini Normal file
View file

@ -0,0 +1,12 @@
# This file isn't used while testing packages in tools/_release.sh so any
# settings we want to also change there must be added to the release script
# directly.
[pytest]
addopts = --numprocesses auto --pyargs
# ResourceWarnings are ignored as errors, since they're raised at close
# decodestring: https://github.com/rthalley/dnspython/issues/338
# ignore our own TLS-SNI-01 warning
filterwarnings =
error
ignore:decodestring:DeprecationWarning
ignore:TLS-SNI-01:DeprecationWarning

View file

@ -31,13 +31,13 @@ version = meta['version']
# 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.26.0',
'acme>=0.29.0',
# We technically need ConfigArgParse 0.10.0 for Python 2.6 support, but
# saying so here causes a runtime error against our temporary fork of 0.9.3
# in which we added 2.6 support (see #2243), so we relax the requirement.
'ConfigArgParse>=0.9.3',
'configobj',
'cryptography>=1.2', # load_pem_x509_certificate
'cryptography>=1.2.3', # load_pem_x509_certificate
'josepy',
'mock',
'parsedatetime>=1.3', # Calendar.parseDT
@ -68,9 +68,10 @@ dev3_extras = [
]
docs_extras = [
# If you have Sphinx<1.5.1, you need docutils<0.13.1
# https://github.com/sphinx-doc/sphinx/issues/3212
'repoze.sphinx.autointerface',
# sphinx.ext.imgconverter
'Sphinx >=1.6',
'Sphinx>=1.2', # Annotation support
'sphinx_rtd_theme',
]

View file

@ -11,7 +11,6 @@ if [ ! -d ${BOULDERPATH} ]; then
fi
cd ${BOULDERPATH}
sed -i "s/FAKE_DNS: .*/FAKE_DNS: 10.77.77.1/" docker-compose.yml
docker-compose up -d boulder
@ -28,3 +27,6 @@ if ! curl http://localhost:4000/directory 2>/dev/null; then
echo "timed out waiting for boulder to start"
exit 1
fi
# Setup the DNS resolution used by boulder instance to docker host
curl -X POST -d '{"ip":"10.77.77.1"}' http://localhost:8055/set-default-ipv4

View file

@ -174,7 +174,7 @@ CheckRenewHook() {
TotalAndDistinctLines() {
total=$1
distinct=$2
awk '{a[$1] = 1}; END {exit(NR !='$total' || length(a) !='$distinct')}'
awk '{a[$1] = 1}; END {n = 0; for (i in a) { n++ }; exit(NR !='$total' || n !='$distinct')}'
}
# Cleanup coverage data
@ -207,10 +207,16 @@ common unregister
common register --email ex1@domain.org,ex2@domain.org
# TODO: When `certbot register --update-registration` is fully deprecated, delete the two following deprecated uses
common register --update-registration --email ex1@domain.org
common register --update-registration --email ex1@domain.org,ex2@domain.org
common update_account --email example@domain.org
common update_account --email ex1@domain.org,ex2@domain.org
common plugins --init --prepare | grep webroot
# We start a server listening on the port for the
@ -280,7 +286,38 @@ CheckCertCount() {
fi
}
CheckPermissions() {
# Args: <filepath_1> <filepath_2> <mask>
# Checks mode of two files match under <mask>
masked_mode() { echo $((0`stat -c %a $1` & 0$2)); }
if [ `masked_mode $1 $3` -ne `masked_mode $2 $3` ] ; then
echo "With $3 mask, expected mode `masked_mode $1 $3`, got `masked_mode $2 $3` on file $2"
exit 1
fi
}
CheckGID() {
# Args: <filepath_1> <filepath_2>
# Checks group owner of two files match
group_owner() { echo `stat -c %G $1`; }
if [ `group_owner $1` != `group_owner $2` ] ; then
echo "Expected group owner `group_owner $1`, got `group_owner $2` on file $2"
exit 1
fi
}
CheckOthersPermission() {
# Args: <filepath_1> <expected mode>
# Tests file's other/world permission against expected mode
other_permission=$((0`stat -c %a $1` & 07))
if [ $other_permission -ne $2 ] ; then
echo "Expected file $1 to have others mode $2, got $other_permission instead"
exit 1
fi
}
CheckCertCount "le.wtf" 1
# This won't renew (because it's not time yet)
common_no_force_renew renew
CheckCertCount "le.wtf" 1
@ -294,6 +331,12 @@ rm -rf "$renewal_hooks_root"
common renew --cert-name le.wtf --authenticator manual
CheckCertCount "le.wtf" 2
CheckOthersPermission "${root}/conf/archive/le.wtf/privkey1.pem" 0
CheckOthersPermission "${root}/conf/archive/le.wtf/privkey2.pem" 0
CheckPermissions "${root}/conf/archive/le.wtf/privkey1.pem" "${root}/conf/archive/le.wtf/privkey2.pem" 074
CheckGID "${root}/conf/archive/le.wtf/privkey1.pem" "${root}/conf/archive/le.wtf/privkey2.pem"
chmod 0444 "${root}/conf/archive/le.wtf/privkey2.pem"
# test renewal with no executables in hook directories
for hook_dir in $renewal_hooks_dirs; do
touch "$hook_dir/file"
@ -310,6 +353,10 @@ CreateDirHooks
sed -i "4arenew_before_expiry = 4 years" "$root/conf/renewal/le.wtf.conf"
common_no_force_renew renew --rsa-key-size 2048 --no-directory-hooks
CheckCertCount "le.wtf" 3
CheckGID "${root}/conf/archive/le.wtf/privkey2.pem" "${root}/conf/archive/le.wtf/privkey3.pem"
CheckPermissions "${root}/conf/archive/le.wtf/privkey2.pem" "${root}/conf/archive/le.wtf/privkey3.pem" 074
CheckOthersPermission "${root}/conf/archive/le.wtf/privkey3.pem" 04
if [ -s "$HOOK_DIRS_TEST" ]; then
echo "Directory hooks were executed with --no-directory-hooks!" >&2
exit 1

View file

@ -0,0 +1,16 @@
#!/bin/bash
# Simple integration test. Make sure to activate virtualenv beforehand
# (source venv/bin/activate) and that you are running Pebble test
# instance (see ./pebble-fetch.sh).
cleanup_and_exit() {
EXIT_STATUS=$?
unset SERVER
exit $EXIT_STATUS
}
trap cleanup_and_exit EXIT
export SERVER=https://localhost:14000/dir
./tests/certbot-boulder-integration.sh

View file

@ -3,12 +3,15 @@
root=${root:-$(mktemp -d -t leitXXXX)}
echo "Root integration tests directory: $root"
config_dir="$root/conf"
store_flags="--config-dir $config_dir --work-dir $root/work"
store_flags="$store_flags --logs-dir $root/logs"
tls_sni_01_port=5001
http_01_port=5002
sources="acme/,$(ls -dm certbot*/ | tr -d ' \n')"
export root config_dir store_flags tls_sni_01_port http_01_port sources
export root config_dir tls_sni_01_port http_01_port sources
certbot_path="$(command -v certbot)"
# Flags that are added here will be added to Certbot calls within
# certbot_test_no_force_renew.
other_flags="--config-dir $config_dir --work-dir $root/work"
other_flags="$other_flags --logs-dir $root/logs"
certbot_test () {
certbot_test_no_force_renew \
@ -16,11 +19,35 @@ certbot_test () {
"$@"
}
# Succeeds if Certbot version is at least the given version number and fails
# otherwise. This is useful for making sure Certbot has certain features
# available. The patch version is currently ignored.
#
# Arguments:
# First argument is the minimum major version
# Second argument is the minimum minor version
version_at_least () {
# Certbot major and minor version (e.g. 0.30)
major_minor=$("$certbot_path" --version 2>&1 | cut -d' ' -f2 | cut -d. -f1,2)
major=$(echo "$major_minor" | cut -d. -f1)
minor=$(echo "$major_minor" | cut -d. -f2)
# Test that either the major version is greater or major version is equal
# and minor version is greater than or equal to.
[ \( "$major" -gt "$1" \) -o \( "$major" -eq "$1" -a "$minor" -ge "$2" \) ]
}
# Use local ACMEv2 endpoint if requested and SERVER isn't already set.
if [ "${BOULDER_INTEGRATION:-v1}" = "v2" -a -z "${SERVER:+x}" ]; then
SERVER="http://localhost:4001/directory"
fi
# --no-random-sleep-on-renew was added in
# https://github.com/certbot/certbot/pull/6599 and first released in Certbot
# 0.30.0.
if version_at_least 0 30; then
other_flags="$other_flags --no-random-sleep-on-renew"
fi
certbot_test_no_force_renew () {
omit_patterns="*/*.egg-info/*,*/dns_common*,*/setup.py,*/test_*,*/tests/*"
omit_patterns="$omit_patterns,*_test.py,*_test_*,certbot-apache/*"
@ -30,13 +57,13 @@ certbot_test_no_force_renew () {
--append \
--source $sources \
--omit $omit_patterns \
$(command -v certbot) \
"$certbot_path" \
--server "${SERVER:-http://localhost:4000/directory}" \
--no-verify-ssl \
--tls-sni-01-port $tls_sni_01_port \
--http-01-port $http_01_port \
--manual-public-ip-logging-ok \
$store_flags \
$other_flags \
--non-interactive \
--no-redirect \
--agree-tos \

View file

@ -54,6 +54,7 @@ if [ $? -ne 0 ] ; then
fi
if [ "$OS_TYPE" = "ubuntu" ] ; then
export SERVER="$BOULDER_URL"
venv/bin/tox -e apacheconftest
else
echo Not running hackish apache tests on $OS_TYPE

View file

@ -10,7 +10,7 @@ VERSION=$(letsencrypt-auto-source/version.py)
export VENV_ARGS="-p $PYTHON"
# setup venv
tools/_venv_common.sh --requirement letsencrypt-auto-source/pieces/dependency-requirements.txt
tools/_venv_common.py --requirement letsencrypt-auto-source/pieces/dependency-requirements.txt
. ./venv/bin/activate
# build sdists

View file

@ -70,7 +70,7 @@ def validate_scripts_content(repo_path, temp_cwd):
# Compare file against the latest released version
latest_version = subprocess.check_output(
[sys.executable, 'fetch.py', '--latest-version'], cwd=temp_cwd)
subprocess.call(
subprocess.check_call(
[sys.executable, 'fetch.py', '--le-auto-script',
'v{0}'.format(latest_version.decode().strip())], cwd=temp_cwd)
if compare_files(
@ -95,7 +95,7 @@ def main():
os.path.normpath(os.path.join(repo_path, 'letsencrypt-auto-source/letsencrypt-auto')),
os.path.join(temp_cwd, 'original-lea')
)
subprocess.call([sys.executable, os.path.normpath(os.path.join(
subprocess.check_call([sys.executable, os.path.normpath(os.path.join(
repo_path, 'letsencrypt-auto-source/build.py'))])
shutil.copyfile(
os.path.normpath(os.path.join(repo_path, 'letsencrypt-auto-source/letsencrypt-auto')),

41
tests/pebble-fetch.sh Executable file
View file

@ -0,0 +1,41 @@
#!/bin/bash
# Download and run Pebble instance for integration testing
set -xe
PEBBLE_VERSION=2018-11-02
# We reuse the same GOPATH-style directory than for Boulder.
# Pebble does not need it, but it will make the installation consistent with Boulder's one.
export GOPATH=${GOPATH:-$HOME/gopath}
PEBBLEPATH=${PEBBLEPATH:-$GOPATH/src/github.com/letsencrypt/pebble}
mkdir -p ${PEBBLEPATH}
cat << UNLIKELY_EOF > "$PEBBLEPATH/docker-compose.yml"
version: '3'
services:
pebble:
image: letsencrypt/pebble:${PEBBLE_VERSION}
command: pebble -strict ${PEBBLE_STRICT:-false} -dnsserver 10.77.77.1
ports:
- 14000:14000
environment:
- PEBBLE_VA_NOSLEEP=1
UNLIKELY_EOF
docker-compose -f "$PEBBLEPATH/docker-compose.yml" up -d pebble
set +x # reduce verbosity while waiting for boulder
for n in `seq 1 150` ; do
if curl -k https://localhost:14000/dir 2>/dev/null; then
break
else
sleep 1
fi
done
if ! curl -k https://localhost:14000/dir 2>/dev/null; then
echo "timed out waiting for pebble to start"
exit 1
fi

View file

@ -1,17 +0,0 @@
#!/bin/bash -e
#
# Set up the test environment for macOS on Travis.
# Install the given package with brew if it's not already installed.
brew_install() {
if ! brew list "$1" > /dev/null 2>&1; then
brew install "$1"
fi
}
brew_install augeas
brew_install python
brew_install python3
# Ensure we use python from brew.
brew link python

Some files were not shown because too many files have changed in this diff Show more