mirror of
https://github.com/certbot/certbot.git
synced 2026-06-09 08:42:57 -04:00
Merge branch 'master' into update-test-everything2
This commit is contained in:
commit
5569b1ce19
111 changed files with 1878 additions and 753 deletions
|
|
@ -16,6 +16,7 @@ before_install:
|
|||
|
||||
before_script:
|
||||
- 'if [ $TRAVIS_OS_NAME = osx ] ; then ulimit -n 1024 ; fi'
|
||||
- export TOX_TESTENV_PASSENV=TRAVIS
|
||||
|
||||
matrix:
|
||||
include:
|
||||
|
|
@ -75,8 +76,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
|
||||
|
|
|
|||
94
CHANGELOG.md
94
CHANGELOG.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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
53
acme/acme/jose_test.py
Normal 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
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
18
appveyor.yml
18
appveyor.yml
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
acme[dev]==0.25.0
|
||||
-e .[dev]
|
||||
certbot[dev]==0.26.0
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
154
certbot-auto
154
certbot-auto
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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 []
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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__":
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
It’s 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 doesn’t 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
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
154
letsencrypt-auto
154
letsencrypt-auto
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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-----
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
acme[dev]==0.26.0
|
||||
acme[dev]==0.29.0
|
||||
|
|
|
|||
|
|
@ -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
12
pytest.ini
Normal 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
|
||||
9
setup.py
9
setup.py
|
|
@ -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',
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
16
tests/certbot-pebble-integration.sh
Executable file
16
tests/certbot-pebble-integration.sh
Executable 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
|
||||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
41
tests/pebble-fetch.sh
Executable 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
|
||||
21
tools/_changelog_top.txt
Normal file
21
tools/_changelog_top.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
## nextversion - master
|
||||
|
||||
### Added
|
||||
|
||||
*
|
||||
|
||||
### Changed
|
||||
|
||||
*
|
||||
|
||||
### Fixed
|
||||
|
||||
*
|
||||
|
||||
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:
|
||||
|
||||
*
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue